refactor: 仓储表 C_* 改为 c_*,优化专线小包批量模板

- 仓储实体与 db.sql 统一为小写 c_ 前缀,新增 migrate_rename_warehouse_C_to_c.sql
- 专线小包批量模板精简版/标准版双模板,Excel 必填标红与发件地址上传参数

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
zzh 2026-07-22 16:18:59 +08:00
parent d791f7b251
commit a1daea6b35
29 changed files with 562 additions and 224 deletions

66
db.sql
View File

@ -1,4 +1,4 @@
-- OMS Admin schema -- OMS Admin schema
-- Target database: MySQL 8.0+ -- Target database: MySQL 8.0+
-- Notes: -- Notes:
-- 1. Physical foreign keys are intentionally omitted. The project uses logical foreign keys plus indexes to reduce import friction, -- 1. Physical foreign keys are intentionally omitted. The project uses logical foreign keys plus indexes to reduce import friction,
@ -36,16 +36,16 @@ DROP TABLE IF EXISTS `t_xhs_analysis_session`;
DROP TABLE IF EXISTS `t_xhs_publish_record`; DROP TABLE IF EXISTS `t_xhs_publish_record`;
DROP TABLE IF EXISTS `t_xhs_task`; DROP TABLE IF EXISTS `t_xhs_task`;
DROP TABLE IF EXISTS `t_xhs_agent`; DROP TABLE IF EXISTS `t_xhs_agent`;
DROP TABLE IF EXISTS `C_inbound_box_line`; DROP TABLE IF EXISTS `c_inbound_box_line`;
DROP TABLE IF EXISTS `C_inbound_box`; DROP TABLE IF EXISTS `c_inbound_box`;
DROP TABLE IF EXISTS `C_inbound_notice`; DROP TABLE IF EXISTS `c_inbound_notice`;
DROP TABLE IF EXISTS `C_parcel_item`; DROP TABLE IF EXISTS `c_parcel_item`;
DROP TABLE IF EXISTS `C_parcel_arrival`; DROP TABLE IF EXISTS `c_parcel_arrival`;
DROP TABLE IF EXISTS `C_parcel`; DROP TABLE IF EXISTS `c_parcel`;
DROP TABLE IF EXISTS `C_inventory`; DROP TABLE IF EXISTS `c_inventory`;
DROP TABLE IF EXISTS `C_sku_platform_map`; DROP TABLE IF EXISTS `c_sku_platform_map`;
DROP TABLE IF EXISTS `C_sku`; DROP TABLE IF EXISTS `c_sku`;
DROP TABLE IF EXISTS `C_bin_location`; DROP TABLE IF EXISTS `c_bin_location`;
DROP TABLE IF EXISTS `t_sales_store_order_line`; DROP TABLE IF EXISTS `t_sales_store_order_line`;
DROP TABLE IF EXISTS `t_sales_store_order`; DROP TABLE IF EXISTS `t_sales_store_order`;
DROP TABLE IF EXISTS `t_sales_store`; DROP TABLE IF EXISTS `t_sales_store`;
@ -1229,10 +1229,10 @@ CREATE TABLE `t_customer_account_flow` (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户账户资金流水'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户账户资金流水';
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
-- 仓储模块(C_*)+ 店铺订单(t_sales_store*) -- 仓储模块(c_*)+ 店铺订单(t_sales_store*)
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
CREATE TABLE `C_sku` ( CREATE TABLE `c_sku` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '客户ID → t_customer.Id', `CustomerId` INT NOT NULL COMMENT '客户ID → t_customer.Id',
`SkuCode` VARCHAR(64) NOT NULL COMMENT '库内SKU编码', `SkuCode` VARCHAR(64) NOT NULL COMMENT '库内SKU编码',
@ -1254,7 +1254,7 @@ CREATE TABLE `C_sku` (
KEY `idx_c_sku_customer` (`CustomerId`, `IsDeleted`) KEY `idx_c_sku_customer` (`CustomerId`, `IsDeleted`)
) COMMENT='仓储-SKU主数据'; ) COMMENT='仓储-SKU主数据';
CREATE TABLE `C_bin_location` ( CREATE TABLE `c_bin_location` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`WarehouseId` INT NOT NULL COMMENT '仓库ID → t_warehouse_address.Id', `WarehouseId` INT NOT NULL COMMENT '仓库ID → t_warehouse_address.Id',
`BinCode` VARCHAR(32) NOT NULL COMMENT '库位编码', `BinCode` VARCHAR(32) NOT NULL COMMENT '库位编码',
@ -1273,12 +1273,12 @@ CREATE TABLE `C_bin_location` (
KEY `idx_c_bin_warehouse` (`WarehouseId`, `IsEnabled`) KEY `idx_c_bin_warehouse` (`WarehouseId`, `IsEnabled`)
) COMMENT='仓储-库位主数据'; ) COMMENT='仓储-库位主数据';
CREATE TABLE `C_inventory` ( CREATE TABLE `c_inventory` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '客户ID', `CustomerId` INT NOT NULL COMMENT '客户ID',
`WarehouseId` INT NOT NULL COMMENT '仓库ID', `WarehouseId` INT NOT NULL COMMENT '仓库ID',
`SkuId` INT NOT NULL COMMENT 'SKU ID → C_sku.Id', `SkuId` INT NOT NULL COMMENT 'SKU ID → c_sku.Id',
`BinLocationId` INT NOT NULL COMMENT '库位ID → C_bin_location.Id', `BinLocationId` INT NOT NULL COMMENT '库位ID → c_bin_location.Id',
`Qty` INT NOT NULL DEFAULT 0 COMMENT '可用库存数量', `Qty` INT NOT NULL DEFAULT 0 COMMENT '可用库存数量',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是', `IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳', `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
@ -1291,7 +1291,7 @@ CREATE TABLE `C_inventory` (
KEY `idx_c_inv_sku_wh` (`CustomerId`, `SkuId`, `WarehouseId`) KEY `idx_c_inv_sku_wh` (`CustomerId`, `SkuId`, `WarehouseId`)
) COMMENT='仓储-库存余额(按库位)'; ) COMMENT='仓储-库存余额(按库位)';
CREATE TABLE `C_inbound_notice` ( CREATE TABLE `c_inbound_notice` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '客户ID', `CustomerId` INT NOT NULL COMMENT '客户ID',
`WarehouseId` INT NOT NULL COMMENT '仓库ID', `WarehouseId` INT NOT NULL COMMENT '仓库ID',
@ -1313,9 +1313,9 @@ CREATE TABLE `C_inbound_notice` (
KEY `idx_c_inbound_customer_status` (`CustomerId`, `ReceiptStatus`, `CreateTime`) KEY `idx_c_inbound_customer_status` (`CustomerId`, `ReceiptStatus`, `CreateTime`)
) COMMENT='仓储-入库预报主表'; ) COMMENT='仓储-入库预报主表';
CREATE TABLE `C_inbound_box` ( CREATE TABLE `c_inbound_box` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`InboundNoticeId` INT NOT NULL COMMENT '入库预报ID → C_inbound_notice.Id', `InboundNoticeId` INT NOT NULL COMMENT '入库预报ID → c_inbound_notice.Id',
`BoxSeqNo` VARCHAR(16) NOT NULL COMMENT '箱序号', `BoxSeqNo` VARCHAR(16) NOT NULL COMMENT '箱序号',
`MarkBoxNo` VARCHAR(64) NULL COMMENT '箱唛号/箱号', `MarkBoxNo` VARCHAR(64) NULL COMMENT '箱唛号/箱号',
`WeightG` DECIMAL(10,2) NULL COMMENT '箱重(g)', `WeightG` DECIMAL(10,2) NULL COMMENT '箱重(g)',
@ -1333,14 +1333,14 @@ CREATE TABLE `C_inbound_box` (
KEY `idx_c_inbound_box_notice` (`InboundNoticeId`, `IsDeleted`) KEY `idx_c_inbound_box_notice` (`InboundNoticeId`, `IsDeleted`)
) COMMENT='仓储-入库预报箱'; ) COMMENT='仓储-入库预报箱';
CREATE TABLE `C_inbound_box_line` ( CREATE TABLE `c_inbound_box_line` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`InboundBoxId` INT NOT NULL COMMENT '入库箱ID → C_inbound_box.Id', `InboundBoxId` INT NOT NULL COMMENT '入库箱ID → c_inbound_box.Id',
`SkuId` INT NOT NULL COMMENT 'SKU ID → C_sku.Id', `SkuId` INT NOT NULL COMMENT 'SKU ID → c_sku.Id',
`SkuCode` VARCHAR(64) NOT NULL COMMENT '冗余SKU编码', `SkuCode` VARCHAR(64) NOT NULL COMMENT '冗余SKU编码',
`PlannedQty` INT NOT NULL DEFAULT 0 COMMENT '计划入库数量', `PlannedQty` INT NOT NULL DEFAULT 0 COMMENT '计划入库数量',
`ReceivedQty` INT NULL COMMENT '实收数量', `ReceivedQty` INT NULL COMMENT '实收数量',
`BinLocationId` INT NULL COMMENT '上架库位ID → C_bin_location.Id', `BinLocationId` INT NULL COMMENT '上架库位ID → c_bin_location.Id',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是', `IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳', `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人', `CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',
@ -1352,7 +1352,7 @@ CREATE TABLE `C_inbound_box_line` (
KEY `idx_c_inbound_box_line_sku` (`SkuId`) KEY `idx_c_inbound_box_line_sku` (`SkuId`)
) COMMENT='仓储-入库箱内SKU行'; ) COMMENT='仓储-入库箱内SKU行';
CREATE TABLE `C_parcel` ( CREATE TABLE `c_parcel` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '客户ID', `CustomerId` INT NOT NULL COMMENT '客户ID',
`WarehouseId` INT NOT NULL COMMENT '仓库ID', `WarehouseId` INT NOT NULL COMMENT '仓库ID',
@ -1372,11 +1372,11 @@ CREATE TABLE `C_parcel` (
KEY `idx_c_parcel_status` (`CustomerId`, `LifecycleStatus`) KEY `idx_c_parcel_status` (`CustomerId`, `LifecycleStatus`)
) COMMENT='仓储-客户包裹'; ) COMMENT='仓储-客户包裹';
CREATE TABLE `C_parcel_arrival` ( CREATE TABLE `c_parcel_arrival` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '客户ID', `CustomerId` INT NOT NULL COMMENT '客户ID',
`WarehouseId` INT NOT NULL COMMENT '仓库ID', `WarehouseId` INT NOT NULL COMMENT '仓库ID',
`ParcelId` INT NULL COMMENT '关联包裹 C_parcel.Id', `ParcelId` INT NULL COMMENT '关联包裹 c_parcel.Id',
`TrackingNo` VARCHAR(64) NOT NULL COMMENT '国内快递单号', `TrackingNo` VARCHAR(64) NOT NULL COMMENT '国内快递单号',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是', `IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳', `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
@ -1389,14 +1389,14 @@ CREATE TABLE `C_parcel_arrival` (
KEY `idx_c_parcel_arrival_ctime` (`CreateTime`) KEY `idx_c_parcel_arrival_ctime` (`CreateTime`)
) COMMENT='仓储-快递到仓记录'; ) COMMENT='仓储-快递到仓记录';
CREATE TABLE `C_parcel_item` ( CREATE TABLE `c_parcel_item` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`ParcelId` INT NOT NULL COMMENT '包裹ID → C_parcel.Id', `ParcelId` INT NOT NULL COMMENT '包裹ID → c_parcel.Id',
`ProductName` VARCHAR(512) NULL COMMENT '商品名称', `ProductName` VARCHAR(512) NULL COMMENT '商品名称',
`AttributeText` VARCHAR(256) NULL COMMENT '规格属性', `AttributeText` VARCHAR(256) NULL COMMENT '规格属性',
`ImageUrl` VARCHAR(1024) NULL COMMENT '商品图片URL', `ImageUrl` VARCHAR(1024) NULL COMMENT '商品图片URL',
`Quantity` INT NOT NULL DEFAULT 0 COMMENT '数量', `Quantity` INT NOT NULL DEFAULT 0 COMMENT '数量',
`SkuId` INT NULL COMMENT '库内SKU ID → C_sku.Id', `SkuId` INT NULL COMMENT '库内SKU ID → c_sku.Id',
`SkuCode` VARCHAR(64) NULL COMMENT '冗余SKU编码', `SkuCode` VARCHAR(64) NULL COMMENT '冗余SKU编码',
`BinLocationId` INT NULL COMMENT '上架库位ID', `BinLocationId` INT NULL COMMENT '上架库位ID',
`Remark` VARCHAR(512) NULL COMMENT '备注', `Remark` VARCHAR(512) NULL COMMENT '备注',
@ -1410,12 +1410,12 @@ CREATE TABLE `C_parcel_item` (
KEY `idx_c_parcel_item_parcel` (`ParcelId`, `IsDeleted`) KEY `idx_c_parcel_item_parcel` (`ParcelId`, `IsDeleted`)
) COMMENT='仓储-包裹明细'; ) COMMENT='仓储-包裹明细';
CREATE TABLE `C_sku_platform_map` ( CREATE TABLE `c_sku_platform_map` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '客户ID', `CustomerId` INT NOT NULL COMMENT '客户ID',
`StoreId` INT NOT NULL COMMENT '店铺ID → t_sales_store.Id', `StoreId` INT NOT NULL COMMENT '店铺ID → t_sales_store.Id',
`PlatformSku` VARCHAR(128) NOT NULL COMMENT '平台listing SKU', `PlatformSku` VARCHAR(128) NOT NULL COMMENT '平台listing SKU',
`SkuId` INT NOT NULL COMMENT '库内SKU ID → C_sku.Id', `SkuId` INT NOT NULL COMMENT '库内SKU ID → c_sku.Id',
`SkuCode` VARCHAR(64) NOT NULL COMMENT '冗余SKU编码', `SkuCode` VARCHAR(64) NOT NULL COMMENT '冗余SKU编码',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是', `IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳', `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
@ -1518,7 +1518,7 @@ CREATE TABLE `t_sales_store_order_line` (
`Quantity` INT NOT NULL DEFAULT 0 COMMENT '数量', `Quantity` INT NOT NULL DEFAULT 0 COMMENT '数量',
`Currency` VARCHAR(8) NULL COMMENT '币种', `Currency` VARCHAR(8) NULL COMMENT '币种',
`IsShipExcluded` TINYINT NOT NULL DEFAULT 0 COMMENT '是否拒绝发货', `IsShipExcluded` TINYINT NOT NULL DEFAULT 0 COMMENT '是否拒绝发货',
`MappedSkuId` INT NULL COMMENT '映射库内SKU → C_sku.Id', `MappedSkuId` INT NULL COMMENT '映射库内SKU → c_sku.Id',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是', `IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳', `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',
`CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人', `CreateUserId` INT NOT NULL DEFAULT 0 COMMENT '创建人',

View File

@ -1,12 +1,12 @@
-- 已有库增量:快递到仓表 C_parcel_arrival -- 已有库增量:快递到仓表 c_parcel_arrival
-- 对应功能:操作 → 快递到仓;全量结构见根目录 db.sql -- 对应功能:操作 → 快递到仓;全量结构见根目录 db.sql
-- 可重复执行(IF NOT EXISTS) -- 可重复执行(IF NOT EXISTS)
CREATE TABLE IF NOT EXISTS `C_parcel_arrival` ( CREATE TABLE IF NOT EXISTS `c_parcel_arrival` (
`Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `Id` INT NOT NULL AUTO_INCREMENT COMMENT '主键',
`CustomerId` INT NOT NULL COMMENT '客户ID', `CustomerId` INT NOT NULL COMMENT '客户ID',
`WarehouseId` INT NOT NULL COMMENT '仓库ID', `WarehouseId` INT NOT NULL COMMENT '仓库ID',
`ParcelId` INT NULL COMMENT '关联包裹 C_parcel.Id', `ParcelId` INT NULL COMMENT '关联包裹 c_parcel.Id',
`TrackingNo` VARCHAR(64) NOT NULL COMMENT '国内快递单号', `TrackingNo` VARCHAR(64) NOT NULL COMMENT '国内快递单号',
`IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是', `IsDeleted` TINYINT NOT NULL DEFAULT 0 COMMENT '软删除:0-否,1-是',
`ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳', `ConcurrencyStamp` CHAR(36) NOT NULL DEFAULT (UUID()) COMMENT '并发戳',

View File

@ -1,20 +1,16 @@
-- 已有库增量:包裹明细增加属性字段 AttributeText -- 已有库增量:c_parcel_item.AttributeText
-- 可重复执行 -- 可重复执行
SET @col_exists := ( SET @col_exists := (
SELECT COUNT(1) SELECT COUNT(1)
FROM information_schema.COLUMNS FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'C_parcel_item' AND TABLE_NAME = 'c_parcel_item'
AND COLUMN_NAME = 'AttributeText' AND COLUMN_NAME = 'AttributeText'
); );
SET @sql := IF( SET @sql := IF(
@col_exists = 0, @col_exists = 0,
'ALTER TABLE `C_parcel_item` ADD COLUMN `AttributeText` VARCHAR(256) NULL COMMENT ''规格属性'' AFTER `ProductName`', 'ALTER TABLE `c_parcel_item` ADD COLUMN `AttributeText` VARCHAR(256) NULL COMMENT ''规格属性'' AFTER `ProductName`',
'SELECT ''C_parcel_item.AttributeText already exists'' AS info' 'SELECT ''c_parcel_item.AttributeText already exists'' AS info'
); );
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -1,20 +1,16 @@
-- 已有库增量:包裹明细增加商品图片 ImageUrl -- 已有库增量:c_parcel_item.ImageUrl
-- 可重复执行 -- 可重复执行
SET @col_exists := ( SET @col_exists := (
SELECT COUNT(1) SELECT COUNT(1)
FROM information_schema.COLUMNS FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'C_parcel_item' AND TABLE_NAME = 'c_parcel_item'
AND COLUMN_NAME = 'ImageUrl' AND COLUMN_NAME = 'ImageUrl'
); );
SET @sql := IF( SET @sql := IF(
@col_exists = 0, @col_exists = 0,
'ALTER TABLE `C_parcel_item` ADD COLUMN `ImageUrl` VARCHAR(1024) NULL COMMENT ''商品图片URL'' AFTER `AttributeText`', 'ALTER TABLE `c_parcel_item` ADD COLUMN `ImageUrl` VARCHAR(1024) NULL COMMENT ''商品图片URL'' AFTER `AttributeText`',
'SELECT ''C_parcel_item.ImageUrl already exists'' AS info' 'SELECT ''c_parcel_item.ImageUrl already exists'' AS info'
); );
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

View File

@ -3,5 +3,5 @@
OMS / 共用库的建表脚本已统一到仓库根目录 `db.sql`。 OMS / 共用库的建表脚本已统一到仓库根目录 `db.sql`。
- 新环境:直接执行 `db.sql`(会 DROP 后重建,勿在有生产数据的库上执行) - 新环境:直接执行 `db.sql`(会 DROP 后重建,勿在有生产数据的库上执行)
- 已有库:用 diff 对比 `db.sql` 与现库结构后手工 ALTER;本目录下单个表脚本(如 `C_parcel_arrival.sql`、`C_parcel_item_AttributeText.sql`、`C_parcel_item_ImageUrl.sql`)可直接在已有库执行增量建表 - 已有库:用 diff 对比 `db.sql` 与现库结构后手工 ALTER;本目录下单个表脚本(如 `c_parcel_arrival.sql`、`c_parcel_item_AttributeText.sql`、`c_parcel_item_ImageUrl.sql`)可直接在已有库执行增量建表;若仍为旧名 `C_*`,先执行 `migrate_rename_warehouse_C_to_c.sql`
- 数据权限表(`user_org_perm` 等)若 RBAC 与 OMS 分库,请只在 RBAC 库执行 `db.sql` 末尾对应段落 - 数据权限表(`user_org_perm` 等)若 RBAC 与 OMS 分库,请只在 RBAC 库执行 `db.sql` 末尾对应段落

View File

@ -8,7 +8,7 @@
-- --
-- 用法:在业务库执行本脚本;若仍报错,把报错 SQL 里的表名追加到下方列表。 -- 用法:在业务库执行本脚本;若仍报错,把报错 SQL 里的表名追加到下方列表。
SET @tables := 't_order_stock_bind,C_inventory,C_parcel,C_parcel_item,C_parcel_arrival,C_sku,C_bin_location,C_inbound_notice,C_inbound_box,C_inbound_box_line,C_sku_platform_map,t_sales_store,t_sales_store_order,t_sales_store_order_line,t_customer_sender_address'; SET @tables := 't_order_stock_bind,c_inventory,c_parcel,c_parcel_item,c_parcel_arrival,c_sku,c_bin_location,c_inbound_notice,c_inbound_box,c_inbound_box_line,c_sku_platform_map,t_sales_store,t_sales_store_order,t_sales_store_order_line,t_customer_sender_address';
DROP PROCEDURE IF EXISTS sp_add_concurrency_stamp_if_missing; DROP PROCEDURE IF EXISTS sp_add_concurrency_stamp_if_missing;
DELIMITER $$ DELIMITER $$

View File

@ -0,0 +1,34 @@
-- 仓储表 C_* → c_*(小写前缀,对齐 t_* 命名风格)
-- 可重复执行:源表不存在或目标表已存在则跳过
DROP PROCEDURE IF EXISTS sp_rename_warehouse_table_if_needed;
DELIMITER $$
CREATE PROCEDURE sp_rename_warehouse_table_if_needed(IN old_name VARCHAR(128), IN new_name VARCHAR(128))
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = old_name
) AND NOT EXISTS (
SELECT 1 FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = new_name
) THEN
SET @ddl := CONCAT('RENAME TABLE `', old_name, '` TO `', new_name, '`');
PREPARE stmt FROM @ddl;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END$$
DELIMITER ;
CALL sp_rename_warehouse_table_if_needed('C_sku', 'c_sku');
CALL sp_rename_warehouse_table_if_needed('C_bin_location', 'c_bin_location');
CALL sp_rename_warehouse_table_if_needed('C_inventory', 'c_inventory');
CALL sp_rename_warehouse_table_if_needed('C_inbound_notice', 'c_inbound_notice');
CALL sp_rename_warehouse_table_if_needed('C_inbound_box', 'c_inbound_box');
CALL sp_rename_warehouse_table_if_needed('C_inbound_box_line', 'c_inbound_box_line');
CALL sp_rename_warehouse_table_if_needed('C_parcel', 'c_parcel');
CALL sp_rename_warehouse_table_if_needed('C_parcel_arrival', 'c_parcel_arrival');
CALL sp_rename_warehouse_table_if_needed('C_parcel_item', 'c_parcel_item');
CALL sp_rename_warehouse_table_if_needed('C_sku_platform_map', 'c_sku_platform_map');
DROP PROCEDURE IF EXISTS sp_rename_warehouse_table_if_needed;

View File

@ -1,4 +1,4 @@
-- 物流单绑定包裹/库存:t_order_stock_bind + C_inventory/C_parcel_item 冻结与可用字段 -- 物流单绑定包裹/库存:t_order_stock_bind + c_inventory/c_parcel_item 冻结与可用字段
-- 见 oms系统设计-050801.md §4.2.11 / §4.4.6 / §3.1.2 -- 见 oms系统设计-050801.md §4.2.11 / §4.4.6 / §3.1.2
-- ConcurrencyStamp 默认用固定 GUID,避免 DEFAULT (UUID()) 触发复制 1674 -- ConcurrencyStamp 默认用固定 GUID,避免 DEFAULT (UUID()) 触发复制 1674
-- 可重复执行 -- 可重复执行
@ -10,10 +10,10 @@ CREATE TABLE IF NOT EXISTS `t_order_stock_bind` (
`WarehouseId` INT NOT NULL COMMENT '仓库', `WarehouseId` INT NOT NULL COMMENT '仓库',
`BindType` VARCHAR(20) NOT NULL COMMENT 'inventory / parcelItem', `BindType` VARCHAR(20) NOT NULL COMMENT 'inventory / parcelItem',
`SkuId` INT NULL COMMENT 'SKU', `SkuId` INT NULL COMMENT 'SKU',
`InventoryId` INT NULL COMMENT 'C_inventory.Id,可空=按SKU分摊', `InventoryId` INT NULL COMMENT 'c_inventory.Id,可空=按SKU分摊',
`BinLocationId` INT NULL COMMENT '库位快照', `BinLocationId` INT NULL COMMENT '库位快照',
`ParcelId` INT NULL COMMENT 'C_parcel.Id', `ParcelId` INT NULL COMMENT 'c_parcel.Id',
`ParcelItemId` INT NULL COMMENT 'C_parcel_item.Id', `ParcelItemId` INT NULL COMMENT 'c_parcel_item.Id',
`BindQty` INT NOT NULL COMMENT '绑定数量', `BindQty` INT NOT NULL COMMENT '绑定数量',
`Status` VARCHAR(20) NOT NULL DEFAULT 'bound' COMMENT 'bound/deducted/released', `Status` VARCHAR(20) NOT NULL DEFAULT 'bound' COMMENT 'bound/deducted/released',
`SkuCode` VARCHAR(64) NULL, `SkuCode` VARCHAR(64) NULL,
@ -47,60 +47,74 @@ SET @sql := IF(
); );
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- C_inventory.FrozenQty -- c_inventory.FrozenQty(兼容尚未 rename 的旧表名 C_inventory)
SET @col_exists := ( SET @inv_table := IF(
SELECT COUNT(1) FROM information_schema.COLUMNS EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'c_inventory'),
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'C_inventory' AND COLUMN_NAME = 'FrozenQty' 'c_inventory',
IF(
EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'C_inventory'),
'C_inventory',
NULL
)
); );
SET @sql := IF( SET @col_exists := IF(@inv_table IS NULL, 1,
@col_exists = 0, (SELECT COUNT(1) FROM information_schema.COLUMNS
'ALTER TABLE `C_inventory` ADD COLUMN `FrozenQty` INT NOT NULL DEFAULT 0 COMMENT ''冻结数量'' AFTER `Qty`', WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @inv_table AND COLUMN_NAME = 'FrozenQty')
'SELECT ''C_inventory.FrozenQty already exists'' AS info' );
SET @sql := IF(@inv_table IS NULL OR @col_exists > 0,
'SELECT ''c_inventory.FrozenQty already exists or table missing'' AS info',
CONCAT('ALTER TABLE `', @inv_table, '` ADD COLUMN `FrozenQty` INT NOT NULL DEFAULT 0 COMMENT ''冻结数量'' AFTER `Qty`')
); );
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- C_inventory.AvailableQty SET @col_exists := IF(@inv_table IS NULL, 1,
SET @col_exists := ( (SELECT COUNT(1) FROM information_schema.COLUMNS
SELECT COUNT(1) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @inv_table AND COLUMN_NAME = 'AvailableQty')
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'C_inventory' AND COLUMN_NAME = 'AvailableQty'
); );
SET @sql := IF( SET @sql := IF(@inv_table IS NULL OR @col_exists > 0,
@col_exists = 0, 'SELECT ''c_inventory.AvailableQty already exists or table missing'' AS info',
'ALTER TABLE `C_inventory` ADD COLUMN `AvailableQty` INT NOT NULL DEFAULT 0 COMMENT ''可用=Qty-FrozenQty'' AFTER `FrozenQty`', CONCAT('ALTER TABLE `', @inv_table, '` ADD COLUMN `AvailableQty` INT NOT NULL DEFAULT 0 COMMENT ''可用=Qty-FrozenQty'' AFTER `FrozenQty`')
'SELECT ''C_inventory.AvailableQty already exists'' AS info'
); );
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
UPDATE `C_inventory` SET @sql := IF(@inv_table IS NULL,
SET `AvailableQty` = GREATEST(0, `Qty` - IFNULL(`FrozenQty`, 0)) 'SELECT ''c_inventory table missing'' AS info',
WHERE `AvailableQty` <> GREATEST(0, `Qty` - IFNULL(`FrozenQty`, 0)) CONCAT('UPDATE `', @inv_table, '` SET `AvailableQty` = GREATEST(0, `Qty` - IFNULL(`FrozenQty`, 0)) WHERE `AvailableQty` <> GREATEST(0, `Qty` - IFNULL(`FrozenQty`, 0)) OR (`AvailableQty` = 0 AND `Qty` > 0 AND IFNULL(`FrozenQty`, 0) = 0)')
OR (`AvailableQty` = 0 AND `Qty` > 0 AND IFNULL(`FrozenQty`, 0) = 0);
-- C_parcel_item.FrozenQty
SET @col_exists := (
SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'C_parcel_item' AND COLUMN_NAME = 'FrozenQty'
);
SET @sql := IF(
@col_exists = 0,
'ALTER TABLE `C_parcel_item` ADD COLUMN `FrozenQty` INT NOT NULL DEFAULT 0 COMMENT ''冻结数量'' AFTER `Quantity`',
'SELECT ''C_parcel_item.FrozenQty already exists'' AS info'
); );
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
-- C_parcel_item.AvailableQty -- c_parcel_item.FrozenQty
SET @col_exists := ( SET @pi_table := IF(
SELECT COUNT(1) FROM information_schema.COLUMNS EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'c_parcel_item'),
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'C_parcel_item' AND COLUMN_NAME = 'AvailableQty' 'c_parcel_item',
IF(
EXISTS (SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'C_parcel_item'),
'C_parcel_item',
NULL
)
); );
SET @sql := IF( SET @col_exists := IF(@pi_table IS NULL, 1,
@col_exists = 0, (SELECT COUNT(1) FROM information_schema.COLUMNS
'ALTER TABLE `C_parcel_item` ADD COLUMN `AvailableQty` INT NOT NULL DEFAULT 0 COMMENT ''可用=Quantity-FrozenQty'' AFTER `FrozenQty`', WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @pi_table AND COLUMN_NAME = 'FrozenQty')
'SELECT ''C_parcel_item.AvailableQty already exists'' AS info' );
SET @sql := IF(@pi_table IS NULL OR @col_exists > 0,
'SELECT ''c_parcel_item.FrozenQty already exists or table missing'' AS info',
CONCAT('ALTER TABLE `', @pi_table, '` ADD COLUMN `FrozenQty` INT NOT NULL DEFAULT 0 COMMENT ''冻结数量'' AFTER `Quantity`')
); );
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
UPDATE `C_parcel_item` SET @col_exists := IF(@pi_table IS NULL, 1,
SET `AvailableQty` = GREATEST(0, `Quantity` - IFNULL(`FrozenQty`, 0)) (SELECT COUNT(1) FROM information_schema.COLUMNS
WHERE `AvailableQty` <> GREATEST(0, `Quantity` - IFNULL(`FrozenQty`, 0)) WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = @pi_table AND COLUMN_NAME = 'AvailableQty')
OR (`AvailableQty` = 0 AND `Quantity` > 0 AND IFNULL(`FrozenQty`, 0) = 0); );
SET @sql := IF(@pi_table IS NULL OR @col_exists > 0,
'SELECT ''c_parcel_item.AvailableQty already exists or table missing'' AS info',
CONCAT('ALTER TABLE `', @pi_table, '` ADD COLUMN `AvailableQty` INT NOT NULL DEFAULT 0 COMMENT ''可用=Quantity-FrozenQty'' AFTER `FrozenQty`')
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
SET @sql := IF(@pi_table IS NULL,
'SELECT ''c_parcel_item table missing'' AS info',
CONCAT('UPDATE `', @pi_table, '` SET `AvailableQty` = GREATEST(0, `Quantity` - IFNULL(`FrozenQty`, 0)) WHERE `AvailableQty` <> GREATEST(0, `Quantity` - IFNULL(`FrozenQty`, 0)) OR (`AvailableQty` = 0 AND `Quantity` > 0 AND IFNULL(`FrozenQty`, 0) = 0)')
);
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;

View File

@ -40,6 +40,8 @@ public class OrderBatchImportRowPayload
public int TemplateId { get; set; } public int TemplateId { get; set; }
public int? CustomerSenderAddressId { get; set; }
public string RowDataJson { get; set; } = string.Empty; public string RowDataJson { get; set; } = string.Empty;
} }
@ -53,4 +55,9 @@ public class OrderBatchImportJobPayload
public int TemplateId { get; set; } public int TemplateId { get; set; }
public int CustomerId { get; set; } public int CustomerId { get; set; }
/// <summary>
/// 批量导入统一使用的默认发件地址。
/// </summary>
public int? CustomerSenderAddressId { get; set; }
} }

View File

@ -27,6 +27,8 @@ public class OrderTemplateFieldSchemaItemOutput
public string Group { get; set; } = string.Empty; public string Group { get; set; } = string.Empty;
public string Example { get; set; } = string.Empty; public string Example { get; set; } = string.Empty;
public string Hint { get; set; } = string.Empty;
} }
public class OrderTemplateDictOptionsOutput public class OrderTemplateDictOptionsOutput

View File

@ -16,4 +16,9 @@ public class OrderUploadInput
/// 模板主键。 /// 模板主键。
/// </summary> /// </summary>
public int TemplateId { get; set; } public int TemplateId { get; set; }
/// <summary>
/// 默认发件地址 ID(地址管理中的发件人地址)。
/// </summary>
public int? SenderAddressId { get; set; }
} }

View File

@ -30,7 +30,21 @@ public partial class OrderService
?? throw new BusinessException("行数据无效"); ?? throw new BusinessException("行数据无效");
var input = OrderTemplateRowMapper.MapToParcelCreateInput(sourceValues, mapping, payload.CustomerId); var input = OrderTemplateRowMapper.MapToParcelCreateInput(sourceValues, mapping, payload.CustomerId);
return await CreateParcelForBatchAsync(input, operatorUserId, cancellationToken); if (payload.CustomerSenderAddressId is > 0)
{
await ApplyCustomerSenderAddressToParcelInputAsync(
input,
payload.CustomerId,
payload.CustomerSenderAddressId.Value,
cancellationToken);
}
return await CreateParcelForBatchAsync(
input,
operatorUserId,
orderSource: null,
customerSenderAddressId: payload.CustomerSenderAddressId,
cancellationToken);
} }
public async Task<int?> TryResolveImportOrderIdAsync( public async Task<int?> TryResolveImportOrderIdAsync(
@ -78,24 +92,42 @@ public partial class OrderService
int operatorUserId, int operatorUserId,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return await CreateParcelForBatchAsync(input, operatorUserId, orderSource: null, cancellationToken); return await CreateParcelForBatchAsync(input, operatorUserId, orderSource: null, customerSenderAddressId: null, cancellationToken);
}
/// <summary>
/// 批量 Worker 创建小包订单(可指定 <paramref name="orderSource"/> 与默认发件地址)。
/// </summary>
public async Task<OrderParcelCreateOutput> CreateParcelForBatchAsync(
OrderParcelCreateInput input,
int operatorUserId,
string? orderSource,
int? customerSenderAddressId,
CancellationToken cancellationToken = default)
{
ParcelCreateContext? createContext = null;
if (!string.IsNullOrWhiteSpace(orderSource) || customerSenderAddressId is > 0)
{
createContext = new ParcelCreateContext
{
OrderSource = string.IsNullOrWhiteSpace(orderSource) ? null : orderSource.Trim(),
CustomerSenderAddressId = customerSenderAddressId,
};
}
return await CreateParcelCoreAsync(input, operatorUserId, createContext);
} }
/// <summary> /// <summary>
/// 批量 Worker 创建小包订单(可指定 <paramref name="orderSource"/>)。 /// 批量 Worker 创建小包订单(可指定 <paramref name="orderSource"/>)。
/// </summary> /// </summary>
[Obsolete("Use CreateParcelForBatchAsync with customerSenderAddressId when importing.")]
public async Task<OrderParcelCreateOutput> CreateParcelForBatchAsync( public async Task<OrderParcelCreateOutput> CreateParcelForBatchAsync(
OrderParcelCreateInput input, OrderParcelCreateInput input,
int operatorUserId, int operatorUserId,
string? orderSource, string? orderSource,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
ParcelCreateContext? createContext = null; return await CreateParcelForBatchAsync(input, operatorUserId, orderSource, null, cancellationToken);
if (!string.IsNullOrWhiteSpace(orderSource))
{
createContext = new ParcelCreateContext { OrderSource = orderSource.Trim() };
}
return await CreateParcelCoreAsync(input, operatorUserId, createContext);
} }
} }

View File

@ -380,6 +380,11 @@ public partial class OrderService
throw new BusinessException($"单次最多导入 {ParcelBatchMaxCount} 行"); throw new BusinessException($"单次最多导入 {ParcelBatchMaxCount} 行");
} }
var senderAddressId = await ResolveImportSenderAddressIdAsync(
effectiveCustomer.Id,
input.SenderAddressId,
cancellationToken: default);
var taskName = $"批量下单_{DateTime.Now:yyyyMMddHHmmss}"; var taskName = $"批量下单_{DateTime.Now:yyyyMMddHHmmss}";
var requestId = $"import-{effectiveCustomer.Id}-{template.Id}-{DateTime.Now:yyyyMMddHHmmssfff}"; var requestId = $"import-{effectiveCustomer.Id}-{template.Id}-{DateTime.Now:yyyyMMddHHmmssfff}";
@ -388,6 +393,7 @@ public partial class OrderService
TaskName = taskName, TaskName = taskName,
TemplateId = template.Id, TemplateId = template.Id,
CustomerId = effectiveCustomer.Id, CustomerId = effectiveCustomer.Id,
CustomerSenderAddressId = senderAddressId,
}); });
var payloadItems = parsedRows.Select(row => var payloadItems = parsedRows.Select(row =>
@ -404,6 +410,7 @@ public partial class OrderService
RowNumber = row.RowNumber, RowNumber = row.RowNumber,
CustomerId = effectiveCustomer.Id, CustomerId = effectiveCustomer.Id,
TemplateId = template.Id, TemplateId = template.Id,
CustomerSenderAddressId = senderAddressId,
RowDataJson = JsonConvert.SerializeObject(row.SourceValues), RowDataJson = JsonConvert.SerializeObject(row.SourceValues),
}), }),
}; };

View File

@ -298,4 +298,76 @@ public partial class OrderService
{ {
return string.IsNullOrWhiteSpace(value) ? null : value.Trim(); return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
} }
private async Task<int> ResolveImportSenderAddressIdAsync(
int customerId,
int? senderAddressId,
CancellationToken cancellationToken = default)
{
if (senderAddressId is > 0)
{
var specified = await CustomerSenderAddressRepository.GetOwnedAsync(senderAddressId.Value, customerId, cancellationToken)
?? throw new BusinessException("发件地址不存在");
EnsureSenderAddressType(specified);
return specified.Id;
}
var defaultAddress = await CustomerSenderAddressRepository.FirstOrDefaultAsync(
x => x.CustomerId == customerId
&& x.AddressType == CustomerAddressTypeCodes.Sender
&& x.IsDefault > 0,
cancellationToken);
if (defaultAddress != null)
{
return defaultAddress.Id;
}
throw new BusinessException("请先在批量下单页选择默认发件地址,或在地址管理中设置默认发件地址");
}
private static void EnsureSenderAddressType(CustomerSenderAddress address)
{
var addressType = string.IsNullOrWhiteSpace(address.AddressType)
? CustomerAddressTypeCodes.Sender
: address.AddressType;
if (!string.Equals(addressType, CustomerAddressTypeCodes.Sender, StringComparison.OrdinalIgnoreCase))
{
throw new BusinessException("请选择发件类型地址");
}
}
private async Task ApplyCustomerSenderAddressToParcelInputAsync(
OrderParcelCreateInput input,
int customerId,
int senderAddressId,
CancellationToken cancellationToken = default)
{
var address = await CustomerSenderAddressRepository.GetOwnedAsync(senderAddressId, customerId, cancellationToken)
?? throw new BusinessException("发件地址不存在");
EnsureSenderAddressType(address);
string? countryCode = null;
if (address.Country.HasValue)
{
var country = await CountryRepository.FirstOrDefaultAsync(x => x.Id == address.Country.Value, cancellationToken);
countryCode = country?.CountryCode;
}
input.Sender = new OrderParcelSenderInput
{
SenderName = address.SenderName,
Company = address.Company,
Phone = address.Phone,
TelePhone = address.TelePhone,
Email = address.Email,
CountryCode = countryCode ?? "CN",
Province = address.Province,
City = address.City,
ZipCode = address.ZipCode,
StreetAddress1 = address.StreetAddress1,
StreetAddress2 = address.StreetAddress2,
StreetAddress3 = address.StreetAddress3,
HouseNumber = address.HouseNumber,
};
}
} }

View File

@ -17,12 +17,12 @@ public partial class OrderService
[HttpGet("/order/template/field-schema")] [HttpGet("/order/template/field-schema")]
public Task<OrderTemplateFieldSchemaOutput> GetTemplateFieldSchemaAsync([FromQuery] string? templateKey) public Task<OrderTemplateFieldSchemaOutput> GetTemplateFieldSchemaAsync([FromQuery] string? templateKey)
{ {
var key = string.IsNullOrWhiteSpace(templateKey) ? OrderTemplateKeys.DedicatedSmallParcel : templateKey.Trim(); var key = string.IsNullOrWhiteSpace(templateKey) ? OrderTemplateKeys.DedicatedSmallParcelStandard : templateKey.Trim();
var fields = OrderTemplateFieldRegistry.GetFields(key); var fields = OrderTemplateFieldRegistry.GetFields(key);
return Task.FromResult(new OrderTemplateFieldSchemaOutput return Task.FromResult(new OrderTemplateFieldSchemaOutput
{ {
TemplateKey = key, TemplateKey = key,
TemplateName = key == OrderTemplateKeys.DedicatedSmallParcel ? "专线小包模板" : key, TemplateName = OrderTemplateFieldRegistry.GetTemplateDisplayName(key),
Fields = fields.Select(MapFieldSchemaItem).ToList() Fields = fields.Select(MapFieldSchemaItem).ToList()
}); });
} }
@ -79,13 +79,13 @@ public partial class OrderService
customerId, customerId,
WebHostEnvironment); WebHostEnvironment);
var suggested = OrderTemplateHeaderMatcher.BuildSuggestedMapping(OrderTemplateKeys.DedicatedSmallParcel, headers); var suggested = OrderTemplateHeaderMatcher.BuildSuggestedMapping(OrderTemplateKeys.DedicatedSmallParcelStandard, headers);
var entity = new OrderTemplate var entity = new OrderTemplate
{ {
Name = name, Name = name,
Type = "custom", Type = "custom",
TemplateKey = $"custom_{Guid.NewGuid():N}".Substring(0, 24), TemplateKey = $"custom_{Guid.NewGuid():N}".Substring(0, 24),
StandardTemplateKey = OrderTemplateKeys.DedicatedSmallParcel, StandardTemplateKey = OrderTemplateKeys.DedicatedSmallParcelStandard,
MappingStatus = OrderTemplateMappingStatus.Draft, MappingStatus = OrderTemplateMappingStatus.Draft,
CustomerId = customerId, CustomerId = customerId,
SourceFileName = originalName, SourceFileName = originalName,
@ -236,23 +236,39 @@ public partial class OrderService
} }
/// <summary> /// <summary>
/// 列表查询前确保标准「专线小包模板」存在(未执行种子脚本时自动补齐)。 /// 列表查询前确保标准「专线小包」精简版与标准版模板存在。
/// </summary> /// </summary>
private async Task EnsureDedicatedSmallParcelStandardTemplateAsync() private async Task EnsureDedicatedSmallParcelStandardTemplateAsync()
{
await EnsureBuiltinStandardTemplateAsync(
OrderTemplateKeys.DedicatedSmallParcelStandard,
"专线小包模板(标准版)");
await EnsureBuiltinStandardTemplateAsync(
OrderTemplateKeys.DedicatedSmallParcelSimple,
"专线小包模板(精简版)");
}
private async Task EnsureBuiltinStandardTemplateAsync(string templateKey, string name)
{ {
var existing = await OrderTemplateRepository.FirstOrDefaultAsync(x => var existing = await OrderTemplateRepository.FirstOrDefaultAsync(x =>
x.Type == "standard" && x.TemplateKey == OrderTemplateKeys.DedicatedSmallParcel); x.Type == "standard" && x.TemplateKey == templateKey);
if (existing != null) if (existing != null)
{ {
if (!string.Equals(existing.Name, name, StringComparison.Ordinal))
{
existing.Name = name;
await OrderTemplateRepository.UpdateAsync(existing);
}
return; return;
} }
await OrderTemplateRepository.InsertAsync(new OrderTemplate await OrderTemplateRepository.InsertAsync(new OrderTemplate
{ {
Name = "专线小包模板", Name = name,
Type = "standard", Type = "standard",
TemplateKey = OrderTemplateKeys.DedicatedSmallParcel, TemplateKey = templateKey,
StandardTemplateKey = OrderTemplateKeys.DedicatedSmallParcel, StandardTemplateKey = templateKey,
MappingStatus = OrderTemplateMappingStatus.Ready, MappingStatus = OrderTemplateMappingStatus.Ready,
CustomerId = null, CustomerId = null,
}); });
@ -283,7 +299,8 @@ public partial class OrderService
DataType = field.DataType, DataType = field.DataType,
DictType = field.DictType, DictType = field.DictType,
Group = field.Group, Group = field.Group,
Example = field.Example Example = field.Example,
Hint = field.Hint,
}; };
private static List<string> DeserializeStringList(string? json) private static List<string> DeserializeStringList(string? json)

View File

@ -26,7 +26,21 @@ public static class OrderTemplateExcelGenerator
emptyDataRow[field.Label] = string.Empty; emptyDataRow[field.Label] = string.Empty;
} }
var guideRows = fields.Select(f => new Dictionary<string, object?> var guideIntro = new Dictionary<string, object?>
{
["列名"] = "【填写说明】",
["字段Key"] = string.Empty,
["是否必填"] = string.Empty,
["数据类型"] = string.Empty,
["字典类型"] = string.Empty,
["可选值"] = string.Empty,
["分组"] = string.Empty,
["示例"] = string.Empty,
["填写说明"] = BuildGuideIntroText(templateKey),
};
var guideRows = new List<Dictionary<string, object?>> { guideIntro };
guideRows.AddRange(fields.Select(f => new Dictionary<string, object?>
{ {
["列名"] = f.Label, ["列名"] = f.Label,
["字段Key"] = f.FieldKey, ["字段Key"] = f.FieldKey,
@ -34,9 +48,10 @@ public static class OrderTemplateExcelGenerator
["数据类型"] = f.DataType, ["数据类型"] = f.DataType,
["字典类型"] = f.DictType ?? string.Empty, ["字典类型"] = f.DictType ?? string.Empty,
["可选值"] = BuildGuideOptionText(f, dictOptions ?? []), ["可选值"] = BuildGuideOptionText(f, dictOptions ?? []),
["分组"] = f.Group, ["分组"] = MapGroupLabel(f.Group),
["示例"] = f.Example ["示例"] = f.Example,
}).ToList(); ["填写说明"] = string.IsNullOrWhiteSpace(f.Hint) ? f.Example : f.Hint,
}));
var dictRows = (dictOptions ?? []) var dictRows = (dictOptions ?? [])
.Select(x => new Dictionary<string, object?> .Select(x => new Dictionary<string, object?>
@ -91,4 +106,24 @@ public static class OrderTemplateExcelGenerator
return options.Count == 0 ? string.Empty : string.Join(";", options); return options.Count == 0 ? string.Empty : string.Join(";", options);
} }
private static string MapGroupLabel(string group) =>
group switch
{
"basic" => "订单信息",
"receiver" => "收件人",
"declaration" => "申报",
_ => group,
};
private static string BuildGuideIntroText(string templateKey)
{
var common = "红色表头为必填;带下拉的列请点选编码;发件人请在网页批量下单页选择默认地址,无需在 Excel 填写。";
if (OrderTemplateFieldRegistry.IsSimpleTemplate(templateKey))
{
return $"【精简版】仅含日常批量所需核心列,推荐大多数客户使用。{common}";
}
return $"【标准版】含 COD、税号、申报扩展字段等不常用列,有特殊清关/平台要求时使用。{common}";
}
} }

View File

@ -11,7 +11,8 @@ internal static class OrderTemplateExcelOpenXmlEnhancer
{ {
private const uint DataValidationStartRow = 2; private const uint DataValidationStartRow = 2;
private const uint DataValidationEndRow = 5000; private const uint DataValidationEndRow = 5000;
private const string YellowHeaderRgb = "FFFFFF00"; private const string RequiredHeaderFillRgb = "FFFFE5E5";
private const string RequiredHeaderFontRgb = "FFC00000";
public static byte[] Enhance( public static byte[] Enhance(
byte[] workbookBytes, byte[] workbookBytes,
@ -96,6 +97,10 @@ internal static class OrderTemplateExcelOpenXmlEnhancer
AllowBlank = !field.Required, AllowBlank = !field.Required,
ShowInputMessage = true, ShowInputMessage = true,
ShowErrorMessage = true, ShowErrorMessage = true,
ErrorTitle = "输入无效",
Error = field.Required ? "请从下拉列表选择或填写有效值" : "请填写有效值",
PromptTitle = field.Label,
Prompt = BuildValidationPrompt(field),
SequenceOfReferences = new ListValue<StringValue> SequenceOfReferences = new ListValue<StringValue>
{ {
InnerText = $"{columnName}{DataValidationStartRow}:{columnName}{DataValidationEndRow}" InnerText = $"{columnName}{DataValidationStartRow}:{columnName}{DataValidationEndRow}"
@ -130,6 +135,21 @@ internal static class OrderTemplateExcelOpenXmlEnhancer
return null; return null;
} }
private static string BuildValidationPrompt(OrderTemplateFieldDefinition field)
{
if (!string.IsNullOrWhiteSpace(field.Hint))
{
return field.Hint;
}
if (string.Equals(field.DataType, "boolean", StringComparison.OrdinalIgnoreCase))
{
return "填写 true 或 false";
}
return field.Required ? "必填" : "选填";
}
private static Dictionary<string, string> BuildDictTypeRanges( private static Dictionary<string, string> BuildDictTypeRanges(
WorkbookPart workbookPart, WorkbookPart workbookPart,
IReadOnlyList<OrderTemplateDictOption> dictOptions) IReadOnlyList<OrderTemplateDictOption> dictOptions)
@ -255,20 +275,22 @@ internal static class OrderTemplateExcelOpenXmlEnhancer
cellFormats.Count = 1; cellFormats.Count = 1;
} }
var yellowFill = new Fill var requiredFill = new Fill
{ {
PatternFill = new PatternFill PatternFill = new PatternFill
{ {
PatternType = PatternValues.Solid, PatternType = PatternValues.Solid,
ForegroundColor = new ForegroundColor { Rgb = YellowHeaderRgb }, ForegroundColor = new ForegroundColor { Rgb = RequiredHeaderFillRgb },
BackgroundColor = new BackgroundColor { Indexed = 64 } BackgroundColor = new BackgroundColor { Indexed = 64 }
} }
}; };
fills.Append(yellowFill); fills.Append(requiredFill);
var yellowFillId = (uint)fills.ChildElements.Count - 1; var requiredFillId = (uint)fills.ChildElements.Count - 1;
fills.Count = (uint)fills.ChildElements.Count; fills.Count = (uint)fills.ChildElements.Count;
var headerFont = new Font(new Bold()); var headerFont = new Font(
new Bold(),
new Color { Rgb = RequiredHeaderFontRgb });
fonts.Append(headerFont); fonts.Append(headerFont);
var headerFontId = (uint)fonts.ChildElements.Count - 1; var headerFontId = (uint)fonts.ChildElements.Count - 1;
fonts.Count = (uint)fonts.ChildElements.Count; fonts.Count = (uint)fonts.ChildElements.Count;
@ -276,7 +298,7 @@ internal static class OrderTemplateExcelOpenXmlEnhancer
var headerFormat = new CellFormat var headerFormat = new CellFormat
{ {
FontId = headerFontId, FontId = headerFontId,
FillId = yellowFillId, FillId = requiredFillId,
BorderId = 0, BorderId = 0,
ApplyFont = true, ApplyFont = true,
ApplyFill = true ApplyFill = true

View File

@ -2,7 +2,14 @@ namespace Oms.Application.Services.Orders.Templates;
public static class OrderTemplateKeys public static class OrderTemplateKeys
{ {
public const string DedicatedSmallParcel = "dedicated_small_parcel_v1"; /// <summary>专线小包标准版(含扩展字段)。</summary>
public const string DedicatedSmallParcelStandard = "dedicated_small_parcel_v1";
/// <summary>专线小包精简版(日常批量推荐)。</summary>
public const string DedicatedSmallParcelSimple = "dedicated_small_parcel_simple_v1";
/// <summary>自定义模板字段映射默认对齐标准版字段集。</summary>
public const string DedicatedSmallParcel = DedicatedSmallParcelStandard;
} }
public static class OrderTemplateMappingStatus public static class OrderTemplateMappingStatus
@ -20,20 +27,37 @@ public sealed class OrderTemplateFieldDefinition
public string? DictType { get; init; } public string? DictType { get; init; }
public string Group { get; init; } = string.Empty; public string Group { get; init; } = string.Empty;
public string Example { get; init; } = string.Empty; public string Example { get; init; } = string.Empty;
public string Hint { get; init; } = string.Empty;
public int Sort { get; init; } public int Sort { get; init; }
} }
public static class OrderTemplateFieldRegistry public static class OrderTemplateFieldRegistry
{ {
private static readonly IReadOnlyList<OrderTemplateFieldDefinition> DedicatedSmallParcelFields = BuildDedicatedSmallParcelFields(); private static readonly IReadOnlyList<OrderTemplateFieldDefinition> SimpleFields = BuildDedicatedSmallParcelSimpleFields();
private static readonly IReadOnlyList<OrderTemplateFieldDefinition> StandardFields = BuildDedicatedSmallParcelStandardFields();
public static string GetTemplateDisplayName(string? templateKey)
{
var key = NormalizeTemplateKey(templateKey);
return key switch
{
OrderTemplateKeys.DedicatedSmallParcelSimple => "专线小包模板(精简版)",
OrderTemplateKeys.DedicatedSmallParcelStandard => "专线小包模板(标准版)",
_ => key,
};
}
public static bool IsSimpleTemplate(string? templateKey) =>
string.Equals(NormalizeTemplateKey(templateKey), OrderTemplateKeys.DedicatedSmallParcelSimple, StringComparison.OrdinalIgnoreCase);
public static IReadOnlyList<OrderTemplateFieldDefinition> GetFields(string? templateKey) public static IReadOnlyList<OrderTemplateFieldDefinition> GetFields(string? templateKey)
{ {
var key = string.IsNullOrWhiteSpace(templateKey) ? OrderTemplateKeys.DedicatedSmallParcel : templateKey.Trim(); var key = NormalizeTemplateKey(templateKey);
return key switch return key switch
{ {
OrderTemplateKeys.DedicatedSmallParcel => DedicatedSmallParcelFields, OrderTemplateKeys.DedicatedSmallParcelSimple => SimpleFields,
_ => DedicatedSmallParcelFields OrderTemplateKeys.DedicatedSmallParcelStandard => StandardFields,
_ => StandardFields,
}; };
} }
@ -48,11 +72,22 @@ public static class OrderTemplateFieldRegistry
string.Equals(x.FieldKey, fieldKey.Trim(), StringComparison.OrdinalIgnoreCase)); string.Equals(x.FieldKey, fieldKey.Trim(), StringComparison.OrdinalIgnoreCase));
} }
private static IReadOnlyList<OrderTemplateFieldDefinition> BuildDedicatedSmallParcelFields() private static string NormalizeTemplateKey(string? templateKey) =>
string.IsNullOrWhiteSpace(templateKey) ? OrderTemplateKeys.DedicatedSmallParcelStandard : templateKey.Trim();
private static IReadOnlyList<OrderTemplateFieldDefinition> BuildDedicatedSmallParcelSimpleFields()
{ {
var list = new List<OrderTemplateFieldDefinition>(); var list = new List<OrderTemplateFieldDefinition>();
var sort = 0; var sort = 0;
void Add(string key, string label, bool required, string dataType = "string", string? dictType = null, string group = "basic", string example = "") void Add(
string key,
string label,
bool required,
string dataType = "string",
string? dictType = null,
string group = "basic",
string example = "",
string hint = "")
{ {
sort += 10; sort += 10;
list.Add(new OrderTemplateFieldDefinition list.Add(new OrderTemplateFieldDefinition
@ -64,87 +99,133 @@ public static class OrderTemplateFieldRegistry
DictType = dictType, DictType = dictType,
Group = group, Group = group,
Example = example, Example = example,
Sort = sort Hint = hint,
Sort = sort,
}); });
} }
Add("requestId", "幂等请求ID", false); Add("clientNo", "客户单号", true, example: "C202604230001", hint: "您自己的订单编号,同一客户不可重复");
Add("clientNo", "客户单号", true, example: "C202604230001"); Add("productCode", "运输方式", true, "select", "sales_product", "basic", "PROD001", hint: "从下拉选择产品编码(与查价一致)");
Add("productCode", "运输方式", true, "select", "sales_product", "basic", "PROD001"); Add("cargoAttribute", "货物属性", true, "select", "cargo_attribute", "basic", "general", hint: "普货选 general,带电/敏感品选对应用编码");
Add("cargoAttribute", "货物属性", true, "select", "cargo_attribute", "basic", "general"); Add("warehouseId", "发货仓库", true, "select", "warehouse", "basic", "1", hint: "从下拉选择发货仓库 ID");
Add("exportCustomsMode", "出口报关方式", false, "select", "export_customs_mode", "basic", "9610Simple"); Add("actualWeight", "实重(kg)", true, "number", example: "1.2", hint: "包裹实际重量,单位千克");
Add("warehouseId", "仓库", true, "select", "warehouse", "basic", "1"); Add("quantity", "包裹件数", true, "number", example: "1", hint: "一般为 1");
Add("length", "长(cm)", false, "number", example: "10"); Add("length", "长(cm)", false, "number", example: "10", hint: "选填,用于材积计费");
Add("width", "宽(cm)", false, "number", example: "10"); Add("width", "宽(cm)", false, "number", example: "10", hint: "选填,用于材积计费");
Add("height", "高(cm)", false, "number", example: "10"); Add("height", "高(cm)", false, "number", example: "10", hint: "选填,用于材积计费");
Add("actualWeight", "实重(kg)", true, "number", example: "1.2"); Add("exportCustomsMode", "出口报关", false, "select", "export_customs_mode", "basic", "9610Simple", hint: "不选则按简易申报");
Add("isCod", "是否COD", false, "boolean", example: "false");
Add("codCurrency", "COD币种", false, "select", "currency", example: "USD");
Add("codSum", "COD金额", false, "number", example: "0");
Add("quantity", "包裹数量", true, "number", example: "1");
Add("receiver.recipientsName", "收件人名称", true, group: "receiver", example: "Demo Receiver"); Add("receiver.recipientsName", "收件人姓名", true, group: "receiver", example: "John Smith", hint: "必填");
Add("receiver.companyName", "收件人公司", false, group: "receiver", example: "Demo Receiver LLC"); Add("receiver.phone", "收件人手机", true, group: "receiver", example: "13900139000", hint: "必填");
Add("receiver.phone", "收件人手机", true, group: "receiver", example: "13900139000"); Add("receiver.countryCode", "目的国代码", true, group: "receiver", example: "US", hint: "二字码,如 US、GB");
Add("receiver.telePhone", "收件人电话", false, group: "receiver"); Add("receiver.province", "省/州", false, group: "receiver", example: "California", hint: "建议填写");
Add("receiver.email", "收件人邮箱", false, group: "receiver", example: "receiver@example.com"); Add("receiver.city", "城市", false, group: "receiver", example: "Los Angeles", hint: "建议填写");
Add("receiver.countryCode", "收件国家代码", true, group: "receiver", example: "US"); Add("receiver.streetAddress1", "详细地址", true, group: "receiver", example: "100 Main St", hint: "街道门牌等主地址");
Add("receiver.province", "收件省/州", false, group: "receiver", example: "California"); Add("receiver.zipCode", "邮编", true, group: "receiver", example: "90001", hint: "必填");
Add("receiver.city", "收件城市", false, group: "receiver", example: "Los Angeles"); Add("receiver.companyName", "收件公司", false, group: "receiver", example: "Demo LLC", hint: "选填");
Add("receiver.streetNumber", "收件人门牌号", false, group: "receiver", example: "1001");
Add("receiver.streetAddress1", "收件地址1", true, group: "receiver", example: "Sunset Blvd");
Add("receiver.streetAddress2", "收件地址2", false, group: "receiver");
Add("receiver.streetAddress3", "收件地址3", false, group: "receiver");
Add("receiver.zipCode", "收件邮编", true, group: "receiver", example: "90001");
Add("receiver.taxType", "税号类型", false, group: "receiver", example: "0");
Add("receiver.taxNo", "税号", false, group: "receiver");
Add("receiver.certificateType", "证件类型", false, group: "receiver", example: "0");
Add("receiver.certificateNo", "证件号码", false, group: "receiver");
Add("receiver.eori", "EORI", false, group: "receiver");
Add("receiver.vat", "VAT", false, group: "receiver");
Add("sender.senderName", "发件人姓名", false, group: "sender", example: "Demo Sender"); Add("declaredCurrency", "申报币种", true, "select", "currency", "declaration", "USD", hint: "默认 USD");
Add("sender.company", "发件人公司", false, group: "sender", example: "Demo Sender Co.,Ltd"); Add("items[0].cnName", "申报品名(中文)", false, group: "declaration", example: "手机壳", hint: "选填");
Add("sender.phone", "发件人手机", false, group: "sender", example: "13800138000"); Add("items[0].enName", "申报品名(英文)", true, group: "declaration", example: "Phone Case", hint: "必填,用于清关");
Add("sender.telePhone", "发件人电话", false, group: "sender"); Add("items[0].unitPrice", "申报单价", true, "number", group: "declaration", example: "12.5", hint: "与申报币种一致");
Add("sender.email", "发件人邮箱", false, group: "sender", example: "sender@example.com"); Add("items[0].unitWeight", "单品重量(kg)", true, "number", group: "declaration", example: "0.3", hint: "单个 SKU 重量");
Add("sender.countryCode", "发件国家代码", false, group: "sender", example: "CN"); Add("items[0].quantity", "申报数量", true, "number", group: "declaration", example: "1", hint: "必填");
Add("sender.province", "发件省/州", false, group: "sender", example: "Guangdong");
Add("sender.city", "发件城市", false, group: "sender", example: "Shenzhen");
Add("sender.zipCode", "发件邮编", false, group: "sender", example: "518000");
Add("sender.streetAddress1", "发件地址1", false, group: "sender", example: "Nanshan District");
Add("sender.streetAddress2", "发件地址2", false, group: "sender");
Add("sender.streetAddress3", "发件地址3", false, group: "sender");
Add("sender.houseNumber", "发件门牌号", false, group: "sender", example: "88");
Add("declaredCurrency", "申报币种", true, "select", "currency", "declaration", "USD"); return list;
Add("declaredSum", "申报总金额", false, "number", group: "declaration", example: "可选,不填自动汇总"); }
Add("items[0].cnName", "申报品名(中)", false, group: "declaration", example: "手机壳");
Add("items[0].enName", "申报品名(英)", true, group: "declaration", example: "Phone Case"); private static IReadOnlyList<OrderTemplateFieldDefinition> BuildDedicatedSmallParcelStandardFields()
Add("items[0].sku", "SKU", false, group: "declaration", example: "SKU001"); {
Add("items[0].unit", "单位", false, group: "declaration", example: "pcs"); var list = new List<OrderTemplateFieldDefinition>();
Add("items[0].unitPrice", "单价(元)", true, "number", group: "declaration", example: "12.5"); var sort = 0;
Add("items[0].unitWeight", "单重(kg)", true, "number", group: "declaration", example: "0.3"); void Add(
Add("items[0].quantity", "申报数量", true, "number", group: "declaration", example: "1"); string key,
Add("items[0].amount", "总金额(元)", false, "number", group: "declaration", example: "自动计算=单价×数量"); string label,
Add("items[0].weight", "总重量(kg)", false, "number", group: "declaration", example: "自动计算=单重×数量"); bool required,
Add("items[0].imHsCode", "进口HsCode", false, group: "declaration"); string dataType = "string",
Add("items[0].exHsCode", "出口HsCode", false, group: "declaration"); string? dictType = null,
Add("items[0].skuImage", "商品图片URL", false, group: "declaration"); string group = "basic",
Add("items[0].category", "类别", false, group: "declaration", example: "Accessories"); string example = "",
Add("items[0].brand", "品牌", false, group: "declaration", example: "DemoBrand"); string hint = "")
Add("items[0].brandType", "品牌类型", false, group: "declaration"); {
Add("items[0].isMagnetic", "是否带磁带电", false, "boolean", group: "declaration", example: "false"); sort += 10;
Add("items[0].distribution", "配货信息", false, group: "declaration"); list.Add(new OrderTemplateFieldDefinition
Add("items[0].material", "材质", false, group: "declaration", example: "plastic"); {
Add("items[0].enMaterial", "英文材质", false, group: "declaration", example: "plastic"); FieldKey = key,
Add("items[0].purpose", "用途", false, group: "declaration", example: "protection"); Label = label,
Add("items[0].attributes", "属性", false, group: "declaration"); Required = required,
Add("items[0].originCountry", "原产国", false, group: "declaration", example: "CN"); DataType = dataType,
Add("items[0].salesLink", "销售链接", false, group: "declaration"); DictType = dictType,
Add("items[0].model", "型号", false, group: "declaration", example: "Model-X"); Group = group,
Add("items[0].specifications", "规格", false, group: "declaration", example: "Standard"); Example = example,
Add("items[0].comment", "备注", false, group: "declaration", example: "示例订单数据"); Hint = hint,
Sort = sort,
});
}
Add("requestId", "幂等请求ID", false, example: "可选", hint: "防重复提交,一般留空");
Add("clientNo", "客户单号", true, example: "C202604230001", hint: "您自己的订单编号,同一客户不可重复");
Add("productCode", "运输方式", true, "select", "sales_product", "basic", "PROD001", hint: "从下拉选择产品编码");
Add("cargoAttribute", "货物属性", true, "select", "cargo_attribute", "basic", "general", hint: "普货选 general");
Add("exportCustomsMode", "出口报关方式", false, "select", "export_customs_mode", "basic", "9610Simple", hint: "不选则简易申报");
Add("warehouseId", "发货仓库", true, "select", "warehouse", "basic", "1", hint: "从下拉选择仓库 ID");
Add("length", "长(cm)", false, "number", example: "10", hint: "材积计费用");
Add("width", "宽(cm)", false, "number", example: "10", hint: "材积计费用");
Add("height", "高(cm)", false, "number", example: "10", hint: "材积计费用");
Add("actualWeight", "实重(kg)", true, "number", example: "1.2", hint: "必填");
Add("isCod", "是否COD", false, "boolean", example: "false", hint: "货到付款填 true");
Add("codCurrency", "COD币种", false, "select", "currency", example: "USD", hint: "COD 订单填写");
Add("codSum", "COD金额", false, "number", example: "0", hint: "COD 订单填写");
Add("quantity", "包裹件数", true, "number", example: "1", hint: "一般为 1");
Add("receiver.recipientsName", "收件人姓名", true, group: "receiver", example: "Demo Receiver", hint: "必填");
Add("receiver.companyName", "收件公司", false, group: "receiver", example: "Demo LLC", hint: "选填");
Add("receiver.phone", "收件人手机", true, group: "receiver", example: "13900139000", hint: "必填");
Add("receiver.telePhone", "收件人电话", false, group: "receiver", hint: "选填");
Add("receiver.email", "收件人邮箱", false, group: "receiver", example: "receiver@example.com", hint: "选填");
Add("receiver.countryCode", "目的国代码", true, group: "receiver", example: "US", hint: "二字码");
Add("receiver.province", "省/州", false, group: "receiver", example: "California", hint: "建议填写");
Add("receiver.city", "城市", false, group: "receiver", example: "Los Angeles", hint: "建议填写");
Add("receiver.streetNumber", "门牌号", false, group: "receiver", example: "1001", hint: "选填");
Add("receiver.streetAddress1", "地址1", true, group: "receiver", example: "Sunset Blvd", hint: "主地址,必填");
Add("receiver.streetAddress2", "地址2", false, group: "receiver", hint: "选填");
Add("receiver.streetAddress3", "地址3", false, group: "receiver", hint: "选填");
Add("receiver.zipCode", "邮编", true, group: "receiver", example: "90001", hint: "必填");
Add("receiver.taxType", "税号类型", false, group: "receiver", example: "0", hint: "欧盟等场景选填");
Add("receiver.taxNo", "税号", false, group: "receiver", hint: "选填");
Add("receiver.certificateType", "证件类型", false, group: "receiver", example: "0", hint: "选填");
Add("receiver.certificateNo", "证件号码", false, group: "receiver", hint: "选填");
Add("receiver.eori", "EORI", false, group: "receiver", hint: "选填");
Add("receiver.vat", "VAT", false, group: "receiver", hint: "选填");
Add("declaredCurrency", "申报币种", true, "select", "currency", "declaration", "USD", hint: "默认 USD");
Add("declaredSum", "申报总金额", false, "number", group: "declaration", example: "可选", hint: "不填则按明细汇总");
Add("items[0].cnName", "申报品名(中)", false, group: "declaration", example: "手机壳", hint: "选填");
Add("items[0].enName", "申报品名(英)", true, group: "declaration", example: "Phone Case", hint: "必填");
Add("items[0].sku", "SKU", false, group: "declaration", example: "SKU001", hint: "选填");
Add("items[0].unit", "单位", false, group: "declaration", example: "pcs", hint: "选填");
Add("items[0].unitPrice", "单价", true, "number", group: "declaration", example: "12.5", hint: "与申报币种一致");
Add("items[0].unitWeight", "单重(kg)", true, "number", group: "declaration", example: "0.3", hint: "必填");
Add("items[0].quantity", "申报数量", true, "number", group: "declaration", example: "1", hint: "必填");
Add("items[0].amount", "总金额", false, "number", group: "declaration", example: "自动计算", hint: "不填=单价×数量");
Add("items[0].weight", "总重量(kg)", false, "number", group: "declaration", example: "自动计算", hint: "不填=单重×数量");
Add("items[0].imHsCode", "进口HsCode", false, group: "declaration", hint: "选填");
Add("items[0].exHsCode", "出口HsCode", false, group: "declaration", hint: "选填");
Add("items[0].skuImage", "商品图片URL", false, group: "declaration", hint: "选填");
Add("items[0].category", "类别", false, group: "declaration", example: "Accessories", hint: "选填");
Add("items[0].brand", "品牌", false, group: "declaration", example: "DemoBrand", hint: "选填");
Add("items[0].brandType", "品牌类型", false, group: "declaration", hint: "选填");
Add("items[0].isMagnetic", "是否带磁带电", false, "boolean", group: "declaration", example: "false", hint: "选填");
Add("items[0].distribution", "配货信息", false, group: "declaration", hint: "选填");
Add("items[0].material", "材质", false, group: "declaration", example: "plastic", hint: "选填");
Add("items[0].enMaterial", "英文材质", false, group: "declaration", example: "plastic", hint: "选填");
Add("items[0].purpose", "用途", false, group: "declaration", example: "protection", hint: "选填");
Add("items[0].attributes", "属性", false, group: "declaration", hint: "选填");
Add("items[0].originCountry", "原产国", false, group: "declaration", example: "CN", hint: "选填");
Add("items[0].salesLink", "销售链接", false, group: "declaration", hint: "选填");
Add("items[0].model", "型号", false, group: "declaration", example: "Model-X", hint: "选填");
Add("items[0].specifications", "规格", false, group: "declaration", example: "Standard", hint: "选填");
Add("items[0].comment", "备注", false, group: "declaration", example: "示例", hint: "选填");
return list; return list;
} }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_bin_location")] [SugarTable(WarehouseTableNames.BinLocation)]
public class CBinLocation : OmsAuditedAggregateRoot public class CBinLocation : OmsAuditedAggregateRoot
{ {
public int WarehouseId { get; set; } public int WarehouseId { get; set; }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_inbound_box")] [SugarTable(WarehouseTableNames.InboundBox)]
public class CInboundBox : OmsAuditedAggregateRoot public class CInboundBox : OmsAuditedAggregateRoot
{ {
public int InboundNoticeId { get; set; } public int InboundNoticeId { get; set; }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_inbound_box_line")] [SugarTable(WarehouseTableNames.InboundBoxLine)]
public class CInboundBoxLine : OmsAuditedAggregateRoot public class CInboundBoxLine : OmsAuditedAggregateRoot
{ {
public int InboundBoxId { get; set; } public int InboundBoxId { get; set; }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_inbound_notice")] [SugarTable(WarehouseTableNames.InboundNotice)]
public class CInboundNotice : OmsAuditedAggregateRoot public class CInboundNotice : OmsAuditedAggregateRoot
{ {
public int CustomerId { get; set; } public int CustomerId { get; set; }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_inventory")] [SugarTable(WarehouseTableNames.Inventory)]
public class CInventory : OmsAuditedAggregateRoot public class CInventory : OmsAuditedAggregateRoot
{ {
public int CustomerId { get; set; } public int CustomerId { get; set; }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_parcel")] [SugarTable(WarehouseTableNames.Parcel)]
public class CParcel : OmsAuditedAggregateRoot public class CParcel : OmsAuditedAggregateRoot
{ {
public int CustomerId { get; set; } public int CustomerId { get; set; }

View File

@ -6,7 +6,7 @@ namespace Oms.Domain.WarehouseAggregate.Entities;
/// <summary> /// <summary>
/// 快递到仓扫描记录。 /// 快递到仓扫描记录。
/// </summary> /// </summary>
[SugarTable("C_parcel_arrival")] [SugarTable(WarehouseTableNames.ParcelArrival)]
public class CParcelArrival : OmsAuditedAggregateRoot public class CParcelArrival : OmsAuditedAggregateRoot
{ {
public int CustomerId { get; set; } public int CustomerId { get; set; }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_parcel_item")] [SugarTable(WarehouseTableNames.ParcelItem)]
public class CParcelItem : OmsAuditedAggregateRoot public class CParcelItem : OmsAuditedAggregateRoot
{ {
public int ParcelId { get; set; } public int ParcelId { get; set; }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_sku")] [SugarTable(WarehouseTableNames.Sku)]
public class CSku : OmsAuditedAggregateRoot public class CSku : OmsAuditedAggregateRoot
{ {
public int CustomerId { get; set; } public int CustomerId { get; set; }

View File

@ -3,7 +3,7 @@ using SqlSugar;
namespace Oms.Domain.WarehouseAggregate.Entities; namespace Oms.Domain.WarehouseAggregate.Entities;
[SugarTable("C_sku_platform_map")] [SugarTable(WarehouseTableNames.SkuPlatformMap)]
public class CSkuPlatformMap : OmsAuditedAggregateRoot public class CSkuPlatformMap : OmsAuditedAggregateRoot
{ {
public int CustomerId { get; set; } public int CustomerId { get; set; }

View File

@ -0,0 +1,18 @@
namespace Oms.Domain.WarehouseAggregate;
/// <summary>
/// 仓储模块表名(<c>c_</c> 前缀 + 小写 snake_case,与 <c>t_order</c> 等命名风格对齐)。
/// </summary>
public static class WarehouseTableNames
{
public const string Sku = "c_sku";
public const string BinLocation = "c_bin_location";
public const string Inventory = "c_inventory";
public const string InboundNotice = "c_inbound_notice";
public const string InboundBox = "c_inbound_box";
public const string InboundBoxLine = "c_inbound_box_line";
public const string Parcel = "c_parcel";
public const string ParcelArrival = "c_parcel_arrival";
public const string ParcelItem = "c_parcel_item";
public const string SkuPlatformMap = "c_sku_platform_map";
}