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

@ -30,9 +30,12 @@ func (l *CreateProductLogic) CreateProduct(req *types.CreateProductReq) (resp *t
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

@ -38,9 +38,12 @@ type CreateProductReq struct {
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"`
}

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

@ -33,11 +33,14 @@ 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 {
@ -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

@ -12,10 +12,17 @@ type ParsedProduct struct {
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
}

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,
})
for i, p := range in.Products {
if _, err := creator.CreateProduct(p); err != nil {
errs = append(errs, fmt.Sprintf("第%d行: %v", i+1, err))
continue
}
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)
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 {
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 {
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,