Compare commits

..

No commits in common. "e3f6fa236d0edbfeef8e28d5b4f70c168900ce24" and "7a8df1f0d492814aef21944432cfa44c5c4d8db7" have entirely different histories.

174 changed files with 719 additions and 16804 deletions

8
.gitignore vendored
View File

@ -99,11 +99,3 @@ Temporary Items
# Local runtime data and certificates # Local runtime data and certificates
data/ data/
deploy/bin/ deploy/bin/
# Node modules — install via `cd deploy/tools && npm ci`
node_modules/
# Environment secrets
.env
deploy/.env
!deploy/.env.example

View File

@ -1,7 +1,6 @@
stages: stages:
- build-1 - build-1
- build-2 - build-2
- build-3
variables: variables:
HARBOR_REGISTRY: harbor-in-k3s.cheverjohn.me HARBOR_REGISTRY: harbor-in-k3s.cheverjohn.me
@ -14,8 +13,7 @@ variables:
.build-template: &build-template .build-template: &build-template
image: docker:27 image: docker:27
services: services:
- name: docker:27-dind - docker:27-dind
command: ["--dns", "10.43.0.10", "--dns", "223.5.5.5"]
retry: 2 retry: 2
before_script: before_script:
- until docker info >/dev/null 2>&1; do echo "Waiting for Docker daemon..."; sleep 2; done - until docker info >/dev/null 2>&1; do echo "Waiting for Docker daemon..."; sleep 2; done
@ -24,18 +22,14 @@ variables:
- | - |
if [ -n "$CI_COMMIT_TAG" ]; then if [ -n "$CI_COMMIT_TAG" ]; then
TAG="$CI_COMMIT_TAG" TAG="$CI_COMMIT_TAG"
elif [ "$CI_COMMIT_BRANCH" = "main" ]; then
TAG="main-${CI_COMMIT_SHORT_SHA}"
else else
TAG="feat-${CI_COMMIT_SHORT_SHA}" TAG="main-${CI_COMMIT_SHORT_SHA}"
fi fi
- docker build --network host -t $HARBOR_REGISTRY/$HARBOR_PROJECT/$IMAGE_NAME:$TAG -f $DOCKERFILE_PATH . - docker build -t $HARBOR_REGISTRY/$HARBOR_PROJECT/$IMAGE_NAME:$TAG -f $DOCKERFILE_PATH .
- docker push $HARBOR_REGISTRY/$HARBOR_PROJECT/$IMAGE_NAME:$TAG - docker push $HARBOR_REGISTRY/$HARBOR_PROJECT/$IMAGE_NAME:$TAG
rules: rules:
- if: $CI_COMMIT_BRANCH == "main" - if: $CI_COMMIT_BRANCH == "main"
- if: $CI_COMMIT_TAG =~ /^v/ - if: $CI_COMMIT_TAG =~ /^v/
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH =~ /^feat\//
build-muyu-gateway: build-muyu-gateway:
<<: *build-template <<: *build-template
@ -57,17 +51,3 @@ build-muyu-inventory-rpc:
variables: variables:
IMAGE_NAME: muyu-inventory-rpc IMAGE_NAME: muyu-inventory-rpc
DOCKERFILE_PATH: deploy/Dockerfile.inventory DOCKERFILE_PATH: deploy/Dockerfile.inventory
build-muyu-purchase-rpc:
<<: *build-template
stage: build-2
variables:
IMAGE_NAME: muyu-purchase-rpc
DOCKERFILE_PATH: deploy/Dockerfile.purchase
build-muyu-production-rpc:
<<: *build-template
stage: build-3
variables:
IMAGE_NAME: muyu-production-rpc
DOCKERFILE_PATH: deploy/Dockerfile.production

View File

@ -1,13 +0,0 @@
# Copy this file to .env and fill in real values
# NEVER commit .env to git
# Database passwords (REQUIRED)
MYSQL_ROOT_PASSWORD=your_mysql_password_here
POSTGRES_PASSWORD=your_postgres_password_here
# JWT secret for gateway auth (REQUIRED)
ACCESS_SECRET=your_jwt_secret_here
# Neo4j (only needed for graph stack: docker-compose.graph.yaml)
NEO4J_AUTH=neo4j/your_neo4j_password_here
NEO4J_PASS=your_neo4j_password_here

View File

@ -11,12 +11,10 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /server ./gateway/gateway.
FROM ${BASE_REGISTRY}/alpine:3.20 FROM ${BASE_REGISTRY}/alpine:3.20
RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories
RUN apk add --no-cache ca-certificates tzdata gettext RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app WORKDIR /app
COPY --from=builder /server ./gateway COPY --from=builder /server ./gateway
COPY deploy/etc/gateway.yaml etc/gateway.yaml COPY deploy/etc/gateway.yaml etc/gateway.yaml
COPY deploy/tools/ tools/ COPY deploy/tools/ tools/
COPY deploy/entrypoint.sh /entrypoint.sh
EXPOSE 8888 EXPOSE 8888
ENTRYPOINT ["/entrypoint.sh"]
CMD ["./gateway", "-f", "etc/gateway.yaml"] CMD ["./gateway", "-f", "etc/gateway.yaml"]

View File

@ -11,11 +11,9 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /server ./rpc/inventory/in
FROM ${BASE_REGISTRY}/alpine:3.20 FROM ${BASE_REGISTRY}/alpine:3.20
RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories
RUN apk add --no-cache ca-certificates tzdata gettext RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app WORKDIR /app
COPY --from=builder /server ./inventory COPY --from=builder /server ./inventory
COPY deploy/etc/inventory.yaml etc/inventory.yaml COPY deploy/etc/inventory.yaml etc/inventory.yaml
COPY deploy/entrypoint.sh /entrypoint.sh
EXPOSE 9002 EXPOSE 9002
ENTRYPOINT ["/entrypoint.sh"]
CMD ["./inventory", "-f", "etc/inventory.yaml"] CMD ["./inventory", "-f", "etc/inventory.yaml"]

View File

@ -1,21 +0,0 @@
ARG BASE_REGISTRY=harbor-in-k3s.cheverjohn.me/library
FROM ${BASE_REGISTRY}/golang:1.24-alpine AS builder
ENV GOPROXY=https://goproxy.cn,direct
RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories
RUN apk add --no-cache git
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /server ./rpc/production/production.go
FROM ${BASE_REGISTRY}/alpine:3.20
RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories
RUN apk add --no-cache ca-certificates tzdata gettext
WORKDIR /app
COPY --from=builder /server ./production
COPY deploy/etc/production.yaml etc/production.yaml
COPY deploy/entrypoint.sh /entrypoint.sh
EXPOSE 9004
ENTRYPOINT ["/entrypoint.sh"]
CMD ["./production", "-f", "etc/production.yaml"]

View File

@ -1,21 +1,7 @@
ARG BASE_REGISTRY=harbor-in-k3s.cheverjohn.me/library FROM alpine:3.19
FROM ${BASE_REGISTRY}/golang:1.24-alpine AS builder RUN apk add --no-cache ca-certificates tzdata
ENV GOPROXY=https://goproxy.cn,direct
RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories
RUN apk add --no-cache git
WORKDIR /app WORKDIR /app
COPY go.mod go.sum ./ COPY deploy/bin/purchase .
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /server ./rpc/purchase/purchase.go
FROM ${BASE_REGISTRY}/alpine:3.20
RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories
RUN apk add --no-cache ca-certificates tzdata gettext
WORKDIR /app
COPY --from=builder /server ./purchase
COPY deploy/etc/purchase.yaml etc/purchase.yaml COPY deploy/etc/purchase.yaml etc/purchase.yaml
COPY deploy/entrypoint.sh /entrypoint.sh
EXPOSE 9003 EXPOSE 9003
ENTRYPOINT ["/entrypoint.sh"]
CMD ["./purchase", "-f", "etc/purchase.yaml"] CMD ["./purchase", "-f", "etc/purchase.yaml"]

View File

@ -11,11 +11,9 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o /server ./rpc/system/syste
FROM ${BASE_REGISTRY}/alpine:3.20 FROM ${BASE_REGISTRY}/alpine:3.20
RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories RUN sed -i "s|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g" /etc/apk/repositories
RUN apk add --no-cache ca-certificates tzdata gettext RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app WORKDIR /app
COPY --from=builder /server ./system COPY --from=builder /server ./system
COPY deploy/etc/system.yaml etc/system.yaml COPY deploy/etc/system.yaml etc/system.yaml
COPY deploy/entrypoint.sh /entrypoint.sh
EXPOSE 9001 EXPOSE 9001
ENTRYPOINT ["/entrypoint.sh"]
CMD ["./system", "-f", "etc/system.yaml"] CMD ["./system", "-f", "etc/system.yaml"]

View File

@ -5,7 +5,7 @@ services:
restart: always restart: always
environment: environment:
POSTGRES_USER: muyu POSTGRES_USER: muyu
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} POSTGRES_PASSWORD: muyu2026
POSTGRES_DB: muyu_crm POSTGRES_DB: muyu_crm
TZ: Asia/Shanghai TZ: Asia/Shanghai
ports: ports:
@ -70,7 +70,7 @@ services:
container_name: muyu-neo4j container_name: muyu-neo4j
restart: always restart: always
environment: environment:
NEO4J_AUTH: ${NEO4J_AUTH:?Set NEO4J_AUTH in .env (e.g. neo4j/yourpassword)} NEO4J_AUTH: neo4j/muyu2026
NEO4J_server_memory_heap_initial__size: 512m NEO4J_server_memory_heap_initial__size: 512m
NEO4J_server_memory_heap_max__size: 1024m NEO4J_server_memory_heap_max__size: 1024m
NEO4J_server_default__listen__address: 0.0.0.0 NEO4J_server_default__listen__address: 0.0.0.0
@ -103,7 +103,7 @@ services:
KAFKA_GROUP: graphsync KAFKA_GROUP: graphsync
NEO4J_URI: bolt://neo4j:7687 NEO4J_URI: bolt://neo4j:7687
NEO4J_USER: neo4j NEO4J_USER: neo4j
NEO4J_PASS: ${NEO4J_PASS:?Set NEO4J_PASS in .env} NEO4J_PASS: muyu2026
TZ: Asia/Shanghai TZ: Asia/Shanghai
kafka-ui: kafka-ui:

View File

@ -4,7 +4,7 @@ services:
container_name: muyu-mysql container_name: muyu-mysql
restart: always restart: always
environment: environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?Set MYSQL_ROOT_PASSWORD in .env} MYSQL_ROOT_PASSWORD: muyu2026
MYSQL_DATABASE: muyu_wms MYSQL_DATABASE: muyu_wms
TZ: Asia/Shanghai TZ: Asia/Shanghai
ports: ports:
@ -39,7 +39,7 @@ services:
restart: always restart: always
environment: environment:
POSTGRES_USER: muyu POSTGRES_USER: muyu
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} POSTGRES_PASSWORD: muyu2026
POSTGRES_DB: muyu_crm POSTGRES_DB: muyu_crm
TZ: Asia/Shanghai TZ: Asia/Shanghai
ports: ports:
@ -90,7 +90,6 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
TZ: Asia/Shanghai TZ: Asia/Shanghai
MYSQL_DSN: "root:${MYSQL_ROOT_PASSWORD}@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
inventory-rpc: inventory-rpc:
build: build:
@ -107,7 +106,6 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
TZ: Asia/Shanghai TZ: Asia/Shanghai
MYSQL_DSN: "root:${MYSQL_ROOT_PASSWORD}@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
purchase-rpc: purchase-rpc:
build: build:
@ -124,28 +122,6 @@ services:
condition: service_healthy condition: service_healthy
environment: environment:
TZ: Asia/Shanghai TZ: Asia/Shanghai
MYSQL_DSN: "root:${MYSQL_ROOT_PASSWORD}@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
production-rpc:
build:
context: ..
dockerfile: deploy/Dockerfile.production
container_name: muyu-production-rpc
restart: always
ports:
- "9004:9004"
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
etcd:
condition: service_healthy
volumes:
- ./etc/production.yaml:/etc/muyu/production.yaml
environment:
TZ: Asia/Shanghai
MYSQL_DSN: "root:${MYSQL_ROOT_PASSWORD}@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
gateway: gateway:
build: build:
@ -162,15 +138,10 @@ services:
condition: service_started condition: service_started
purchase-rpc: purchase-rpc:
condition: service_started condition: service_started
production-rpc:
condition: service_started
postgres: postgres:
condition: service_healthy condition: service_healthy
environment: environment:
TZ: Asia/Shanghai TZ: Asia/Shanghai
MYSQL_DSN: "root:${MYSQL_ROOT_PASSWORD}@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
POSTGRES_DSN: "postgres://muyu:${POSTGRES_PASSWORD}@postgres:5432/muyu_crm?sslmode=disable"
ACCESS_SECRET: ${ACCESS_SECRET:?Set ACCESS_SECRET in .env}
nginx: nginx:
image: nginx:alpine image: nginx:alpine

View File

@ -1,28 +0,0 @@
#!/bin/sh
# Substitute environment variables in YAML config templates before starting the service.
# Usage: entrypoint.sh <binary> -f <config.yaml>
#
# The YAML file is treated as a template with ${VAR} placeholders.
# envsubst replaces them with actual environment variable values at container start time.
# The rendered config is written to /tmp to avoid read-only filesystem issues (e.g. ConfigMap mounts).
set -e
BINARY="$1"
shift
# Scan args for -f <config> and render to /tmp
ARGS=""
PREV=""
for arg in "$@"; do
if [ "$PREV" = "-f" ] && [ -f "$arg" ]; then
RENDERED="/tmp/$(basename "$arg")"
envsubst < "$arg" > "$RENDERED"
ARGS="$ARGS $RENDERED"
else
ARGS="$ARGS $arg"
fi
PREV="$arg"
done
exec "$BINARY" $ARGS

View File

@ -4,12 +4,12 @@ Port: 8888
Timeout: 120000 Timeout: 120000
Auth: Auth:
AccessSecret: ${ACCESS_SECRET} AccessSecret: muyu-wms-jwt-secret-key-2026
AccessExpire: 7200 AccessExpire: 7200
DefaultTenantId: t_default_001 DefaultTenantId: t_default_001
DataSource: ${MYSQL_DSN} DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
PostgresDataSource: ${POSTGRES_DSN} PostgresDataSource: "postgres://muyu:muyu2026@postgres:5432/muyu_crm?sslmode=disable"
SystemRpc: SystemRpc:
Etcd: Etcd:
@ -34,14 +34,6 @@ PurchaseRpc:
NonBlock: true NonBlock: true
Timeout: 60000 Timeout: 60000
ProductionRpc:
Etcd:
Hosts:
- etcd:2379
Key: production.rpc
NonBlock: true
Timeout: 60000
Log: Log:
ServiceName: gateway-api ServiceName: gateway-api
Mode: console Mode: console

View File

@ -6,7 +6,7 @@ Etcd:
- etcd:2379 - etcd:2379
Key: inventory.rpc Key: inventory.rpc
DataSource: ${MYSQL_DSN} DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
Cache: Cache:
- Host: redis:6379 - Host: redis:6379

View File

@ -1,17 +0,0 @@
Name: production.rpc
ListenOn: 0.0.0.0:9004
Etcd:
Hosts:
- etcd:2379
Key: production.rpc
DataSource: ${MYSQL_DSN}
Cache:
- Host: redis:6379
Log:
ServiceName: production-rpc
Mode: console
Level: info

View File

@ -6,7 +6,7 @@ Etcd:
- etcd:2379 - etcd:2379
Key: purchase.rpc Key: purchase.rpc
DataSource: ${MYSQL_DSN} DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
Cache: Cache:
- Host: redis:6379 - Host: redis:6379

View File

@ -6,7 +6,7 @@ Etcd:
- etcd:2379 - etcd:2379
Key: system.rpc Key: system.rpc
DataSource: ${MYSQL_DSN} DataSource: root:muyu2026@tcp(mysql:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
Cache: Cache:
- Host: redis:6379 - Host: redis:6379

View File

@ -210,14 +210,16 @@ CREATE TABLE `crm_data_migration_log` (
CREATE TABLE `inv_product` ( CREATE TABLE `inv_product` (
`id` BIGINT NOT NULL AUTO_INCREMENT, `id` BIGINT NOT NULL AUTO_INCREMENT,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT 't_default_001',
`product_id` VARCHAR(32) NOT NULL, `product_id` VARCHAR(32) NOT NULL,
`product_name` VARCHAR(100) NOT NULL, `product_name` VARCHAR(100) NOT NULL,
`image_url` VARCHAR(255) NOT NULL DEFAULT '', `image_url` VARCHAR(255) NOT NULL DEFAULT '',
`spec` VARCHAR(100) NOT NULL DEFAULT '', `spec` VARCHAR(100) NOT NULL DEFAULT '',
`color` VARCHAR(50) NOT NULL DEFAULT '本色', `color` VARCHAR(50) NOT NULL DEFAULT '',
`color_no` VARCHAR(20) NOT NULL DEFAULT '', `unit_pieces` INT NOT NULL DEFAULT 0,
`product_code` VARCHAR(50) NOT NULL DEFAULT '', `unit_rolls` INT NOT NULL DEFAULT 0,
`stock_quantity` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`location` VARCHAR(100) NOT NULL DEFAULT '',
`cost_price` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`sales_price` DECIMAL(10,2) NOT NULL DEFAULT 0.00, `sales_price` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`remark` TEXT, `remark` TEXT,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1:enabled 0:disabled', `status` TINYINT NOT NULL DEFAULT 1 COMMENT '1:enabled 0:disabled',
@ -226,96 +228,12 @@ CREATE TABLE `inv_product` (
`deleted_at` DATETIME DEFAULT NULL, `deleted_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `uk_product_id` (`product_id`), UNIQUE KEY `uk_product_id` (`product_id`),
UNIQUE KEY `uk_tenant_product_code` (`tenant_id`, `product_code`), UNIQUE KEY `uk_product_name_spec_color` (`product_name`, `spec`, `color`),
KEY `idx_tenant_id` (`tenant_id`),
KEY `idx_product_name_tenant` (`product_name`, `tenant_id`),
KEY `idx_color` (`color`), KEY `idx_color` (`color`),
KEY `idx_location` (`location`),
KEY `idx_status` (`status`), KEY `idx_status` (`status`),
KEY `idx_deleted_at` (`deleted_at`) KEY `idx_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品档案'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE `inv_yarn` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`yarn_id` VARCHAR(32) NOT NULL,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`yarn_name` VARCHAR(100) NOT NULL,
`color` VARCHAR(50) NOT NULL DEFAULT '原纱本色',
`weight_g_m` DECIMAL(10,4) NOT NULL DEFAULT 0.0000,
`supplier_id` VARCHAR(32) NOT NULL DEFAULT '',
`dye_factory` VARCHAR(100) NOT NULL DEFAULT '',
`image_url` VARCHAR(255) NOT NULL DEFAULT '',
`remark` TEXT,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1:enabled 0:disabled',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yarn_id` (`yarn_id`),
UNIQUE KEY `uk_tenant_yarn_name_color_supplier` (`tenant_id`, `yarn_name`, `color`, `supplier_id`),
KEY `idx_tenant_id` (`tenant_id`),
KEY `idx_supplier_id` (`supplier_id`),
KEY `idx_status` (`status`),
KEY `idx_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='原料纱档案';
CREATE TABLE `inv_product_batch` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`batch_id` VARCHAR(32) NOT NULL,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`product_id` VARCHAR(32) NOT NULL,
`batch_no` VARCHAR(64) NOT NULL DEFAULT '',
`yarn_ratio` JSON DEFAULT NULL,
`warp_weight_g_m` DECIMAL(10,4) NOT NULL DEFAULT 0.0000,
`weft_weight_g_m` DECIMAL(10,4) NOT NULL DEFAULT 0.0000,
`production_process` TEXT,
`remark` TEXT,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1:enabled 0:disabled',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_batch_id` (`batch_id`),
UNIQUE KEY `uk_tenant_batch_no` (`tenant_id`, `batch_no`),
KEY `idx_product_id` (`product_id`),
KEY `idx_tenant_id` (`tenant_id`),
KEY `idx_status` (`status`),
KEY `idx_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品批次表';
CREATE TABLE `inv_product_pan` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`pan_id` VARCHAR(32) NOT NULL,
`product_id` VARCHAR(32) NOT NULL,
`batch_id` VARCHAR(32) NOT NULL DEFAULT '',
`name` VARCHAR(50) NOT NULL DEFAULT '',
`position` VARCHAR(100) NOT NULL DEFAULT '',
`sort_order` INT NOT NULL DEFAULT 0,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_pan_id` (`pan_id`),
KEY `idx_product_id` (`product_id`),
KEY `idx_batch_id` (`batch_id`),
KEY `idx_tenant_id` (`tenant_id`),
KEY `idx_position` (`position`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品盘库存';
CREATE TABLE `inv_product_bolt` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`bolt_id` VARCHAR(32) NOT NULL,
`pan_id` VARCHAR(32) NOT NULL,
`length_m` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`color` VARCHAR(50) NOT NULL DEFAULT '',
`sort_order` INT NOT NULL DEFAULT 0,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_bolt_id` (`bolt_id`),
KEY `idx_pan_id` (`pan_id`),
KEY `idx_tenant_id` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品匹库存';
CREATE TABLE `inv_stock_import_log` ( CREATE TABLE `inv_stock_import_log` (
`id` BIGINT NOT NULL AUTO_INCREMENT, `id` BIGINT NOT NULL AUTO_INCREMENT,

View File

@ -1,79 +0,0 @@
-- End-to-End Validation Queries for Production Plan Collaboration
-- Run these after testing the full flow to verify data consistency
-- 1. Verify production plan exists and has correct structure
SELECT p.plan_id, p.plan_code, p.product_name, p.target_quantity,
p.completed_quantity, p.status,
ROUND(p.completed_quantity * 100.0 / NULLIF(p.target_quantity, 0), 1) as progress_pct
FROM pro_production_plan p
WHERE p.tenant_id = 'TEST_TENANT'
ORDER BY p.created_at DESC LIMIT 5;
-- 2. Verify yarn ratios sum to 100%
SELECT yr.plan_id,
COUNT(*) as ratio_count,
SUM(yr.ratio) as total_ratio,
CASE WHEN ABS(SUM(yr.ratio) - 1.0) < 0.01 THEN 'OK' ELSE 'MISMATCH' END as check_result
FROM pro_plan_yarn_ratio yr
GROUP BY yr.plan_id;
-- 3. Verify process steps — each plan should have exactly 5 steps
SELECT ps.plan_id,
COUNT(*) as step_count,
SUM(CASE WHEN ps.status = 2 THEN 1 ELSE 0 END) as completed_steps,
CASE WHEN COUNT(*) = 5 THEN 'OK' ELSE 'MISSING_STEPS' END as check_result
FROM pro_plan_process_step ps
GROUP BY ps.plan_id;
-- 4. Verify share link lifecycle
SELECT sl.short_code, sl.status, sl.click_count,
sl.expires_at, NOW() as current_time,
CASE WHEN sl.expires_at > NOW() THEN 'VALID' ELSE 'EXPIRED' END as expiry_check,
pf.factory_tenant_id
FROM pro_share_link sl
LEFT JOIN pro_plan_factory pf ON pf.plan_id = sl.plan_id
ORDER BY sl.created_at DESC LIMIT 5;
-- 5. CRITICAL: Verify data consistency — completed_quantity must equal SUM(inbound)
SELECT p.plan_id, p.plan_code,
p.completed_quantity as plan_completed,
COALESCE(SUM(ir.quantity), 0) as inbound_sum,
CASE
WHEN ABS(p.completed_quantity - COALESCE(SUM(ir.quantity), 0)) < 0.01 THEN 'CONSISTENT'
ELSE 'DRIFT_DETECTED'
END as consistency_check
FROM pro_production_plan p
LEFT JOIN pro_inbound_record ir ON ir.plan_id = p.plan_id
GROUP BY p.plan_id, p.plan_code, p.completed_quantity;
-- 6. Verify auto-completion: plans with completed_quantity >= target_quantity should be status=3
SELECT p.plan_id, p.plan_code,
p.target_quantity, p.completed_quantity, p.status,
CASE
WHEN p.completed_quantity >= p.target_quantity AND p.status != 3 THEN 'SHOULD_BE_COMPLETED'
WHEN p.completed_quantity < p.target_quantity AND p.status = 3 THEN 'PREMATURE_COMPLETION'
ELSE 'OK'
END as auto_complete_check
FROM pro_production_plan p
WHERE p.status IN (2, 3);
-- 7. Verify inventory logs match inbound records
SELECT ir.record_id, ir.quantity,
il.change_qty, il.ref_type, il.ref_id,
CASE
WHEN il.log_id IS NULL THEN 'MISSING_LOG'
WHEN ABS(ir.quantity - il.change_qty) < 0.01 THEN 'OK'
ELSE 'QUANTITY_MISMATCH'
END as log_check
FROM pro_inbound_record ir
LEFT JOIN inv_inventory_log il ON il.ref_id = ir.record_id AND il.ref_type = 'PRODUCTION';
-- 8. Summary report
SELECT
(SELECT COUNT(*) FROM pro_production_plan) as total_plans,
(SELECT COUNT(*) FROM pro_production_plan WHERE status = 3) as completed_plans,
(SELECT COUNT(*) FROM pro_share_link) as total_links,
(SELECT COUNT(*) FROM pro_share_link WHERE status = 2) as confirmed_links,
(SELECT COUNT(*) FROM pro_inbound_record) as total_inbound_records,
(SELECT COALESCE(SUM(quantity), 0) FROM pro_inbound_record) as total_inbound_meters,
(SELECT COUNT(*) FROM inv_inventory_log WHERE ref_type = 'PRODUCTION') as production_inv_logs;

View File

@ -1,133 +0,0 @@
-- Phase 3: Production Plan Collaboration + Batch Inbound
-- ==================== Production Plan ====================
CREATE TABLE IF NOT EXISTS `pro_production_plan` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`plan_id` VARCHAR(32) NOT NULL,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`plan_code` VARCHAR(32) NOT NULL COMMENT 'PP-YYYYMMDD-XXXX',
`product_id` VARCHAR(32) NOT NULL,
`product_name` VARCHAR(100) NOT NULL DEFAULT '',
`color` VARCHAR(50) NOT NULL DEFAULT '',
`fabric_code` VARCHAR(50) NOT NULL DEFAULT '',
`color_code` VARCHAR(20) NOT NULL DEFAULT '',
`target_quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 'target meters',
`completed_quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 'completed meters = SUM(inbound)',
`yarn_usage_per_meter` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 'grams per meter',
`production_price` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 'yuan per meter',
`supplier_id` VARCHAR(32) NOT NULL DEFAULT '' COMMENT 'pur_supplier.supplier_id as textile factory',
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0:draft 1:pending 2:producing 3:completed 4:cancelled',
`remark` TEXT,
`start_time` DATETIME DEFAULT NULL,
`creator` VARCHAR(32) NOT NULL DEFAULT '',
`operator` VARCHAR(32) NOT NULL DEFAULT '',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_plan_id` (`plan_id`),
UNIQUE KEY `uk_plan_code_tenant` (`plan_code`, `tenant_id`),
KEY `idx_tenant_status` (`tenant_id`, `status`),
KEY `idx_product` (`product_id`),
KEY `idx_supplier` (`supplier_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='生产计划';
-- ==================== Yarn Ratio ====================
CREATE TABLE IF NOT EXISTS `pro_plan_yarn_ratio` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`ratio_id` VARCHAR(32) NOT NULL,
`plan_id` VARCHAR(32) NOT NULL,
`yarn_name` VARCHAR(100) NOT NULL DEFAULT '',
`ratio` DECIMAL(5,2) NOT NULL DEFAULT 0.00 COMMENT '0.00-1.00',
`amount_per_meter` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT 'grams',
`total_amount` DECIMAL(14,2) NOT NULL DEFAULT 0.00 COMMENT 'grams',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_ratio_id` (`ratio_id`),
KEY `idx_plan` (`plan_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='纱线配比';
-- ==================== Process Step ====================
CREATE TABLE IF NOT EXISTS `pro_plan_process_step` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`step_id` VARCHAR(32) NOT NULL,
`plan_id` VARCHAR(32) NOT NULL,
`step_type` VARCHAR(30) NOT NULL COMMENT 'confirm|yarn_purchase|dyeing|machine_start|fabric_warehouse',
`step_order` TINYINT NOT NULL DEFAULT 0,
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0:pending 1:in_progress 2:completed',
`operator_id` VARCHAR(32) NOT NULL DEFAULT '',
`completed_at` DATETIME DEFAULT NULL,
`remark` TEXT,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_step_id` (`step_id`),
UNIQUE KEY `uk_plan_step` (`plan_id`, `step_type`),
KEY `idx_plan` (`plan_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='生产流程节点';
-- ==================== Share Link ====================
CREATE TABLE IF NOT EXISTS `pro_share_link` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`link_id` VARCHAR(32) NOT NULL,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`plan_id` VARCHAR(32) NOT NULL,
`short_code` VARCHAR(16) NOT NULL,
`token` VARCHAR(512) NOT NULL DEFAULT '',
`factory_type` VARCHAR(20) NOT NULL DEFAULT 'textile',
`hide_sensitive` TINYINT NOT NULL DEFAULT 0,
`status` TINYINT NOT NULL DEFAULT 0 COMMENT '0:pending 1:clicked 2:confirmed 3:rejected 4:expired',
`click_count` INT NOT NULL DEFAULT 0,
`clicked_at` DATETIME DEFAULT NULL,
`responded_at` DATETIME DEFAULT NULL,
`reject_reason` VARCHAR(500) NOT NULL DEFAULT '',
`expires_at` DATETIME NOT NULL,
`created_by` VARCHAR(32) NOT NULL DEFAULT '',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_link_id` (`link_id`),
UNIQUE KEY `uk_short_code` (`short_code`),
KEY `idx_plan` (`plan_id`),
KEY `idx_tenant` (`tenant_id`),
KEY `idx_status_expires` (`status`, `expires_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='分享链接';
-- ==================== Plan Factory ====================
CREATE TABLE IF NOT EXISTS `pro_plan_factory` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`plan_id` VARCHAR(32) NOT NULL,
`factory_tenant_id` VARCHAR(32) NOT NULL,
`factory_type` VARCHAR(20) NOT NULL DEFAULT 'textile',
`share_link_id` VARCHAR(32) NOT NULL DEFAULT '',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_plan_factory` (`plan_id`, `factory_tenant_id`),
KEY `idx_factory` (`factory_tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='计划-工厂关联';
-- ==================== Inbound Record ====================
CREATE TABLE IF NOT EXISTS `pro_inbound_record` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`record_id` VARCHAR(32) NOT NULL,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`plan_id` VARCHAR(32) NOT NULL,
`product_id` VARCHAR(32) NOT NULL DEFAULT '',
`batch_no` VARCHAR(50) NOT NULL DEFAULT '',
`quantity` DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT 'meters',
`rolls` INT NOT NULL DEFAULT 0,
`price_per_meter` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`operator_id` VARCHAR(32) NOT NULL DEFAULT '',
`remark` TEXT,
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_record_id` (`record_id`),
KEY `idx_plan` (`plan_id`),
KEY `idx_tenant` (`tenant_id`),
KEY `idx_product` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='按计划分批入库';

View File

@ -1,174 +0,0 @@
-- Product/yarn/batch model migration.
-- Adds yarn master data, product batches, frozen product codes, and pan batch ownership.
-- Run after the pan/bolt and purchase-management migrations.
SET @db = DATABASE();
CREATE TABLE IF NOT EXISTS `inv_yarn` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`yarn_id` VARCHAR(32) NOT NULL COMMENT 'business key, UUID',
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`yarn_name` VARCHAR(100) NOT NULL,
`color` VARCHAR(50) NOT NULL DEFAULT '原纱本色',
`weight_g_m` DECIMAL(10,4) NOT NULL DEFAULT 0.0000 COMMENT 'yarn weight (g/m)',
`supplier_id` VARCHAR(32) NOT NULL DEFAULT '',
`dye_factory` VARCHAR(100) NOT NULL DEFAULT '',
`image_url` VARCHAR(255) NOT NULL DEFAULT '',
`remark` TEXT,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1:enabled 0:disabled',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_yarn_id` (`yarn_id`),
UNIQUE KEY `uk_tenant_yarn_name_color_supplier` (`tenant_id`, `yarn_name`, `color`, `supplier_id`),
KEY `idx_tenant_id` (`tenant_id`),
KEY `idx_supplier_id` (`supplier_id`),
KEY `idx_status` (`status`),
KEY `idx_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='原料纱档案';
CREATE TABLE IF NOT EXISTS `inv_product_batch` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`batch_id` VARCHAR(32) NOT NULL COMMENT 'business key, UUID',
`tenant_id` VARCHAR(32) NOT NULL DEFAULT '',
`product_id` VARCHAR(32) NOT NULL,
`batch_no` VARCHAR(64) NOT NULL DEFAULT '' COMMENT 'format: {product_code}-B001',
`yarn_ratio` JSON DEFAULT NULL,
`warp_weight_g_m` DECIMAL(10,4) NOT NULL DEFAULT 0.0000,
`weft_weight_g_m` DECIMAL(10,4) NOT NULL DEFAULT 0.0000,
`production_process` TEXT,
`remark` TEXT,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1:enabled 0:disabled',
`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deleted_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_batch_id` (`batch_id`),
UNIQUE KEY `uk_tenant_batch_no` (`tenant_id`, `batch_no`),
KEY `idx_product_id` (`product_id`),
KEY `idx_tenant_id` (`tenant_id`),
KEY `idx_status` (`status`),
KEY `idx_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品批次表';
-- inv_product.color_no
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND COLUMN_NAME='color_no');
SET @s = IF(@c=0, 'ALTER TABLE `inv_product` ADD COLUMN `color_no` VARCHAR(20) NOT NULL DEFAULT '''' COMMENT ''色号'' AFTER `color`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- inv_product.product_code
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND COLUMN_NAME='product_code');
SET @s = IF(@c=0, 'ALTER TABLE `inv_product` ADD COLUMN `product_code` VARCHAR(50) NOT NULL DEFAULT '''' COMMENT ''产品码,创建后冻结'' AFTER `color_no`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- inv_product_pan.batch_id
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product_pan' AND COLUMN_NAME='batch_id');
SET @s = IF(@c=0, 'ALTER TABLE `inv_product_pan` ADD COLUMN `batch_id` VARCHAR(32) NOT NULL DEFAULT '''' COMMENT ''FK inv_product_batch.batch_id'' AFTER `product_id`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @i = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product_pan' AND INDEX_NAME='idx_batch_id');
SET @s = IF(@i=0, 'ALTER TABLE `inv_product_pan` ADD KEY `idx_batch_id` (`batch_id`)', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- Align older pan/bolt installations with the current model.
SET @c = (SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product_bolt' AND COLUMN_NAME='color');
SET @s = IF(@c=0, 'ALTER TABLE `inv_product_bolt` ADD COLUMN `color` VARCHAR(50) NOT NULL DEFAULT '''' AFTER `length_m`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- Backfill product defaults.
UPDATE `inv_product`
SET `color` = '本色'
WHERE `deleted_at` IS NULL AND TRIM(COALESCE(`color`, '')) = '';
UPDATE `inv_product` p
JOIN (
SELECT id, LPAD(ROW_NUMBER() OVER (PARTITION BY tenant_id, product_name ORDER BY created_at, id), 2, '0') AS generated_color_no
FROM `inv_product`
WHERE `deleted_at` IS NULL
) x ON x.id = p.id
SET p.`color_no` = x.generated_color_no
WHERE TRIM(COALESCE(p.`color_no`, '')) = '';
-- Historical SQL cannot reliably derive pinyin initials. For migrated rows with
-- non-latin product names, use a stable P{id} prefix; new writes use backend pinyin.
UPDATE `inv_product` p
JOIN (
SELECT id,
UPPER(CONCAT(
CASE
WHEN REGEXP_REPLACE(product_name, '[^0-9A-Za-z]', '') = '' THEN CONCAT('P', id)
ELSE REGEXP_REPLACE(product_name, '[^0-9A-Za-z]', '')
END,
color_no
)) AS raw_code
FROM `inv_product`
WHERE `deleted_at` IS NULL
) b ON b.id = p.id
SET p.`product_code` = LEFT(b.raw_code, 50)
WHERE TRIM(COALESCE(p.`product_code`, '')) = '';
UPDATE `inv_product` p
JOIN (
SELECT id,
CASE
WHEN rn = 1 THEN raw_code
ELSE LEFT(CONCAT(raw_code, '-', id), 50)
END AS unique_code
FROM (
SELECT id, tenant_id, product_code AS raw_code,
ROW_NUMBER() OVER (PARTITION BY tenant_id, product_code ORDER BY id) AS rn
FROM `inv_product`
WHERE `deleted_at` IS NULL
) d
) u ON u.id = p.id
SET p.`product_code` = u.unique_code;
-- Default B001 batch for every historical product.
INSERT INTO `inv_product_batch` (
`batch_id`, `tenant_id`, `product_id`, `batch_no`,
`yarn_ratio`, `warp_weight_g_m`, `weft_weight_g_m`,
`production_process`, `remark`, `status`
)
SELECT
MD5(CONCAT('B001:', p.`tenant_id`, ':', p.`product_id`)),
p.`tenant_id`,
p.`product_id`,
CONCAT(p.`product_code`, '-B001'),
NULL,
0.0000,
0.0000,
'',
NULL,
1
FROM `inv_product` p
WHERE p.`deleted_at` IS NULL
AND NOT EXISTS (
SELECT 1 FROM `inv_product_batch` b
WHERE b.`tenant_id` = p.`tenant_id`
AND b.`product_id` = p.`product_id`
AND b.`batch_no` = CONCAT(p.`product_code`, '-B001')
);
UPDATE `inv_product_pan` pan
JOIN `inv_product` p ON p.`product_id` = pan.`product_id`
JOIN `inv_product_batch` b ON b.`product_id` = p.`product_id`
AND b.`tenant_id` = p.`tenant_id`
AND b.`batch_no` = CONCAT(p.`product_code`, '-B001')
SET pan.`batch_id` = b.`batch_id`
WHERE TRIM(COALESCE(pan.`batch_id`, '')) = '';
-- Replace the old natural key after product_code has been populated.
SET @i = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND INDEX_NAME='uk_product_name_spec_color');
SET @s = IF(@i>0, 'ALTER TABLE `inv_product` DROP INDEX `uk_product_name_spec_color`', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @i = (SELECT COUNT(*) FROM information_schema.STATISTICS WHERE TABLE_SCHEMA=@db AND TABLE_NAME='inv_product' AND INDEX_NAME='uk_tenant_product_code');
SET @s = IF(@i=0, 'ALTER TABLE `inv_product` ADD UNIQUE KEY `uk_tenant_product_code` (`tenant_id`, `product_code`)', 'SELECT 1');
PREPARE stmt FROM @s; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- Verification queries for release runbooks:
-- SELECT COUNT(*) AS empty_product_code FROM inv_product WHERE deleted_at IS NULL AND product_code = '';
-- SELECT tenant_id, product_code, COUNT(*) FROM inv_product WHERE deleted_at IS NULL GROUP BY tenant_id, product_code HAVING COUNT(*) > 1;
-- SELECT p.product_id FROM inv_product p LEFT JOIN inv_product_batch b ON b.product_id = p.product_id AND b.batch_no = CONCAT(p.product_code, '-B001') WHERE p.deleted_at IS NULL AND b.batch_id IS NULL;
-- SELECT pan.pan_id FROM inv_product_pan pan WHERE pan.batch_id = '';

View File

@ -1,15 +1,14 @@
# Local development config — fill in credentials before running locally.
Name: gateway-api Name: gateway-api
Host: 0.0.0.0 Host: 0.0.0.0
Port: 8888 Port: 8888
Auth: Auth:
AccessSecret: your_jwt_secret_here AccessSecret: muyu-wms-jwt-secret-key-2026
AccessExpire: 7200 AccessExpire: 7200
DefaultTenantId: t_default_001 DefaultTenantId: t_default_001
DataSource: root:your_password@tcp(127.0.0.1:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai DataSource: root:muyu2026@tcp(127.0.0.1:3306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai
PostgresDataSource: "postgres://muyu:your_password@127.0.0.1:5432/muyu_crm?sslmode=disable" PostgresDataSource: "postgres://muyu:muyu2026@127.0.0.1:5432/muyu_crm?sslmode=disable"
SystemRpc: SystemRpc:
Etcd: Etcd:

View File

@ -224,8 +224,6 @@ type (
ImageUrl string `json:"imageUrl"` ImageUrl string `json:"imageUrl"`
Spec string `json:"spec"` Spec string `json:"spec"`
Color string `json:"color"` Color string `json:"color"`
ColorNo string `json:"colorNo"`
ProductCode string `json:"productCode"`
SalesPrice string `json:"salesPrice"` SalesPrice string `json:"salesPrice"`
Remark string `json:"remark"` Remark string `json:"remark"`
Status int64 `json:"status"` Status int64 `json:"status"`
@ -237,19 +235,15 @@ type (
ImageUrl string `json:"imageUrl,optional"` ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"` Spec string `json:"spec,optional"`
Color string `json:"color,optional"` Color string `json:"color,optional"`
ColorNo string `json:"colorNo,optional"`
ProductCode string `json:"productCode,optional"`
SalesPrice string `json:"salesPrice,optional"` SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"` Remark string `json:"remark,optional"`
Pans []PanInput `json:"pans,optional"` Pans []PanInput `json:"pans,optional"`
InitialBatch ProductBatchInput `json:"initialBatch,optional"`
} }
UpdateProductReq { UpdateProductReq {
ProductName string `json:"productName,optional"` ProductName string `json:"productName,optional"`
ImageUrl string `json:"imageUrl,optional"` ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"` Spec string `json:"spec,optional"`
Color string `json:"color,optional"` Color string `json:"color,optional"`
ColorNo string `json:"colorNo,optional"`
SalesPrice string `json:"salesPrice,optional"` SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"` Remark string `json:"remark,optional"`
} }
@ -259,8 +253,6 @@ type (
ProductName string `form:"productName,optional"` ProductName string `form:"productName,optional"`
Spec string `form:"spec,optional"` Spec string `form:"spec,optional"`
Color string `form:"color,optional"` Color string `form:"color,optional"`
ColorNo string `form:"colorNo,optional"`
ProductCode string `form:"productCode,optional"`
Status int64 `form:"status,optional,default=-1"` Status int64 `form:"status,optional,default=-1"`
} }
ListProductResp { ListProductResp {
@ -287,8 +279,6 @@ type (
PanInfo { PanInfo {
PanId string `json:"panId"` PanId string `json:"panId"`
ProductId string `json:"productId"` ProductId string `json:"productId"`
BatchId string `json:"batchId"`
BatchNo string `json:"batchNo"`
Name string `json:"name"` Name string `json:"name"`
Position string `json:"position"` Position string `json:"position"`
SortOrder int64 `json:"sortOrder"` SortOrder int64 `json:"sortOrder"`
@ -300,7 +290,6 @@ type (
Name string `json:"name"` Name string `json:"name"`
Position string `json:"position,optional"` Position string `json:"position,optional"`
BoltLengths []string `json:"boltLengths,optional"` BoltLengths []string `json:"boltLengths,optional"`
BatchId string `json:"batchId,optional"`
} }
ListPanResp { ListPanResp {
List []PanInfo `json:"list"` List []PanInfo `json:"list"`
@ -342,8 +331,6 @@ type (
ProductId string `json:"productId"` ProductId string `json:"productId"`
ProductName string `json:"productName"` ProductName string `json:"productName"`
Color string `json:"color"` Color string `json:"color"`
ColorNo string `json:"colorNo"`
ProductCode string `json:"productCode"`
Spec string `json:"spec"` Spec string `json:"spec"`
ImageUrl string `json:"imageUrl"` ImageUrl string `json:"imageUrl"`
PanCount int64 `json:"panCount"` PanCount int64 `json:"panCount"`
@ -351,7 +338,6 @@ type (
TotalLengthM string `json:"totalLengthM"` TotalLengthM string `json:"totalLengthM"`
Locations string `json:"locations"` Locations string `json:"locations"`
SalesPrice string `json:"salesPrice"` SalesPrice string `json:"salesPrice"`
BatchCount int64 `json:"batchCount"`
} }
ListColorDetailReq { ListColorDetailReq {
ProductName string `form:"productName"` ProductName string `form:"productName"`
@ -361,85 +347,6 @@ type (
} }
) )
// ==================== Yarn & Batch Types ====================
type (
YarnInfo {
YarnId string `json:"yarnId"`
YarnName string `json:"yarnName"`
Color string `json:"color"`
WeightGM string `json:"weightGM"`
SupplierId string `json:"supplierId"`
SupplierName string `json:"supplierName"`
DyeFactory string `json:"dyeFactory"`
ImageUrl string `json:"imageUrl"`
Remark string `json:"remark"`
Status int64 `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
CreateYarnReq {
YarnName string `json:"yarnName"`
Color string `json:"color,optional"`
WeightGM string `json:"weightGM"`
SupplierId string `json:"supplierId"`
DyeFactory string `json:"dyeFactory,optional"`
ImageUrl string `json:"imageUrl,optional"`
Remark string `json:"remark,optional"`
}
UpdateYarnReq {
YarnName string `json:"yarnName,optional"`
Color string `json:"color,optional"`
WeightGM string `json:"weightGM,optional"`
SupplierId string `json:"supplierId,optional"`
DyeFactory string `json:"dyeFactory,optional"`
ImageUrl string `json:"imageUrl,optional"`
Remark string `json:"remark,optional"`
Status int64 `json:"status,optional,default=1"`
}
ListYarnReq {
Page int64 `form:"page,default=1"`
PageSize int64 `form:"pageSize,default=20"`
YarnName string `form:"yarnName,optional"`
SupplierId string `form:"supplierId,optional"`
Status int64 `form:"status,optional,default=-1"`
}
ListYarnResp {
Total int64 `json:"total"`
List []YarnInfo `json:"list"`
}
ProductBatchInput {
BatchNo string `json:"batchNo,optional"`
YarnRatio string `json:"yarnRatio,optional"`
WarpWeightGM string `json:"warpWeightGM,optional"`
WeftWeightGM string `json:"weftWeightGM,optional"`
ProductionProcess string `json:"productionProcess,optional"`
Remark string `json:"remark,optional"`
}
ProductBatchInfo {
BatchId string `json:"batchId"`
ProductId string `json:"productId"`
BatchNo string `json:"batchNo"`
YarnRatio string `json:"yarnRatio"`
WarpWeightGM string `json:"warpWeightGM"`
WeftWeightGM string `json:"weftWeightGM"`
ProductionProcess string `json:"productionProcess"`
Remark string `json:"remark"`
Status int64 `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
CreateProductBatchReq {
Batch ProductBatchInput `json:"batch"`
}
UpdateProductBatchReq {
Batch ProductBatchInput `json:"batch"`
Status int64 `json:"status,optional,default=1"`
}
ListProductBatchResp {
List []ProductBatchInfo `json:"list"`
}
)
// ==================== Stock Statistics Types ==================== // ==================== Stock Statistics Types ====================
type ( type (
StockSummaryResp { StockSummaryResp {
@ -818,51 +725,6 @@ service gateway-api {
put /products/:id/pans (SavePansReq) returns (IdResp) put /products/:id/pans (SavePansReq) returns (IdResp)
} }
// Inventory - Yarn Management
@server (
prefix: /api/v1/inventory
group: inventory/yarn
jwt: Auth
middleware: AuthorityMiddleware
)
service gateway-api {
@handler ListYarnHandler
get /yarns (ListYarnReq) returns (ListYarnResp)
@handler GetYarnHandler
get /yarns/:id returns (YarnInfo)
@handler CreateYarnHandler
post /yarns (CreateYarnReq) returns (IdResp)
@handler UpdateYarnHandler
put /yarns/:id (UpdateYarnReq) returns (IdResp)
@handler DeleteYarnHandler
delete /yarns/:id returns (IdResp)
}
// Inventory - Product Batch Management
@server (
prefix: /api/v1/inventory
group: inventory/productbatch
jwt: Auth
middleware: AuthorityMiddleware
)
service gateway-api {
@handler ListProductBatchHandler
get /products/:id/batches returns (ListProductBatchResp)
@handler CreateProductBatchHandler
post /products/:id/batches (CreateProductBatchReq) returns (IdResp)
@handler UpdateProductBatchHandler
put /products/:id/batches/:batchId (UpdateProductBatchReq) returns (IdResp)
@handler DeleteProductBatchHandler
delete /products/:id/batches/:batchId returns (IdResp)
}
// Inventory - Pan/Bolt Management // Inventory - Pan/Bolt Management
@server ( @server (
prefix: /api/v1/inventory prefix: /api/v1/inventory
@ -943,3 +805,4 @@ service gateway-api {
@handler ApproveStockAdjustHandler @handler ApproveStockAdjustHandler
post /adjusts/:id/approve (ApproveStockAdjustReq) returns (IdResp) post /adjusts/:id/approve (ApproveStockAdjustReq) returns (IdResp)
} }

View File

@ -20,5 +20,4 @@ type Config struct {
SystemRpc zrpc.RpcClientConf SystemRpc zrpc.RpcClientConf
InventoryRpc zrpc.RpcClientConf InventoryRpc zrpc.RpcClientConf
PurchaseRpc zrpc.RpcClientConf PurchaseRpc zrpc.RpcClientConf
ProductionRpc zrpc.RpcClientConf
} }

View File

@ -5,7 +5,6 @@ package product
import ( import (
"net/http" "net/http"
"net/url"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/inventory/product" "muyu-apiserver/gateway/internal/logic/inventory/product"
@ -15,14 +14,11 @@ import (
func ExportProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func ExportProductHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
l := product.NewExportProductLogic(r.Context(), svcCtx) l := product.NewExportProductLogic(r.Context(), svcCtx)
data, filename, err := l.ExportProduct() err := l.ExportProduct()
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)
} else { } else {
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") httpx.Ok(w)
w.Header().Set("Content-Disposition", "attachment; filename*=UTF-8''"+url.PathEscape(filename))
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
} }
} }
} }

View File

@ -1,32 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package productbatch
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/inventory/productbatch"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func CreateProductBatchHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreateProductBatchReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := productbatch.NewCreateProductBatchLogic(ctx, svcCtx)
resp, err := l.CreateProductBatch(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,28 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package productbatch
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/inventory/productbatch"
"muyu-apiserver/gateway/internal/svc"
)
func DeleteProductBatchHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := pathvar.Vars(r)
ctx := context.WithValue(r.Context(), "pathId", vars["id"])
ctx = context.WithValue(ctx, "batchId", vars["batchId"])
l := productbatch.NewDeleteProductBatchLogic(ctx, svcCtx)
resp, err := l.DeleteProductBatch()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,26 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package productbatch
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/inventory/productbatch"
"muyu-apiserver/gateway/internal/svc"
)
func ListProductBatchHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := productbatch.NewListProductBatchLogic(ctx, svcCtx)
resp, err := l.ListProductBatch()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,34 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package productbatch
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/inventory/productbatch"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func UpdateProductBatchHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpdateProductBatchReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
vars := pathvar.Vars(r)
ctx := context.WithValue(r.Context(), "pathId", vars["id"])
ctx = context.WithValue(ctx, "batchId", vars["batchId"])
l := productbatch.NewUpdateProductBatchLogic(ctx, svcCtx)
resp, err := l.UpdateProductBatch(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,29 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package yarn
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/inventory/yarn"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func CreateYarnHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreateYarnReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := yarn.NewCreateYarnLogic(r.Context(), svcCtx)
resp, err := l.CreateYarn(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,26 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package yarn
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/inventory/yarn"
"muyu-apiserver/gateway/internal/svc"
)
func DeleteYarnHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := yarn.NewDeleteYarnLogic(ctx, svcCtx)
resp, err := l.DeleteYarn()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,26 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package yarn
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/inventory/yarn"
"muyu-apiserver/gateway/internal/svc"
)
func GetYarnHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := yarn.NewGetYarnLogic(ctx, svcCtx)
resp, err := l.GetYarn()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,29 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package yarn
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/inventory/yarn"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func ListYarnHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListYarnReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := yarn.NewListYarnLogic(r.Context(), svcCtx)
resp, err := l.ListYarn(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,32 +0,0 @@
// Code scaffolded by goctl. Safe to edit.
package yarn
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/inventory/yarn"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func UpdateYarnHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.UpdateYarnReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"])
l := yarn.NewUpdateYarnLogic(ctx, svcCtx)
resp, err := l.UpdateYarn(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,28 +0,0 @@
package inbound
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/inbound"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func CreateInboundHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GatewayCreateInboundReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := inbound.NewCreateInboundLogic(r.Context(), svcCtx)
resp, err := l.CreateInbound(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,28 +0,0 @@
package inbound
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/inbound"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func ListInboundHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GatewayListInboundReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := inbound.NewListInboundLogic(r.Context(), svcCtx)
resp, err := l.ListInbound(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,28 +0,0 @@
package plan
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/plan"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func ConfirmPlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ConfirmPlanReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := plan.NewConfirmPlanLogic(r.Context(), svcCtx)
err := l.ConfirmPlan(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, types.IdResp{})
}
}
}

View File

@ -1,28 +0,0 @@
package plan
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/plan"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func CreateProductionPlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.CreateProductionPlanReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := plan.NewCreateProductionPlanLogic(r.Context(), svcCtx)
resp, err := l.CreateProductionPlan(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,24 +0,0 @@
package plan
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/production/plan"
"muyu-apiserver/gateway/internal/svc"
)
func GetProductionPlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["planId"])
l := plan.NewGetProductionPlanLogic(ctx, svcCtx)
resp, err := l.GetProductionPlan()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,28 +0,0 @@
package plan
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/plan"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func ListProductionPlanHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListProductionPlanReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := plan.NewListProductionPlanLogic(r.Context(), svcCtx)
resp, err := l.ListProductionPlan(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,28 +0,0 @@
package process
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/process"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func CompleteStepHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GatewayCompleteStepReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := process.NewCompleteStepLogic(r.Context(), svcCtx)
err := l.CompleteStep(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, types.IdResp{})
}
}
}

View File

@ -1,28 +0,0 @@
package share
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/share"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func ClickShareLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GatewayClickShareLinkReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := share.NewClickShareLinkLogic(r.Context(), svcCtx)
err := l.ClickShareLink(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, types.IdResp{})
}
}
}

View File

@ -1,28 +0,0 @@
package share
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/share"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func ConfirmShareLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GatewayConfirmShareLinkReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := share.NewConfirmShareLinkLogic(r.Context(), svcCtx)
err := l.ConfirmShareLink(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, types.IdResp{})
}
}
}

View File

@ -1,28 +0,0 @@
package share
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/share"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func CreateShareLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GatewayCreateShareLinkReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := share.NewCreateShareLinkLogic(r.Context(), svcCtx)
resp, err := l.CreateShareLink(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,24 +0,0 @@
package share
import (
"context"
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/production/share"
"muyu-apiserver/gateway/internal/svc"
)
func GetShareLinkPlanViewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["shortCode"])
l := share.NewGetShareLinkPlanViewLogic(ctx, svcCtx)
resp, err := l.GetShareLinkPlanView()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

View File

@ -1,28 +0,0 @@
package share
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"muyu-apiserver/gateway/internal/logic/production/share"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
)
func RejectShareLinkHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GatewayRejectShareLinkReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := share.NewRejectShareLinkLogic(r.Context(), svcCtx)
err := l.RejectShareLink(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, types.IdResp{})
}
}
}

View File

@ -1,19 +1,16 @@
package order package order
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/order" "muyu-apiserver/gateway/internal/logic/purchase/order"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
) )
func CancelPurchaseOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func CancelPurchaseOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := order.NewCancelPurchaseOrderLogic(r.Context(), svcCtx)
l := order.NewCancelPurchaseOrderLogic(ctx, svcCtx)
err := l.CancelPurchaseOrder() err := l.CancelPurchaseOrder()
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -1,19 +1,16 @@
package order package order
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/order" "muyu-apiserver/gateway/internal/logic/purchase/order"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
) )
func ConfirmPurchaseOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func ConfirmPurchaseOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := order.NewConfirmPurchaseOrderLogic(r.Context(), svcCtx)
l := order.NewConfirmPurchaseOrderLogic(ctx, svcCtx)
err := l.ConfirmPurchaseOrder() err := l.ConfirmPurchaseOrder()
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -1,19 +1,16 @@
package order package order
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/order" "muyu-apiserver/gateway/internal/logic/purchase/order"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
) )
func GetPurchaseOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func GetPurchaseOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := order.NewGetPurchaseOrderLogic(r.Context(), svcCtx)
l := order.NewGetPurchaseOrderLogic(ctx, svcCtx)
resp, err := l.GetPurchaseOrder() resp, err := l.GetPurchaseOrder()
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -1,11 +1,9 @@
package order package order
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/order" "muyu-apiserver/gateway/internal/logic/purchase/order"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types" "muyu-apiserver/gateway/internal/types"
@ -19,8 +17,7 @@ func UpdatePurchaseOrderHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return return
} }
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := order.NewUpdatePurchaseOrderLogic(r.Context(), svcCtx)
l := order.NewUpdatePurchaseOrderLogic(ctx, svcCtx)
err := l.UpdatePurchaseOrder(&req) err := l.UpdatePurchaseOrder(&req)
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -1,19 +1,16 @@
package receipt package receipt
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/receipt" "muyu-apiserver/gateway/internal/logic/purchase/receipt"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
) )
func ConfirmPurchaseReceiptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func ConfirmPurchaseReceiptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := receipt.NewConfirmPurchaseReceiptLogic(r.Context(), svcCtx)
l := receipt.NewConfirmPurchaseReceiptLogic(ctx, svcCtx)
err := l.ConfirmPurchaseReceipt() err := l.ConfirmPurchaseReceipt()
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -1,19 +1,16 @@
package receipt package receipt
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/receipt" "muyu-apiserver/gateway/internal/logic/purchase/receipt"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
) )
func GetPurchaseReceiptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func GetPurchaseReceiptHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := receipt.NewGetPurchaseReceiptLogic(r.Context(), svcCtx)
l := receipt.NewGetPurchaseReceiptLogic(ctx, svcCtx)
resp, err := l.GetPurchaseReceipt() resp, err := l.GetPurchaseReceipt()
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -1,19 +1,16 @@
package supplier package supplier
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/supplier" "muyu-apiserver/gateway/internal/logic/purchase/supplier"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
) )
func DeleteSupplierHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func DeleteSupplierHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := supplier.NewDeleteSupplierLogic(r.Context(), svcCtx)
l := supplier.NewDeleteSupplierLogic(ctx, svcCtx)
err := l.DeleteSupplier() err := l.DeleteSupplier()
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -1,19 +1,16 @@
package supplier package supplier
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/supplier" "muyu-apiserver/gateway/internal/logic/purchase/supplier"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
) )
func GetSupplierHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { func GetSupplierHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := supplier.NewGetSupplierLogic(r.Context(), svcCtx)
l := supplier.NewGetSupplierLogic(ctx, svcCtx)
resp, err := l.GetSupplier() resp, err := l.GetSupplier()
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -1,11 +1,9 @@
package supplier package supplier
import ( import (
"context"
"net/http" "net/http"
"github.com/zeromicro/go-zero/rest/httpx" "github.com/zeromicro/go-zero/rest/httpx"
"github.com/zeromicro/go-zero/rest/pathvar"
"muyu-apiserver/gateway/internal/logic/purchase/supplier" "muyu-apiserver/gateway/internal/logic/purchase/supplier"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types" "muyu-apiserver/gateway/internal/types"
@ -19,8 +17,7 @@ func UpdateSupplierHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return return
} }
ctx := context.WithValue(r.Context(), "pathId", pathvar.Vars(r)["id"]) l := supplier.NewUpdateSupplierLogic(r.Context(), svcCtx)
l := supplier.NewUpdateSupplierLogic(ctx, svcCtx)
err := l.UpdateSupplier(&req) err := l.UpdateSupplier(&req)
if err != nil { if err != nil {
httpx.ErrorCtx(r.Context(), w, err) httpx.ErrorCtx(r.Context(), w, err)

View File

@ -13,13 +13,7 @@ import (
inventorylog "muyu-apiserver/gateway/internal/handler/inventory/log" inventorylog "muyu-apiserver/gateway/internal/handler/inventory/log"
inventorypanbolt "muyu-apiserver/gateway/internal/handler/inventory/panbolt" inventorypanbolt "muyu-apiserver/gateway/internal/handler/inventory/panbolt"
inventoryproduct "muyu-apiserver/gateway/internal/handler/inventory/product" inventoryproduct "muyu-apiserver/gateway/internal/handler/inventory/product"
inventoryproductbatch "muyu-apiserver/gateway/internal/handler/inventory/productbatch"
inventorystock "muyu-apiserver/gateway/internal/handler/inventory/stock" inventorystock "muyu-apiserver/gateway/internal/handler/inventory/stock"
inventoryyarn "muyu-apiserver/gateway/internal/handler/inventory/yarn"
productioninbound "muyu-apiserver/gateway/internal/handler/production/inbound"
productionplan "muyu-apiserver/gateway/internal/handler/production/plan"
productionprocess "muyu-apiserver/gateway/internal/handler/production/process"
productionshare "muyu-apiserver/gateway/internal/handler/production/share"
purchaseorder "muyu-apiserver/gateway/internal/handler/purchase/order" purchaseorder "muyu-apiserver/gateway/internal/handler/purchase/order"
purchasepayment "muyu-apiserver/gateway/internal/handler/purchase/payment" purchasepayment "muyu-apiserver/gateway/internal/handler/purchase/payment"
purchasereceipt "muyu-apiserver/gateway/internal/handler/purchase/receipt" purchasereceipt "muyu-apiserver/gateway/internal/handler/purchase/receipt"
@ -150,71 +144,6 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1/inventory"), rest.WithPrefix("/api/v1/inventory"),
) )
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/yarns",
Handler: inventoryyarn.ListYarnHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/yarns/:id",
Handler: inventoryyarn.GetYarnHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/yarns",
Handler: inventoryyarn.CreateYarnHandler(serverCtx),
},
{
Method: http.MethodPut,
Path: "/yarns/:id",
Handler: inventoryyarn.UpdateYarnHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/yarns/:id",
Handler: inventoryyarn.DeleteYarnHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/inventory"),
)
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},
[]rest.Route{
{
Method: http.MethodGet,
Path: "/products/:id/batches",
Handler: inventoryproductbatch.ListProductBatchHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/products/:id/batches",
Handler: inventoryproductbatch.CreateProductBatchHandler(serverCtx),
},
{
Method: http.MethodPut,
Path: "/products/:id/batches/:batchId",
Handler: inventoryproductbatch.UpdateProductBatchHandler(serverCtx),
},
{
Method: http.MethodDelete,
Path: "/products/:id/batches/:batchId",
Handler: inventoryproductbatch.DeleteProductBatchHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/inventory"),
)
server.AddRoutes( server.AddRoutes(
rest.WithMiddlewares( rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware}, []rest.Middleware{serverCtx.AuthorityMiddleware},
@ -705,120 +634,4 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithJwt(serverCtx.Config.Auth.AccessSecret), rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/inventory"), rest.WithPrefix("/api/v1/inventory"),
) )
// ─── Production Plan (JWT + Authority) ──────────────────────────────────
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/plan",
Handler: productionplan.CreateProductionPlanHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/plan",
Handler: productionplan.ListProductionPlanHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/plan/:planId",
Handler: productionplan.GetProductionPlanHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/plan/confirm",
Handler: productionplan.ConfirmPlanHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/production"),
)
// ─── Production Share (JWT + Authority) ─────────────────────────────────
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/share",
Handler: productionshare.CreateShareLinkHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/share/confirm",
Handler: productionshare.ConfirmShareLinkHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/share/reject",
Handler: productionshare.RejectShareLinkHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/production"),
)
// ─── Production Process (JWT + Authority) ───────────────────────────────
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/process/step",
Handler: productionprocess.CompleteStepHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/production"),
)
// ─── Production Inbound (JWT + Authority) ───────────────────────────────
server.AddRoutes(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},
[]rest.Route{
{
Method: http.MethodPost,
Path: "/inbound",
Handler: productioninbound.CreateInboundHandler(serverCtx),
},
{
Method: http.MethodGet,
Path: "/inbound",
Handler: productioninbound.ListInboundHandler(serverCtx),
},
}...,
),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
rest.WithPrefix("/api/v1/production"),
)
// ─── Production Public Share (NO JWT) ───────────────────────────────────
server.AddRoutes(
[]rest.Route{
{
Method: http.MethodGet,
Path: "/share/:shortCode",
Handler: productionshare.GetShareLinkPlanViewHandler(serverCtx),
},
{
Method: http.MethodPost,
Path: "/share/click",
Handler: productionshare.ClickShareLinkHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1/public/production"),
)
} }

View File

@ -40,8 +40,6 @@ func (l *ListPanViewLogic) ListPanView(req *types.ListPanViewReq) (resp *types.L
list = append(list, types.PanListItem{ list = append(list, types.PanListItem{
PanId: item.PanId, PanId: item.PanId,
ProductId: item.ProductId, ProductId: item.ProductId,
BatchId: item.BatchId,
BatchNo: item.BatchNo,
Name: item.Name, Name: item.Name,
Position: item.Position, Position: item.Position,
ProductName: item.ProductName, ProductName: item.ProductName,

View File

@ -30,12 +30,9 @@ func (l *CreateProductLogic) CreateProduct(req *types.CreateProductReq) (resp *t
ImageUrl: req.ImageUrl, ImageUrl: req.ImageUrl,
Spec: req.Spec, Spec: req.Spec,
Color: req.Color, Color: req.Color,
ColorNo: req.ColorNo,
ProductCode: req.ProductCode,
SalesPrice: req.SalesPrice, SalesPrice: req.SalesPrice,
Remark: req.Remark, Remark: req.Remark,
Pans: panInputsToPb(req.Pans), Pans: panInputsToPb(req.Pans),
InitialBatch: productBatchInputToPb(req.InitialBatch),
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -54,22 +51,7 @@ func panInputsToPb(pans []types.PanInput) []*pb.PanInput {
Name: p.Name, Name: p.Name,
Position: p.Position, Position: p.Position,
BoltLengths: p.BoltLengths, BoltLengths: p.BoltLengths,
BatchId: p.BatchId,
}) })
} }
return out return out
} }
func productBatchInputToPb(input types.ProductBatchInput) *pb.ProductBatchInput {
if input == (types.ProductBatchInput{}) {
return nil
}
return &pb.ProductBatchInput{
BatchNo: input.BatchNo,
YarnRatio: input.YarnRatio,
WarpWeightGM: input.WarpWeightGM,
WeftWeightGM: input.WeftWeightGM,
ProductionProcess: input.ProductionProcess,
Remark: input.Remark,
}
}

View File

@ -2,13 +2,10 @@ package product
import ( import (
"context" "context"
"fmt" "errors"
"time"
"muyu-apiserver/gateway/internal/svc" "muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/xuri/excelize/v2"
"github.com/zeromicro/go-zero/core/logx" "github.com/zeromicro/go-zero/core/logx"
) )
@ -26,99 +23,6 @@ func NewExportProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Exp
} }
} }
func (l *ExportProductLogic) ExportProduct() ([]byte, string, error) { func (l *ExportProductLogic) ExportProduct() error {
products, err := l.listAllProducts() return errors.New("export product requires response writer, will be implemented in handler layer")
if err != nil {
return nil, "", err
}
f := excelize.NewFile()
defer f.Close()
sheet := "产品"
if err := f.SetSheetName("Sheet1", sheet); err != nil {
return nil, "", err
}
headers := []interface{}{"产品名称", "规格型号", "颜色", "色号", "产品码", "销售价", "批次号", "纱线配比", "经线克重(g/m)", "纬线克重(g/m)", "生产工艺", "备注"}
if err := f.SetSheetRow(sheet, "A1", &headers); err != nil {
return nil, "", err
}
row := 2
for _, product := range products {
batchResp, err := l.svcCtx.InventoryRpc.ListProductBatch(l.ctx, &pb.ListProductBatchReq{ProductId: product.ProductId})
if err != nil {
return nil, "", err
}
if len(batchResp.List) == 0 {
if err := setProductExportRow(f, sheet, row, product, nil); err != nil {
return nil, "", err
}
row++
continue
}
for _, batch := range batchResp.List {
if err := setProductExportRow(f, sheet, row, product, batch); err != nil {
return nil, "", err
}
row++
}
}
if err := f.SetColWidth(sheet, "A", "L", 18); err != nil {
return nil, "", err
}
buf, err := f.WriteToBuffer()
if err != nil {
return nil, "", err
}
filename := fmt.Sprintf("products-%s.xlsx", time.Now().Format("20060102150405"))
return buf.Bytes(), filename, nil
}
func (l *ExportProductLogic) listAllProducts() ([]*pb.ProductInfo, error) {
const pageSize int64 = 500
var all []*pb.ProductInfo
for page := int64(1); ; page++ {
resp, err := l.svcCtx.InventoryRpc.ListProduct(l.ctx, &pb.ListProductReq{Page: page, PageSize: pageSize, Status: -1})
if err != nil {
return nil, err
}
all = append(all, resp.List...)
if int64(len(all)) >= resp.Total || len(resp.List) == 0 {
return all, nil
}
}
}
func setProductExportRow(f *excelize.File, sheet string, row int, product *pb.ProductInfo, batch *pb.ProductBatchInfo) error {
values := []interface{}{
product.ProductName,
product.Spec,
product.Color,
product.ColorNo,
product.ProductCode,
product.SalesPrice,
"",
"",
"",
"",
"",
product.Remark,
}
if batch != nil {
values[6] = batch.BatchNo
values[7] = batch.YarnRatio
values[8] = batch.WarpWeightGM
values[9] = batch.WeftWeightGM
values[10] = batch.ProductionProcess
if batch.Remark != "" {
values[11] = batch.Remark
}
}
cell, err := excelize.CoordinatesToCellName(1, row)
if err != nil {
return err
}
return f.SetSheetRow(sheet, cell, &values)
} }

View File

@ -37,8 +37,6 @@ func (l *GetColorDetailLogic) GetColorDetail(req *types.ListColorDetailReq) (res
ProductId: it.ProductId, ProductId: it.ProductId,
ProductName: it.ProductName, ProductName: it.ProductName,
Color: it.Color, Color: it.Color,
ColorNo: it.ColorNo,
ProductCode: it.ProductCode,
Spec: it.Spec, Spec: it.Spec,
ImageUrl: it.ImageUrl, ImageUrl: it.ImageUrl,
PanCount: it.PanCount, PanCount: it.PanCount,
@ -46,7 +44,6 @@ func (l *GetColorDetailLogic) GetColorDetail(req *types.ListColorDetailReq) (res
TotalLengthM: it.TotalLengthM, TotalLengthM: it.TotalLengthM,
Locations: it.Locations, Locations: it.Locations,
SalesPrice: it.SalesPrice, SalesPrice: it.SalesPrice,
BatchCount: it.BatchCount,
}) })
} }
return &types.ColorDetailResp{List: list}, nil return &types.ColorDetailResp{List: list}, nil

View File

@ -42,17 +42,8 @@ func (l *ImportProductLogic) ImportProduct(filePath, fileName string) (resp *typ
ProductName: p.ProductName, ProductName: p.ProductName,
Spec: p.Spec, Spec: p.Spec,
Color: p.Color, Color: p.Color,
ColorNo: p.ColorNo,
ProductCode: p.ProductCode,
SalesPrice: p.SalesPrice, SalesPrice: p.SalesPrice,
Remark: p.Remark, Remark: p.Remark,
InitialBatch: &pb.ProductBatchInput{
BatchNo: p.BatchNo,
YarnRatio: p.YarnRatio,
WarpWeightGM: p.WarpWeightGM,
WeftWeightGM: p.WeftWeightGM,
ProductionProcess: p.ProductionProcess,
},
}) })
} }

View File

@ -57,8 +57,6 @@ func panInfoFromPB(p *pb.PanInfo) types.PanInfo {
return types.PanInfo{ return types.PanInfo{
PanId: p.PanId, PanId: p.PanId,
ProductId: p.ProductId, ProductId: p.ProductId,
BatchId: p.BatchId,
BatchNo: p.BatchNo,
Name: p.Name, Name: p.Name,
Position: p.Position, Position: p.Position,
SortOrder: p.SortOrder, SortOrder: p.SortOrder,
@ -78,8 +76,6 @@ func productInfoFromPB(p *pb.ProductInfo) types.ProductInfo {
ImageUrl: p.ImageUrl, ImageUrl: p.ImageUrl,
Spec: p.Spec, Spec: p.Spec,
Color: p.Color, Color: p.Color,
ColorNo: p.ColorNo,
ProductCode: p.ProductCode,
SalesPrice: p.SalesPrice, SalesPrice: p.SalesPrice,
Remark: p.Remark, Remark: p.Remark,
Status: p.Status, Status: p.Status,

View File

@ -31,8 +31,6 @@ func (l *ListProductLogic) ListProduct(req *types.ListProductReq) (resp *types.L
ProductName: req.ProductName, ProductName: req.ProductName,
Spec: req.Spec, Spec: req.Spec,
Color: req.Color, Color: req.Color,
ColorNo: req.ColorNo,
ProductCode: req.ProductCode,
Status: req.Status, Status: req.Status,
}) })
if err != nil { if err != nil {

View File

@ -34,7 +34,6 @@ func (l *UpdateProductLogic) UpdateProduct(req *types.UpdateProductReq) (resp *t
ImageUrl: req.ImageUrl, ImageUrl: req.ImageUrl,
Spec: req.Spec, Spec: req.Spec,
Color: req.Color, Color: req.Color,
ColorNo: req.ColorNo,
SalesPrice: req.SalesPrice, SalesPrice: req.SalesPrice,
Remark: req.Remark, Remark: req.Remark,
}) })

View File

@ -1,34 +0,0 @@
package productbatch
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateProductBatchLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateProductBatchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateProductBatchLogic {
return &CreateProductBatchLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *CreateProductBatchLogic) CreateProductBatch(req *types.CreateProductBatchReq) (*types.IdResp, error) {
productId := ctxdata.GetPathId(l.ctx)
resp, err := l.svcCtx.InventoryRpc.CreateProductBatch(l.ctx, &pb.CreateProductBatchReq{
ProductId: productId,
Batch: batchInputToPB(req.Batch),
})
if err != nil {
return nil, err
}
return &types.IdResp{Id: resp.Id}, nil
}

View File

@ -1,31 +0,0 @@
package productbatch
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteProductBatchLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteProductBatchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteProductBatchLogic {
return &DeleteProductBatchLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *DeleteProductBatchLogic) DeleteProductBatch() (*types.IdResp, error) {
productId := ctxdata.GetPathId(l.ctx)
batchId, _ := l.ctx.Value("batchId").(string)
if _, err := l.svcCtx.InventoryRpc.DeleteProductBatch(l.ctx, &pb.DeleteProductBatchReq{ProductId: productId, BatchId: batchId}); err != nil {
return nil, err
}
return &types.IdResp{Id: batchId}, nil
}

View File

@ -1,36 +0,0 @@
package productbatch
import (
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/inventory/pb"
)
func batchInputToPB(input types.ProductBatchInput) *pb.ProductBatchInput {
return &pb.ProductBatchInput{
BatchNo: input.BatchNo,
YarnRatio: input.YarnRatio,
WarpWeightGM: input.WarpWeightGM,
WeftWeightGM: input.WeftWeightGM,
ProductionProcess: input.ProductionProcess,
Remark: input.Remark,
}
}
func batchInfoFromPB(b *pb.ProductBatchInfo) types.ProductBatchInfo {
if b == nil {
return types.ProductBatchInfo{}
}
return types.ProductBatchInfo{
BatchId: b.BatchId,
ProductId: b.ProductId,
BatchNo: b.BatchNo,
YarnRatio: b.YarnRatio,
WarpWeightGM: b.WarpWeightGM,
WeftWeightGM: b.WeftWeightGM,
ProductionProcess: b.ProductionProcess,
Remark: b.Remark,
Status: b.Status,
CreatedAt: b.CreatedAt,
UpdatedAt: b.UpdatedAt,
}
}

View File

@ -1,35 +0,0 @@
package productbatch
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type ListProductBatchLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListProductBatchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListProductBatchLogic {
return &ListProductBatchLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *ListProductBatchLogic) ListProductBatch() (*types.ListProductBatchResp, error) {
productId := ctxdata.GetPathId(l.ctx)
resp, err := l.svcCtx.InventoryRpc.ListProductBatch(l.ctx, &pb.ListProductBatchReq{ProductId: productId})
if err != nil {
return nil, err
}
list := make([]types.ProductBatchInfo, 0, len(resp.List))
for _, item := range resp.List {
list = append(list, batchInfoFromPB(item))
}
return &types.ListProductBatchResp{List: list}, nil
}

View File

@ -1,37 +0,0 @@
package productbatch
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateProductBatchLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateProductBatchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateProductBatchLogic {
return &UpdateProductBatchLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *UpdateProductBatchLogic) UpdateProductBatch(req *types.UpdateProductBatchReq) (*types.IdResp, error) {
productId := ctxdata.GetPathId(l.ctx)
batchId, _ := l.ctx.Value("batchId").(string)
_, err := l.svcCtx.InventoryRpc.UpdateProductBatch(l.ctx, &pb.UpdateProductBatchReq{
ProductId: productId,
BatchId: batchId,
Batch: batchInputToPB(req.Batch),
Status: req.Status,
})
if err != nil {
return nil, err
}
return &types.IdResp{Id: batchId}, nil
}

View File

@ -1,37 +0,0 @@
package yarn
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateYarnLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateYarnLogic {
return &CreateYarnLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *CreateYarnLogic) CreateYarn(req *types.CreateYarnReq) (*types.IdResp, error) {
resp, err := l.svcCtx.InventoryRpc.CreateYarn(l.ctx, &pb.CreateYarnReq{
YarnName: req.YarnName,
Color: req.Color,
WeightGM: req.WeightGM,
SupplierId: req.SupplierId,
DyeFactory: req.DyeFactory,
ImageUrl: req.ImageUrl,
Remark: req.Remark,
})
if err != nil {
return nil, err
}
return &types.IdResp{Id: resp.Id}, nil
}

View File

@ -1,30 +0,0 @@
package yarn
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type DeleteYarnLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewDeleteYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteYarnLogic {
return &DeleteYarnLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *DeleteYarnLogic) DeleteYarn() (*types.IdResp, error) {
id := ctxdata.GetPathId(l.ctx)
if _, err := l.svcCtx.InventoryRpc.DeleteYarn(l.ctx, &pb.DeleteYarnReq{YarnId: id}); err != nil {
return nil, err
}
return &types.IdResp{Id: id}, nil
}

View File

@ -1,32 +0,0 @@
package yarn
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type GetYarnLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetYarnLogic {
return &GetYarnLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *GetYarnLogic) GetYarn() (*types.YarnInfo, error) {
id := ctxdata.GetPathId(l.ctx)
resp, err := l.svcCtx.InventoryRpc.GetYarn(l.ctx, &pb.GetYarnReq{YarnId: id})
if err != nil {
return nil, err
}
out := yarnInfoFromPB(resp)
return &out, nil
}

View File

@ -1,26 +0,0 @@
package yarn
import (
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/inventory/pb"
)
func yarnInfoFromPB(y *pb.YarnInfo) types.YarnInfo {
if y == nil {
return types.YarnInfo{}
}
return types.YarnInfo{
YarnId: y.YarnId,
YarnName: y.YarnName,
Color: y.Color,
WeightGM: y.WeightGM,
SupplierId: y.SupplierId,
SupplierName: y.SupplierName,
DyeFactory: y.DyeFactory,
ImageUrl: y.ImageUrl,
Remark: y.Remark,
Status: y.Status,
CreatedAt: y.CreatedAt,
UpdatedAt: y.UpdatedAt,
}
}

View File

@ -1,39 +0,0 @@
package yarn
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type ListYarnLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListYarnLogic {
return &ListYarnLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *ListYarnLogic) ListYarn(req *types.ListYarnReq) (*types.ListYarnResp, error) {
resp, err := l.svcCtx.InventoryRpc.ListYarn(l.ctx, &pb.ListYarnReq{
Page: req.Page,
PageSize: req.PageSize,
YarnName: req.YarnName,
SupplierId: req.SupplierId,
Status: req.Status,
})
if err != nil {
return nil, err
}
list := make([]types.YarnInfo, 0, len(resp.List))
for _, item := range resp.List {
list = append(list, yarnInfoFromPB(item))
}
return &types.ListYarnResp{Total: resp.Total, List: list}, nil
}

View File

@ -1,41 +0,0 @@
package yarn
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type UpdateYarnLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewUpdateYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateYarnLogic {
return &UpdateYarnLogic{Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx}
}
func (l *UpdateYarnLogic) UpdateYarn(req *types.UpdateYarnReq) (*types.IdResp, error) {
id := ctxdata.GetPathId(l.ctx)
_, err := l.svcCtx.InventoryRpc.UpdateYarn(l.ctx, &pb.UpdateYarnReq{
YarnId: id,
YarnName: req.YarnName,
Color: req.Color,
WeightGM: req.WeightGM,
SupplierId: req.SupplierId,
DyeFactory: req.DyeFactory,
ImageUrl: req.ImageUrl,
Remark: req.Remark,
Status: req.Status,
})
if err != nil {
return nil, err
}
return &types.IdResp{Id: id}, nil
}

View File

@ -1,43 +0,0 @@
package inbound
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateInboundLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateInboundLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateInboundLogic {
return &CreateInboundLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateInboundLogic) CreateInbound(req *types.GatewayCreateInboundReq) (*types.IdResp, error) {
resp, err := l.svcCtx.ProductionRpc.CreateInbound(l.ctx, &productionservice.CreateInboundReq{
PlanId: req.PlanId,
ProductId: req.ProductId,
BatchNo: req.BatchNo,
Quantity: req.Quantity,
Rolls: req.Rolls,
PricePerMeter: req.PricePerMeter,
OperatorId: ctxdata.GetUserId(l.ctx),
Remark: req.Remark,
})
if err != nil {
return nil, err
}
return &types.IdResp{Id: resp.Id}, nil
}

View File

@ -1,54 +0,0 @@
package inbound
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type ListInboundLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListInboundLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListInboundLogic {
return &ListInboundLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListInboundLogic) ListInbound(req *types.GatewayListInboundReq) (*types.ListInboundResp, error) {
rpcResp, err := l.svcCtx.ProductionRpc.ListInbound(l.ctx, &productionservice.ListInboundReq{
PlanId: req.PlanId,
Page: req.Page,
PageSize: req.PageSize,
})
if err != nil {
return nil, err
}
list := make([]types.InboundRecordInfo, 0, len(rpcResp.List))
for _, ir := range rpcResp.List {
list = append(list, types.InboundRecordInfo{
RecordId: ir.RecordId,
TenantId: ir.TenantId,
PlanId: ir.PlanId,
ProductId: ir.ProductId,
BatchNo: ir.BatchNo,
Quantity: ir.Quantity,
Rolls: ir.Rolls,
PricePerMeter: ir.PricePerMeter,
OperatorId: ir.OperatorId,
Remark: ir.Remark,
CreatedAt: ir.CreatedAt,
})
}
return &types.ListInboundResp{Total: rpcResp.Total, List: list}, nil
}

View File

@ -1,34 +0,0 @@
package plan
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type ConfirmPlanLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewConfirmPlanLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ConfirmPlanLogic {
return &ConfirmPlanLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ConfirmPlanLogic) ConfirmPlan(req *types.ConfirmPlanReq) error {
_, err := l.svcCtx.ProductionRpc.ConfirmPlan(l.ctx, &productionservice.ConfirmPlanReq{
PlanId: req.PlanId,
Operator: ctxdata.GetUserId(l.ctx),
})
return err
}

View File

@ -1,68 +0,0 @@
package plan
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateProductionPlanLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateProductionPlanLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateProductionPlanLogic {
return &CreateProductionPlanLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateProductionPlanLogic) CreateProductionPlan(req *types.CreateProductionPlanReq) (*types.IdResp, error) {
yarnRatios := make([]*productionservice.YarnRatioReq, 0, len(req.YarnRatios))
for _, yr := range req.YarnRatios {
yarnRatios = append(yarnRatios, &productionservice.YarnRatioReq{
YarnName: yr.YarnName,
Ratio: yr.Ratio,
AmountPerMeter: yr.AmountPerMeter,
TotalAmount: yr.TotalAmount,
})
}
processSteps := make([]*productionservice.ProcessStepReq, 0, len(req.ProcessSteps))
for _, ps := range req.ProcessSteps {
processSteps = append(processSteps, &productionservice.ProcessStepReq{
StepType: ps.StepType,
StepOrder: ps.StepOrder,
Remark: ps.Remark,
})
}
resp, err := l.svcCtx.ProductionRpc.CreateProductionPlan(l.ctx, &productionservice.CreateProductionPlanReq{
ProductId: req.ProductId,
ProductName: req.ProductName,
Color: req.Color,
FabricCode: req.FabricCode,
ColorCode: req.ColorCode,
TargetQuantity: req.TargetQuantity,
YarnUsagePerMeter: req.YarnUsagePerMeter,
ProductionPrice: req.ProductionPrice,
SupplierId: req.SupplierId,
Remark: req.Remark,
StartTime: req.StartTime,
Creator: ctxdata.GetUserId(l.ctx),
YarnRatios: yarnRatios,
ProcessSteps: processSteps,
})
if err != nil {
return nil, err
}
return &types.IdResp{Id: resp.Id}, nil
}

View File

@ -1,37 +0,0 @@
package plan
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type GetProductionPlanLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetProductionPlanLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductionPlanLogic {
return &GetProductionPlanLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetProductionPlanLogic) GetProductionPlan() (*types.ProductionPlanInfo, error) {
planId := ctxdata.GetPathId(l.ctx)
resp, err := l.svcCtx.ProductionRpc.GetProductionPlan(l.ctx, &productionservice.GetProductionPlanReq{
PlanId: planId,
})
if err != nil {
return nil, err
}
return productionPlanInfoFromPB(resp), nil
}

View File

@ -1,122 +0,0 @@
package plan
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type ListProductionPlanLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewListProductionPlanLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListProductionPlanLogic {
return &ListProductionPlanLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListProductionPlanLogic) ListProductionPlan(req *types.ListProductionPlanReq) (*types.ListProductionPlanResp, error) {
rpcResp, err := l.svcCtx.ProductionRpc.ListProductionPlan(l.ctx, &productionservice.ListProductionPlanReq{
Page: req.Page,
PageSize: req.PageSize,
Status: req.Status,
ProductName: req.ProductName,
PlanCode: req.PlanCode,
})
if err != nil {
return nil, err
}
list := make([]types.ProductionPlanInfo, 0, len(rpcResp.List))
for _, p := range rpcResp.List {
list = append(list, *productionPlanInfoFromPB(p))
}
return &types.ListProductionPlanResp{Total: rpcResp.Total, List: list}, nil
}
func productionPlanInfoFromPB(p *productionservice.ProductionPlanInfo) *types.ProductionPlanInfo {
if p == nil {
return &types.ProductionPlanInfo{}
}
yarnRatios := make([]types.YarnRatioInfo, 0, len(p.YarnRatios))
for _, yr := range p.YarnRatios {
yarnRatios = append(yarnRatios, types.YarnRatioInfo{
RatioId: yr.RatioId,
PlanId: yr.PlanId,
YarnName: yr.YarnName,
Ratio: yr.Ratio,
AmountPerMeter: yr.AmountPerMeter,
TotalAmount: yr.TotalAmount,
CreatedAt: yr.CreatedAt,
})
}
processSteps := make([]types.ProcessStepInfo, 0, len(p.ProcessSteps))
for _, ps := range p.ProcessSteps {
processSteps = append(processSteps, types.ProcessStepInfo{
StepId: ps.StepId,
PlanId: ps.PlanId,
StepType: ps.StepType,
StepOrder: ps.StepOrder,
Status: ps.Status,
OperatorId: ps.OperatorId,
CompletedAt: ps.CompletedAt,
Remark: ps.Remark,
CreatedAt: ps.CreatedAt,
UpdatedAt: ps.UpdatedAt,
})
}
inboundRecords := make([]types.InboundRecordInfo, 0, len(p.InboundRecords))
for _, ir := range p.InboundRecords {
inboundRecords = append(inboundRecords, types.InboundRecordInfo{
RecordId: ir.RecordId,
TenantId: ir.TenantId,
PlanId: ir.PlanId,
ProductId: ir.ProductId,
BatchNo: ir.BatchNo,
Quantity: ir.Quantity,
Rolls: ir.Rolls,
PricePerMeter: ir.PricePerMeter,
OperatorId: ir.OperatorId,
Remark: ir.Remark,
CreatedAt: ir.CreatedAt,
})
}
return &types.ProductionPlanInfo{
PlanId: p.PlanId,
TenantId: p.TenantId,
PlanCode: p.PlanCode,
ProductId: p.ProductId,
ProductName: p.ProductName,
Color: p.Color,
FabricCode: p.FabricCode,
ColorCode: p.ColorCode,
TargetQuantity: p.TargetQuantity,
CompletedQuantity: p.CompletedQuantity,
YarnUsagePerMeter: p.YarnUsagePerMeter,
ProductionPrice: p.ProductionPrice,
SupplierId: p.SupplierId,
Status: p.Status,
Remark: p.Remark,
StartTime: p.StartTime,
Creator: p.Creator,
Operator: p.Operator,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
YarnRatios: yarnRatios,
ProcessSteps: processSteps,
InboundRecords: inboundRecords,
}
}

View File

@ -1,35 +0,0 @@
package process
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type CompleteStepLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCompleteStepLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CompleteStepLogic {
return &CompleteStepLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CompleteStepLogic) CompleteStep(req *types.GatewayCompleteStepReq) error {
_, err := l.svcCtx.ProductionRpc.CompleteStep(l.ctx, &productionservice.CompleteStepReq{
PlanId: req.PlanId,
StepType: req.StepType,
OperatorId: ctxdata.GetUserId(l.ctx),
})
return err
}

View File

@ -1,32 +0,0 @@
package share
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type ClickShareLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewClickShareLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ClickShareLinkLogic {
return &ClickShareLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ClickShareLinkLogic) ClickShareLink(req *types.GatewayClickShareLinkReq) error {
_, err := l.svcCtx.ProductionRpc.ClickShareLink(l.ctx, &productionservice.ClickShareLinkReq{
ShortCode: req.ShortCode,
})
return err
}

View File

@ -1,35 +0,0 @@
package share
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type ConfirmShareLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewConfirmShareLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ConfirmShareLinkLogic {
return &ConfirmShareLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ConfirmShareLinkLogic) ConfirmShareLink(req *types.GatewayConfirmShareLinkReq) error {
_, err := l.svcCtx.ProductionRpc.ConfirmShareLink(l.ctx, &productionservice.ConfirmShareLinkReq{
ShortCode: req.ShortCode,
Token: req.Token,
FactoryTenantId: ctxdata.GetTenantId(l.ctx),
})
return err
}

View File

@ -1,108 +0,0 @@
package share
import (
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/production/productionservice"
)
func productionPlanInfoFromPB(p *productionservice.ProductionPlanInfo) *types.ProductionPlanInfo {
if p == nil {
return &types.ProductionPlanInfo{}
}
yarnRatios := make([]types.YarnRatioInfo, 0, len(p.YarnRatios))
for _, yr := range p.YarnRatios {
yarnRatios = append(yarnRatios, types.YarnRatioInfo{
RatioId: yr.RatioId,
PlanId: yr.PlanId,
YarnName: yr.YarnName,
Ratio: yr.Ratio,
AmountPerMeter: yr.AmountPerMeter,
TotalAmount: yr.TotalAmount,
CreatedAt: yr.CreatedAt,
})
}
processSteps := make([]types.ProcessStepInfo, 0, len(p.ProcessSteps))
for _, ps := range p.ProcessSteps {
processSteps = append(processSteps, types.ProcessStepInfo{
StepId: ps.StepId,
PlanId: ps.PlanId,
StepType: ps.StepType,
StepOrder: ps.StepOrder,
Status: ps.Status,
OperatorId: ps.OperatorId,
CompletedAt: ps.CompletedAt,
Remark: ps.Remark,
CreatedAt: ps.CreatedAt,
UpdatedAt: ps.UpdatedAt,
})
}
inboundRecords := make([]types.InboundRecordInfo, 0, len(p.InboundRecords))
for _, ir := range p.InboundRecords {
inboundRecords = append(inboundRecords, types.InboundRecordInfo{
RecordId: ir.RecordId,
TenantId: ir.TenantId,
PlanId: ir.PlanId,
ProductId: ir.ProductId,
BatchNo: ir.BatchNo,
Quantity: ir.Quantity,
Rolls: ir.Rolls,
PricePerMeter: ir.PricePerMeter,
OperatorId: ir.OperatorId,
Remark: ir.Remark,
CreatedAt: ir.CreatedAt,
})
}
return &types.ProductionPlanInfo{
PlanId: p.PlanId,
TenantId: p.TenantId,
PlanCode: p.PlanCode,
ProductId: p.ProductId,
ProductName: p.ProductName,
Color: p.Color,
FabricCode: p.FabricCode,
ColorCode: p.ColorCode,
TargetQuantity: p.TargetQuantity,
CompletedQuantity: p.CompletedQuantity,
YarnUsagePerMeter: p.YarnUsagePerMeter,
ProductionPrice: p.ProductionPrice,
SupplierId: p.SupplierId,
Status: p.Status,
Remark: p.Remark,
StartTime: p.StartTime,
Creator: p.Creator,
Operator: p.Operator,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
YarnRatios: yarnRatios,
ProcessSteps: processSteps,
InboundRecords: inboundRecords,
}
}
func shareLinkInfoFromPB(s *productionservice.ShareLinkInfo) *types.ShareLinkInfoResp {
if s == nil {
return &types.ShareLinkInfoResp{}
}
return &types.ShareLinkInfoResp{
LinkId: s.LinkId,
TenantId: s.TenantId,
PlanId: s.PlanId,
ShortCode: s.ShortCode,
Token: s.Token,
FactoryType: s.FactoryType,
HideSensitive: s.HideSensitive,
Status: s.Status,
ClickCount: s.ClickCount,
ClickedAt: s.ClickedAt,
RespondedAt: s.RespondedAt,
RejectReason: s.RejectReason,
ExpiresAt: s.ExpiresAt,
CreatedBy: s.CreatedBy,
CreatedAt: s.CreatedAt,
UpdatedAt: s.UpdatedAt,
}
}

View File

@ -1,40 +0,0 @@
package share
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type CreateShareLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewCreateShareLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateShareLinkLogic {
return &CreateShareLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *CreateShareLinkLogic) CreateShareLink(req *types.GatewayCreateShareLinkReq) (*types.ShareLinkInfoResp, error) {
resp, err := l.svcCtx.ProductionRpc.CreateShareLink(l.ctx, &productionservice.CreateShareLinkReq{
PlanId: req.PlanId,
FactoryType: req.FactoryType,
HideSensitive: req.HideSensitive,
ExpiresHours: req.ExpiresHours,
CreatedBy: ctxdata.GetUserId(l.ctx),
})
if err != nil {
return nil, err
}
return shareLinkInfoFromPB(resp), nil
}

View File

@ -1,46 +0,0 @@
package share
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/pkg/ctxdata"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type GetShareLinkPlanViewLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewGetShareLinkPlanViewLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetShareLinkPlanViewLogic {
return &GetShareLinkPlanViewLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetShareLinkPlanViewLogic) GetShareLinkPlanView() (*types.ShareLinkPlanViewResp, error) {
shortCode := ctxdata.GetPathId(l.ctx)
resp, err := l.svcCtx.ProductionRpc.GetShareLinkPlanView(l.ctx, &productionservice.GetShareLinkReq{
ShortCode: shortCode,
})
if err != nil {
return nil, err
}
result := &types.ShareLinkPlanViewResp{}
if resp.Plan != nil {
plan := productionPlanInfoFromPB(resp.Plan)
result.Plan = plan
}
if resp.Link != nil {
result.Link = shareLinkInfoFromPB(resp.Link)
}
return result, nil
}

View File

@ -1,34 +0,0 @@
package share
import (
"context"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/gateway/internal/types"
"muyu-apiserver/rpc/production/productionservice"
"github.com/zeromicro/go-zero/core/logx"
)
type RejectShareLinkLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewRejectShareLinkLogic(ctx context.Context, svcCtx *svc.ServiceContext) *RejectShareLinkLogic {
return &RejectShareLinkLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *RejectShareLinkLogic) RejectShareLink(req *types.GatewayRejectShareLinkReq) error {
_, err := l.svcCtx.ProductionRpc.RejectShareLink(l.ctx, &productionservice.RejectShareLinkReq{
ShortCode: req.ShortCode,
Token: req.Token,
RejectReason: req.RejectReason,
})
return err
}

View File

@ -13,7 +13,6 @@ import (
pkgcasbin "muyu-apiserver/pkg/casbin" pkgcasbin "muyu-apiserver/pkg/casbin"
"muyu-apiserver/pkg/tenantctx" "muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/inventoryservice" "muyu-apiserver/rpc/inventory/inventoryservice"
"muyu-apiserver/rpc/production/productionservice"
"muyu-apiserver/rpc/purchase/purchaseservice" "muyu-apiserver/rpc/purchase/purchaseservice"
"muyu-apiserver/rpc/system/systemservice" "muyu-apiserver/rpc/system/systemservice"
) )
@ -26,7 +25,6 @@ type ServiceContext struct {
SystemRpc systemservice.SystemService SystemRpc systemservice.SystemService
InventoryRpc inventoryservice.InventoryService InventoryRpc inventoryservice.InventoryService
PurchaseRpc purchaseservice.PurchaseService PurchaseRpc purchaseservice.PurchaseService
ProductionRpc productionservice.ProductionService
} }
func NewServiceContext(c config.Config) *ServiceContext { func NewServiceContext(c config.Config) *ServiceContext {
@ -64,6 +62,5 @@ func NewServiceContext(c config.Config) *ServiceContext {
SystemRpc: systemservice.NewSystemService(zrpc.MustNewClient(c.SystemRpc, zrpc.WithUnaryClientInterceptor(tenantctx.UnaryClientInterceptor()))), SystemRpc: systemservice.NewSystemService(zrpc.MustNewClient(c.SystemRpc, zrpc.WithUnaryClientInterceptor(tenantctx.UnaryClientInterceptor()))),
InventoryRpc: inventoryservice.NewInventoryService(zrpc.MustNewClient(c.InventoryRpc, zrpc.WithUnaryClientInterceptor(tenantctx.UnaryClientInterceptor()))), InventoryRpc: inventoryservice.NewInventoryService(zrpc.MustNewClient(c.InventoryRpc, zrpc.WithUnaryClientInterceptor(tenantctx.UnaryClientInterceptor()))),
PurchaseRpc: purchaseservice.NewPurchaseService(zrpc.MustNewClient(c.PurchaseRpc, zrpc.WithUnaryClientInterceptor(tenantctx.UnaryClientInterceptor()))), PurchaseRpc: purchaseservice.NewPurchaseService(zrpc.MustNewClient(c.PurchaseRpc, zrpc.WithUnaryClientInterceptor(tenantctx.UnaryClientInterceptor()))),
ProductionRpc: productionservice.NewProductionService(zrpc.MustNewClient(c.ProductionRpc, zrpc.WithUnaryClientInterceptor(tenantctx.UnaryClientInterceptor()))),
} }
} }

View File

@ -38,12 +38,9 @@ type CreateProductReq struct {
ImageUrl string `json:"imageUrl,optional"` ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"` Spec string `json:"spec,optional"`
Color string `json:"color,optional"` Color string `json:"color,optional"`
ColorNo string `json:"colorNo,optional"`
ProductCode string `json:"productCode,optional"`
SalesPrice string `json:"salesPrice,optional"` SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"` Remark string `json:"remark,optional"`
Pans []PanInput `json:"pans,optional"` Pans []PanInput `json:"pans,optional"`
InitialBatch ProductBatchInput `json:"initialBatch,optional"`
} }
type CreateRoleReq struct { type CreateRoleReq struct {
@ -117,8 +114,6 @@ type ListProductReq struct {
ProductName string `form:"productName,optional"` ProductName string `form:"productName,optional"`
Spec string `form:"spec,optional"` Spec string `form:"spec,optional"`
Color string `form:"color,optional"` Color string `form:"color,optional"`
ColorNo string `form:"colorNo,optional"`
ProductCode string `form:"productCode,optional"`
Status int64 `form:"status,optional,default=-1"` Status int64 `form:"status,optional,default=-1"`
} }
@ -235,8 +230,6 @@ type ProductInfo struct {
ImageUrl string `json:"imageUrl"` ImageUrl string `json:"imageUrl"`
Spec string `json:"spec"` Spec string `json:"spec"`
Color string `json:"color"` Color string `json:"color"`
ColorNo string `json:"colorNo"`
ProductCode string `json:"productCode"`
SalesPrice string `json:"salesPrice"` SalesPrice string `json:"salesPrice"`
Remark string `json:"remark"` Remark string `json:"remark"`
Status int64 `json:"status"` Status int64 `json:"status"`
@ -254,8 +247,6 @@ type BoltInfo struct {
type PanInfo struct { type PanInfo struct {
PanId string `json:"panId"` PanId string `json:"panId"`
ProductId string `json:"productId"` ProductId string `json:"productId"`
BatchId string `json:"batchId"`
BatchNo string `json:"batchNo"`
Name string `json:"name"` Name string `json:"name"`
Position string `json:"position"` Position string `json:"position"`
SortOrder int64 `json:"sortOrder"` SortOrder int64 `json:"sortOrder"`
@ -268,7 +259,6 @@ type PanInput struct {
Name string `json:"name"` Name string `json:"name"`
Position string `json:"position,optional"` Position string `json:"position,optional"`
BoltLengths []string `json:"boltLengths,optional"` BoltLengths []string `json:"boltLengths,optional"`
BatchId string `json:"batchId,optional"`
} }
type ListPanResp struct { type ListPanResp struct {
@ -314,8 +304,6 @@ type ColorDetailItem struct {
ProductId string `json:"productId"` ProductId string `json:"productId"`
ProductName string `json:"productName"` ProductName string `json:"productName"`
Color string `json:"color"` Color string `json:"color"`
ColorNo string `json:"colorNo"`
ProductCode string `json:"productCode"`
Spec string `json:"spec"` Spec string `json:"spec"`
ImageUrl string `json:"imageUrl"` ImageUrl string `json:"imageUrl"`
PanCount int64 `json:"panCount"` PanCount int64 `json:"panCount"`
@ -323,7 +311,6 @@ type ColorDetailItem struct {
TotalLengthM string `json:"totalLengthM"` TotalLengthM string `json:"totalLengthM"`
Locations string `json:"locations"` Locations string `json:"locations"`
SalesPrice string `json:"salesPrice"` SalesPrice string `json:"salesPrice"`
BatchCount int64 `json:"batchCount"`
} }
type ListColorDetailReq struct { type ListColorDetailReq struct {
@ -334,96 +321,9 @@ type ColorDetailResp struct {
List []ColorDetailItem `json:"list"` List []ColorDetailItem `json:"list"`
} }
type YarnInfo struct {
YarnId string `json:"yarnId"`
YarnName string `json:"yarnName"`
Color string `json:"color"`
WeightGM string `json:"weightGM"`
SupplierId string `json:"supplierId"`
SupplierName string `json:"supplierName"`
DyeFactory string `json:"dyeFactory"`
ImageUrl string `json:"imageUrl"`
Remark string `json:"remark"`
Status int64 `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type CreateYarnReq struct {
YarnName string `json:"yarnName"`
Color string `json:"color,optional"`
WeightGM string `json:"weightGM"`
SupplierId string `json:"supplierId"`
DyeFactory string `json:"dyeFactory,optional"`
ImageUrl string `json:"imageUrl,optional"`
Remark string `json:"remark,optional"`
}
type UpdateYarnReq struct {
YarnName string `json:"yarnName"`
Color string `json:"color,optional"`
WeightGM string `json:"weightGM"`
SupplierId string `json:"supplierId"`
DyeFactory string `json:"dyeFactory,optional"`
ImageUrl string `json:"imageUrl,optional"`
Remark string `json:"remark,optional"`
Status int64 `json:"status,optional,default=1"`
}
type ListYarnReq struct {
Page int64 `form:"page,default=1"`
PageSize int64 `form:"pageSize,default=20"`
YarnName string `form:"yarnName,optional"`
SupplierId string `form:"supplierId,optional"`
Status int64 `form:"status,optional,default=-1"`
}
type ListYarnResp struct {
Total int64 `json:"total"`
List []YarnInfo `json:"list"`
}
type ProductBatchInput struct {
BatchNo string `json:"batchNo,optional"`
YarnRatio string `json:"yarnRatio,optional"`
WarpWeightGM string `json:"warpWeightGM,optional"`
WeftWeightGM string `json:"weftWeightGM,optional"`
ProductionProcess string `json:"productionProcess,optional"`
Remark string `json:"remark,optional"`
}
type ProductBatchInfo struct {
BatchId string `json:"batchId"`
ProductId string `json:"productId"`
BatchNo string `json:"batchNo"`
YarnRatio string `json:"yarnRatio"`
WarpWeightGM string `json:"warpWeightGM"`
WeftWeightGM string `json:"weftWeightGM"`
ProductionProcess string `json:"productionProcess"`
Remark string `json:"remark"`
Status int64 `json:"status"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type CreateProductBatchReq struct {
Batch ProductBatchInput `json:"batch"`
}
type UpdateProductBatchReq struct {
Batch ProductBatchInput `json:"batch"`
Status int64 `json:"status,optional,default=1"`
}
type ListProductBatchResp struct {
List []ProductBatchInfo `json:"list"`
}
type PanListItem struct { type PanListItem struct {
PanId string `json:"panId"` PanId string `json:"panId"`
ProductId string `json:"productId"` ProductId string `json:"productId"`
BatchId string `json:"batchId"`
BatchNo string `json:"batchNo"`
Name string `json:"name"` Name string `json:"name"`
Position string `json:"position"` Position string `json:"position"`
ProductName string `json:"productName"` ProductName string `json:"productName"`
@ -584,7 +484,6 @@ type UpdateProductReq struct {
ImageUrl string `json:"imageUrl,optional"` ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"` Spec string `json:"spec,optional"`
Color string `json:"color,optional"` Color string `json:"color,optional"`
ColorNo string `json:"colorNo,optional"`
SalesPrice string `json:"salesPrice,optional"` SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"` Remark string `json:"remark,optional"`
} }
@ -960,192 +859,3 @@ type PurchaseStatsByProductItem struct {
type PurchaseStatsByProductResp struct { type PurchaseStatsByProductResp struct {
List []PurchaseStatsByProductItem `json:"list"` List []PurchaseStatsByProductItem `json:"list"`
} }
// ─── Production Plan ────────────────────────────────────────────────────────
type GatewayYarnRatioReq struct {
YarnName string `json:"yarnName"`
Ratio string `json:"ratio"`
AmountPerMeter string `json:"amountPerMeter,optional"`
TotalAmount string `json:"totalAmount,optional"`
}
type GatewayProcessStepReq struct {
StepType string `json:"stepType"`
StepOrder int64 `json:"stepOrder"`
Remark string `json:"remark,optional"`
}
type CreateProductionPlanReq struct {
ProductId string `json:"productId"`
ProductName string `json:"productName"`
Color string `json:"color,optional"`
FabricCode string `json:"fabricCode,optional"`
ColorCode string `json:"colorCode,optional"`
TargetQuantity string `json:"targetQuantity"`
YarnUsagePerMeter string `json:"yarnUsagePerMeter,optional"`
ProductionPrice string `json:"productionPrice,optional"`
SupplierId string `json:"supplierId,optional"`
Remark string `json:"remark,optional"`
StartTime string `json:"startTime,optional"`
YarnRatios []GatewayYarnRatioReq `json:"yarnRatios,optional"`
ProcessSteps []GatewayProcessStepReq `json:"processSteps,optional"`
}
type ListProductionPlanReq struct {
Page int64 `form:"page,default=1"`
PageSize int64 `form:"pageSize,default=20"`
Status int64 `form:"status,optional,default=-1"`
ProductName string `form:"productName,optional"`
PlanCode string `form:"planCode,optional"`
}
type YarnRatioInfo struct {
RatioId string `json:"ratioId"`
PlanId string `json:"planId"`
YarnName string `json:"yarnName"`
Ratio string `json:"ratio"`
AmountPerMeter string `json:"amountPerMeter"`
TotalAmount string `json:"totalAmount"`
CreatedAt string `json:"createdAt"`
}
type ProcessStepInfo struct {
StepId string `json:"stepId"`
PlanId string `json:"planId"`
StepType string `json:"stepType"`
StepOrder int64 `json:"stepOrder"`
Status int64 `json:"status"`
OperatorId string `json:"operatorId"`
CompletedAt string `json:"completedAt"`
Remark string `json:"remark"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type InboundRecordInfo struct {
RecordId string `json:"recordId"`
TenantId string `json:"tenantId"`
PlanId string `json:"planId"`
ProductId string `json:"productId"`
BatchNo string `json:"batchNo"`
Quantity string `json:"quantity"`
Rolls int64 `json:"rolls"`
PricePerMeter string `json:"pricePerMeter"`
OperatorId string `json:"operatorId"`
Remark string `json:"remark"`
CreatedAt string `json:"createdAt"`
}
type ProductionPlanInfo struct {
PlanId string `json:"planId"`
TenantId string `json:"tenantId"`
PlanCode string `json:"planCode"`
ProductId string `json:"productId"`
ProductName string `json:"productName"`
Color string `json:"color"`
FabricCode string `json:"fabricCode"`
ColorCode string `json:"colorCode"`
TargetQuantity string `json:"targetQuantity"`
CompletedQuantity string `json:"completedQuantity"`
YarnUsagePerMeter string `json:"yarnUsagePerMeter"`
ProductionPrice string `json:"productionPrice"`
SupplierId string `json:"supplierId"`
Status int64 `json:"status"`
Remark string `json:"remark"`
StartTime string `json:"startTime"`
Creator string `json:"creator"`
Operator string `json:"operator"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
YarnRatios []YarnRatioInfo `json:"yarnRatios"`
ProcessSteps []ProcessStepInfo `json:"processSteps"`
InboundRecords []InboundRecordInfo `json:"inboundRecords"`
}
type ListProductionPlanResp struct {
Total int64 `json:"total"`
List []ProductionPlanInfo `json:"list"`
}
type ConfirmPlanReq struct {
PlanId string `json:"planId"`
}
// ─── Share Link ─────────────────────────────────────────────────────────────
type GatewayCreateShareLinkReq struct {
PlanId string `json:"planId"`
FactoryType string `json:"factoryType,optional"`
HideSensitive int64 `json:"hideSensitive,optional"`
ExpiresHours int64 `json:"expiresHours,optional"`
}
type ShareLinkInfoResp struct {
LinkId string `json:"linkId"`
TenantId string `json:"tenantId"`
PlanId string `json:"planId"`
ShortCode string `json:"shortCode"`
Token string `json:"token"`
FactoryType string `json:"factoryType"`
HideSensitive int64 `json:"hideSensitive"`
Status int64 `json:"status"`
ClickCount int64 `json:"clickCount"`
ClickedAt string `json:"clickedAt"`
RespondedAt string `json:"respondedAt"`
RejectReason string `json:"rejectReason"`
ExpiresAt string `json:"expiresAt"`
CreatedBy string `json:"createdBy"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
}
type ShareLinkPlanViewResp struct {
Plan *ProductionPlanInfo `json:"plan"`
Link *ShareLinkInfoResp `json:"link"`
}
type GatewayConfirmShareLinkReq struct {
ShortCode string `json:"shortCode"`
Token string `json:"token,optional"`
}
type GatewayRejectShareLinkReq struct {
ShortCode string `json:"shortCode"`
Token string `json:"token,optional"`
RejectReason string `json:"rejectReason,optional"`
}
type GatewayClickShareLinkReq struct {
ShortCode string `json:"shortCode"`
}
// ─── Process Step ───────────────────────────────────────────────────────────
type GatewayCompleteStepReq struct {
PlanId string `json:"planId"`
StepType string `json:"stepType"`
}
// ─── Inbound ────────────────────────────────────────────────────────────────
type GatewayCreateInboundReq struct {
PlanId string `json:"planId"`
ProductId string `json:"productId"`
BatchNo string `json:"batchNo,optional"`
Quantity string `json:"quantity"`
Rolls int64 `json:"rolls,optional"`
PricePerMeter string `json:"pricePerMeter,optional"`
Remark string `json:"remark,optional"`
}
type GatewayListInboundReq struct {
PlanId string `form:"planId,optional"`
Page int64 `form:"page,default=1"`
PageSize int64 `form:"pageSize,default=20"`
}
type ListInboundResp struct {
Total int64 `json:"total"`
List []InboundRecordInfo `json:"list"`
}

1
go.mod
View File

@ -66,7 +66,6 @@ require (
github.com/microsoft/go-mssqldb v1.9.5 // indirect github.com/microsoft/go-mssqldb v1.9.5 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/mozillazg/go-pinyin v0.21.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect github.com/openzipkin/zipkin-go v0.4.3 // indirect

2
go.sum
View File

@ -189,8 +189,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8=
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
github.com/mozillazg/go-pinyin v0.21.0 h1:Wo8/NT45z7P3er/9YSLHA3/kjZzbLz5hR7i+jGeIGao=
github.com/mozillazg/go-pinyin v0.21.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=

View File

@ -1,81 +0,0 @@
package model
import (
"context"
"fmt"
"time"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ InvProductBatchModel = (*customInvProductBatchModel)(nil)
type (
InvProductBatchModel interface {
invProductBatchModel
FindByProductId(ctx context.Context, tenantId, productId string) ([]*InvProductBatch, error)
FindLatestByProductId(ctx context.Context, tenantId, productId string) (*InvProductBatch, error)
FindOneByProductAndBatchId(ctx context.Context, tenantId, productId, batchId string) (*InvProductBatch, error)
NextBatchNo(ctx context.Context, tenantId, productCode string) (string, error)
SoftDeleteByBatchId(ctx context.Context, batchId string) error
}
customInvProductBatchModel struct {
*defaultInvProductBatchModel
}
)
func NewInvProductBatchModel(conn sqlx.SqlConn) InvProductBatchModel {
return &customInvProductBatchModel{
defaultInvProductBatchModel: newInvProductBatchModel(conn),
}
}
func (m *customInvProductBatchModel) FindByProductId(ctx context.Context, tenantId, productId string) ([]*InvProductBatch, error) {
var list []*InvProductBatch
query := fmt.Sprintf("SELECT %s FROM %s WHERE tenant_id = ? AND product_id = ? AND deleted_at IS NULL ORDER BY batch_no", invProductBatchRows, m.table)
if err := m.conn.QueryRowsCtx(ctx, &list, query, tenantId, productId); err != nil {
return nil, err
}
return list, nil
}
func (m *customInvProductBatchModel) FindLatestByProductId(ctx context.Context, tenantId, productId string) (*InvProductBatch, error) {
var resp InvProductBatch
query := fmt.Sprintf("SELECT %s FROM %s WHERE tenant_id = ? AND product_id = ? AND deleted_at IS NULL AND status = 1 ORDER BY batch_no DESC LIMIT 1", invProductBatchRows, m.table)
if err := m.conn.QueryRowCtx(ctx, &resp, query, tenantId, productId); err != nil {
return nil, err
}
return &resp, nil
}
func (m *customInvProductBatchModel) FindOneByProductAndBatchId(ctx context.Context, tenantId, productId, batchId string) (*InvProductBatch, error) {
var resp InvProductBatch
query := fmt.Sprintf("SELECT %s FROM %s WHERE tenant_id = ? AND product_id = ? AND batch_id = ? AND deleted_at IS NULL LIMIT 1", invProductBatchRows, m.table)
if err := m.conn.QueryRowCtx(ctx, &resp, query, tenantId, productId, batchId); err != nil {
return nil, err
}
return &resp, nil
}
func (m *customInvProductBatchModel) NextBatchNo(ctx context.Context, tenantId, productCode string) (string, error) {
var next int64
query := fmt.Sprintf(`SELECT COALESCE(MAX(CAST(SUBSTRING(batch_no, CHAR_LENGTH(?) + 3) AS UNSIGNED)), 0) + 1
FROM %s
WHERE tenant_id = ? AND batch_no LIKE CONCAT(?, '-B%%')`, m.table)
if err := m.conn.QueryRowCtx(ctx, &next, query, productCode, tenantId, productCode); err != nil {
return "", err
}
return fmt.Sprintf("%s-B%03d", productCode, next), nil
}
func (m *customInvProductBatchModel) SoftDeleteByBatchId(ctx context.Context, batchId string) error {
batch, err := m.FindOneByBatchId(ctx, batchId)
if err != nil {
return err
}
batch.Status = 0
batch.DeletedAt.Time = time.Now()
batch.DeletedAt.Valid = true
return m.Update(ctx, batch)
}

View File

@ -1,100 +0,0 @@
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
invProductBatchFieldNames = builder.RawFieldNames(&InvProductBatch{})
invProductBatchRows = strings.Join(invProductBatchFieldNames, ",")
invProductBatchRowsExpectAutoSet = strings.Join(stringx.Remove(invProductBatchFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",")
invProductBatchRowsWithPlaceHolder = strings.Join(stringx.Remove(invProductBatchFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?"
)
type (
invProductBatchModel interface {
Insert(ctx context.Context, data *InvProductBatch) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*InvProductBatch, error)
FindOneByBatchId(ctx context.Context, batchId string) (*InvProductBatch, error)
Update(ctx context.Context, data *InvProductBatch) error
Delete(ctx context.Context, id int64) error
}
defaultInvProductBatchModel struct {
conn sqlx.SqlConn
table string
}
InvProductBatch struct {
Id int64 `db:"id"`
BatchId string `db:"batch_id"`
TenantId string `db:"tenant_id"`
ProductId string `db:"product_id"`
BatchNo string `db:"batch_no"`
YarnRatio sql.NullString `db:"yarn_ratio"`
WarpWeightGM float64 `db:"warp_weight_g_m"`
WeftWeightGM float64 `db:"weft_weight_g_m"`
ProductionProcess sql.NullString `db:"production_process"`
Remark sql.NullString `db:"remark"`
Status int64 `db:"status"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
DeletedAt sql.NullTime `db:"deleted_at"`
}
)
func newInvProductBatchModel(conn sqlx.SqlConn) *defaultInvProductBatchModel {
return &defaultInvProductBatchModel{
conn: conn,
table: "`inv_product_batch`",
}
}
func (m *defaultInvProductBatchModel) Insert(ctx context.Context, data *InvProductBatch) (sql.Result, error) {
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, invProductBatchRowsExpectAutoSet)
return m.conn.ExecCtx(ctx, query, data.BatchId, data.TenantId, data.ProductId, data.BatchNo, data.YarnRatio, data.WarpWeightGM, data.WeftWeightGM, data.ProductionProcess, data.Remark, data.Status, data.DeletedAt)
}
func (m *defaultInvProductBatchModel) FindOne(ctx context.Context, id int64) (*InvProductBatch, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE `id` = ? LIMIT 1", invProductBatchRows, m.table)
var resp InvProductBatch
err := m.conn.QueryRowCtx(ctx, &resp, query, id)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultInvProductBatchModel) FindOneByBatchId(ctx context.Context, batchId string) (*InvProductBatch, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE `batch_id` = ? AND `deleted_at` IS NULL LIMIT 1", invProductBatchRows, m.table)
var resp InvProductBatch
err := m.conn.QueryRowCtx(ctx, &resp, query, batchId)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultInvProductBatchModel) Update(ctx context.Context, data *InvProductBatch) error {
query := fmt.Sprintf("UPDATE %s SET %s WHERE `id` = ?", m.table, invProductBatchRowsWithPlaceHolder)
_, err := m.conn.ExecCtx(ctx, query, data.BatchId, data.TenantId, data.ProductId, data.BatchNo, data.YarnRatio, data.WarpWeightGM, data.WeftWeightGM, data.ProductionProcess, data.Remark, data.Status, data.DeletedAt, data.Id)
return err
}
func (m *defaultInvProductBatchModel) Delete(ctx context.Context, id int64) error {
query := fmt.Sprintf("DELETE FROM %s WHERE `id` = ?", m.table)
_, err := m.conn.ExecCtx(ctx, query, id)
return err
}
func (m *defaultInvProductBatchModel) tableName() string {
return m.table
}

View File

@ -59,7 +59,7 @@ func (m *customInvProductBoltModel) FindByPanId(ctx context.Context, panId strin
func (m *customInvProductBoltModel) FindByProductId(ctx context.Context, productId string) ([]*InvProductBolt, error) { func (m *customInvProductBoltModel) FindByProductId(ctx context.Context, productId string) ([]*InvProductBolt, error) {
var list []*InvProductBolt var list []*InvProductBolt
query := fmt.Sprintf( query := fmt.Sprintf(
"SELECT b.`id`, b.`bolt_id`, b.`pan_id`, b.`length_m`, b.`color`, b.`sort_order`, b.`tenant_id`, b.`created_at`, b.`updated_at` "+ "SELECT b.`id`, b.`bolt_id`, b.`pan_id`, b.`length_m`, b.`sort_order`, b.`tenant_id`, b.`created_at`, b.`updated_at` "+
"FROM %s b INNER JOIN `inv_product_pan` p ON b.`pan_id` = p.`pan_id` "+ "FROM %s b INNER JOIN `inv_product_pan` p ON b.`pan_id` = p.`pan_id` "+
"WHERE p.`product_id` = ? ORDER BY p.`sort_order`, b.`sort_order`", m.table) "WHERE p.`product_id` = ? ORDER BY p.`sort_order`, b.`sort_order`", m.table)
err := m.conn.QueryRowsCtx(ctx, &list, query, productId) err := m.conn.QueryRowsCtx(ctx, &list, query, productId)

View File

@ -33,14 +33,11 @@ type ColorDetail struct {
ProductId string `db:"product_id"` ProductId string `db:"product_id"`
ProductName string `db:"product_name"` ProductName string `db:"product_name"`
Color string `db:"color"` Color string `db:"color"`
ColorNo string `db:"color_no"`
ProductCode string `db:"product_code"`
Spec string `db:"spec"` Spec string `db:"spec"`
ImageUrl string `db:"image_url"` ImageUrl string `db:"image_url"`
SalesPrice float64 `db:"sales_price"` SalesPrice float64 `db:"sales_price"`
PanCount int64 `db:"pan_count"` PanCount int64 `db:"pan_count"`
BoltCount int64 `db:"bolt_count"` BoltCount int64 `db:"bolt_count"`
BatchCount int64 `db:"batch_count"`
TotalLengthM float64 `db:"total_length_m"` TotalLengthM float64 `db:"total_length_m"`
Locations string `db:"locations"` Locations string `db:"locations"`
} }
@ -48,13 +45,11 @@ type ColorDetail struct {
type ( type (
InvProductModel interface { InvProductModel interface {
invProductModel invProductModel
FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, colorNo, productCode string, status int64) ([]*InvProduct, int64, error) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color string, status int64) ([]*InvProduct, int64, error)
FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error) FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error)
FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error) FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error)
FindOneByTenantProductCode(ctx context.Context, tenantId, productCode string) (*InvProduct, error)
FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error) FindGroupByColor(ctx context.Context, tenantId string) ([]StockGroupResult, error)
FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error) FindGroupByLocation(ctx context.Context, tenantId string) ([]StockGroupResult, error)
NextColorNo(ctx context.Context, tenantId, productName string) (string, error)
BulkInsert(ctx context.Context, products []*InvProduct) (int64, error) BulkInsert(ctx context.Context, products []*InvProduct) (int64, error)
} }
@ -84,16 +79,15 @@ func (m *customInvProductModel) BulkInsert(ctx context.Context, products []*InvP
} }
batch := products[i:end] batch := products[i:end]
placeholder := "(" + strings.Repeat("?,", 11) + "?)" placeholder := "(" + strings.Repeat("?,", 9) + "?)"
placeholders := make([]string, len(batch)) placeholders := make([]string, len(batch))
args := make([]interface{}, 0, len(batch)*12) args := make([]interface{}, 0, len(batch)*10)
for j, p := range batch { for j, p := range batch {
placeholders[j] = placeholder placeholders[j] = placeholder
args = append(args, args = append(args,
p.ProductId, p.ProductName, p.ImageUrl, p.Spec, p.Color, p.ProductId, p.ProductName, p.ImageUrl, p.Spec, p.Color,
p.ColorNo, p.ProductCode, p.SalesPrice, p.Remark, p.Status, p.SalesPrice, p.Remark, p.Status, p.TenantId, p.DeletedAt,
p.TenantId, p.DeletedAt,
) )
} }
@ -111,7 +105,7 @@ func (m *customInvProductModel) BulkInsert(ctx context.Context, products []*InvP
return totalAffected, nil return totalAffected, nil
} }
func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, colorNo, productCode string, status int64) ([]*InvProduct, int64, error) { func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color string, status int64) ([]*InvProduct, int64, error) {
where := "WHERE deleted_at IS NULL AND tenant_id = ?" where := "WHERE deleted_at IS NULL AND tenant_id = ?"
args := []interface{}{tenantId} args := []interface{}{tenantId}
@ -127,14 +121,6 @@ func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, p
where += " AND color = ?" where += " AND color = ?"
args = append(args, color) args = append(args, color)
} }
if colorNo != "" {
where += " AND color_no LIKE ?"
args = append(args, "%"+colorNo+"%")
}
if productCode != "" {
where += " AND product_code LIKE ?"
args = append(args, "%"+productCode+"%")
}
if status >= 0 { if status >= 0 {
where += " AND status = ?" where += " AND status = ?"
args = append(args, status) args = append(args, status)
@ -158,27 +144,6 @@ func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, p
return list, total, nil return list, total, nil
} }
func (m *customInvProductModel) FindOneByTenantProductCode(ctx context.Context, tenantId, productCode string) (*InvProduct, error) {
var resp InvProduct
query := fmt.Sprintf("SELECT %s FROM %s WHERE tenant_id = ? AND product_code = ? AND deleted_at IS NULL LIMIT 1", invProductRows, m.table)
err := m.QueryRowNoCacheCtx(ctx, &resp, query, tenantId, productCode)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *customInvProductModel) NextColorNo(ctx context.Context, tenantId, productName string) (string, error) {
var next int64
query := fmt.Sprintf(`SELECT COALESCE(MAX(CAST(color_no AS UNSIGNED)), 0) + 1
FROM %s
WHERE tenant_id = ? AND product_name = ? AND deleted_at IS NULL AND color_no REGEXP '^[0-9]+$'`, m.table)
if err := m.QueryRowNoCacheCtx(ctx, &next, query, tenantId, productName); err != nil {
return "", err
}
return fmt.Sprintf("%02d", next), nil
}
func (m *customInvProductModel) FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error) { func (m *customInvProductModel) FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, error) {
var list []*ProductSummary var list []*ProductSummary
query := `SELECT p.product_name, p.spec, query := `SELECT p.product_name, p.spec,
@ -186,8 +151,8 @@ func (m *customInvProductModel) FindProductSummary(ctx context.Context, tenantId
COUNT(DISTINCT pan.pan_id) AS total_pan_count, COUNT(DISTINCT pan.pan_id) AS total_pan_count,
COUNT(bolt.bolt_id) AS total_bolt_count, COUNT(bolt.bolt_id) AS total_bolt_count,
COALESCE(SUM(bolt.length_m), 0) AS total_length_m, COALESCE(SUM(bolt.length_m), 0) AS total_length_m,
COALESCE(GROUP_CONCAT(DISTINCT p.color), '') AS colors, GROUP_CONCAT(DISTINCT p.color) AS colors,
COALESCE(GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position), '') AS locations GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position) AS locations
FROM ` + m.table + ` p FROM ` + m.table + ` p
LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id
LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id
@ -235,18 +200,11 @@ func (m *customInvProductModel) FindGroupByLocation(ctx context.Context, tenantI
func (m *customInvProductModel) FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error) { func (m *customInvProductModel) FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error) {
var list []*ColorDetail var list []*ColorDetail
query := `SELECT p.product_id, p.product_name, p.color, p.color_no, p.product_code, p.spec, p.image_url, p.sales_price, query := `SELECT p.product_id, p.product_name, p.color, p.spec, p.image_url, p.sales_price,
COUNT(DISTINCT pan.pan_id) AS pan_count, COUNT(DISTINCT pan.pan_id) AS pan_count,
COUNT(bolt.bolt_id) AS bolt_count, COUNT(bolt.bolt_id) AS bolt_count,
(
SELECT COUNT(DISTINCT batch.batch_id)
FROM ` + "`inv_product_batch`" + ` batch
WHERE batch.product_id = p.product_id
AND batch.tenant_id = p.tenant_id
AND batch.deleted_at IS NULL
) AS batch_count,
COALESCE(SUM(bolt.length_m), 0) AS total_length_m, COALESCE(SUM(bolt.length_m), 0) AS total_length_m,
COALESCE(GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position), '') AS locations GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position) AS locations
FROM ` + m.table + ` p FROM ` + m.table + ` p
LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id LEFT JOIN ` + "`inv_product_pan`" + ` pan ON pan.product_id = p.product_id
LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id LEFT JOIN ` + "`inv_product_bolt`" + ` bolt ON bolt.pan_id = pan.pan_id

View File

@ -27,7 +27,6 @@ var (
cacheInvProductIdPrefix = "cache:invProduct:id:" cacheInvProductIdPrefix = "cache:invProduct:id:"
cacheInvProductProductIdPrefix = "cache:invProduct:productId:" cacheInvProductProductIdPrefix = "cache:invProduct:productId:"
cacheInvProductProductNamePrefix = "cache:invProduct:productName:" cacheInvProductProductNamePrefix = "cache:invProduct:productName:"
cacheInvProductProductCodePrefix = "cache:invProduct:productCode:"
) )
type ( type (
@ -36,7 +35,6 @@ type (
FindOne(ctx context.Context, id int64) (*InvProduct, error) FindOne(ctx context.Context, id int64) (*InvProduct, error)
FindOneByProductId(ctx context.Context, productId string) (*InvProduct, error) FindOneByProductId(ctx context.Context, productId string) (*InvProduct, error)
FindOneByProductName(ctx context.Context, productName string) (*InvProduct, error) FindOneByProductName(ctx context.Context, productName string) (*InvProduct, error)
FindOneByProductCode(ctx context.Context, productCode string) (*InvProduct, error)
Update(ctx context.Context, data *InvProduct) error Update(ctx context.Context, data *InvProduct) error
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
} }
@ -53,9 +51,8 @@ type (
ImageUrl string `db:"image_url"` ImageUrl string `db:"image_url"`
Spec string `db:"spec"` Spec string `db:"spec"`
Color string `db:"color"` Color string `db:"color"`
ColorNo string `db:"color_no"`
ProductCode string `db:"product_code"`
SalesPrice float64 `db:"sales_price"` SalesPrice float64 `db:"sales_price"`
CostPrice float64 `db:"cost_price"`
Remark sql.NullString `db:"remark"` Remark sql.NullString `db:"remark"`
Status int64 `db:"status"` Status int64 `db:"status"`
TenantId string `db:"tenant_id"` TenantId string `db:"tenant_id"`
@ -81,11 +78,10 @@ func (m *defaultInvProductModel) Delete(ctx context.Context, id int64) error {
invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, id) invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, id)
invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId) invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId)
invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, data.ProductName) invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, data.ProductName)
invProductProductCodeKey := fmt.Sprintf("%s%v", cacheInvProductProductCodePrefix, data.ProductCode)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table) query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id) return conn.ExecCtx(ctx, query, id)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey, invProductProductCodeKey) }, invProductIdKey, invProductProductIdKey, invProductProductNameKey)
return err return err
} }
@ -126,26 +122,6 @@ func (m *defaultInvProductModel) FindOneByProductId(ctx context.Context, product
} }
} }
func (m *defaultInvProductModel) FindOneByProductCode(ctx context.Context, productCode string) (*InvProduct, error) {
invProductProductCodeKey := fmt.Sprintf("%s%v", cacheInvProductProductCodePrefix, productCode)
var resp InvProduct
err := m.QueryRowIndexCtx(ctx, &resp, invProductProductCodeKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v any) (i any, e error) {
query := fmt.Sprintf("select %s from %s where `product_code` = ? limit 1", invProductRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, productCode); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultInvProductModel) FindOneByProductName(ctx context.Context, productName string) (*InvProduct, error) { func (m *defaultInvProductModel) FindOneByProductName(ctx context.Context, productName string) (*InvProduct, error) {
invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, productName) invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, productName)
var resp InvProduct var resp InvProduct
@ -170,11 +146,10 @@ func (m *defaultInvProductModel) Insert(ctx context.Context, data *InvProduct) (
invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, data.Id) invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, data.Id)
invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId) invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId)
invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, data.ProductName) invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, data.ProductName)
invProductProductCodeKey := fmt.Sprintf("%s%v", cacheInvProductProductCodePrefix, data.ProductCode)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, invProductRowsExpectAutoSet) query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, invProductRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.ProductId, data.ProductName, data.ImageUrl, data.Spec, data.Color, data.ColorNo, data.ProductCode, data.SalesPrice, data.Remark, data.Status, data.TenantId, data.DeletedAt) return conn.ExecCtx(ctx, query, data.ProductId, data.ProductName, data.ImageUrl, data.Spec, data.Color, data.SalesPrice, data.CostPrice, data.Remark, data.Status, data.TenantId, data.DeletedAt)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey, invProductProductCodeKey) }, invProductIdKey, invProductProductIdKey, invProductProductNameKey)
return ret, err return ret, err
} }
@ -187,11 +162,10 @@ func (m *defaultInvProductModel) Update(ctx context.Context, newData *InvProduct
invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, data.Id) invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, data.Id)
invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId) invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId)
invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, data.ProductName) invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, data.ProductName)
invProductProductCodeKey := fmt.Sprintf("%s%v", cacheInvProductProductCodePrefix, data.ProductCode)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) { _, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, invProductRowsWithPlaceHolder) query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, invProductRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, newData.ProductId, newData.ProductName, newData.ImageUrl, newData.Spec, newData.Color, newData.ColorNo, newData.ProductCode, newData.SalesPrice, newData.Remark, newData.Status, newData.TenantId, newData.DeletedAt, newData.Id) return conn.ExecCtx(ctx, query, newData.ProductId, newData.ProductName, newData.ImageUrl, newData.Spec, newData.Color, newData.SalesPrice, newData.CostPrice, newData.Remark, newData.Status, newData.TenantId, newData.DeletedAt, newData.Id)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey, invProductProductCodeKey) }, invProductIdKey, invProductProductIdKey, invProductProductNameKey)
return err return err
} }

View File

@ -1,451 +0,0 @@
package model
// Integration tests for InvProduct model against the real dev database.
// These tests guard against model/schema drift (e.g. missing or extra columns).
//
// Run with the dev DB reachable:
// go test ./model/ -run TestInvProduct -v
// Skip in CI where DB is unavailable:
// go test ./model/ -short
import (
"context"
"database/sql"
"fmt"
"testing"
"time"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/redis"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
const testDSN = "root:muyu2026@tcp(127.0.0.1:13306)/muyu_wms?charset=utf8mb4&parseTime=true&loc=Asia%2FShanghai"
func newTestProductModel(t *testing.T) InvProductModel {
t.Helper()
conn := sqlx.NewMysql(testDSN)
cacheConf := cache.CacheConf{{RedisConf: redis.RedisConf{Host: "127.0.0.1:6379", Type: "node"}, Weight: 100}}
return NewInvProductModel(conn, cacheConf)
}
func uniqueID(prefix string) string {
return fmt.Sprintf("%s_%d", prefix, time.Now().UnixNano())
}
// TestInvProductInsert verifies that Insert succeeds against the live DB schema.
// This catches model/schema drift like the cost_price column removal.
func TestInvProductInsert(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
m := newTestProductModel(t)
ctx := context.Background()
p := &InvProduct{
ProductId: uniqueID("pid"),
ProductName: uniqueID("test_product"),
ImageUrl: "",
Spec: "100cm",
Color: "red",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 99.9,
Remark: sql.NullString{String: "test remark", Valid: true},
Status: 1,
TenantId: "t_default_001",
}
result, err := m.Insert(ctx, p)
if err != nil {
t.Fatalf("Insert failed: %v", err)
}
id, _ := result.LastInsertId()
if id == 0 {
t.Fatal("expected non-zero LastInsertId")
}
t.Logf("inserted product id=%d", id)
// cleanup
t.Cleanup(func() { _ = m.Delete(ctx, id) })
}
// TestInvProductFindOne verifies Insert + FindOne round-trip.
func TestInvProductFindOne(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
m := newTestProductModel(t)
ctx := context.Background()
p := &InvProduct{
ProductId: uniqueID("pid"),
ProductName: uniqueID("test_product"),
Spec: "200cm",
Color: "blue",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 49.5,
Status: 1,
TenantId: "t_default_001",
}
res, err := m.Insert(ctx, p)
if err != nil {
t.Fatalf("Insert: %v", err)
}
id, _ := res.LastInsertId()
t.Cleanup(func() { _ = m.Delete(ctx, id) })
got, err := m.FindOne(ctx, id)
if err != nil {
t.Fatalf("FindOne: %v", err)
}
if got.ProductName != p.ProductName {
t.Errorf("ProductName: got %q, want %q", got.ProductName, p.ProductName)
}
if got.SalesPrice != p.SalesPrice {
t.Errorf("SalesPrice: got %v, want %v", got.SalesPrice, p.SalesPrice)
}
}
// TestInvProductUpdate verifies Update writes back correctly.
func TestInvProductUpdate(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
m := newTestProductModel(t)
ctx := context.Background()
p := &InvProduct{
ProductId: uniqueID("pid"),
ProductName: uniqueID("test_product"),
Spec: "50cm",
Color: "green",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 10.0,
Status: 1,
TenantId: "t_default_001",
}
res, err := m.Insert(ctx, p)
if err != nil {
t.Fatalf("Insert: %v", err)
}
id, _ := res.LastInsertId()
t.Cleanup(func() { _ = m.Delete(ctx, id) })
got, err := m.FindOne(ctx, id)
if err != nil {
t.Fatalf("FindOne: %v", err)
}
got.SalesPrice = 25.0
got.Remark = sql.NullString{String: "updated", Valid: true}
if err := m.Update(ctx, got); err != nil {
t.Fatalf("Update: %v", err)
}
updated, err := m.FindOne(ctx, id)
if err != nil {
t.Fatalf("FindOne after Update: %v", err)
}
if updated.SalesPrice != 25.0 {
t.Errorf("SalesPrice after update: got %v, want 25.0", updated.SalesPrice)
}
if updated.Remark.String != "updated" {
t.Errorf("Remark after update: got %q, want \"updated\"", updated.Remark.String)
}
}
// TestInvProductBulkInsert verifies BulkInsert (used by the import flow).
func TestInvProductBulkInsert(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
m := newTestProductModel(t)
ctx := context.Background()
ids := make([]int64, 0, 3)
products := make([]*InvProduct, 3)
for i := range products {
products[i] = &InvProduct{
ProductId: uniqueID(fmt.Sprintf("bulk_pid_%d", i)),
ProductName: uniqueID(fmt.Sprintf("bulk_product_%d", i)),
Spec: "10cm",
Color: "white",
ColorNo: fmt.Sprintf("%02d", i+1),
ProductCode: uniqueID(fmt.Sprintf("BTPC%d", i)),
SalesPrice: float64(i+1) * 5.0,
Status: 1,
TenantId: "t_default_001",
}
}
affected, err := m.BulkInsert(ctx, products)
if err != nil {
t.Fatalf("BulkInsert: %v", err)
}
if affected != 3 {
t.Errorf("BulkInsert: affected=%d, want 3", affected)
}
t.Cleanup(func() {
for _, p := range products {
row, err := m.FindOneByProductId(ctx, p.ProductId)
if err == nil {
_ = m.Delete(ctx, row.Id)
}
}
_ = ids
})
}
// TestInvProductSchemaColumns queries information_schema to assert the DB columns
// match what the InvProduct struct declares. Fails immediately on any mismatch.
func TestInvProductSchemaColumns(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
db, err := sql.Open("mysql", testDSN)
if err != nil {
t.Fatalf("open db: %v", err)
}
defer db.Close()
rows, err := db.QueryContext(context.Background(),
"SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='inv_product' ORDER BY ORDINAL_POSITION")
if err != nil {
t.Fatalf("query schema: %v", err)
}
defer rows.Close()
dbCols := map[string]bool{}
for rows.Next() {
var col string
if err := rows.Scan(&col); err != nil {
t.Fatalf("scan: %v", err)
}
dbCols[col] = true
}
// Columns that the InvProduct struct declares (via db tags).
structCols := []string{
"id", "product_id", "product_name", "image_url", "spec", "color",
"color_no", "product_code", "sales_price", "remark", "status", "tenant_id",
"created_at", "updated_at", "deleted_at",
}
for _, col := range structCols {
if !dbCols[col] {
t.Errorf("struct declares column %q but it does NOT exist in inv_product", col)
}
}
// Also check for DB columns not in the struct (forward-drift guard).
structSet := map[string]bool{}
for _, c := range structCols {
structSet[c] = true
}
for col := range dbCols {
if !structSet[col] {
t.Logf("WARNING: DB column %q exists but is not in the InvProduct struct", col)
}
}
}
// TestFindProductSummaryNoPans guards against the GROUP_CONCAT NULL scan bug:
// when a product has no pans, the LEFT JOIN produces NULL for pan.position and
// GROUP_CONCAT over all-NULL values returns NULL, which cannot be scanned into a
// Go string. The fix wraps both GROUP_CONCAT calls with COALESCE(..., '').
func TestFindProductSummaryNoPans(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
m := newTestProductModel(t)
ctx := context.Background()
p := &InvProduct{
ProductId: uniqueID("pid_nopan"),
ProductName: uniqueID("nopan_product"),
Spec: "50cm",
Color: "red",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 99.0,
Status: 1,
TenantId: "t_default_001",
}
res, err := m.Insert(ctx, p)
if err != nil {
t.Fatalf("Insert: %v", err)
}
id, _ := res.LastInsertId()
t.Cleanup(func() { _ = m.Delete(ctx, id) })
list, err := m.FindProductSummary(ctx, "t_default_001")
if err != nil {
t.Fatalf("FindProductSummary returned error for product with no pans: %v", err)
}
var found *ProductSummary
for _, s := range list {
if s.ProductName == p.ProductName {
found = s
break
}
}
if found == nil {
t.Fatalf("product %q not found in summary (len=%d)", p.ProductName, len(list))
}
if found.ColorCount != 1 {
t.Errorf("ColorCount: got %d, want 1", found.ColorCount)
}
if found.TotalPanCount != 0 {
t.Errorf("TotalPanCount: got %d, want 0", found.TotalPanCount)
}
if found.Locations != "" {
t.Errorf("Locations: got %q, want empty string", found.Locations)
}
}
// TestFindColorDetailNoPans is the same NULL guard for the color-detail query.
func TestFindColorDetailNoPans(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
m := newTestProductModel(t)
ctx := context.Background()
p := &InvProduct{
ProductId: uniqueID("pid_color"),
ProductName: uniqueID("color_nopan_product"),
Spec: "80cm",
Color: "blue",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 50.0,
Status: 1,
TenantId: "t_default_001",
}
res, err := m.Insert(ctx, p)
if err != nil {
t.Fatalf("Insert: %v", err)
}
id, _ := res.LastInsertId()
t.Cleanup(func() { _ = m.Delete(ctx, id) })
list, err := m.FindColorDetail(ctx, "t_default_001", p.ProductName)
if err != nil {
t.Fatalf("FindColorDetail returned error for product with no pans: %v", err)
}
if len(list) != 1 {
t.Fatalf("expected 1 color detail row, got %d", len(list))
}
if list[0].Color != "blue" {
t.Errorf("Color: got %q, want %q", list[0].Color, "blue")
}
if list[0].Locations != "" {
t.Errorf("Locations: got %q, want empty string", list[0].Locations)
}
}
func TestFindColorDetailBatchCountDoesNotDuplicateStock(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test in short mode")
}
conn := sqlx.NewMysql(testDSN)
m := newTestProductModel(t)
panM := newInvProductPanModel(conn)
boltM := newInvProductBoltModel(conn)
batchM := newInvProductBatchModel(conn)
ctx := context.Background()
productId := uniqueID("pcb")
p := &InvProduct{
ProductId: productId,
ProductName: uniqueID("color_batch_product"),
Spec: "120cm",
Color: "natural",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 20.0,
Status: 1,
TenantId: "t_default_001",
}
res, err := m.Insert(ctx, p)
if err != nil {
t.Fatalf("Insert product: %v", err)
}
productRowId, _ := res.LastInsertId()
t.Cleanup(func() { _ = m.Delete(ctx, productRowId) })
batchAId := uniqueID("b1")
batchBId := uniqueID("b2")
for _, batch := range []*InvProductBatch{
{BatchId: batchAId, TenantId: p.TenantId, ProductId: productId, BatchNo: p.ProductCode + "-B001", Status: 1},
{BatchId: batchBId, TenantId: p.TenantId, ProductId: productId, BatchNo: p.ProductCode + "-B002", Status: 1},
} {
res, err := batchM.Insert(ctx, batch)
if err != nil {
t.Fatalf("Insert batch: %v", err)
}
id, _ := res.LastInsertId()
t.Cleanup(func() { _ = batchM.Delete(ctx, id) })
}
pans := []*InvProductPan{
{PanId: uniqueID("pa"), ProductId: productId, BatchId: batchAId, Name: "A", Position: "A-01", SortOrder: 1, TenantId: p.TenantId},
{PanId: uniqueID("pb"), ProductId: productId, BatchId: batchBId, Name: "B", Position: "B-02", SortOrder: 2, TenantId: p.TenantId},
}
for _, pan := range pans {
res, err := panM.Insert(ctx, pan)
if err != nil {
t.Fatalf("Insert pan: %v", err)
}
id, _ := res.LastInsertId()
t.Cleanup(func() { _ = panM.Delete(ctx, id) })
}
for _, bolt := range []*InvProductBolt{
{BoltId: uniqueID("ba1"), PanId: pans[0].PanId, LengthM: 10.5, Color: p.Color, SortOrder: 1, TenantId: p.TenantId},
{BoltId: uniqueID("ba2"), PanId: pans[0].PanId, LengthM: 11.5, Color: p.Color, SortOrder: 2, TenantId: p.TenantId},
{BoltId: uniqueID("bb1"), PanId: pans[1].PanId, LengthM: 12.0, Color: p.Color, SortOrder: 1, TenantId: p.TenantId},
} {
res, err := boltM.Insert(ctx, bolt)
if err != nil {
t.Fatalf("Insert bolt: %v", err)
}
id, _ := res.LastInsertId()
t.Cleanup(func() { _ = boltM.Delete(ctx, id) })
}
list, err := m.FindColorDetail(ctx, p.TenantId, p.ProductName)
if err != nil {
t.Fatalf("FindColorDetail: %v", err)
}
if len(list) != 1 {
t.Fatalf("expected 1 color detail row, got %d", len(list))
}
got := list[0]
if got.PanCount != 2 {
t.Errorf("PanCount: got %d, want 2", got.PanCount)
}
if got.BoltCount != 3 {
t.Errorf("BoltCount: got %d, want 3", got.BoltCount)
}
if got.BatchCount != 2 {
t.Errorf("BatchCount: got %d, want 2", got.BatchCount)
}
if got.TotalLengthM != 34.0 {
t.Errorf("TotalLengthM: got %.2f, want 34.00", got.TotalLengthM)
}
}

View File

@ -15,8 +15,6 @@ var _ InvProductPanModel = (*customInvProductPanModel)(nil)
type PanListItem struct { type PanListItem struct {
PanId string `db:"pan_id"` PanId string `db:"pan_id"`
ProductId string `db:"product_id"` ProductId string `db:"product_id"`
BatchId string `db:"batch_id"`
BatchNo string `db:"batch_no"`
Name string `db:"name"` Name string `db:"name"`
Position string `db:"position"` Position string `db:"position"`
ProductName string `db:"product_name"` ProductName string `db:"product_name"`
@ -32,7 +30,6 @@ type (
DeleteByProductId(ctx context.Context, productId string) error DeleteByProductId(ctx context.Context, productId string) error
BulkReplace(ctx context.Context, productId, tenantId string, pans []*InvProductPan) error BulkReplace(ctx context.Context, productId, tenantId string, pans []*InvProductPan) error
FindPanList(ctx context.Context, tenantId string, page, pageSize int64, productName, position string) ([]*PanListItem, int64, error) FindPanList(ctx context.Context, tenantId string, page, pageSize int64, productName, position string) ([]*PanListItem, int64, error)
CountByBatchId(ctx context.Context, batchId string) (int64, error)
} }
customInvProductPanModel struct { customInvProductPanModel struct {
@ -82,14 +79,13 @@ func (m *customInvProductPanModel) FindPanList(ctx context.Context, tenantId str
return nil, 0, err return nil, 0, err
} }
query := fmt.Sprintf(`SELECT pan.pan_id, pan.product_id, pan.batch_id, COALESCE(batch.batch_no, '') AS batch_no, query := fmt.Sprintf(`SELECT pan.pan_id, pan.product_id, pan.name, pan.position,
pan.name, pan.position, p.product_name, p.product_name,
COALESCE(GROUP_CONCAT(DISTINCT b.color ORDER BY b.color SEPARATOR ', '), '') AS colors, COALESCE(GROUP_CONCAT(DISTINCT b.color ORDER BY b.color SEPARATOR ', '), '') AS colors,
COUNT(b.bolt_id) AS bolt_count, COUNT(b.bolt_id) AS bolt_count,
COALESCE(SUM(b.length_m), 0) AS total_length_m COALESCE(SUM(b.length_m), 0) AS total_length_m
FROM %s pan FROM %s pan
JOIN inv_product p ON p.product_id = pan.product_id JOIN inv_product p ON p.product_id = pan.product_id
LEFT JOIN inv_product_batch batch ON batch.batch_id = pan.batch_id AND batch.deleted_at IS NULL
LEFT JOIN inv_product_bolt b ON b.pan_id = pan.pan_id LEFT JOIN inv_product_bolt b ON b.pan_id = pan.pan_id
%s %s
GROUP BY pan.pan_id GROUP BY pan.pan_id
@ -104,15 +100,6 @@ func (m *customInvProductPanModel) FindPanList(ctx context.Context, tenantId str
return list, total, nil return list, total, nil
} }
func (m *customInvProductPanModel) CountByBatchId(ctx context.Context, batchId string) (int64, error) {
var total int64
query := fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE `batch_id` = ?", m.table)
if err := m.conn.QueryRowCtx(ctx, &total, query, batchId); err != nil {
return 0, err
}
return total, nil
}
func (m *customInvProductPanModel) BulkReplace(ctx context.Context, productId, tenantId string, pans []*InvProductPan) error { func (m *customInvProductPanModel) BulkReplace(ctx context.Context, productId, tenantId string, pans []*InvProductPan) error {
return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error { return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
delQuery := fmt.Sprintf("DELETE FROM %s WHERE `product_id` = ?", m.table) delQuery := fmt.Sprintf("DELETE FROM %s WHERE `product_id` = ?", m.table)
@ -122,12 +109,12 @@ func (m *customInvProductPanModel) BulkReplace(ctx context.Context, productId, t
if len(pans) == 0 { if len(pans) == 0 {
return nil return nil
} }
placeholder := "(?, ?, ?, ?, ?, ?, ?)" placeholder := "(?, ?, ?, ?, ?, ?)"
placeholders := make([]string, len(pans)) placeholders := make([]string, len(pans))
args := make([]interface{}, 0, len(pans)*7) args := make([]interface{}, 0, len(pans)*6)
for i, p := range pans { for i, p := range pans {
placeholders[i] = placeholder placeholders[i] = placeholder
args = append(args, p.PanId, productId, p.BatchId, p.Name, p.Position, p.SortOrder, tenantId) args = append(args, p.PanId, productId, p.Name, p.Position, p.SortOrder, tenantId)
} }
insQuery := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", m.table, invProductPanRowsExpectAutoSet, strings.Join(placeholders, ",")) insQuery := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", m.table, invProductPanRowsExpectAutoSet, strings.Join(placeholders, ","))
_, err := session.ExecCtx(ctx, insQuery, args...) _, err := session.ExecCtx(ctx, insQuery, args...)

View File

@ -37,7 +37,6 @@ type (
Id int64 `db:"id"` Id int64 `db:"id"`
PanId string `db:"pan_id"` PanId string `db:"pan_id"`
ProductId string `db:"product_id"` ProductId string `db:"product_id"`
BatchId string `db:"batch_id"`
Name string `db:"name"` Name string `db:"name"`
Position string `db:"position"` Position string `db:"position"`
SortOrder int64 `db:"sort_order"` SortOrder int64 `db:"sort_order"`
@ -55,8 +54,8 @@ func newInvProductPanModel(conn sqlx.SqlConn) *defaultInvProductPanModel {
} }
func (m *defaultInvProductPanModel) Insert(ctx context.Context, data *InvProductPan) (sql.Result, error) { func (m *defaultInvProductPanModel) Insert(ctx context.Context, data *InvProductPan) (sql.Result, error) {
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (?, ?, ?, ?, ?, ?, ?)", m.table, invProductPanRowsExpectAutoSet) query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (?, ?, ?, ?, ?, ?)", m.table, invProductPanRowsExpectAutoSet)
return m.conn.ExecCtx(ctx, query, data.PanId, data.ProductId, data.BatchId, data.Name, data.Position, data.SortOrder, data.TenantId) return m.conn.ExecCtx(ctx, query, data.PanId, data.ProductId, data.Name, data.Position, data.SortOrder, data.TenantId)
} }
func (m *defaultInvProductPanModel) FindOne(ctx context.Context, id int64) (*InvProductPan, error) { func (m *defaultInvProductPanModel) FindOne(ctx context.Context, id int64) (*InvProductPan, error) {
@ -81,7 +80,7 @@ func (m *defaultInvProductPanModel) FindOneByPanId(ctx context.Context, panId st
func (m *defaultInvProductPanModel) Update(ctx context.Context, data *InvProductPan) error { func (m *defaultInvProductPanModel) Update(ctx context.Context, data *InvProductPan) error {
query := fmt.Sprintf("UPDATE %s SET %s WHERE `id` = ?", m.table, invProductPanRowsWithPlaceHolder) query := fmt.Sprintf("UPDATE %s SET %s WHERE `id` = ?", m.table, invProductPanRowsWithPlaceHolder)
_, err := m.conn.ExecCtx(ctx, query, data.PanId, data.ProductId, data.BatchId, data.Name, data.Position, data.SortOrder, data.TenantId, data.Id) _, err := m.conn.ExecCtx(ctx, query, data.PanId, data.ProductId, data.Name, data.Position, data.SortOrder, data.TenantId, data.Id)
return err return err
} }

View File

@ -1,82 +0,0 @@
package model
import (
"context"
"fmt"
"time"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ InvYarnModel = (*customInvYarnModel)(nil)
type (
InvYarnModel interface {
invYarnModel
FindList(ctx context.Context, tenantId string, page, pageSize int64, yarnName, supplierId string, status int64) ([]*InvYarn, int64, error)
FindOneByUnique(ctx context.Context, tenantId, yarnName, color, supplierId string) (*InvYarn, error)
SoftDeleteByYarnId(ctx context.Context, yarnId string) error
}
customInvYarnModel struct {
*defaultInvYarnModel
}
)
func NewInvYarnModel(conn sqlx.SqlConn) InvYarnModel {
return &customInvYarnModel{
defaultInvYarnModel: newInvYarnModel(conn),
}
}
func (m *customInvYarnModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, yarnName, supplierId string, status int64) ([]*InvYarn, int64, error) {
where := "WHERE tenant_id = ? AND deleted_at IS NULL"
args := []interface{}{tenantId}
if yarnName != "" {
where += " AND yarn_name LIKE ?"
args = append(args, "%"+yarnName+"%")
}
if supplierId != "" {
where += " AND supplier_id = ?"
args = append(args, supplierId)
}
if status >= 0 {
where += " AND status = ?"
args = append(args, status)
}
var total int64
countQuery := fmt.Sprintf("SELECT COUNT(*) FROM %s %s", m.table, where)
if err := m.conn.QueryRowCtx(ctx, &total, countQuery, args...); err != nil {
return nil, 0, err
}
var list []*InvYarn
query := fmt.Sprintf("SELECT %s FROM %s %s ORDER BY created_at DESC LIMIT ? OFFSET ?", invYarnRows, m.table, where)
args = append(args, pageSize, (page-1)*pageSize)
if err := m.conn.QueryRowsCtx(ctx, &list, query, args...); err != nil {
return nil, 0, err
}
return list, total, nil
}
func (m *customInvYarnModel) FindOneByUnique(ctx context.Context, tenantId, yarnName, color, supplierId string) (*InvYarn, error) {
var resp InvYarn
query := fmt.Sprintf("SELECT %s FROM %s WHERE tenant_id = ? AND yarn_name = ? AND color = ? AND supplier_id = ? AND deleted_at IS NULL LIMIT 1", invYarnRows, m.table)
if err := m.conn.QueryRowCtx(ctx, &resp, query, tenantId, yarnName, color, supplierId); err != nil {
return nil, err
}
return &resp, nil
}
func (m *customInvYarnModel) SoftDeleteByYarnId(ctx context.Context, yarnId string) error {
yarn, err := m.FindOneByYarnId(ctx, yarnId)
if err != nil {
return err
}
yarn.Status = 0
yarn.DeletedAt.Time = time.Now()
yarn.DeletedAt.Valid = true
return m.Update(ctx, yarn)
}

Some files were not shown because too many files have changed in this diff Show More