diff --git a/deploy/sql/海外区域统一增量-MySQL8.sql b/deploy/sql/海外区域统一增量-MySQL8.sql index 8f92cd0ce..75cbd045f 100644 --- a/deploy/sql/海外区域统一增量-MySQL8.sql +++ b/deploy/sql/海外区域统一增量-MySQL8.sql @@ -1365,6 +1365,19 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='total_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总重量(千克)'', ADD COLUMN total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总体积(立方米)'', ADD COLUMN planned_delivery_at DATETIME(3) NULL COMMENT ''预计派送时间'', ADD COLUMN dispatched_at DATETIME(3) NULL COMMENT ''实际派送时间'', ADD COLUMN expected_arrival_at DATETIME(3) NULL COMMENT ''预计到达时间'', ADD COLUMN signed_at DATETIME(3) NULL COMMENT ''签收时间'', ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''派送备注'' AFTER pallet_count')); PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='version'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN version BIGINT NOT NULL DEFAULT 0 COMMENT ''派送单乐观锁版本'' AFTER created_by')); +PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; + +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='handling_result'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN handling_result VARCHAR(2000) NULL COMMENT ''异常处理结果'' AFTER handling_action')); +PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='evidence_file_ids_json'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN evidence_file_ids_json JSON NULL COMMENT ''异常处理证据文件ID列表'' AFTER handling_result')); +PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='inventory_adjustment_id'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN inventory_adjustment_id BIGINT NULL COMMENT ''退回库存恢复操作ID'' AFTER evidence_file_ids_json')); +PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='completed_at'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN completed_at DATETIME(3) NULL COMMENT ''异常处理完成时间'' AFTER exception_at')); +PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='version'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN version BIGINT NOT NULL DEFAULT 0 COMMENT ''异常订单乐观锁版本'' AFTER completed_at')); +PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_dropship_order_profile' AND column_name='source_pallet_no'), 'SELECT 1', 'ALTER TABLE ll_dropship_order_profile ADD COLUMN source_pallet_no VARCHAR(128) NULL COMMENT ''来源托盘号快照'' AFTER pallet_no')); PREPARE phase5_structure_ddl FROM @ddl; EXECUTE phase5_structure_ddl; DEALLOCATE PREPARE phase5_structure_ddl; @@ -1424,7 +1437,7 @@ BEGIN 'll_outbound_order','ll_outbound_pallet_item','ll_outbound_pda_task','ll_outbound_pda_pick', 'll_delivery_order','ll_delivery_fee','ll_delivery_operation', 'll_dropship_order_profile','ll_dropship_carton','ll_dropship_sku_inventory','ll_dropship_carton_ledger', - 'll_exception_order','ll_customer_visit')) <> 22 THEN + 'll_exception_order','ll_exception_return_item','ll_exception_order_command','ll_customer_visit')) <> 24 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期排车、出库、派送、一件代发、异常或回访表未完整部署'; END IF; IF EXISTS (SELECT dispatch_no FROM ll_dispatch_order GROUP BY dispatch_no HAVING COUNT(*)>1) THEN @@ -1540,7 +1553,7 @@ BEGIN 'll_outbound_order','ll_outbound_pallet_item','ll_outbound_pda_task','ll_outbound_pda_pick', 'll_delivery_order','ll_delivery_fee','ll_delivery_operation', 'll_dropship_order_profile','ll_dropship_carton','ll_dropship_sku_inventory','ll_dropship_carton_ledger', - 'll_exception_order','ll_customer_visit')) <> 22 THEN + 'll_exception_order','ll_exception_return_item','ll_exception_order_command','ll_customer_visit')) <> 24 THEN SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT='五期完整业务表集合未部署'; END IF; IF (SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='baiyue_lastleg' diff --git a/firstleg/src/main/java/com/baiyue/firstleg/terminaldrayage/service/ContainerWorkbenchService.java b/firstleg/src/main/java/com/baiyue/firstleg/terminaldrayage/service/ContainerWorkbenchService.java index 018f20be1..8c2fc4b0d 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/terminaldrayage/service/ContainerWorkbenchService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/terminaldrayage/service/ContainerWorkbenchService.java @@ -38,7 +38,6 @@ public class ContainerWorkbenchService { public WorkbenchItem domesticDetail(long id, Set permissions) { ContainerBookingEntity value = required(id); - if (repository.departed(id)) throw new BizException(ResultCode.NOT_FOUND, "国内工作台货柜不存在"); ContainerActualFacts facts = actualFacts(List.of(id)).get(id); return item("DOMESTIC", value, facts, null, permissions); } diff --git a/firstleg/src/main/resources/openapi/firstleg-v1.yaml b/firstleg/src/main/resources/openapi/firstleg-v1.yaml index 332742003..9f8e072c5 100644 --- a/firstleg/src/main/resources/openapi/firstleg-v1.yaml +++ b/firstleg/src/main/resources/openapi/firstleg-v1.yaml @@ -445,7 +445,7 @@ paths: /api/platform/v1/containers/domestic/{id}: get: summary: 查询国内货柜工作台详情 - description: 需要 platform:firstleg:container:list;装柜清单只返回唯一loadingPlanId/planNo/planStatus关联 + description: 需要 platform:firstleg:container:list;国内ACTIVE与HISTORY共用该只读详情,已真实离仓柜仍可打开;装柜清单只返回唯一loadingPlanId/planNo/planStatus关联 parameters: [{$ref: '#/components/parameters/ContainerBookingId'}] responses: {'200': {description: 成功, content: {application/json: {schema: {$ref: '#/components/schemas/ContainerWorkbenchEnvelope'}}}}} /api/platform/v1/containers/overseas/{id}: diff --git a/firstleg/src/test/java/com/baiyue/firstleg/controller/ContainerControllerContractTest.java b/firstleg/src/test/java/com/baiyue/firstleg/controller/ContainerControllerContractTest.java index e89e1e424..c272ef9e4 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/controller/ContainerControllerContractTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/controller/ContainerControllerContractTest.java @@ -66,6 +66,7 @@ class ContainerControllerContractTest { .contains("/api/platform/v1/containers/{id}/ocean-booking-link:") .contains("/api/platform/v1/containers/domestic:") .contains("/api/platform/v1/containers/overseas:") + .contains("国内ACTIVE与HISTORY共用该只读详情,已真实离仓柜仍可打开") .contains("scope, in: query, required: true") .contains("首次关联允许 WAIT_LOADING 或已有预排货的 EDITING 模拟柜") .contains("RELEASED oceanBookingId") diff --git a/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/service/ContainerWorkbenchServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/service/ContainerWorkbenchServiceTest.java index 89bdb28b8..0216d1caf 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/service/ContainerWorkbenchServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/terminaldrayage/service/ContainerWorkbenchServiceTest.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -83,6 +84,35 @@ class ContainerWorkbenchServiceTest { verifyNoInteractions(carrierSchedules); } + @Test + void domesticHistoryDetailKeepsTheDepartedContainerAndItsUniqueLoadingPlan() { + ContainerBookingEntity container = container(); + container.setOutboundConfirmedAt(LocalDateTime.of(2026, 9, 8, 10, 0)); + when(repository.find(11L)).thenReturn(container); + when(repository.departed(11L)).thenReturn(true); + when(carrierSchedules.containerActualFacts(List.of(11L))).thenReturn(List.of(facts("DEPARTED_WAREHOUSE"))); + + var result = service.domesticDetail(11L, Set.of("platform:firstleg:container:list")); + + assertThat(result.workbenchStage()).isEqualTo("DEPARTED_WAREHOUSE"); + assertThat(result.container().getPlanId()).isEqualTo(31L); + assertThat(result.container().getPlanNo()).isEqualTo("LP-260908-0001"); + assertThat(result.allowedActions()).isEmpty(); + verify(repository, never()).departed(11L); + verifyNoInteractions(terminalDrayages); + } + + @Test + void domesticDetailStillRejectsContainersOutsideTheActiveMasterSet() { + when(repository.find(404L)).thenReturn(null); + + assertThatThrownBy(() -> service.domesticDetail(404L, Set.of("platform:firstleg:container:list"))) + .isInstanceOf(com.baiyue.common.core.exception.BizException.class) + .hasMessage("货柜不存在"); + + verifyNoInteractions(carrierSchedules, terminalDrayages); + } + private static Query query(WorkbenchScope scope) { return new Query(null, null, null, null, null, null, null, null, null, null, null, null, null, null, scope, 1, 20); diff --git a/lastleg-service/src/main/resources/db/lastleg.sql b/lastleg-service/src/main/resources/db/lastleg.sql index 1e3c85c21..66af33402 100644 --- a/lastleg-service/src/main/resources/db/lastleg.sql +++ b/lastleg-service/src/main/resources/db/lastleg.sql @@ -300,6 +300,8 @@ SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE tab PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='total_weight_kg'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN total_weight_kg DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总重量(千克)'', ADD COLUMN total_volume_cbm DECIMAL(18,4) NOT NULL DEFAULT 0 COMMENT ''总体积(立方米)'', ADD COLUMN planned_delivery_at DATETIME(3) NULL COMMENT ''预计派送时间'', ADD COLUMN dispatched_at DATETIME(3) NULL COMMENT ''实际派送时间'', ADD COLUMN expected_arrival_at DATETIME(3) NULL COMMENT ''预计到达时间'', ADD COLUMN signed_at DATETIME(3) NULL COMMENT ''签收时间'', ADD COLUMN remark VARCHAR(1000) NULL COMMENT ''派送备注'' AFTER pallet_count')); PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_delivery_order' AND column_name='version'), 'SELECT 1', 'ALTER TABLE ll_delivery_order ADD COLUMN version BIGINT NOT NULL DEFAULT 0 COMMENT ''派送单乐观锁版本'' AFTER created_by')); +PREPARE delivery_detail_stmt FROM @ddl; EXECUTE delivery_detail_stmt; DEALLOCATE PREPARE delivery_detail_stmt; CREATE TABLE IF NOT EXISTS ll_delivery_fee ( id BIGINT NOT NULL COMMENT '派送费用主键', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', delivery_order_id BIGINT NOT NULL COMMENT '派送单ID', fee_direction VARCHAR(16) NOT NULL DEFAULT 'PAYABLE' COMMENT '应收应付方向', fee_type VARCHAR(64) NOT NULL COMMENT '费用类型', currency VARCHAR(8) NOT NULL COMMENT '币种', @@ -1963,6 +1965,17 @@ CREATE TABLE IF NOT EXISTS ll_exception_order_command ( KEY idx_ll_exception_command_order(customer_id,exception_order_id,created_at) ) ENGINE=InnoDB COMMENT='异常处理命令幂等审计'; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='handling_result'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN handling_result VARCHAR(2000) NULL COMMENT ''异常处理结果'' AFTER handling_action')); +PREPARE exception_lifecycle_stmt FROM @ddl; EXECUTE exception_lifecycle_stmt; DEALLOCATE PREPARE exception_lifecycle_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='evidence_file_ids_json'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN evidence_file_ids_json JSON NULL COMMENT ''异常处理证据文件ID列表'' AFTER handling_result')); +PREPARE exception_lifecycle_stmt FROM @ddl; EXECUTE exception_lifecycle_stmt; DEALLOCATE PREPARE exception_lifecycle_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='inventory_adjustment_id'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN inventory_adjustment_id BIGINT NULL COMMENT ''退回库存恢复操作ID'' AFTER evidence_file_ids_json')); +PREPARE exception_lifecycle_stmt FROM @ddl; EXECUTE exception_lifecycle_stmt; DEALLOCATE PREPARE exception_lifecycle_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='completed_at'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN completed_at DATETIME(3) NULL COMMENT ''异常处理完成时间'' AFTER exception_at')); +PREPARE exception_lifecycle_stmt FROM @ddl; EXECUTE exception_lifecycle_stmt; DEALLOCATE PREPARE exception_lifecycle_stmt; +SET @ddl = (SELECT IF(EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema=DATABASE() AND table_name='ll_exception_order' AND column_name='version'), 'SELECT 1', 'ALTER TABLE ll_exception_order ADD COLUMN version BIGINT NOT NULL DEFAULT 0 COMMENT ''异常订单乐观锁版本'' AFTER completed_at')); +PREPARE exception_lifecycle_stmt FROM @ddl; EXECUTE exception_lifecycle_stmt; DEALLOCATE PREPARE exception_lifecycle_stmt; + CREATE TABLE IF NOT EXISTS ll_customer_visit ( id BIGINT NOT NULL COMMENT '客户回访记录ID', customer_id VARCHAR(128) NOT NULL COMMENT '客户数据隔离键', diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5OverseasUpgradeSqlContractTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5OverseasUpgradeSqlContractTest.java index 92418b512..28dfd865b 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5OverseasUpgradeSqlContractTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/schema/Phase5OverseasUpgradeSqlContractTest.java @@ -17,7 +17,7 @@ class Phase5OverseasUpgradeSqlContractTest { "ll_outbound_pda_task", "ll_outbound_pda_pick", "ll_delivery_order", "ll_delivery_fee", "ll_delivery_operation", "ll_dropship_order_profile", "ll_dropship_carton", "ll_dropship_sku_inventory", "ll_dropship_carton_ledger", "ll_exception_order", - "ll_customer_visit"); + "ll_exception_return_item", "ll_exception_order_command", "ll_customer_visit"); @Test void overseasFixedEntryCreatesEveryPhase5TableBeforeAnyPhase5AlterOrAssertion() throws Exception { @@ -32,7 +32,7 @@ class Phase5OverseasUpgradeSqlContractTest { assertThat(overseas.indexOf(table)).as(table + " first reference") .isEqualTo(overseas.indexOf(create) + "CREATE TABLE IF NOT EXISTS ".length()); } - assertThat(overseas).contains("<> 22", "五期完整业务表集合未部署"); + assertThat(overseas).contains("<> 24", "五期完整业务表集合未部署"); } @Test