From 9b333df720e74f0ed468506040fa54d2560a7562 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Mon, 12 Oct 2026 18:11:23 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=B4=E6=A0=87=E7=94=B3=E8=AF=B7=EF=BC=8C?= =?UTF-8?q?=E4=BB=93=E5=82=A8=E8=BD=AC=E8=BF=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/sql/海外区域统一增量-MySQL8.sql | 5 ++++- lastleg-service/src/main/resources/db/lastleg.sql | 6 +++++- .../service/PdaPalletOperationServiceTest.java | 4 ++++ .../service/ReturnCartonCountServiceTest.java | 7 ++++--- scripts/README.md | 2 ++ .../return-consolidated-incremental-20261012.sql | 14 ++++++++++++-- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/deploy/sql/海外区域统一增量-MySQL8.sql b/deploy/sql/海外区域统一增量-MySQL8.sql index 6cae76174..d8a93c473 100644 --- a/deploy/sql/海外区域统一增量-MySQL8.sql +++ b/deploy/sql/海外区域统一增量-MySQL8.sql @@ -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 ( diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index dc7ab58c5..f8afdc134 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -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')) diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationServiceTest.java index 2262626d0..c9fb4a745 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationServiceTest.java @@ -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) diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/returnreceipt/service/ReturnCartonCountServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/returnreceipt/service/ReturnCartonCountServiceTest.java index bbbb95cf8..44572c72a 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/returnreceipt/service/ReturnCartonCountServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/returnreceipt/service/ReturnCartonCountServiceTest.java @@ -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 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()); } diff --git a/scripts/README.md b/scripts/README.md index 43bf3d506..6d4cf93c7 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -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`;内部模块安全合同见模块索引。 diff --git a/scripts/db/return-consolidated-incremental-20261012.sql b/scripts/db/return-consolidated-incremental-20261012.sql index 0469fef9d..7fde07b3d 100644 --- a/scripts/db/return-consolidated-incremental-20261012.sql +++ b/scripts/db/return-consolidated-incremental-20261012.sql @@ -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'; \ No newline at end of file +SELECT constraint_name,check_clause FROM information_schema.check_constraints WHERE constraint_schema=DATABASE() AND constraint_name='chk_ll_return_outbound_movement_delta';