feat:improve product yarn ratio workflow (#5)

* feat: support product yarn batches

* feat: improve product yarn ratio workflow

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
kae_mihara 2026-07-05 08:06:28 +09:00 committed by GitHub
parent bb212aff36
commit e3f6fa236d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
79 changed files with 5146 additions and 389 deletions

View File

@ -210,16 +210,14 @@ CREATE TABLE `crm_data_migration_log` (
CREATE TABLE `inv_product` (
`id` BIGINT NOT NULL AUTO_INCREMENT,
`tenant_id` VARCHAR(32) NOT NULL DEFAULT 't_default_001',
`product_id` VARCHAR(32) NOT NULL,
`product_name` VARCHAR(100) NOT NULL,
`image_url` VARCHAR(255) NOT NULL DEFAULT '',
`spec` VARCHAR(100) NOT NULL DEFAULT '',
`color` VARCHAR(50) NOT NULL DEFAULT '',
`unit_pieces` INT NOT NULL DEFAULT 0,
`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,
`color` VARCHAR(50) NOT NULL DEFAULT '本色',
`color_no` VARCHAR(20) NOT NULL DEFAULT '',
`product_code` VARCHAR(50) NOT NULL DEFAULT '',
`sales_price` DECIMAL(10,2) NOT NULL DEFAULT 0.00,
`remark` TEXT,
`status` TINYINT NOT NULL DEFAULT 1 COMMENT '1:enabled 0:disabled',
@ -228,12 +226,96 @@ CREATE TABLE `inv_product` (
`deleted_at` DATETIME DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uk_product_id` (`product_id`),
UNIQUE KEY `uk_product_name_spec_color` (`product_name`, `spec`, `color`),
UNIQUE KEY `uk_tenant_product_code` (`tenant_id`, `product_code`),
KEY `idx_tenant_id` (`tenant_id`),
KEY `idx_product_name_tenant` (`product_name`, `tenant_id`),
KEY `idx_color` (`color`),
KEY `idx_location` (`location`),
KEY `idx_status` (`status`),
KEY `idx_deleted_at` (`deleted_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='产品档案';
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` (
`id` BIGINT NOT NULL AUTO_INCREMENT,

View File

@ -0,0 +1,174 @@
-- 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

@ -224,6 +224,8 @@ type (
ImageUrl string `json:"imageUrl"`
Spec string `json:"spec"`
Color string `json:"color"`
ColorNo string `json:"colorNo"`
ProductCode string `json:"productCode"`
SalesPrice string `json:"salesPrice"`
Remark string `json:"remark"`
Status int64 `json:"status"`
@ -235,15 +237,19 @@ type (
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
ColorNo string `json:"colorNo,optional"`
ProductCode string `json:"productCode,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
Pans []PanInput `json:"pans,optional"`
InitialBatch ProductBatchInput `json:"initialBatch,optional"`
}
UpdateProductReq {
ProductName string `json:"productName,optional"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
ColorNo string `json:"colorNo,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
}
@ -253,6 +259,8 @@ type (
ProductName string `form:"productName,optional"`
Spec string `form:"spec,optional"`
Color string `form:"color,optional"`
ColorNo string `form:"colorNo,optional"`
ProductCode string `form:"productCode,optional"`
Status int64 `form:"status,optional,default=-1"`
}
ListProductResp {
@ -279,6 +287,8 @@ type (
PanInfo {
PanId string `json:"panId"`
ProductId string `json:"productId"`
BatchId string `json:"batchId"`
BatchNo string `json:"batchNo"`
Name string `json:"name"`
Position string `json:"position"`
SortOrder int64 `json:"sortOrder"`
@ -290,6 +300,7 @@ type (
Name string `json:"name"`
Position string `json:"position,optional"`
BoltLengths []string `json:"boltLengths,optional"`
BatchId string `json:"batchId,optional"`
}
ListPanResp {
List []PanInfo `json:"list"`
@ -331,6 +342,8 @@ type (
ProductId string `json:"productId"`
ProductName string `json:"productName"`
Color string `json:"color"`
ColorNo string `json:"colorNo"`
ProductCode string `json:"productCode"`
Spec string `json:"spec"`
ImageUrl string `json:"imageUrl"`
PanCount int64 `json:"panCount"`
@ -338,6 +351,7 @@ type (
TotalLengthM string `json:"totalLengthM"`
Locations string `json:"locations"`
SalesPrice string `json:"salesPrice"`
BatchCount int64 `json:"batchCount"`
}
ListColorDetailReq {
ProductName string `form:"productName"`
@ -347,6 +361,85 @@ 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 ====================
type (
StockSummaryResp {
@ -725,6 +818,51 @@ service gateway-api {
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
@server (
prefix: /api/v1/inventory
@ -805,4 +943,3 @@ service gateway-api {
@handler ApproveStockAdjustHandler
post /adjusts/:id/approve (ApproveStockAdjustReq) returns (IdResp)
}

View File

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

View File

@ -0,0 +1,32 @@
// 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

@ -0,0 +1,28 @@
// 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

@ -0,0 +1,26 @@
// 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

@ -0,0 +1,34 @@
// 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

@ -0,0 +1,29 @@
// 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

@ -0,0 +1,26 @@
// 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

@ -0,0 +1,26 @@
// 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

@ -0,0 +1,29 @@
// 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

@ -0,0 +1,32 @@
// 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

@ -13,7 +13,9 @@ import (
inventorylog "muyu-apiserver/gateway/internal/handler/inventory/log"
inventorypanbolt "muyu-apiserver/gateway/internal/handler/inventory/panbolt"
inventoryproduct "muyu-apiserver/gateway/internal/handler/inventory/product"
inventoryproductbatch "muyu-apiserver/gateway/internal/handler/inventory/productbatch"
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"
@ -148,6 +150,71 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
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(
rest.WithMiddlewares(
[]rest.Middleware{serverCtx.AuthorityMiddleware},

View File

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

View File

@ -26,13 +26,16 @@ func NewCreateProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Cre
func (l *CreateProductLogic) CreateProduct(req *types.CreateProductReq) (resp *types.IdResp, err error) {
rpcResp, err := l.svcCtx.InventoryRpc.CreateProduct(l.ctx, &pb.CreateProductReq{
ProductName: req.ProductName,
ImageUrl: req.ImageUrl,
Spec: req.Spec,
Color: req.Color,
SalesPrice: req.SalesPrice,
Remark: req.Remark,
Pans: panInputsToPb(req.Pans),
ProductName: req.ProductName,
ImageUrl: req.ImageUrl,
Spec: req.Spec,
Color: req.Color,
ColorNo: req.ColorNo,
ProductCode: req.ProductCode,
SalesPrice: req.SalesPrice,
Remark: req.Remark,
Pans: panInputsToPb(req.Pans),
InitialBatch: productBatchInputToPb(req.InitialBatch),
})
if err != nil {
return nil, err
@ -51,7 +54,22 @@ func panInputsToPb(pans []types.PanInput) []*pb.PanInput {
Name: p.Name,
Position: p.Position,
BoltLengths: p.BoltLengths,
BatchId: p.BatchId,
})
}
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,10 +2,13 @@ package product
import (
"context"
"errors"
"fmt"
"time"
"muyu-apiserver/gateway/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/xuri/excelize/v2"
"github.com/zeromicro/go-zero/core/logx"
)
@ -23,6 +26,99 @@ func NewExportProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Exp
}
}
func (l *ExportProductLogic) ExportProduct() error {
return errors.New("export product requires response writer, will be implemented in handler layer")
func (l *ExportProductLogic) ExportProduct() ([]byte, string, error) {
products, err := l.listAllProducts()
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,6 +37,8 @@ func (l *GetColorDetailLogic) GetColorDetail(req *types.ListColorDetailReq) (res
ProductId: it.ProductId,
ProductName: it.ProductName,
Color: it.Color,
ColorNo: it.ColorNo,
ProductCode: it.ProductCode,
Spec: it.Spec,
ImageUrl: it.ImageUrl,
PanCount: it.PanCount,
@ -44,6 +46,7 @@ func (l *GetColorDetailLogic) GetColorDetail(req *types.ListColorDetailReq) (res
TotalLengthM: it.TotalLengthM,
Locations: it.Locations,
SalesPrice: it.SalesPrice,
BatchCount: it.BatchCount,
})
}
return &types.ColorDetailResp{List: list}, nil

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,34 @@
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

@ -0,0 +1,31 @@
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

@ -0,0 +1,36 @@
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

@ -0,0 +1,35 @@
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

@ -0,0 +1,37 @@
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

@ -0,0 +1,37 @@
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

@ -0,0 +1,30 @@
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

@ -0,0 +1,32 @@
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

@ -0,0 +1,26 @@
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

@ -0,0 +1,39 @@
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

@ -0,0 +1,41 @@
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

@ -34,13 +34,16 @@ type CreateMenuReq struct {
}
type CreateProductReq struct {
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
Pans []PanInput `json:"pans,optional"`
ProductName string `json:"productName"`
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
ColorNo string `json:"colorNo,optional"`
ProductCode string `json:"productCode,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
Pans []PanInput `json:"pans,optional"`
InitialBatch ProductBatchInput `json:"initialBatch,optional"`
}
type CreateRoleReq struct {
@ -114,6 +117,8 @@ type ListProductReq struct {
ProductName string `form:"productName,optional"`
Spec string `form:"spec,optional"`
Color string `form:"color,optional"`
ColorNo string `form:"colorNo,optional"`
ProductCode string `form:"productCode,optional"`
Status int64 `form:"status,optional,default=-1"`
}
@ -230,6 +235,8 @@ type ProductInfo struct {
ImageUrl string `json:"imageUrl"`
Spec string `json:"spec"`
Color string `json:"color"`
ColorNo string `json:"colorNo"`
ProductCode string `json:"productCode"`
SalesPrice string `json:"salesPrice"`
Remark string `json:"remark"`
Status int64 `json:"status"`
@ -247,6 +254,8 @@ type BoltInfo struct {
type PanInfo struct {
PanId string `json:"panId"`
ProductId string `json:"productId"`
BatchId string `json:"batchId"`
BatchNo string `json:"batchNo"`
Name string `json:"name"`
Position string `json:"position"`
SortOrder int64 `json:"sortOrder"`
@ -259,6 +268,7 @@ type PanInput struct {
Name string `json:"name"`
Position string `json:"position,optional"`
BoltLengths []string `json:"boltLengths,optional"`
BatchId string `json:"batchId,optional"`
}
type ListPanResp struct {
@ -304,6 +314,8 @@ type ColorDetailItem struct {
ProductId string `json:"productId"`
ProductName string `json:"productName"`
Color string `json:"color"`
ColorNo string `json:"colorNo"`
ProductCode string `json:"productCode"`
Spec string `json:"spec"`
ImageUrl string `json:"imageUrl"`
PanCount int64 `json:"panCount"`
@ -311,6 +323,7 @@ type ColorDetailItem struct {
TotalLengthM string `json:"totalLengthM"`
Locations string `json:"locations"`
SalesPrice string `json:"salesPrice"`
BatchCount int64 `json:"batchCount"`
}
type ListColorDetailReq struct {
@ -321,9 +334,96 @@ type ColorDetailResp struct {
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 {
PanId string `json:"panId"`
ProductId string `json:"productId"`
BatchId string `json:"batchId"`
BatchNo string `json:"batchNo"`
Name string `json:"name"`
Position string `json:"position"`
ProductName string `json:"productName"`
@ -484,6 +584,7 @@ type UpdateProductReq struct {
ImageUrl string `json:"imageUrl,optional"`
Spec string `json:"spec,optional"`
Color string `json:"color,optional"`
ColorNo string `json:"colorNo,optional"`
SalesPrice string `json:"salesPrice,optional"`
Remark string `json:"remark,optional"`
}
@ -702,12 +803,12 @@ type ListPurchaseOrderResp struct {
// ─── Purchase Receipt ────────────────────────────────────────────────────────
type PurchaseReceiptDetailInfo struct {
DetailId string `json:"detailId"`
DetailId string `json:"detailId"`
OrderDetailId string `json:"orderDetailId"`
ProductId string `json:"productId"`
ActualQty string `json:"actualQty"`
UnitCost string `json:"unitCost"`
Remark string `json:"remark"`
ProductId string `json:"productId"`
ActualQty string `json:"actualQty"`
UnitCost string `json:"unitCost"`
Remark string `json:"remark"`
}
type PurchaseReceiptDetailReq struct {

1
go.mod
View File

@ -66,6 +66,7 @@ require (
github.com/microsoft/go-mssqldb v1.9.5 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 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/ncruces/go-strftime v1.0.0 // indirect
github.com/openzipkin/zipkin-go v0.4.3 // indirect

2
go.sum
View File

@ -189,6 +189,8 @@ 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/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/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/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=

View File

@ -0,0 +1,81 @@
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

@ -0,0 +1,100 @@
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) {
var list []*InvProductBolt
query := fmt.Sprintf(
"SELECT b.`id`, b.`bolt_id`, b.`pan_id`, b.`length_m`, 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.`color`, 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` "+
"WHERE p.`product_id` = ? ORDER BY p.`sort_order`, b.`sort_order`", m.table)
err := m.conn.QueryRowsCtx(ctx, &list, query, productId)

View File

@ -19,25 +19,28 @@ type StockGroupResult struct {
}
type ProductSummary struct {
ProductName string `db:"product_name"`
Spec string `db:"spec"`
ColorCount int64 `db:"color_count"`
TotalPanCount int64 `db:"total_pan_count"`
TotalBoltCount int64 `db:"total_bolt_count"`
TotalLengthM float64 `db:"total_length_m"`
Colors string `db:"colors"`
Locations string `db:"locations"`
ProductName string `db:"product_name"`
Spec string `db:"spec"`
ColorCount int64 `db:"color_count"`
TotalPanCount int64 `db:"total_pan_count"`
TotalBoltCount int64 `db:"total_bolt_count"`
TotalLengthM float64 `db:"total_length_m"`
Colors string `db:"colors"`
Locations string `db:"locations"`
}
type ColorDetail struct {
ProductId string `db:"product_id"`
ProductName string `db:"product_name"`
Color string `db:"color"`
ColorNo string `db:"color_no"`
ProductCode string `db:"product_code"`
Spec string `db:"spec"`
ImageUrl string `db:"image_url"`
SalesPrice float64 `db:"sales_price"`
PanCount int64 `db:"pan_count"`
BoltCount int64 `db:"bolt_count"`
BatchCount int64 `db:"batch_count"`
TotalLengthM float64 `db:"total_length_m"`
Locations string `db:"locations"`
}
@ -45,11 +48,13 @@ type ColorDetail struct {
type (
InvProductModel interface {
invProductModel
FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color string, status int64) ([]*InvProduct, int64, error)
FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, colorNo, productCode string, status int64) ([]*InvProduct, int64, error)
FindProductSummary(ctx context.Context, tenantId string) ([]*ProductSummary, 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)
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)
}
@ -79,15 +84,16 @@ func (m *customInvProductModel) BulkInsert(ctx context.Context, products []*InvP
}
batch := products[i:end]
placeholder := "(" + strings.Repeat("?,", 9) + "?)"
placeholder := "(" + strings.Repeat("?,", 11) + "?)"
placeholders := make([]string, len(batch))
args := make([]interface{}, 0, len(batch)*10)
args := make([]interface{}, 0, len(batch)*12)
for j, p := range batch {
placeholders[j] = placeholder
args = append(args,
p.ProductId, p.ProductName, p.ImageUrl, p.Spec, p.Color,
p.SalesPrice, p.Remark, p.Status, p.TenantId, p.DeletedAt,
p.ColorNo, p.ProductCode, p.SalesPrice, p.Remark, p.Status,
p.TenantId, p.DeletedAt,
)
}
@ -105,7 +111,7 @@ func (m *customInvProductModel) BulkInsert(ctx context.Context, products []*InvP
return totalAffected, nil
}
func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color string, status int64) ([]*InvProduct, int64, error) {
func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, page, pageSize int64, productName, spec, color, colorNo, productCode string, status int64) ([]*InvProduct, int64, error) {
where := "WHERE deleted_at IS NULL AND tenant_id = ?"
args := []interface{}{tenantId}
@ -121,6 +127,14 @@ func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, p
where += " AND 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 {
where += " AND status = ?"
args = append(args, status)
@ -144,6 +158,27 @@ func (m *customInvProductModel) FindList(ctx context.Context, tenantId string, p
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) {
var list []*ProductSummary
query := `SELECT p.product_name, p.spec,
@ -200,9 +235,16 @@ func (m *customInvProductModel) FindGroupByLocation(ctx context.Context, tenantI
func (m *customInvProductModel) FindColorDetail(ctx context.Context, tenantId, productName string) ([]*ColorDetail, error) {
var list []*ColorDetail
query := `SELECT p.product_id, p.product_name, p.color, p.spec, p.image_url, p.sales_price,
query := `SELECT p.product_id, p.product_name, p.color, p.color_no, p.product_code, p.spec, p.image_url, p.sales_price,
COUNT(DISTINCT pan.pan_id) AS pan_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(GROUP_CONCAT(DISTINCT pan.position ORDER BY pan.position), '') AS locations
FROM ` + m.table + ` p

View File

@ -27,6 +27,7 @@ var (
cacheInvProductIdPrefix = "cache:invProduct:id:"
cacheInvProductProductIdPrefix = "cache:invProduct:productId:"
cacheInvProductProductNamePrefix = "cache:invProduct:productName:"
cacheInvProductProductCodePrefix = "cache:invProduct:productCode:"
)
type (
@ -35,6 +36,7 @@ type (
FindOne(ctx context.Context, id int64) (*InvProduct, error)
FindOneByProductId(ctx context.Context, productId 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
Delete(ctx context.Context, id int64) error
}
@ -51,6 +53,8 @@ type (
ImageUrl string `db:"image_url"`
Spec string `db:"spec"`
Color string `db:"color"`
ColorNo string `db:"color_no"`
ProductCode string `db:"product_code"`
SalesPrice float64 `db:"sales_price"`
Remark sql.NullString `db:"remark"`
Status int64 `db:"status"`
@ -77,10 +81,11 @@ func (m *defaultInvProductModel) Delete(ctx context.Context, id int64) error {
invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, id)
invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId)
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) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey, invProductProductCodeKey)
return err
}
@ -121,6 +126,26 @@ 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) {
invProductProductNameKey := fmt.Sprintf("%s%v", cacheInvProductProductNamePrefix, productName)
var resp InvProduct
@ -145,10 +170,11 @@ func (m *defaultInvProductModel) Insert(ctx context.Context, data *InvProduct) (
invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, data.Id)
invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId)
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) {
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.SalesPrice, data.Remark, data.Status, data.TenantId, data.DeletedAt)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey)
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)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey, invProductProductCodeKey)
return ret, err
}
@ -161,10 +187,11 @@ func (m *defaultInvProductModel) Update(ctx context.Context, newData *InvProduct
invProductIdKey := fmt.Sprintf("%s%v", cacheInvProductIdPrefix, data.Id)
invProductProductIdKey := fmt.Sprintf("%s%v", cacheInvProductProductIdPrefix, data.ProductId)
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) {
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.SalesPrice, newData.Remark, newData.Status, newData.TenantId, newData.DeletedAt, newData.Id)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey)
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)
}, invProductIdKey, invProductProductIdKey, invProductProductNameKey, invProductProductCodeKey)
return err
}

View File

@ -49,6 +49,8 @@ func TestInvProductInsert(t *testing.T) {
ImageUrl: "",
Spec: "100cm",
Color: "red",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 99.9,
Remark: sql.NullString{String: "test remark", Valid: true},
Status: 1,
@ -83,6 +85,8 @@ func TestInvProductFindOne(t *testing.T) {
ProductName: uniqueID("test_product"),
Spec: "200cm",
Color: "blue",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 49.5,
Status: 1,
TenantId: "t_default_001",
@ -121,6 +125,8 @@ func TestInvProductUpdate(t *testing.T) {
ProductName: uniqueID("test_product"),
Spec: "50cm",
Color: "green",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 10.0,
Status: 1,
TenantId: "t_default_001",
@ -172,6 +178,8 @@ func TestInvProductBulkInsert(t *testing.T) {
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",
@ -229,7 +237,8 @@ func TestInvProductSchemaColumns(t *testing.T) {
// Columns that the InvProduct struct declares (via db tags).
structCols := []string{
"id", "product_id", "product_name", "image_url", "spec", "color",
"sales_price", "remark", "status", "tenant_id", "created_at", "updated_at", "deleted_at",
"color_no", "product_code", "sales_price", "remark", "status", "tenant_id",
"created_at", "updated_at", "deleted_at",
}
for _, col := range structCols {
@ -253,7 +262,7 @@ func TestInvProductSchemaColumns(t *testing.T) {
// 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(..., '').
// 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")
@ -267,6 +276,8 @@ func TestFindProductSummaryNoPans(t *testing.T) {
ProductName: uniqueID("nopan_product"),
Spec: "50cm",
Color: "red",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 99.0,
Status: 1,
TenantId: "t_default_001",
@ -318,6 +329,8 @@ func TestFindColorDetailNoPans(t *testing.T) {
ProductName: uniqueID("color_nopan_product"),
Spec: "80cm",
Color: "blue",
ColorNo: "01",
ProductCode: uniqueID("TPC"),
SalesPrice: 50.0,
Status: 1,
TenantId: "t_default_001",
@ -343,3 +356,96 @@ func TestFindColorDetailNoPans(t *testing.T) {
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,6 +15,8 @@ var _ InvProductPanModel = (*customInvProductPanModel)(nil)
type PanListItem struct {
PanId string `db:"pan_id"`
ProductId string `db:"product_id"`
BatchId string `db:"batch_id"`
BatchNo string `db:"batch_no"`
Name string `db:"name"`
Position string `db:"position"`
ProductName string `db:"product_name"`
@ -30,6 +32,7 @@ type (
DeleteByProductId(ctx context.Context, productId string) 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)
CountByBatchId(ctx context.Context, batchId string) (int64, error)
}
customInvProductPanModel struct {
@ -79,13 +82,14 @@ func (m *customInvProductPanModel) FindPanList(ctx context.Context, tenantId str
return nil, 0, err
}
query := fmt.Sprintf(`SELECT pan.pan_id, pan.product_id, pan.name, pan.position,
p.product_name,
query := fmt.Sprintf(`SELECT pan.pan_id, pan.product_id, pan.batch_id, COALESCE(batch.batch_no, '') AS batch_no,
pan.name, pan.position, p.product_name,
COALESCE(GROUP_CONCAT(DISTINCT b.color ORDER BY b.color SEPARATOR ', '), '') AS colors,
COUNT(b.bolt_id) AS bolt_count,
COALESCE(SUM(b.length_m), 0) AS total_length_m
FROM %s pan
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
%s
GROUP BY pan.pan_id
@ -100,6 +104,15 @@ func (m *customInvProductPanModel) FindPanList(ctx context.Context, tenantId str
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 {
return m.conn.TransactCtx(ctx, func(ctx context.Context, session sqlx.Session) error {
delQuery := fmt.Sprintf("DELETE FROM %s WHERE `product_id` = ?", m.table)
@ -109,12 +122,12 @@ func (m *customInvProductPanModel) BulkReplace(ctx context.Context, productId, t
if len(pans) == 0 {
return nil
}
placeholder := "(?, ?, ?, ?, ?, ?)"
placeholder := "(?, ?, ?, ?, ?, ?, ?)"
placeholders := make([]string, len(pans))
args := make([]interface{}, 0, len(pans)*6)
args := make([]interface{}, 0, len(pans)*7)
for i, p := range pans {
placeholders[i] = placeholder
args = append(args, p.PanId, productId, p.Name, p.Position, p.SortOrder, tenantId)
args = append(args, p.PanId, productId, p.BatchId, p.Name, p.Position, p.SortOrder, tenantId)
}
insQuery := fmt.Sprintf("INSERT INTO %s (%s) VALUES %s", m.table, invProductPanRowsExpectAutoSet, strings.Join(placeholders, ","))
_, err := session.ExecCtx(ctx, insQuery, args...)

View File

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

82
model/invyarnmodel.go Normal file
View File

@ -0,0 +1,82 @@
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)
}

100
model/invyarnmodel_gen.go Normal file
View File

@ -0,0 +1,100 @@
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 (
invYarnFieldNames = builder.RawFieldNames(&InvYarn{})
invYarnRows = strings.Join(invYarnFieldNames, ",")
invYarnRowsExpectAutoSet = strings.Join(stringx.Remove(invYarnFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), ",")
invYarnRowsWithPlaceHolder = strings.Join(stringx.Remove(invYarnFieldNames, "`id`", "`create_at`", "`create_time`", "`created_at`", "`update_at`", "`update_time`", "`updated_at`"), "=?,") + "=?"
)
type (
invYarnModel interface {
Insert(ctx context.Context, data *InvYarn) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*InvYarn, error)
FindOneByYarnId(ctx context.Context, yarnId string) (*InvYarn, error)
Update(ctx context.Context, data *InvYarn) error
Delete(ctx context.Context, id int64) error
}
defaultInvYarnModel struct {
conn sqlx.SqlConn
table string
}
InvYarn struct {
Id int64 `db:"id"`
YarnId string `db:"yarn_id"`
TenantId string `db:"tenant_id"`
YarnName string `db:"yarn_name"`
Color string `db:"color"`
WeightGM float64 `db:"weight_g_m"`
SupplierId string `db:"supplier_id"`
DyeFactory string `db:"dye_factory"`
ImageUrl string `db:"image_url"`
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 newInvYarnModel(conn sqlx.SqlConn) *defaultInvYarnModel {
return &defaultInvYarnModel{
conn: conn,
table: "`inv_yarn`",
}
}
func (m *defaultInvYarnModel) Insert(ctx context.Context, data *InvYarn) (sql.Result, error) {
query := fmt.Sprintf("INSERT INTO %s (%s) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", m.table, invYarnRowsExpectAutoSet)
return m.conn.ExecCtx(ctx, query, data.YarnId, data.TenantId, data.YarnName, data.Color, data.WeightGM, data.SupplierId, data.DyeFactory, data.ImageUrl, data.Remark, data.Status, data.DeletedAt)
}
func (m *defaultInvYarnModel) FindOne(ctx context.Context, id int64) (*InvYarn, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE `id` = ? LIMIT 1", invYarnRows, m.table)
var resp InvYarn
err := m.conn.QueryRowCtx(ctx, &resp, query, id)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultInvYarnModel) FindOneByYarnId(ctx context.Context, yarnId string) (*InvYarn, error) {
query := fmt.Sprintf("SELECT %s FROM %s WHERE `yarn_id` = ? AND `deleted_at` IS NULL LIMIT 1", invYarnRows, m.table)
var resp InvYarn
err := m.conn.QueryRowCtx(ctx, &resp, query, yarnId)
if err != nil {
return nil, err
}
return &resp, nil
}
func (m *defaultInvYarnModel) Update(ctx context.Context, data *InvYarn) error {
query := fmt.Sprintf("UPDATE %s SET %s WHERE `id` = ?", m.table, invYarnRowsWithPlaceHolder)
_, err := m.conn.ExecCtx(ctx, query, data.YarnId, data.TenantId, data.YarnName, data.Color, data.WeightGM, data.SupplierId, data.DyeFactory, data.ImageUrl, data.Remark, data.Status, data.DeletedAt, data.Id)
return err
}
func (m *defaultInvYarnModel) 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 *defaultInvYarnModel) tableName() string {
return m.table
}

86
pkg/excelparse/native.go Normal file
View File

@ -0,0 +1,86 @@
package excelparse
import "strings"
// NativeProductParser handles the MuYu product import template and product exports.
type NativeProductParser struct{}
func (p *NativeProductParser) Name() string { return "木语产品模板" }
func (p *NativeProductParser) Match(headers []string) bool {
idx := buildIndex(headers)
if !hasAny(idx, "产品名称", "名称") {
return false
}
return hasAny(idx, "产品码", "色号", "批次号", "纱线配比", "生产工艺")
}
func (p *NativeProductParser) ParseRows(headers []string, rows [][]string) (*ParseResult, error) {
idx := buildIndex(headers)
seen := make(map[string]struct{}, len(rows))
products := make([]ParsedProduct, 0, len(rows))
skipped := 0
for _, row := range rows {
name := firstCol(row, idx, "产品名称", "名称")
if name == "" {
skipped++
continue
}
productCode := firstCol(row, idx, "产品码", "Product Code", "product_code")
key := productCode
if key == "" {
key = strings.Join([]string{
name,
firstCol(row, idx, "规格型号", "规格"),
firstCol(row, idx, "颜色"),
firstCol(row, idx, "色号"),
}, "\x00")
}
if _, ok := seen[key]; ok {
skipped++
continue
}
seen[key] = struct{}{}
products = append(products, ParsedProduct{
ProductName: name,
Spec: firstCol(row, idx, "规格型号", "规格"),
Color: firstCol(row, idx, "颜色"),
ColorNo: firstCol(row, idx, "色号"),
ProductCode: productCode,
SalesPrice: stripCurrency(firstCol(row, idx, "销售价", "参考售价")),
BatchNo: firstCol(row, idx, "批次号"),
YarnRatio: firstCol(row, idx, "纱线配比"),
WarpWeightGM: stripUnit(firstCol(row, idx, "经线克重(g/m)", "经线克重", "经纱克重")),
WeftWeightGM: stripUnit(firstCol(row, idx, "纬线克重(g/m)", "纬线克重", "纬纱克重")),
ProductionProcess: firstCol(row, idx, "生产工艺"),
Remark: firstCol(row, idx, "备注"),
})
}
return &ParseResult{Products: products, SkippedRows: skipped}, nil
}
func hasAny(idx map[string]int, names ...string) bool {
for _, name := range names {
if _, ok := idx[name]; ok {
return true
}
}
return false
}
func firstCol(row []string, idx map[string]int, names ...string) string {
for _, name := range names {
col, ok := idx[name]
if !ok {
continue
}
if val := colVal(row, col); val != "" {
return val
}
}
return ""
}

View File

@ -17,6 +17,7 @@ import (
var parsers []FormatParser
func init() {
parsers = append(parsers, &NativeProductParser{})
parsers = append(parsers, &MiaozhangParser{})
}

View File

@ -3,25 +3,32 @@ package excelparse
import "errors"
var (
ErrUnknownFormat = errors.New("unrecognized excel format; supported: 秒账, 领星")
ErrEmptyFile = errors.New("excel file has no data rows")
ErrUnknownFormat = errors.New("unrecognized excel format; supported: 秒账, 领星")
ErrEmptyFile = errors.New("excel file has no data rows")
ErrUnsupportedExt = errors.New("unsupported file extension; use .xls or .xlsx")
)
type ParsedProduct struct {
ProductName string
Spec string
Color string
UnitRolls int64
StockQuantity string
CostPrice string
SalesPrice string
Remark string
ProductName string
Spec string
Color string
ColorNo string
ProductCode string
UnitRolls int64
StockQuantity string
CostPrice string
SalesPrice string
BatchNo string
YarnRatio string
WarpWeightGM string
WeftWeightGM string
ProductionProcess string
Remark string
}
type ParseResult struct {
Products []ParsedProduct
ParserName string
Products []ParsedProduct
ParserName string
SkippedRows int
}

View File

@ -0,0 +1,140 @@
package logic
import (
"database/sql"
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"muyu-apiserver/model"
"muyu-apiserver/rpc/inventory/pb"
)
func nullString(v string) sql.NullString {
return sql.NullString{String: v, Valid: v != ""}
}
func nullableJSON(v string) sql.NullString {
v = strings.TrimSpace(v)
return sql.NullString{String: v, Valid: v != ""}
}
func parseDecimalString(v string) float64 {
n, _ := strconv.ParseFloat(strings.TrimSpace(v), 64)
return n
}
type yarnRatioPayload struct {
Warp []yarnRatioItem `json:"warp"`
Weft []yarnRatioItem `json:"weft"`
}
type yarnRatioItem struct {
YarnID string `json:"yarn_id"`
YarnId string `json:"yarnId"`
Ratio float64 `json:"ratio"`
}
func (i yarnRatioItem) yarnID() string {
if i.YarnID != "" {
return i.YarnID
}
return i.YarnId
}
func validateProductBatchInput(batch *pb.ProductBatchInput) error {
if batch == nil {
return nil
}
return validateYarnRatio(batch.YarnRatio)
}
func validateYarnRatio(raw string) error {
raw = strings.TrimSpace(raw)
if raw == "" {
return nil
}
var payload yarnRatioPayload
if err := json.Unmarshal([]byte(raw), &payload); err != nil {
return fmt.Errorf("纱线配比格式无效")
}
if err := validateYarnRatioSide("经线", payload.Warp); err != nil {
return err
}
return validateYarnRatioSide("纬线", payload.Weft)
}
func validateYarnRatioSide(label string, items []yarnRatioItem) error {
if len(items) == 0 {
return nil
}
total := 0
for _, item := range items {
if strings.TrimSpace(item.yarnID()) == "" {
return fmt.Errorf("%s纱线不能为空", label)
}
if item.Ratio < 1 || item.Ratio > 10 || math.Trunc(item.Ratio) != item.Ratio {
return fmt.Errorf("%s使用配比必须为 1-10 的整数", label)
}
total += int(item.Ratio)
}
if total != 10 {
return fmt.Errorf("%s配比合计必须为 10当前为 %d", label, total)
}
return nil
}
func batchInputToModelFields(batch *pb.ProductBatchInput, target *model.InvProductBatch) error {
if batch == nil {
target.ProductionProcess = nullString(defaultProcess)
return nil
}
if err := validateProductBatchInput(batch); err != nil {
return err
}
target.YarnRatio = nullableJSON(batch.YarnRatio)
target.WarpWeightGM = parseDecimalString(batch.WarpWeightGM)
target.WeftWeightGM = parseDecimalString(batch.WeftWeightGM)
process := strings.TrimSpace(batch.ProductionProcess)
if process == "" {
process = defaultProcess
}
target.ProductionProcess = nullString(process)
target.Remark = nullString(batch.Remark)
return nil
}
func batchToPb(batch *model.InvProductBatch) *pb.ProductBatchInfo {
return &pb.ProductBatchInfo{
BatchId: batch.BatchId,
ProductId: batch.ProductId,
BatchNo: batch.BatchNo,
YarnRatio: batch.YarnRatio.String,
WarpWeightGM: fmt.Sprintf("%.4f", batch.WarpWeightGM),
WeftWeightGM: fmt.Sprintf("%.4f", batch.WeftWeightGM),
ProductionProcess: batch.ProductionProcess.String,
Remark: batch.Remark.String,
Status: batch.Status,
CreatedAt: batch.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: batch.UpdatedAt.Format("2006-01-02 15:04:05"),
}
}
func productToPb(product *model.InvProduct) *pb.ProductInfo {
return &pb.ProductInfo{
ProductId: product.ProductId,
ProductName: product.ProductName,
ImageUrl: product.ImageUrl,
Spec: product.Spec,
Color: product.Color,
SalesPrice: fmt.Sprintf("%.2f", product.SalesPrice),
Remark: product.Remark.String,
Status: product.Status,
CreatedAt: product.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: product.UpdatedAt.Format("2006-01-02 15:04:05"),
ColorNo: product.ColorNo,
ProductCode: product.ProductCode,
}
}

View File

@ -0,0 +1,56 @@
package logic
import "testing"
func TestValidateYarnRatio(t *testing.T) {
tests := []struct {
name string
raw string
wantErr bool
}{
{
name: "empty ratio is valid",
raw: "",
wantErr: false,
},
{
name: "both sides add up to ten",
raw: `{"warp":[{"yarn_id":"y1","ratio":6},{"yarn_id":"y2","ratio":4}],"weft":[{"yarn_id":"y3","ratio":10}]}`,
wantErr: false,
},
{
name: "one empty side is valid",
raw: `{"warp":[{"yarn_id":"y1","ratio":10}],"weft":[]}`,
wantErr: false,
},
{
name: "side with yarn must add up to ten",
raw: `{"warp":[{"yarn_id":"y1","ratio":9}],"weft":[]}`,
wantErr: true,
},
{
name: "ratio must be integer",
raw: `{"warp":[{"yarn_id":"y1","ratio":9.5}],"weft":[]}`,
wantErr: true,
},
{
name: "yarn id is required",
raw: `{"warp":[{"ratio":10}],"weft":[]}`,
wantErr: true,
},
{
name: "invalid json is rejected",
raw: `{warp}`,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateYarnRatio(tt.raw)
if (err != nil) != tt.wantErr {
t.Fatalf("validateYarnRatio() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@ -0,0 +1,56 @@
package logic
import (
"context"
"strings"
"muyu-apiserver/model"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type CreateProductBatchLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCreateProductBatchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateProductBatchLogic {
return &CreateProductBatchLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *CreateProductBatchLogic) CreateProductBatch(in *pb.CreateProductBatchReq) (*pb.IdResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, in.ProductId)
if err != nil || product.TenantId != tenantId {
return nil, status.Error(codes.NotFound, "product not found")
}
batchNo, err := l.svcCtx.ProductBatchModel.NextBatchNo(l.ctx, tenantId, product.ProductCode)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if requestedBatchNo := strings.TrimSpace(in.Batch.GetBatchNo()); requestedBatchNo != "" {
batchNo = requestedBatchNo
}
batchId := uid.Generate()
batch := &model.InvProductBatch{
BatchId: batchId,
TenantId: tenantId,
ProductId: product.ProductId,
BatchNo: batchNo,
Status: 1,
}
if err := batchInputToModelFields(in.Batch, batch); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if _, err := l.svcCtx.ProductBatchModel.Insert(l.ctx, batch); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.IdResp{Id: batchId}, nil
}

View File

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"strconv"
"strings"
"muyu-apiserver/model"
"muyu-apiserver/pkg/metrics"
@ -35,23 +36,64 @@ func (l *CreateProductLogic) CreateProduct(in *pb.CreateProductReq) (*pb.IdResp,
tenantId := tenantctx.ExtractTenantId(l.ctx)
productId := uid.Generate()
salesPrice, _ := strconv.ParseFloat(in.SalesPrice, 64)
productName := strings.TrimSpace(in.ProductName)
if productName == "" {
return nil, status.Error(codes.InvalidArgument, "产品名称不能为空")
}
color := normalizeProductColor(in.Color)
colorNo, err := resolveColorNo(l.ctx, l.svcCtx.ProductModel, tenantId, productName, in.ColorNo)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
productCode := strings.ToUpper(strings.TrimSpace(in.ProductCode))
if productCode == "" {
productCode = generateProductCode(productName, colorNo)
}
if err := ensureProductCodeAvailable(l.ctx, l.svcCtx.ProductModel, tenantId, productCode); err != nil {
return nil, status.Error(codes.AlreadyExists, err.Error())
}
if err := validateProductBatchInput(in.InitialBatch); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
product := &model.InvProduct{
ProductId: productId,
TenantId: tenantId,
ProductName: in.ProductName,
ProductName: productName,
ImageUrl: in.ImageUrl,
Spec: in.Spec,
Color: in.Color,
Color: color,
ColorNo: colorNo,
ProductCode: productCode,
SalesPrice: salesPrice,
Remark: sql.NullString{String: in.Remark, Valid: in.Remark != ""},
Status: 1,
}
_, err := l.svcCtx.ProductModel.Insert(l.ctx, product)
_, err = l.svcCtx.ProductModel.Insert(l.ctx, product)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
firstBatchNo := productCode + "-B001"
if batchNo := strings.TrimSpace(in.InitialBatch.GetBatchNo()); batchNo != "" {
firstBatchNo = batchNo
}
firstBatch := &model.InvProductBatch{
BatchId: uid.Generate(),
TenantId: tenantId,
ProductId: productId,
BatchNo: firstBatchNo,
Status: 1,
}
if err := batchInputToModelFields(in.InitialBatch, firstBatch); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if _, err := l.svcCtx.ProductBatchModel.Insert(l.ctx, firstBatch); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
var totalLengthM float64
var totalBolts int
@ -59,12 +101,21 @@ func (l *CreateProductLogic) CreateProduct(in *pb.CreateProductReq) (*pb.IdResp,
pans := make([]*model.InvProductPan, 0, len(in.Pans))
for i, panInput := range in.Pans {
panId := uid.Generate()
batchId := panInput.BatchId
if batchId == "" {
batchId = firstBatch.BatchId
}
if _, err := l.svcCtx.ProductBatchModel.FindOneByProductAndBatchId(l.ctx, tenantId, productId, batchId); err != nil {
return nil, status.Error(codes.InvalidArgument, "批次不存在或不属于该产品")
}
pans = append(pans, &model.InvProductPan{
PanId: panId,
ProductId: productId,
BatchId: batchId,
Name: panInput.Name,
Position: panInput.Position,
SortOrder: int64(i + 1),
TenantId: tenantId,
})
if len(panInput.BoltLengths) > 0 {
@ -76,23 +127,25 @@ func (l *CreateProductLogic) CreateProduct(in *pb.CreateProductReq) (*pb.IdResp,
BoltId: uid.Generate(),
PanId: panId,
LengthM: lengthM,
Color: color,
SortOrder: int64(j + 1),
TenantId: tenantId,
})
}
totalBolts += len(panInput.BoltLengths)
if err := l.svcCtx.BoltModel.BulkReplace(l.ctx, panId, "", bolts); err != nil {
if err := l.svcCtx.BoltModel.BulkReplace(l.ctx, panId, tenantId, bolts); err != nil {
l.Errorf("create bolts for pan %s failed: %v", panId, err)
}
}
}
if err := l.svcCtx.PanModel.BulkReplace(l.ctx, productId, "", pans); err != nil {
if err := l.svcCtx.PanModel.BulkReplace(l.ctx, productId, tenantId, pans); err != nil {
l.Errorf("create pans for product %s failed: %v", productId, err)
}
}
metrics.ProductCreatedTotal.WithLabelValues(tenantId, in.ProductName, in.Spec, in.Color).Inc()
metrics.ProductCreatedTotal.WithLabelValues(tenantId, productName, in.Spec, color).Inc()
if totalLengthM > 0 {
metrics.InboundLengthMetersTotal.WithLabelValues(tenantId, in.ProductName, in.Color, "create").Add(totalLengthM)
metrics.InboundLengthMetersTotal.WithLabelValues(tenantId, productName, color, "create").Add(totalLengthM)
}
if totalBolts > 0 {
metrics.InboundBoltsTotal.WithLabelValues(tenantId, "create").Add(float64(totalBolts))

View File

@ -0,0 +1,68 @@
package logic
import (
"context"
"database/sql"
"errors"
"strings"
"muyu-apiserver/model"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/pkg/uid"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type CreateYarnLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewCreateYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CreateYarnLogic {
return &CreateYarnLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *CreateYarnLogic) CreateYarn(in *pb.CreateYarnReq) (*pb.IdResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
yarnName := strings.TrimSpace(in.YarnName)
if yarnName == "" {
return nil, status.Error(codes.InvalidArgument, "纱线名称不能为空")
}
if strings.TrimSpace(in.SupplierId) == "" {
return nil, status.Error(codes.InvalidArgument, "供应商不能为空")
}
supplier, err := l.svcCtx.SupplierModel.FindOneBySupplierId(l.ctx, in.SupplierId)
if err != nil || supplier.TenantId != tenantId {
return nil, status.Error(codes.InvalidArgument, "供应商不存在")
}
color := normalizeYarnColor(in.Color)
if _, err := l.svcCtx.YarnModel.FindOneByUnique(l.ctx, tenantId, yarnName, color, in.SupplierId); err == nil {
return nil, status.Error(codes.AlreadyExists, "纱线已存在")
} else if !errors.Is(err, model.ErrNotFound) {
return nil, status.Error(codes.Internal, err.Error())
}
yarnId := uid.Generate()
yarn := &model.InvYarn{
YarnId: yarnId,
TenantId: tenantId,
YarnName: yarnName,
Color: color,
WeightGM: parseDecimalString(in.WeightGM),
SupplierId: in.SupplierId,
DyeFactory: normalizeDyeFactory(in.DyeFactory),
ImageUrl: in.ImageUrl,
Remark: sql.NullString{String: in.Remark, Valid: in.Remark != ""},
Status: 1,
}
if _, err := l.svcCtx.YarnModel.Insert(l.ctx, yarn); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.IdResp{Id: yarnId}, nil
}

View File

@ -0,0 +1,46 @@
package logic
import (
"context"
"strings"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type DeleteProductBatchLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDeleteProductBatchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteProductBatchLogic {
return &DeleteProductBatchLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *DeleteProductBatchLogic) DeleteProductBatch(in *pb.DeleteProductBatchReq) (*pb.Empty, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
batch, err := l.svcCtx.ProductBatchModel.FindOneByProductAndBatchId(l.ctx, tenantId, in.ProductId, in.BatchId)
if err != nil {
return nil, status.Error(codes.NotFound, "批次不存在")
}
if strings.HasSuffix(batch.BatchNo, "-B001") {
return nil, status.Error(codes.FailedPrecondition, "B001 批次不可删除")
}
count, err := l.svcCtx.PanModel.CountByBatchId(l.ctx, in.BatchId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if count > 0 {
return nil, status.Error(codes.FailedPrecondition, "批次已有库存引用,不能删除")
}
if err := l.svcCtx.ProductBatchModel.SoftDeleteByBatchId(l.ctx, in.BatchId); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.Empty{}, nil
}

View File

@ -0,0 +1,35 @@
package logic
import (
"context"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type DeleteYarnLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewDeleteYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteYarnLogic {
return &DeleteYarnLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *DeleteYarnLogic) DeleteYarn(in *pb.DeleteYarnReq) (*pb.Empty, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
yarn, err := l.svcCtx.YarnModel.FindOneByYarnId(l.ctx, in.YarnId)
if err != nil || yarn.TenantId != tenantId {
return nil, status.Error(codes.NotFound, "纱线不存在")
}
if err := l.svcCtx.YarnModel.SoftDeleteByYarnId(l.ctx, in.YarnId); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.Empty{}, nil
}

View File

@ -2,7 +2,6 @@ package logic
import (
"context"
"fmt"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -32,16 +31,5 @@ func (l *GetProductLogic) GetProduct(in *pb.GetProductReq) (*pb.ProductInfo, err
return nil, status.Error(codes.NotFound, "product not found")
}
return &pb.ProductInfo{
ProductId: product.ProductId,
ProductName: product.ProductName,
ImageUrl: product.ImageUrl,
Spec: product.Spec,
Color: product.Color,
SalesPrice: fmt.Sprintf("%.2f", product.SalesPrice),
Remark: product.Remark.String,
Status: product.Status,
CreatedAt: product.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: product.UpdatedAt.Format("2006-01-02 15:04:05"),
}, nil
return productToPb(product), nil
}

View File

@ -36,6 +36,14 @@ func (l *GetProductTreeLogic) GetProductTree(in *pb.GetProductReq) (*pb.ProductT
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
batches, err := l.svcCtx.ProductBatchModel.FindByProductId(l.ctx, product.TenantId, in.ProductId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
batchNoById := make(map[string]string, len(batches))
for _, b := range batches {
batchNoById[b.BatchId] = b.BatchNo
}
var totalBoltCount int64
var totalLengthM float64
@ -65,6 +73,8 @@ func (l *GetProductTreeLogic) GetProductTree(in *pb.GetProductReq) (*pb.ProductT
pbPans = append(pbPans, &pb.PanInfo{
PanId: pan.PanId,
ProductId: pan.ProductId,
BatchId: pan.BatchId,
BatchNo: batchNoById[pan.BatchId],
Name: pan.Name,
Position: pan.Position,
SortOrder: pan.SortOrder,
@ -75,18 +85,7 @@ func (l *GetProductTreeLogic) GetProductTree(in *pb.GetProductReq) (*pb.ProductT
}
return &pb.ProductTreeResp{
Product: &pb.ProductInfo{
ProductId: product.ProductId,
ProductName: product.ProductName,
ImageUrl: product.ImageUrl,
Spec: product.Spec,
Color: product.Color,
SalesPrice: fmt.Sprintf("%.2f", product.SalesPrice),
Remark: product.Remark.String,
Status: product.Status,
CreatedAt: product.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: product.UpdatedAt.Format("2006-01-02 15:04:05"),
},
Product: productToPb(product),
Pans: pbPans,
TotalPanCount: int64(len(pans)),
TotalBoltCount: totalBoltCount,

View File

@ -0,0 +1,36 @@
package logic
import (
"context"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type GetYarnLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewGetYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetYarnLogic {
return &GetYarnLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *GetYarnLogic) GetYarn(in *pb.GetYarnReq) (*pb.YarnInfo, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
yarn, err := l.svcCtx.YarnModel.FindOneByYarnId(l.ctx, in.YarnId)
if err != nil || yarn.TenantId != tenantId {
return nil, status.Error(codes.NotFound, "纱线不存在")
}
supplierName := ""
if supplier, err := l.svcCtx.SupplierModel.FindOneBySupplierId(l.ctx, yarn.SupplierId); err == nil {
supplierName = supplier.SupplierName
}
return yarnToPb(yarn, supplierName), nil
}

View File

@ -3,7 +3,7 @@ package logic
import (
"context"
"database/sql"
"strconv"
"fmt"
"strings"
"muyu-apiserver/model"
@ -33,33 +33,20 @@ func NewImportProductsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Im
}
func (l *ImportProductsLogic) ImportProducts(in *pb.ImportProductReq) (*pb.ImportProductResp, error) {
products := make([]*model.InvProduct, 0, len(in.Products))
totalCount := int64(len(in.Products))
var successCount int64
errs := make([]string, 0)
creator := NewCreateProductLogic(l.ctx, l.svcCtx)
for _, p := range in.Products {
salesPrice, _ := strconv.ParseFloat(p.SalesPrice, 64)
products = append(products, &model.InvProduct{
ProductId: uid.Generate(),
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
SalesPrice: salesPrice,
Remark: sql.NullString{String: p.Remark, Valid: p.Remark != ""},
Status: 1,
})
}
totalCount := int64(len(products))
affected, err := l.svcCtx.ProductModel.BulkInsert(l.ctx, products)
var errorMsg string
successCount := affected
failCount := totalCount - affected
if err != nil {
errorMsg = err.Error()
l.Errorf("bulk insert partial failure: %v", err)
for i, p := range in.Products {
if _, err := creator.CreateProduct(p); err != nil {
errs = append(errs, fmt.Sprintf("第%d行: %v", i+1, err))
continue
}
successCount++
}
failCount := totalCount - successCount
errorMsg := strings.Join(errs, "; ")
importId := uid.Generate()
importLog := &model.InvStockImportLog{
@ -74,7 +61,7 @@ func (l *ImportProductsLogic) ImportProducts(in *pb.ImportProductReq) (*pb.Impor
if _, logErr := l.svcCtx.ImportLogModel.Insert(l.ctx, importLog); logErr != nil {
l.Errorf("save import log failed: %v", logErr)
if err != nil {
if errorMsg != "" {
return nil, status.Error(codes.Internal, strings.Join([]string{errorMsg, logErr.Error()}, "; "))
}
return nil, status.Error(codes.Internal, logErr.Error())
@ -87,9 +74,6 @@ func (l *ImportProductsLogic) ImportProducts(in *pb.ImportProductReq) (*pb.Impor
metrics.ImportProductsTotal.WithLabelValues(tenantId, "fail").Add(float64(failCount))
}
metrics.ImportBatchSize.WithLabelValues(tenantId).Observe(float64(totalCount))
for _, p := range in.Products {
metrics.ProductCreatedTotal.WithLabelValues(tenantId, p.ProductName, p.Spec, p.Color).Inc()
}
return &pb.ImportProductResp{
TotalCount: totalCount,

View File

@ -40,10 +40,13 @@ func (l *ListColorDetailLogic) ListColorDetail(in *pb.ListColorDetailReq) (*pb.L
ProductId: d.ProductId,
ProductName: d.ProductName,
Color: d.Color,
ColorNo: d.ColorNo,
ProductCode: d.ProductCode,
Spec: d.Spec,
ImageUrl: d.ImageUrl,
PanCount: d.PanCount,
BoltCount: d.BoltCount,
BatchCount: d.BatchCount,
TotalLengthM: fmt.Sprintf("%.2f", d.TotalLengthM),
Locations: d.Locations,
SalesPrice: fmt.Sprintf("%.2f", d.SalesPrice),

View File

@ -27,10 +27,22 @@ func NewListPansLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListPans
}
func (l *ListPansLogic) ListPans(in *pb.ListPanReq) (*pb.ListPanResp, error) {
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, in.ProductId)
if err != nil {
return nil, status.Error(codes.NotFound, "product not found")
}
pans, err := l.svcCtx.PanModel.FindByProductId(l.ctx, in.ProductId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
batches, err := l.svcCtx.ProductBatchModel.FindByProductId(l.ctx, product.TenantId, in.ProductId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
batchNoById := make(map[string]string, len(batches))
for _, b := range batches {
batchNoById[b.BatchId] = b.BatchNo
}
pbPans := make([]*pb.PanInfo, 0, len(pans))
for _, pan := range pans {
@ -52,6 +64,8 @@ func (l *ListPansLogic) ListPans(in *pb.ListPanReq) (*pb.ListPanResp, error) {
pbPans = append(pbPans, &pb.PanInfo{
PanId: pan.PanId,
ProductId: pan.ProductId,
BatchId: pan.BatchId,
BatchNo: batchNoById[pan.BatchId],
Name: pan.Name,
Position: pan.Position,
SortOrder: pan.SortOrder,

View File

@ -39,6 +39,8 @@ func (l *ListPanViewLogic) ListPanView(in *pb.ListPanViewReq) (*pb.ListPanViewRe
pbList = append(pbList, &pb.PanListItem{
PanId: item.PanId,
ProductId: item.ProductId,
BatchId: item.BatchId,
BatchNo: item.BatchNo,
Name: item.Name,
Position: item.Position,
ProductName: item.ProductName,

View File

@ -0,0 +1,40 @@
package logic
import (
"context"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ListProductBatchLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListProductBatchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListProductBatchLogic {
return &ListProductBatchLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *ListProductBatchLogic) ListProductBatch(in *pb.ListProductBatchReq) (*pb.ListProductBatchResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, in.ProductId)
if err != nil || product.TenantId != tenantId {
return nil, status.Error(codes.NotFound, "product not found")
}
list, err := l.svcCtx.ProductBatchModel.FindByProductId(l.ctx, tenantId, in.ProductId)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbList := make([]*pb.ProductBatchInfo, 0, len(list))
for _, b := range list {
pbList = append(pbList, batchToPb(b))
}
return &pb.ListProductBatchResp{List: pbList}, nil
}

View File

@ -2,7 +2,6 @@ package logic
import (
"context"
"fmt"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
@ -29,25 +28,14 @@ func NewListProductLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListP
func (l *ListProductLogic) ListProduct(in *pb.ListProductReq) (*pb.ListProductResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
list, total, err := l.svcCtx.ProductModel.FindList(l.ctx, tenantId, in.Page, in.PageSize, in.ProductName, in.Spec, in.Color, in.Status)
list, total, err := l.svcCtx.ProductModel.FindList(l.ctx, tenantId, in.Page, in.PageSize, in.ProductName, in.Spec, in.Color, in.ColorNo, in.ProductCode, in.Status)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbList := make([]*pb.ProductInfo, 0, len(list))
for _, p := range list {
pbList = append(pbList, &pb.ProductInfo{
ProductId: p.ProductId,
ProductName: p.ProductName,
ImageUrl: p.ImageUrl,
Spec: p.Spec,
Color: p.Color,
SalesPrice: fmt.Sprintf("%.2f", p.SalesPrice),
Remark: p.Remark.String,
Status: p.Status,
CreatedAt: p.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: p.UpdatedAt.Format("2006-01-02 15:04:05"),
})
pbList = append(pbList, productToPb(p))
}
return &pb.ListProductResp{

View File

@ -0,0 +1,42 @@
package logic
import (
"context"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ListYarnLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewListYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListYarnLogic {
return &ListYarnLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *ListYarnLogic) ListYarn(in *pb.ListYarnReq) (*pb.ListYarnResp, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
list, total, err := l.svcCtx.YarnModel.FindList(l.ctx, tenantId, in.Page, in.PageSize, in.YarnName, in.SupplierId, in.Status)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
pbList := make([]*pb.YarnInfo, 0, len(list))
for _, y := range list {
supplierName := ""
if supplier, err := l.svcCtx.SupplierModel.FindOneBySupplierId(l.ctx, y.SupplierId); err == nil {
supplierName = supplier.SupplierName
}
pbList = append(pbList, yarnToPb(y, supplierName))
}
return &pb.ListYarnResp{Total: total, List: pbList}, nil
}

View File

@ -0,0 +1,101 @@
package logic
import (
"context"
"errors"
"strings"
"unicode"
"muyu-apiserver/model"
"github.com/mozillazg/go-pinyin"
)
const (
defaultProductColor = "本色"
defaultYarnColor = "原纱本色"
defaultDyeFactory = "无"
defaultProcess = "无"
)
func normalizeColorNo(colorNo string) string {
return strings.ToUpper(strings.TrimSpace(colorNo))
}
func normalizeProductColor(color string) string {
color = strings.TrimSpace(color)
if color == "" {
return defaultProductColor
}
return color
}
func normalizeYarnColor(color string) string {
color = strings.TrimSpace(color)
if color == "" {
return defaultYarnColor
}
return color
}
func normalizeDyeFactory(factory string) string {
factory = strings.TrimSpace(factory)
if factory == "" {
return defaultDyeFactory
}
return factory
}
func productNameCodePrefix(productName string) string {
args := pinyin.NewArgs()
args.Style = pinyin.Normal
var b strings.Builder
for _, r := range productName {
switch {
case r >= 'a' && r <= 'z':
b.WriteRune(unicode.ToUpper(r))
case r >= 'A' && r <= 'Z':
b.WriteRune(r)
case r >= '0' && r <= '9':
b.WriteRune(r)
case unicode.Is(unicode.Han, r):
py := pinyin.Pinyin(string(r), args)
if len(py) > 0 && len(py[0]) > 0 && py[0][0] != "" {
first := []rune(py[0][0])
if len(first) > 0 {
b.WriteRune(unicode.ToUpper(first[0]))
}
}
}
}
code := b.String()
if code == "" {
return "P"
}
return code
}
func generateProductCode(productName, colorNo string) string {
return productNameCodePrefix(productName) + normalizeColorNo(colorNo)
}
func resolveColorNo(ctx context.Context, productModel model.InvProductModel, tenantId, productName, colorNo string) (string, error) {
colorNo = normalizeColorNo(colorNo)
if colorNo != "" {
return colorNo, nil
}
return productModel.NextColorNo(ctx, tenantId, productName)
}
func ensureProductCodeAvailable(ctx context.Context, productModel model.InvProductModel, tenantId, productCode string) error {
_, err := productModel.FindOneByTenantProductCode(ctx, tenantId, productCode)
if err == nil {
return errors.New("产品码已存在")
}
if errors.Is(err, model.ErrNotFound) {
return nil
}
return err
}

View File

@ -32,6 +32,14 @@ func NewSaveBoltsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SaveBol
func (l *SaveBoltsLogic) SaveBolts(in *pb.SaveBoltsReq) (*pb.Empty, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
pan, err := l.svcCtx.PanModel.FindOneByPanId(l.ctx, in.PanId)
if err != nil || pan.TenantId != tenantId {
return nil, status.Error(codes.NotFound, "pan not found")
}
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, pan.ProductId)
if err != nil || product.TenantId != tenantId {
return nil, status.Error(codes.NotFound, "product not found")
}
var totalLengthM float64
bolts := make([]*model.InvProductBolt, 0, len(in.Lengths))
@ -42,7 +50,9 @@ func (l *SaveBoltsLogic) SaveBolts(in *pb.SaveBoltsReq) (*pb.Empty, error) {
BoltId: uid.Generate(),
PanId: in.PanId,
LengthM: lengthM,
Color: product.Color,
SortOrder: int64(i + 1),
TenantId: tenantId,
})
}

View File

@ -2,6 +2,7 @@ package logic
import (
"context"
"errors"
"strconv"
"muyu-apiserver/model"
@ -33,19 +34,53 @@ func NewSavePansLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SavePans
func (l *SavePansLogic) SavePans(in *pb.SavePansReq) (*pb.Empty, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
if err := l.svcCtx.BoltModel.DeleteByProductId(l.ctx, in.ProductId); err != nil {
return nil, status.Error(codes.Internal, err.Error())
product, err := l.svcCtx.ProductModel.FindOneByProductId(l.ctx, in.ProductId)
if err != nil || product.TenantId != tenantId {
return nil, status.Error(codes.NotFound, "product not found")
}
defaultBatch, err := l.svcCtx.ProductBatchModel.FindLatestByProductId(l.ctx, tenantId, in.ProductId)
if err != nil {
if !errors.Is(err, model.ErrNotFound) {
return nil, status.Error(codes.Internal, err.Error())
}
defaultBatch = &model.InvProductBatch{
BatchId: uid.Generate(),
TenantId: tenantId,
ProductId: in.ProductId,
BatchNo: product.ProductCode + "-B001",
Status: 1,
}
if err := batchInputToModelFields(nil, defaultBatch); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if _, err := l.svcCtx.ProductBatchModel.Insert(l.ctx, defaultBatch); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
}
var totalLengthM float64
var totalBolts int
type panBoltSet struct {
panId string
bolts []*model.InvProductBolt
}
boltsByPan := make([]panBoltSet, 0, len(in.Pans))
pans := make([]*model.InvProductPan, 0, len(in.Pans))
for i, panInput := range in.Pans {
panId := uid.Generate()
batchId := panInput.BatchId
if batchId == "" {
batchId = defaultBatch.BatchId
}
if _, err := l.svcCtx.ProductBatchModel.FindOneByProductAndBatchId(l.ctx, tenantId, in.ProductId, batchId); err != nil {
return nil, status.Error(codes.InvalidArgument, "批次不存在或不属于该产品")
}
pans = append(pans, &model.InvProductPan{
PanId: panId,
ProductId: in.ProductId,
BatchId: batchId,
Name: panInput.Name,
Position: panInput.Position,
SortOrder: int64(i + 1),
@ -61,20 +96,30 @@ func (l *SavePansLogic) SavePans(in *pb.SavePansReq) (*pb.Empty, error) {
BoltId: uid.Generate(),
PanId: panId,
LengthM: lengthM,
Color: product.Color,
SortOrder: int64(j + 1),
TenantId: tenantId,
})
}
totalBolts += len(panInput.BoltLengths)
if err := l.svcCtx.BoltModel.BulkReplace(l.ctx, panId, tenantId, bolts); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
boltsByPan = append(boltsByPan, panBoltSet{panId: panId, bolts: bolts})
}
}
if err := l.svcCtx.BoltModel.DeleteByProductId(l.ctx, in.ProductId); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
if err := l.svcCtx.PanModel.BulkReplace(l.ctx, in.ProductId, tenantId, pans); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
for _, item := range boltsByPan {
if err := l.svcCtx.BoltModel.BulkReplace(l.ctx, item.panId, tenantId, item.bolts); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
}
metrics.PansSavedTotal.WithLabelValues(tenantId, in.ProductId).Inc()
if totalLengthM > 0 {
productName := l.resolveProductName(in.ProductId)

View File

@ -0,0 +1,43 @@
package logic
import (
"context"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type UpdateProductBatchLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateProductBatchLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateProductBatchLogic {
return &UpdateProductBatchLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *UpdateProductBatchLogic) UpdateProductBatch(in *pb.UpdateProductBatchReq) (*pb.Empty, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
batch, err := l.svcCtx.ProductBatchModel.FindOneByProductAndBatchId(l.ctx, tenantId, in.ProductId, in.BatchId)
if err != nil {
return nil, status.Error(codes.NotFound, "批次不存在")
}
if err := batchInputToModelFields(in.Batch, batch); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
if in.Status == 0 {
batch.Status = 0
} else {
batch.Status = 1
}
if err := l.svcCtx.ProductBatchModel.Update(l.ctx, batch); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.Empty{}, nil
}

View File

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"strconv"
"strings"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
@ -35,10 +36,18 @@ func (l *UpdateProductLogic) UpdateProduct(in *pb.UpdateProductReq) (*pb.Empty,
salesPrice, _ := strconv.ParseFloat(in.SalesPrice, 64)
product.ProductName = in.ProductName
productName := strings.TrimSpace(in.ProductName)
if productName == "" {
return nil, status.Error(codes.InvalidArgument, "产品名称不能为空")
}
product.ProductName = productName
product.ImageUrl = in.ImageUrl
product.Spec = in.Spec
product.Color = in.Color
product.Color = normalizeProductColor(in.Color)
if strings.TrimSpace(in.ColorNo) != "" {
product.ColorNo = normalizeColorNo(in.ColorNo)
}
product.SalesPrice = salesPrice
product.Remark = sql.NullString{String: in.Remark, Valid: in.Remark != ""}

View File

@ -0,0 +1,62 @@
package logic
import (
"context"
"database/sql"
"strings"
"muyu-apiserver/pkg/tenantctx"
"muyu-apiserver/rpc/inventory/internal/svc"
"muyu-apiserver/rpc/inventory/pb"
"github.com/zeromicro/go-zero/core/logx"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type UpdateYarnLogic struct {
ctx context.Context
svcCtx *svc.ServiceContext
logx.Logger
}
func NewUpdateYarnLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UpdateYarnLogic {
return &UpdateYarnLogic{ctx: ctx, svcCtx: svcCtx, Logger: logx.WithContext(ctx)}
}
func (l *UpdateYarnLogic) UpdateYarn(in *pb.UpdateYarnReq) (*pb.Empty, error) {
tenantId := tenantctx.ExtractTenantId(l.ctx)
yarn, err := l.svcCtx.YarnModel.FindOneByYarnId(l.ctx, in.YarnId)
if err != nil || yarn.TenantId != tenantId {
return nil, status.Error(codes.NotFound, "纱线不存在")
}
yarnName := strings.TrimSpace(in.YarnName)
if yarnName == "" {
return nil, status.Error(codes.InvalidArgument, "纱线名称不能为空")
}
if strings.TrimSpace(in.SupplierId) == "" {
return nil, status.Error(codes.InvalidArgument, "供应商不能为空")
}
supplier, err := l.svcCtx.SupplierModel.FindOneBySupplierId(l.ctx, in.SupplierId)
if err != nil || supplier.TenantId != tenantId {
return nil, status.Error(codes.InvalidArgument, "供应商不存在")
}
yarn.YarnName = yarnName
yarn.Color = normalizeYarnColor(in.Color)
yarn.WeightGM = parseDecimalString(in.WeightGM)
yarn.SupplierId = in.SupplierId
yarn.DyeFactory = normalizeDyeFactory(in.DyeFactory)
yarn.ImageUrl = in.ImageUrl
yarn.Remark = sql.NullString{String: in.Remark, Valid: in.Remark != ""}
if in.Status == 0 {
yarn.Status = 0
} else {
yarn.Status = 1
}
if err := l.svcCtx.YarnModel.Update(l.ctx, yarn); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &pb.Empty{}, nil
}

View File

@ -0,0 +1,25 @@
package logic
import (
"fmt"
"muyu-apiserver/model"
"muyu-apiserver/rpc/inventory/pb"
)
func yarnToPb(yarn *model.InvYarn, supplierName string) *pb.YarnInfo {
return &pb.YarnInfo{
YarnId: yarn.YarnId,
YarnName: yarn.YarnName,
Color: yarn.Color,
WeightGM: fmt.Sprintf("%.4f", yarn.WeightGM),
SupplierId: yarn.SupplierId,
SupplierName: supplierName,
DyeFactory: yarn.DyeFactory,
ImageUrl: yarn.ImageUrl,
Remark: yarn.Remark.String,
Status: yarn.Status,
CreatedAt: yarn.CreatedAt.Format("2006-01-02 15:04:05"),
UpdatedAt: yarn.UpdatedAt.Format("2006-01-02 15:04:05"),
}
}

View File

@ -67,6 +67,53 @@ func (s *InventoryServiceServer) ListColorDetail(ctx context.Context, in *pb.Lis
return l.ListColorDetail(in)
}
// Yarn CRUD
func (s *InventoryServiceServer) CreateYarn(ctx context.Context, in *pb.CreateYarnReq) (*pb.IdResp, error) {
l := logic.NewCreateYarnLogic(ctx, s.svcCtx)
return l.CreateYarn(in)
}
func (s *InventoryServiceServer) UpdateYarn(ctx context.Context, in *pb.UpdateYarnReq) (*pb.Empty, error) {
l := logic.NewUpdateYarnLogic(ctx, s.svcCtx)
return l.UpdateYarn(in)
}
func (s *InventoryServiceServer) DeleteYarn(ctx context.Context, in *pb.DeleteYarnReq) (*pb.Empty, error) {
l := logic.NewDeleteYarnLogic(ctx, s.svcCtx)
return l.DeleteYarn(in)
}
func (s *InventoryServiceServer) GetYarn(ctx context.Context, in *pb.GetYarnReq) (*pb.YarnInfo, error) {
l := logic.NewGetYarnLogic(ctx, s.svcCtx)
return l.GetYarn(in)
}
func (s *InventoryServiceServer) ListYarn(ctx context.Context, in *pb.ListYarnReq) (*pb.ListYarnResp, error) {
l := logic.NewListYarnLogic(ctx, s.svcCtx)
return l.ListYarn(in)
}
// Product Batch CRUD
func (s *InventoryServiceServer) CreateProductBatch(ctx context.Context, in *pb.CreateProductBatchReq) (*pb.IdResp, error) {
l := logic.NewCreateProductBatchLogic(ctx, s.svcCtx)
return l.CreateProductBatch(in)
}
func (s *InventoryServiceServer) UpdateProductBatch(ctx context.Context, in *pb.UpdateProductBatchReq) (*pb.Empty, error) {
l := logic.NewUpdateProductBatchLogic(ctx, s.svcCtx)
return l.UpdateProductBatch(in)
}
func (s *InventoryServiceServer) DeleteProductBatch(ctx context.Context, in *pb.DeleteProductBatchReq) (*pb.Empty, error) {
l := logic.NewDeleteProductBatchLogic(ctx, s.svcCtx)
return l.DeleteProductBatch(in)
}
func (s *InventoryServiceServer) ListProductBatch(ctx context.Context, in *pb.ListProductBatchReq) (*pb.ListProductBatchResp, error) {
l := logic.NewListProductBatchLogic(ctx, s.svcCtx)
return l.ListProductBatch(in)
}
// Pan / Bolt list views
func (s *InventoryServiceServer) ListPanView(ctx context.Context, in *pb.ListPanViewReq) (*pb.ListPanViewResp, error) {
l := logic.NewListPanViewLogic(ctx, s.svcCtx)

View File

@ -10,8 +10,11 @@ import (
type ServiceContext struct {
Config config.Config
ProductModel model.InvProductModel
YarnModel model.InvYarnModel
ProductBatchModel model.InvProductBatchModel
PanModel model.InvProductPanModel
BoltModel model.InvProductBoltModel
SupplierModel model.PurSupplierModel
ImportLogModel model.InvStockImportLogModel
StockCheckModel model.InvStockCheckModel
CheckDetailModel model.InvStockCheckDetailModel
@ -24,8 +27,11 @@ func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
ProductModel: model.NewInvProductModel(conn, c.Cache),
YarnModel: model.NewInvYarnModel(conn),
ProductBatchModel: model.NewInvProductBatchModel(conn),
PanModel: model.NewInvProductPanModel(conn),
BoltModel: model.NewInvProductBoltModel(conn),
SupplierModel: model.NewPurSupplierModel(conn, c.Cache),
ImportLogModel: model.NewInvStockImportLogModel(conn, c.Cache),
StockCheckModel: model.NewInvStockCheckModel(conn, c.Cache),
CheckDetailModel: model.NewInvStockCheckDetailModel(conn, c.Cache),

View File

@ -17,6 +17,8 @@ message ProductInfo {
int64 status = 8;
string created_at = 9;
string updated_at = 10;
string color_no = 11;
string product_code = 12;
}
message CreateProductReq {
@ -27,6 +29,9 @@ message CreateProductReq {
string sales_price = 5;
string remark = 6;
repeated PanInput pans = 7;
string color_no = 8;
ProductBatchInput initial_batch = 9;
string product_code = 10;
}
message UpdateProductReq {
@ -37,6 +42,7 @@ message UpdateProductReq {
string color = 5;
string sales_price = 6;
string remark = 7;
string color_no = 8;
}
message DeleteProductReq {
@ -54,6 +60,8 @@ message ListProductReq {
string spec = 4;
string color = 5;
int64 status = 6;
string color_no = 7;
string product_code = 8;
}
message ListProductResp {
@ -72,12 +80,15 @@ message PanInfo {
repeated BoltInfo bolts = 6;
int64 bolt_count = 7;
string total_length = 8;
string batch_id = 9;
string batch_no = 10;
}
message PanInput {
string name = 1;
string position = 2;
repeated string bolt_lengths = 3;
string batch_id = 4;
}
message ListPanReq {
@ -157,6 +168,9 @@ message ColorDetailItem {
string total_length_m = 8;
string locations = 9;
string sales_price = 10;
string color_no = 11;
string product_code = 12;
int64 batch_count = 13;
}
message ListColorDetailReq {
@ -178,6 +192,8 @@ message PanListItem {
string colors = 6; // aggregated from bolt.color, no spec
int64 bolt_count = 7;
string total_length_m = 8;
string batch_id = 9;
string batch_no = 10;
}
message ListPanViewReq {
@ -242,6 +258,116 @@ message ListImportLogResp {
repeated ImportLogInfo list = 2;
}
// ==================== Yarn ====================
message YarnInfo {
string yarn_id = 1;
string yarn_name = 2;
string color = 3;
string weight_g_m = 4;
string supplier_id = 5;
string supplier_name = 6;
string dye_factory = 7;
string image_url = 8;
string remark = 9;
int64 status = 10;
string created_at = 11;
string updated_at = 12;
}
message CreateYarnReq {
string yarn_name = 1;
string color = 2;
string weight_g_m = 3;
string supplier_id = 4;
string dye_factory = 5;
string image_url = 6;
string remark = 7;
}
message UpdateYarnReq {
string yarn_id = 1;
string yarn_name = 2;
string color = 3;
string weight_g_m = 4;
string supplier_id = 5;
string dye_factory = 6;
string image_url = 7;
string remark = 8;
int64 status = 9;
}
message DeleteYarnReq {
string yarn_id = 1;
}
message GetYarnReq {
string yarn_id = 1;
}
message ListYarnReq {
int64 page = 1;
int64 page_size = 2;
string yarn_name = 3;
string supplier_id = 4;
int64 status = 5;
}
message ListYarnResp {
int64 total = 1;
repeated YarnInfo list = 2;
}
// ==================== Product Batch ====================
message ProductBatchInput {
string yarn_ratio = 1;
string warp_weight_g_m = 2;
string weft_weight_g_m = 3;
string production_process = 4;
string remark = 5;
string batch_no = 6;
}
message ProductBatchInfo {
string batch_id = 1;
string product_id = 2;
string batch_no = 3;
string yarn_ratio = 4;
string warp_weight_g_m = 5;
string weft_weight_g_m = 6;
string production_process = 7;
string remark = 8;
int64 status = 9;
string created_at = 10;
string updated_at = 11;
}
message CreateProductBatchReq {
string product_id = 1;
ProductBatchInput batch = 2;
}
message UpdateProductBatchReq {
string product_id = 1;
string batch_id = 2;
ProductBatchInput batch = 3;
int64 status = 4;
}
message DeleteProductBatchReq {
string product_id = 1;
string batch_id = 2;
}
message ListProductBatchReq {
string product_id = 1;
}
message ListProductBatchResp {
repeated ProductBatchInfo list = 1;
}
message ImportLogInfo {
string import_id = 1;
string file_name = 2;
@ -440,6 +566,19 @@ service InventoryService {
// Color Detail (panel 2)
rpc ListColorDetail(ListColorDetailReq) returns (ListColorDetailResp);
// Yarn CRUD
rpc CreateYarn(CreateYarnReq) returns (IdResp);
rpc UpdateYarn(UpdateYarnReq) returns (Empty);
rpc DeleteYarn(DeleteYarnReq) returns (Empty);
rpc GetYarn(GetYarnReq) returns (YarnInfo);
rpc ListYarn(ListYarnReq) returns (ListYarnResp);
// Product Batch CRUD
rpc CreateProductBatch(CreateProductBatchReq) returns (IdResp);
rpc UpdateProductBatch(UpdateProductBatchReq) returns (Empty);
rpc DeleteProductBatch(DeleteProductBatchReq) returns (Empty);
rpc ListProductBatch(ListProductBatchReq) returns (ListProductBatchResp);
// Pan / Bolt list views (dimension panels)
rpc ListPanView(ListPanViewReq) returns (ListPanViewResp);
rpc ListBoltView(ListBoltViewReq) returns (ListBoltViewResp);

View File

@ -20,13 +20,18 @@ type (
ColorDetailItem = pb.ColorDetailItem
ConfirmStockCheckReq = pb.ConfirmStockCheckReq
CreateProductReq = pb.CreateProductReq
CreateProductBatchReq = pb.CreateProductBatchReq
CreateStockAdjustReq = pb.CreateStockAdjustReq
CreateStockCheckReq = pb.CreateStockCheckReq
CreateYarnReq = pb.CreateYarnReq
DeleteProductBatchReq = pb.DeleteProductBatchReq
DeleteProductReq = pb.DeleteProductReq
DeleteYarnReq = pb.DeleteYarnReq
Empty = pb.Empty
GetProductReq = pb.GetProductReq
GetStockAdjustReq = pb.GetStockAdjustReq
GetStockCheckReq = pb.GetStockCheckReq
GetYarnReq = pb.GetYarnReq
IdResp = pb.IdResp
ImportLogInfo = pb.ImportLogInfo
ImportProductReq = pb.ImportProductReq
@ -44,6 +49,8 @@ type (
ListImportLogResp = pb.ListImportLogResp
ListPanReq = pb.ListPanReq
ListPanResp = pb.ListPanResp
ListProductBatchReq = pb.ListProductBatchReq
ListProductBatchResp = pb.ListProductBatchResp
ListProductReq = pb.ListProductReq
ListProductResp = pb.ListProductResp
ListProductSummaryReq = pb.ListProductSummaryReq
@ -52,8 +59,12 @@ type (
ListStockAdjustResp = pb.ListStockAdjustResp
ListStockCheckReq = pb.ListStockCheckReq
ListStockCheckResp = pb.ListStockCheckResp
ListYarnReq = pb.ListYarnReq
ListYarnResp = pb.ListYarnResp
PanInfo = pb.PanInfo
PanInput = pb.PanInput
ProductBatchInfo = pb.ProductBatchInfo
ProductBatchInput = pb.ProductBatchInput
ProductInfo = pb.ProductInfo
ProductSummaryItem = pb.ProductSummaryItem
ProductTreeResp = pb.ProductTreeResp
@ -70,8 +81,11 @@ type (
StockGroupResp = pb.StockGroupResp
StockSummaryReq = pb.StockSummaryReq
StockSummaryResp = pb.StockSummaryResp
UpdateProductBatchReq = pb.UpdateProductBatchReq
UpdateProductReq = pb.UpdateProductReq
UpdateStockCheckReq = pb.UpdateStockCheckReq
UpdateYarnReq = pb.UpdateYarnReq
YarnInfo = pb.YarnInfo
InventoryService interface {
// Product CRUD
@ -86,6 +100,17 @@ type (
ListProductSummary(ctx context.Context, in *ListProductSummaryReq, opts ...grpc.CallOption) (*ListProductSummaryResp, error)
// Color Detail (panel 2)
ListColorDetail(ctx context.Context, in *ListColorDetailReq, opts ...grpc.CallOption) (*ListColorDetailResp, error)
// Yarn CRUD
CreateYarn(ctx context.Context, in *CreateYarnReq, opts ...grpc.CallOption) (*IdResp, error)
UpdateYarn(ctx context.Context, in *UpdateYarnReq, opts ...grpc.CallOption) (*Empty, error)
DeleteYarn(ctx context.Context, in *DeleteYarnReq, opts ...grpc.CallOption) (*Empty, error)
GetYarn(ctx context.Context, in *GetYarnReq, opts ...grpc.CallOption) (*YarnInfo, error)
ListYarn(ctx context.Context, in *ListYarnReq, opts ...grpc.CallOption) (*ListYarnResp, error)
// Product Batch CRUD
CreateProductBatch(ctx context.Context, in *CreateProductBatchReq, opts ...grpc.CallOption) (*IdResp, error)
UpdateProductBatch(ctx context.Context, in *UpdateProductBatchReq, opts ...grpc.CallOption) (*Empty, error)
DeleteProductBatch(ctx context.Context, in *DeleteProductBatchReq, opts ...grpc.CallOption) (*Empty, error)
ListProductBatch(ctx context.Context, in *ListProductBatchReq, opts ...grpc.CallOption) (*ListProductBatchResp, error)
// Pan / Bolt list views
ListPanView(ctx context.Context, in *ListPanViewReq, opts ...grpc.CallOption) (*ListPanViewResp, error)
ListBoltView(ctx context.Context, in *ListBoltViewReq, opts ...grpc.CallOption) (*ListBoltViewResp, error)
@ -166,6 +191,51 @@ func (m *defaultInventoryService) ListColorDetail(ctx context.Context, in *ListC
return client.ListColorDetail(ctx, in, opts...)
}
func (m *defaultInventoryService) CreateYarn(ctx context.Context, in *CreateYarnReq, opts ...grpc.CallOption) (*IdResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.CreateYarn(ctx, in, opts...)
}
func (m *defaultInventoryService) UpdateYarn(ctx context.Context, in *UpdateYarnReq, opts ...grpc.CallOption) (*Empty, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.UpdateYarn(ctx, in, opts...)
}
func (m *defaultInventoryService) DeleteYarn(ctx context.Context, in *DeleteYarnReq, opts ...grpc.CallOption) (*Empty, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.DeleteYarn(ctx, in, opts...)
}
func (m *defaultInventoryService) GetYarn(ctx context.Context, in *GetYarnReq, opts ...grpc.CallOption) (*YarnInfo, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.GetYarn(ctx, in, opts...)
}
func (m *defaultInventoryService) ListYarn(ctx context.Context, in *ListYarnReq, opts ...grpc.CallOption) (*ListYarnResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListYarn(ctx, in, opts...)
}
func (m *defaultInventoryService) CreateProductBatch(ctx context.Context, in *CreateProductBatchReq, opts ...grpc.CallOption) (*IdResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.CreateProductBatch(ctx, in, opts...)
}
func (m *defaultInventoryService) UpdateProductBatch(ctx context.Context, in *UpdateProductBatchReq, opts ...grpc.CallOption) (*Empty, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.UpdateProductBatch(ctx, in, opts...)
}
func (m *defaultInventoryService) DeleteProductBatch(ctx context.Context, in *DeleteProductBatchReq, opts ...grpc.CallOption) (*Empty, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.DeleteProductBatch(ctx, in, opts...)
}
func (m *defaultInventoryService) ListProductBatch(ctx context.Context, in *ListProductBatchReq, opts ...grpc.CallOption) (*ListProductBatchResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListProductBatch(ctx, in, opts...)
}
func (m *defaultInventoryService) ListPanView(ctx context.Context, in *ListPanViewReq, opts ...grpc.CallOption) (*ListPanViewResp, error) {
client := pb.NewInventoryServiceClient(m.cli.Conn())
return client.ListPanView(ctx, in, opts...)

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.5.1
// - protoc v5.29.3
// - protoc-gen-go-grpc v1.6.2
// - protoc v7.34.1
// source: inventory.proto
package pb
@ -27,6 +27,15 @@ const (
InventoryService_GetProductTree_FullMethodName = "/inventory.InventoryService/GetProductTree"
InventoryService_ListProductSummary_FullMethodName = "/inventory.InventoryService/ListProductSummary"
InventoryService_ListColorDetail_FullMethodName = "/inventory.InventoryService/ListColorDetail"
InventoryService_CreateYarn_FullMethodName = "/inventory.InventoryService/CreateYarn"
InventoryService_UpdateYarn_FullMethodName = "/inventory.InventoryService/UpdateYarn"
InventoryService_DeleteYarn_FullMethodName = "/inventory.InventoryService/DeleteYarn"
InventoryService_GetYarn_FullMethodName = "/inventory.InventoryService/GetYarn"
InventoryService_ListYarn_FullMethodName = "/inventory.InventoryService/ListYarn"
InventoryService_CreateProductBatch_FullMethodName = "/inventory.InventoryService/CreateProductBatch"
InventoryService_UpdateProductBatch_FullMethodName = "/inventory.InventoryService/UpdateProductBatch"
InventoryService_DeleteProductBatch_FullMethodName = "/inventory.InventoryService/DeleteProductBatch"
InventoryService_ListProductBatch_FullMethodName = "/inventory.InventoryService/ListProductBatch"
InventoryService_ListPanView_FullMethodName = "/inventory.InventoryService/ListPanView"
InventoryService_ListBoltView_FullMethodName = "/inventory.InventoryService/ListBoltView"
InventoryService_ListPans_FullMethodName = "/inventory.InventoryService/ListPans"
@ -64,6 +73,17 @@ type InventoryServiceClient interface {
ListProductSummary(ctx context.Context, in *ListProductSummaryReq, opts ...grpc.CallOption) (*ListProductSummaryResp, error)
// Color Detail (panel 2)
ListColorDetail(ctx context.Context, in *ListColorDetailReq, opts ...grpc.CallOption) (*ListColorDetailResp, error)
// Yarn CRUD
CreateYarn(ctx context.Context, in *CreateYarnReq, opts ...grpc.CallOption) (*IdResp, error)
UpdateYarn(ctx context.Context, in *UpdateYarnReq, opts ...grpc.CallOption) (*Empty, error)
DeleteYarn(ctx context.Context, in *DeleteYarnReq, opts ...grpc.CallOption) (*Empty, error)
GetYarn(ctx context.Context, in *GetYarnReq, opts ...grpc.CallOption) (*YarnInfo, error)
ListYarn(ctx context.Context, in *ListYarnReq, opts ...grpc.CallOption) (*ListYarnResp, error)
// Product Batch CRUD
CreateProductBatch(ctx context.Context, in *CreateProductBatchReq, opts ...grpc.CallOption) (*IdResp, error)
UpdateProductBatch(ctx context.Context, in *UpdateProductBatchReq, opts ...grpc.CallOption) (*Empty, error)
DeleteProductBatch(ctx context.Context, in *DeleteProductBatchReq, opts ...grpc.CallOption) (*Empty, error)
ListProductBatch(ctx context.Context, in *ListProductBatchReq, opts ...grpc.CallOption) (*ListProductBatchResp, error)
// Pan / Bolt list views (dimension panels)
ListPanView(ctx context.Context, in *ListPanViewReq, opts ...grpc.CallOption) (*ListPanViewResp, error)
ListBoltView(ctx context.Context, in *ListBoltViewReq, opts ...grpc.CallOption) (*ListBoltViewResp, error)
@ -180,6 +200,96 @@ func (c *inventoryServiceClient) ListColorDetail(ctx context.Context, in *ListCo
return out, nil
}
func (c *inventoryServiceClient) CreateYarn(ctx context.Context, in *CreateYarnReq, opts ...grpc.CallOption) (*IdResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IdResp)
err := c.cc.Invoke(ctx, InventoryService_CreateYarn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) UpdateYarn(ctx context.Context, in *UpdateYarnReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, InventoryService_UpdateYarn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) DeleteYarn(ctx context.Context, in *DeleteYarnReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, InventoryService_DeleteYarn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) GetYarn(ctx context.Context, in *GetYarnReq, opts ...grpc.CallOption) (*YarnInfo, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(YarnInfo)
err := c.cc.Invoke(ctx, InventoryService_GetYarn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListYarn(ctx context.Context, in *ListYarnReq, opts ...grpc.CallOption) (*ListYarnResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListYarnResp)
err := c.cc.Invoke(ctx, InventoryService_ListYarn_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) CreateProductBatch(ctx context.Context, in *CreateProductBatchReq, opts ...grpc.CallOption) (*IdResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(IdResp)
err := c.cc.Invoke(ctx, InventoryService_CreateProductBatch_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) UpdateProductBatch(ctx context.Context, in *UpdateProductBatchReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, InventoryService_UpdateProductBatch_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) DeleteProductBatch(ctx context.Context, in *DeleteProductBatchReq, opts ...grpc.CallOption) (*Empty, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(Empty)
err := c.cc.Invoke(ctx, InventoryService_DeleteProductBatch_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListProductBatch(ctx context.Context, in *ListProductBatchReq, opts ...grpc.CallOption) (*ListProductBatchResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListProductBatchResp)
err := c.cc.Invoke(ctx, InventoryService_ListProductBatch_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *inventoryServiceClient) ListPanView(ctx context.Context, in *ListPanViewReq, opts ...grpc.CallOption) (*ListPanViewResp, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListPanViewResp)
@ -386,6 +496,17 @@ type InventoryServiceServer interface {
ListProductSummary(context.Context, *ListProductSummaryReq) (*ListProductSummaryResp, error)
// Color Detail (panel 2)
ListColorDetail(context.Context, *ListColorDetailReq) (*ListColorDetailResp, error)
// Yarn CRUD
CreateYarn(context.Context, *CreateYarnReq) (*IdResp, error)
UpdateYarn(context.Context, *UpdateYarnReq) (*Empty, error)
DeleteYarn(context.Context, *DeleteYarnReq) (*Empty, error)
GetYarn(context.Context, *GetYarnReq) (*YarnInfo, error)
ListYarn(context.Context, *ListYarnReq) (*ListYarnResp, error)
// Product Batch CRUD
CreateProductBatch(context.Context, *CreateProductBatchReq) (*IdResp, error)
UpdateProductBatch(context.Context, *UpdateProductBatchReq) (*Empty, error)
DeleteProductBatch(context.Context, *DeleteProductBatchReq) (*Empty, error)
ListProductBatch(context.Context, *ListProductBatchReq) (*ListProductBatchResp, error)
// Pan / Bolt list views (dimension panels)
ListPanView(context.Context, *ListPanViewReq) (*ListPanViewResp, error)
ListBoltView(context.Context, *ListBoltViewReq) (*ListBoltViewResp, error)
@ -423,85 +544,112 @@ type InventoryServiceServer interface {
type UnimplementedInventoryServiceServer struct{}
func (UnimplementedInventoryServiceServer) CreateProduct(context.Context, *CreateProductReq) (*IdResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateProduct not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateProduct not implemented")
}
func (UnimplementedInventoryServiceServer) UpdateProduct(context.Context, *UpdateProductReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateProduct not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateProduct not implemented")
}
func (UnimplementedInventoryServiceServer) DeleteProduct(context.Context, *DeleteProductReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method DeleteProduct not implemented")
return nil, status.Error(codes.Unimplemented, "method DeleteProduct not implemented")
}
func (UnimplementedInventoryServiceServer) GetProduct(context.Context, *GetProductReq) (*ProductInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetProduct not implemented")
return nil, status.Error(codes.Unimplemented, "method GetProduct not implemented")
}
func (UnimplementedInventoryServiceServer) ListProduct(context.Context, *ListProductReq) (*ListProductResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListProduct not implemented")
return nil, status.Error(codes.Unimplemented, "method ListProduct not implemented")
}
func (UnimplementedInventoryServiceServer) GetProductTree(context.Context, *GetProductReq) (*ProductTreeResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetProductTree not implemented")
return nil, status.Error(codes.Unimplemented, "method GetProductTree not implemented")
}
func (UnimplementedInventoryServiceServer) ListProductSummary(context.Context, *ListProductSummaryReq) (*ListProductSummaryResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListProductSummary not implemented")
return nil, status.Error(codes.Unimplemented, "method ListProductSummary not implemented")
}
func (UnimplementedInventoryServiceServer) ListColorDetail(context.Context, *ListColorDetailReq) (*ListColorDetailResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListColorDetail not implemented")
return nil, status.Error(codes.Unimplemented, "method ListColorDetail not implemented")
}
func (UnimplementedInventoryServiceServer) CreateYarn(context.Context, *CreateYarnReq) (*IdResp, error) {
return nil, status.Error(codes.Unimplemented, "method CreateYarn not implemented")
}
func (UnimplementedInventoryServiceServer) UpdateYarn(context.Context, *UpdateYarnReq) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateYarn not implemented")
}
func (UnimplementedInventoryServiceServer) DeleteYarn(context.Context, *DeleteYarnReq) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteYarn not implemented")
}
func (UnimplementedInventoryServiceServer) GetYarn(context.Context, *GetYarnReq) (*YarnInfo, error) {
return nil, status.Error(codes.Unimplemented, "method GetYarn not implemented")
}
func (UnimplementedInventoryServiceServer) ListYarn(context.Context, *ListYarnReq) (*ListYarnResp, error) {
return nil, status.Error(codes.Unimplemented, "method ListYarn not implemented")
}
func (UnimplementedInventoryServiceServer) CreateProductBatch(context.Context, *CreateProductBatchReq) (*IdResp, error) {
return nil, status.Error(codes.Unimplemented, "method CreateProductBatch not implemented")
}
func (UnimplementedInventoryServiceServer) UpdateProductBatch(context.Context, *UpdateProductBatchReq) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateProductBatch not implemented")
}
func (UnimplementedInventoryServiceServer) DeleteProductBatch(context.Context, *DeleteProductBatchReq) (*Empty, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteProductBatch not implemented")
}
func (UnimplementedInventoryServiceServer) ListProductBatch(context.Context, *ListProductBatchReq) (*ListProductBatchResp, error) {
return nil, status.Error(codes.Unimplemented, "method ListProductBatch not implemented")
}
func (UnimplementedInventoryServiceServer) ListPanView(context.Context, *ListPanViewReq) (*ListPanViewResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPanView not implemented")
return nil, status.Error(codes.Unimplemented, "method ListPanView not implemented")
}
func (UnimplementedInventoryServiceServer) ListBoltView(context.Context, *ListBoltViewReq) (*ListBoltViewResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBoltView not implemented")
return nil, status.Error(codes.Unimplemented, "method ListBoltView not implemented")
}
func (UnimplementedInventoryServiceServer) ListPans(context.Context, *ListPanReq) (*ListPanResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListPans not implemented")
return nil, status.Error(codes.Unimplemented, "method ListPans not implemented")
}
func (UnimplementedInventoryServiceServer) SavePans(context.Context, *SavePansReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SavePans not implemented")
return nil, status.Error(codes.Unimplemented, "method SavePans not implemented")
}
func (UnimplementedInventoryServiceServer) ListBolts(context.Context, *ListBoltReq) (*ListBoltResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListBolts not implemented")
return nil, status.Error(codes.Unimplemented, "method ListBolts not implemented")
}
func (UnimplementedInventoryServiceServer) SaveBolts(context.Context, *SaveBoltsReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method SaveBolts not implemented")
return nil, status.Error(codes.Unimplemented, "method SaveBolts not implemented")
}
func (UnimplementedInventoryServiceServer) ImportProducts(context.Context, *ImportProductReq) (*ImportProductResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ImportProducts not implemented")
return nil, status.Error(codes.Unimplemented, "method ImportProducts not implemented")
}
func (UnimplementedInventoryServiceServer) ListImportLog(context.Context, *ListImportLogReq) (*ListImportLogResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListImportLog not implemented")
return nil, status.Error(codes.Unimplemented, "method ListImportLog not implemented")
}
func (UnimplementedInventoryServiceServer) GetStockSummary(context.Context, *StockSummaryReq) (*StockSummaryResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStockSummary not implemented")
return nil, status.Error(codes.Unimplemented, "method GetStockSummary not implemented")
}
func (UnimplementedInventoryServiceServer) GetStockGroup(context.Context, *StockGroupReq) (*StockGroupResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStockGroup not implemented")
return nil, status.Error(codes.Unimplemented, "method GetStockGroup not implemented")
}
func (UnimplementedInventoryServiceServer) CreateStockCheck(context.Context, *CreateStockCheckReq) (*IdResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateStockCheck not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateStockCheck not implemented")
}
func (UnimplementedInventoryServiceServer) UpdateStockCheck(context.Context, *UpdateStockCheckReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateStockCheck not implemented")
return nil, status.Error(codes.Unimplemented, "method UpdateStockCheck not implemented")
}
func (UnimplementedInventoryServiceServer) ConfirmStockCheck(context.Context, *ConfirmStockCheckReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ConfirmStockCheck not implemented")
return nil, status.Error(codes.Unimplemented, "method ConfirmStockCheck not implemented")
}
func (UnimplementedInventoryServiceServer) GetStockCheck(context.Context, *GetStockCheckReq) (*StockCheckInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStockCheck not implemented")
return nil, status.Error(codes.Unimplemented, "method GetStockCheck not implemented")
}
func (UnimplementedInventoryServiceServer) ListStockCheck(context.Context, *ListStockCheckReq) (*ListStockCheckResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListStockCheck not implemented")
return nil, status.Error(codes.Unimplemented, "method ListStockCheck not implemented")
}
func (UnimplementedInventoryServiceServer) CreateStockAdjust(context.Context, *CreateStockAdjustReq) (*IdResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateStockAdjust not implemented")
return nil, status.Error(codes.Unimplemented, "method CreateStockAdjust not implemented")
}
func (UnimplementedInventoryServiceServer) ApproveStockAdjust(context.Context, *ApproveStockAdjustReq) (*Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method ApproveStockAdjust not implemented")
return nil, status.Error(codes.Unimplemented, "method ApproveStockAdjust not implemented")
}
func (UnimplementedInventoryServiceServer) GetStockAdjust(context.Context, *GetStockAdjustReq) (*StockAdjustInfo, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetStockAdjust not implemented")
return nil, status.Error(codes.Unimplemented, "method GetStockAdjust not implemented")
}
func (UnimplementedInventoryServiceServer) ListStockAdjust(context.Context, *ListStockAdjustReq) (*ListStockAdjustResp, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListStockAdjust not implemented")
return nil, status.Error(codes.Unimplemented, "method ListStockAdjust not implemented")
}
func (UnimplementedInventoryServiceServer) mustEmbedUnimplementedInventoryServiceServer() {}
func (UnimplementedInventoryServiceServer) testEmbeddedByValue() {}
@ -514,7 +662,7 @@ type UnsafeInventoryServiceServer interface {
}
func RegisterInventoryServiceServer(s grpc.ServiceRegistrar, srv InventoryServiceServer) {
// If the following call pancis, it indicates UnimplementedInventoryServiceServer was
// If the following call panics, it indicates UnimplementedInventoryServiceServer was
// embedded by pointer and is nil. This will cause panics if an
// unimplemented method is ever invoked, so we test this at initialization
// time to prevent it from happening at runtime later due to I/O.
@ -668,6 +816,168 @@ func _InventoryService_ListColorDetail_Handler(srv interface{}, ctx context.Cont
return interceptor(ctx, in, info, handler)
}
func _InventoryService_CreateYarn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateYarnReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).CreateYarn(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_CreateYarn_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).CreateYarn(ctx, req.(*CreateYarnReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_UpdateYarn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateYarnReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).UpdateYarn(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_UpdateYarn_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).UpdateYarn(ctx, req.(*UpdateYarnReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_DeleteYarn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteYarnReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).DeleteYarn(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_DeleteYarn_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).DeleteYarn(ctx, req.(*DeleteYarnReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_GetYarn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetYarnReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).GetYarn(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_GetYarn_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).GetYarn(ctx, req.(*GetYarnReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListYarn_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListYarnReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).ListYarn(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_ListYarn_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListYarn(ctx, req.(*ListYarnReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_CreateProductBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateProductBatchReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).CreateProductBatch(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_CreateProductBatch_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).CreateProductBatch(ctx, req.(*CreateProductBatchReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_UpdateProductBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateProductBatchReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).UpdateProductBatch(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_UpdateProductBatch_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).UpdateProductBatch(ctx, req.(*UpdateProductBatchReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_DeleteProductBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteProductBatchReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).DeleteProductBatch(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_DeleteProductBatch_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).DeleteProductBatch(ctx, req.(*DeleteProductBatchReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListProductBatch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListProductBatchReq)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(InventoryServiceServer).ListProductBatch(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: InventoryService_ListProductBatch_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(InventoryServiceServer).ListProductBatch(ctx, req.(*ListProductBatchReq))
}
return interceptor(ctx, in, info, handler)
}
func _InventoryService_ListPanView_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListPanViewReq)
if err := dec(in); err != nil {
@ -1049,6 +1359,42 @@ var InventoryService_ServiceDesc = grpc.ServiceDesc{
MethodName: "ListColorDetail",
Handler: _InventoryService_ListColorDetail_Handler,
},
{
MethodName: "CreateYarn",
Handler: _InventoryService_CreateYarn_Handler,
},
{
MethodName: "UpdateYarn",
Handler: _InventoryService_UpdateYarn_Handler,
},
{
MethodName: "DeleteYarn",
Handler: _InventoryService_DeleteYarn_Handler,
},
{
MethodName: "GetYarn",
Handler: _InventoryService_GetYarn_Handler,
},
{
MethodName: "ListYarn",
Handler: _InventoryService_ListYarn_Handler,
},
{
MethodName: "CreateProductBatch",
Handler: _InventoryService_CreateProductBatch_Handler,
},
{
MethodName: "UpdateProductBatch",
Handler: _InventoryService_UpdateProductBatch_Handler,
},
{
MethodName: "DeleteProductBatch",
Handler: _InventoryService_DeleteProductBatch_Handler,
},
{
MethodName: "ListProductBatch",
Handler: _InventoryService_ListProductBatch_Handler,
},
{
MethodName: "ListPanView",
Handler: _InventoryService_ListPanView_Handler,