贴标申请,仓储转运

This commit is contained in:
2026-10-12 18:11:23 +08:00
parent 46507e41c6
commit 9b333df720
6 changed files with 31 additions and 7 deletions
@@ -2414,6 +2414,7 @@ CREATE TABLE ll_return_inventory_balance (
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='退货库存按客户海外仓SKU汇总余额';
CREATE TABLE ll_return_inventory_carton (
original_packaging TINYINT(1) NULL COMMENT '是否使用原箱原包装',
label_status varchar(32) DEFAULT NULL COMMENT 'WAIT_LABEL待贴标 CONFIRMED已扫码确认标签',
new_carton_mark varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '换标后新箱唛',
id bigint NOT NULL COMMENT '退货库存箱ID',
@@ -2565,7 +2566,9 @@ CREATE TABLE ll_return_receipt (
length DECIMAL(12,3) NULL COMMENT '实测尺寸cm',
width DECIMAL(12,3) NULL COMMENT '实测尺寸cm',
height DECIMAL(12,3) NULL COMMENT '实测尺寸cm',
weight DECIMAL(12,3) NULL COMMENT '实测重量kg'
weight DECIMAL(12,3) NULL COMMENT '实测重量kg',
original_packaging TINYINT(1) NULL COMMENT '是否使用原箱原包装',
measurement_source VARCHAR(32) NULL COMMENT '尺寸重量来源'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='PDA退货实收签收事实';
CREATE TABLE ll_return_receipt_batch (
@@ -2978,6 +2978,7 @@ CREATE TABLE IF NOT EXISTS ll_return_order_item (
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_item_order_sku(return_order_id,sku_code), KEY idx_ll_return_item_sku(sku_code,received_quantity), CONSTRAINT chk_ll_return_item_quantity CHECK(expected_quantity>0 AND received_quantity>=0 AND received_quantity<=expected_quantity)
) ENGINE=InnoDB COMMENT='客户退货订单的SKU预报与实收明细';
CREATE TABLE IF NOT EXISTS ll_return_receipt (
original_packaging TINYINT(1) NULL COMMENT '是否使用原箱原包装', measurement_source VARCHAR(32) NULL COMMENT '尺寸重量来源',
id BIGINT NOT NULL COMMENT 'PDA退货签收单ID', return_order_id BIGINT NOT NULL COMMENT '退货订单ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', receipt_no VARCHAR(128) NOT NULL COMMENT '签收单号', receipt_batch_no VARCHAR(128) NOT NULL COMMENT '签收批次号', tracking_no VARCHAR(128) NULL COMMENT '本次签收包裹的可选快递单号', bound_by VARCHAR(16) NOT NULL COMMENT '开始签收方式,RETURN_NO退货单号或SKU候选', status VARCHAR(32) NOT NULL COMMENT '签收状态,当前SIGNED', received_quantity BIGINT NOT NULL COMMENT '本次实收数量', operator_id VARCHAR(128) NOT NULL COMMENT 'PDA操作人ID', operator_name VARCHAR(128) NULL COMMENT 'PDA操作人名称', idempotency_key VARCHAR(255) NOT NULL COMMENT 'PDA确认幂等键', request_hash CHAR(64) NOT NULL COMMENT 'PDA确认请求SHA-256', signed_at DATETIME(3) NOT NULL COMMENT '签收时间', created_at DATETIME(3) NOT NULL COMMENT '创建时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_receipt_no(receipt_no), UNIQUE KEY uk_ll_return_receipt_batch(receipt_batch_no), UNIQUE KEY uk_ll_return_receipt_operator_key(operator_id,idempotency_key), KEY idx_ll_return_receipt_order(return_order_id,signed_at), CONSTRAINT chk_ll_return_receipt_quantity CHECK(received_quantity>0), CONSTRAINT chk_ll_return_receipt_bound CHECK(bound_by IN('RETURN_NO','SKU'))
) ENGINE=InnoDB COMMENT='PDA退货实收签收事实';
@@ -3018,7 +3019,9 @@ EXECUTE ll_return_receipt_alter_stmt;
DEALLOCATE PREPARE ll_return_receipt_alter_stmt;
SET @ll_return_receipt_add_pallet = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='pallet_code'), 'SELECT 1', 'ALTER TABLE ll_return_receipt ADD COLUMN pallet_code VARCHAR(128) NULL COMMENT ''本次签收绑定的托盘码'' AFTER receipt_batch_no'));
PREPARE ll_return_receipt_add_pallet_stmt FROM @ll_return_receipt_add_pallet; EXECUTE ll_return_receipt_add_pallet_stmt; DEALLOCATE PREPARE ll_return_receipt_add_pallet_stmt;
SET @ll_return_receipt_add_measurement_facts = (SELECT IF(COUNT(*)=0, 'ALTER TABLE ll_return_receipt ADD COLUMN original_packaging TINYINT(1) NULL COMMENT ''是否使用原箱原包装'', ADD COLUMN measurement_source VARCHAR(32) NULL COMMENT ''尺寸重量来源'' AFTER weight', 'SELECT 1') FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='original_packaging');
SET @ll_return_receipt_add_measurement_facts = (SELECT IF(COUNT(*)=0, 'ALTER TABLE ll_return_receipt ADD COLUMN original_packaging TINYINT(1) NULL COMMENT ''是否使用原箱原包装'' AFTER weight', 'SELECT 1') FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='original_packaging');
PREPARE ll_return_receipt_add_measurement_facts_stmt FROM @ll_return_receipt_add_measurement_facts; EXECUTE ll_return_receipt_add_measurement_facts_stmt; DEALLOCATE PREPARE ll_return_receipt_add_measurement_facts_stmt;
SET @ll_return_receipt_add_measurement_facts = (SELECT IF(COUNT(*)=0, 'ALTER TABLE ll_return_receipt ADD COLUMN measurement_source VARCHAR(32) NULL COMMENT ''尺寸重量来源'' AFTER original_packaging', 'SELECT 1') FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='measurement_source');
PREPARE ll_return_receipt_add_measurement_facts_stmt FROM @ll_return_receipt_add_measurement_facts; EXECUTE ll_return_receipt_add_measurement_facts_stmt; DEALLOCATE PREPARE ll_return_receipt_add_measurement_facts_stmt;
CREATE TABLE IF NOT EXISTS ll_return_receipt_item (
id BIGINT NOT NULL COMMENT '签收SKU明细ID', receipt_id BIGINT NOT NULL COMMENT 'PDA退货签收单ID', return_order_item_id BIGINT NOT NULL COMMENT '退货预报明细ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', sku_code VARCHAR(128) NOT NULL COMMENT 'SKU编码', received_quantity BIGINT NOT NULL COMMENT '本次SKU实收数量',
@@ -3033,6 +3036,7 @@ CREATE TABLE IF NOT EXISTS ll_return_receipt_batch (
SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='carton_mark'), 'SELECT 1', 'ALTER TABLE ll_return_receipt ADD COLUMN carton_mark VARCHAR(128) NULL COMMENT ''签收生成或绑定的退货箱号'' AFTER receipt_batch_no'));
PREPARE return_receipt_carton_mark_stmt FROM @ddl; EXECUTE return_receipt_carton_mark_stmt; DEALLOCATE PREPARE return_receipt_carton_mark_stmt;
CREATE TABLE IF NOT EXISTS ll_return_inventory_carton (
original_packaging TINYINT(1) NULL COMMENT '是否使用原箱原包装',
new_carton_mark VARCHAR(255) NULL COMMENT '换标后新箱唛',
id BIGINT NOT NULL COMMENT '退货库存箱ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户ID和隔离键', overseas_warehouse_id VARCHAR(128) NOT NULL COMMENT '海外仓ID', source_receipt_id BIGINT NULL COMMENT '来源PDA签收单ID', source_batch_no VARCHAR(128) NULL COMMENT '来源签收批次号', carton_mark VARCHAR(128) NOT NULL COMMENT '退货箱号和箱唛编码', status VARCHAR(32) NOT NULL COMMENT 'AVAILABLE可用或MERGED已合箱', sku_types INT NOT NULL COMMENT '箱内SKU种类数', total_quantity BIGINT NOT NULL COMMENT '箱内总件数', length DECIMAL(12,3) NULL COMMENT '箱长CM', width DECIMAL(12,3) NULL COMMENT '箱宽CM', height DECIMAL(12,3) NULL COMMENT '箱高CM', weight DECIMAL(12,3) NULL COMMENT '箱重KG', created_at DATETIME(3) NOT NULL COMMENT '创建时间', updated_at DATETIME(3) NOT NULL COMMENT '最后更新时间', merged_at DATETIME(3) NULL COMMENT '合箱完成时间',
PRIMARY KEY(id), UNIQUE KEY uk_ll_return_inventory_carton_mark(carton_mark), UNIQUE KEY uk_ll_return_inventory_carton_receipt(source_receipt_id), KEY idx_ll_return_inventory_carton_customer(customer_id,overseas_warehouse_id,status,created_at), CONSTRAINT chk_ll_return_inventory_carton_quantity CHECK((status IN('AVAILABLE','RELABEL_PENDING','OUTBOUNDED','DISCARD_PENDING') AND sku_types>0 AND total_quantity>0) OR (status IN('MERGED','DISCARDED') AND sku_types=0 AND total_quantity=0)), CONSTRAINT chk_ll_return_inventory_carton_status CHECK(status IN('AVAILABLE','RELABEL_PENDING','MERGED','OUTBOUNDED','DISCARD_PENDING','DISCARDED'))
@@ -147,6 +147,10 @@ class PdaPalletOperationServiceTest {
assertThrows(ResponseStatusException.class, () -> ReflectionTestUtils.invokeMethod(service,
"bindReturnRelabelPallet", identity(), app, List.of(item), source, target, request));
task.setReviewedAt(null);
when(physical.resolveLocation(88L, 1020L)).thenReturn(
new PalletPhysicalGateway.LocationReference(1020L, "SOURCE-LOC", "STORAGE", "ACTIVE"));
when(physical.resolveLocation(88L, 1010L)).thenReturn(
new PalletPhysicalGateway.LocationReference(1010L, "TARGET-LOC", "STORAGE", "ACTIVE"));
when(physical.resolve("SOURCE")).thenReturn(source);
when(physical.resolve("TARGET")).thenReturn(target);
var result = (com.baiyue.lastleg.palletops.dto.PalletOperationResponses.Operation)
@@ -65,8 +65,9 @@ class ReturnCartonCountServiceTest {
when(cartons.lockReceipt(1L)).thenReturn(List.of(a,b));
when(receiptItems.listReceipt(1L)).thenReturn(expected);
}
private CountBox measuredBox(String mark, List<CountItem> lines) { return new CountBox(mark, lines, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE, java.math.BigDecimal.ONE); }
private Confirm unequal(long first, long second) {
return new Confirm("retry", List.of(new CountBox(a.getCartonMark(),List.of(new CountItem("SKU1",first))),new CountBox(b.getCartonMark(),List.of(new CountItem("SKU1",second)))));
return new Confirm("retry", List.of(measuredBox(a.getCartonMark(),List.of(new CountItem("SKU1",first))),measuredBox(b.getCartonMark(),List.of(new CountItem("SKU1",second)))));
}
@Test void unequalBoxesAreConfirmedWithoutChangingSkuUnitBalanceAndRetryIsStable() {
prepare(List.of(expected("SKU1",10)));
@@ -97,7 +98,7 @@ class ReturnCartonCountServiceTest {
when(items.lockCarton(3L)).thenReturn(List.of(provisional(3,"SKU2",4)));
when(balances.adjustCartonQuantity("C1","123","SKU1",1)).thenReturn(1);
when(balances.adjustCartonQuantity("C1","123","SKU2",1)).thenReturn(1);
var request = new Confirm("mixed",List.of(new CountBox(a.getCartonMark(),List.of(new CountItem("SKU1",2),new CountItem("SKU2",1))),new CountBox(b.getCartonMark(),List.of(new CountItem("SKU1",4),new CountItem("SKU2",3)))));
var request = new Confirm("mixed",List.of(measuredBox(a.getCartonMark(),List.of(new CountItem("SKU1",2),new CountItem("SKU2",1))),measuredBox(b.getCartonMark(),List.of(new CountItem("SKU1",4),new CountItem("SKU2",3)))));
service.confirm(identity,"RR-1",request);
verify(balances).adjustCartonQuantity("C1","123","SKU1",1); verify(balances).adjustCartonQuantity("C1","123","SKU2",1);
verifyNoMoreInteractions(balances); assertThat(a.getSkuTypes()).isEqualTo(2);
@@ -110,7 +111,7 @@ class ReturnCartonCountServiceTest {
}
@Test void duplicateOrForeignBoxesCannotBeSubmitted() {
prepare(List.of(expected("SKU1",10)));
var request = new Confirm("dup",List.of(new CountBox(a.getCartonMark(),List.of(new CountItem("SKU1",5))),new CountBox(a.getCartonMark(),List.of(new CountItem("SKU1",5)))));
var request = new Confirm("dup",List.of(measuredBox(a.getCartonMark(),List.of(new CountItem("SKU1",5))),measuredBox(a.getCartonMark(),List.of(new CountItem("SKU1",5)))));
assertThatThrownBy(() -> service.confirm(identity,"RR-1",request)).isInstanceOf(ResponseStatusException.class).hasMessageContaining("重复");
verify(items,never()).deleteCarton(anyLong());
}
+2
View File
@@ -1,5 +1,7 @@
# 后端脚本管理与全量 HTTP 验收索引
`db/return-consolidated-incremental-20261012.sql`:10月10日/12日退货、签收、清点、换标与按件出库增量汇总,显式 `USE baiyue_lastleg`,MySQL 8.0.16+ 已有业务库可重复执行。本轮补齐签收原包装、尺寸重量来源及库存箱原包装三个字段;逐列判断存在性,不回填签收/清点事实,不改库存数量。连接实例及凭据由执行者明确指定,整份执行、遇错停止;保留执行错误,修复后可重跑,末尾输出字段、索引与约束供核对。托盘重量、体积汇总是应用逻辑,不新增资源库字段;不自动执行到测试或生产环境。
`procurement-sample-test-env.mjs` 是采购样品正常全链路 TEST 专项入口,默认或 `--plan` 零网络。真实执行使用 `node --env-file="<授权目录>/test-acceptance.env" scripts/procurement-sample-test-env.mjs --execute --environment TEST --run-id <唯一短批次> --warehouse-id <已核对有效海外收货仓>`。只允许当前 TEST 工作区及指定配置;由正式 API 创建独立客户并完成资质,人工充值10元,上传凭证/面单,调用内部 `procurement-sample-fulfillment.mjs` 完成两箱正常履约。充值、文件和采购请求前落盘,结果未知停写;报告独占 `reports/procurement-sample-<批次>/report.json`,不覆盖旧报告,不自动重跑,不执行 SQL/部署/删除。结束退出自身平台与客户会话,保留样本。通过只表示该正常路径,不替代异常、退款、权限、真实页面和请求量验收。离线入口合同为 `procurement-sample-test-env.test.mjs`;内部模块安全合同见模块索引。
@@ -101,13 +101,23 @@ PREPARE return_index_stmt FROM @return_index_ddl; EXECUTE return_index_stmt; DEA
SET @return_index_ddl = IF(EXISTS(SELECT 1 FROM information_schema.statistics WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_outbound_movement' AND index_name='idx_ll_return_outbound_carton_sku'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_outbound_movement ADD INDEX idx_ll_return_outbound_carton_sku(customer_id,carton_id,sku_code)');
PREPARE return_index_stmt FROM @return_index_ddl; EXECUTE return_index_stmt; DEALLOCATE PREPARE return_index_stmt;
-- 五、原包装及尺寸重量来源。逐列检查,支持部分执行后重跑。
-- 托盘重量/体积由当前绑定箱的实测尺寸重量汇总,无需新增托盘字段。
SET @return_measurement_ddl = IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='original_packaging'), 'SELECT 1', 'ALTER TABLE ll_return_receipt ADD COLUMN original_packaging TINYINT(1) NULL COMMENT ''是否使用原箱原包装'' AFTER weight');
PREPARE return_measurement_stmt FROM @return_measurement_ddl; EXECUTE return_measurement_stmt; DEALLOCATE PREPARE return_measurement_stmt;
SET @return_measurement_ddl = IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_receipt' AND column_name='measurement_source'), 'SELECT 1', 'ALTER TABLE ll_return_receipt ADD COLUMN measurement_source VARCHAR(32) NULL COMMENT ''尺寸重量来源 PALLET_MEASURED托盘实测 UNIT_SUM子箱汇总'' AFTER original_packaging');
PREPARE return_measurement_stmt FROM @return_measurement_ddl; EXECUTE return_measurement_stmt; DEALLOCATE PREPARE return_measurement_stmt;
SET @return_measurement_ddl = IF(EXISTS(SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_return_inventory_carton' AND column_name='original_packaging'), 'SELECT 1', 'ALTER TABLE ll_return_inventory_carton ADD COLUMN original_packaging TINYINT(1) NULL COMMENT ''是否使用原箱原包装'' AFTER weight');
PREPARE return_measurement_stmt FROM @return_measurement_ddl; EXECUTE return_measurement_stmt; DEALLOCATE PREPARE return_measurement_stmt;
-- 校验输出:核对新增字段、索引和守恒约束。
SELECT table_name,column_name,column_type,is_nullable,column_default FROM information_schema.columns
WHERE table_schema=DATABASE() AND (
(table_name='ll_outbound_pallet_item' AND column_name='return_sku_quantity') OR
(table_name='ll_return_inventory_carton' AND column_name IN('label_status','count_status','counted_at','counted_by')) OR
(table_name='ll_return_inventory_carton' AND column_name IN('label_status','count_status','counted_at','counted_by','original_packaging')) OR
(table_name='ll_return_receipt' AND column_name IN('original_packaging','measurement_source')) OR
(table_name='ll_relabel_pda_unit_fact' AND column_name='quantity')) ORDER BY table_name,column_name;
SELECT table_name,index_name,non_unique,GROUP_CONCAT(column_name ORDER BY seq_in_index) AS indexed_columns
FROM information_schema.statistics WHERE table_schema=DATABASE() AND index_name IN
('idx_ll_return_outbound_carton_sku','uk_ll_return_outbound_pick_sku','idx_ll_relabel_application_order','uk_ll_return_count_session_receipt','idx_ll_return_count') GROUP BY table_name,index_name,non_unique;
SELECT constraint_name,check_clause FROM information_schema.check_constraints WHERE constraint_schema=DATABASE() AND constraint_name='chk_ll_return_outbound_movement_delta';
SELECT constraint_name,check_clause FROM information_schema.check_constraints WHERE constraint_schema=DATABASE() AND constraint_name='chk_ll_return_outbound_movement_delta';