diff --git a/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundSplitSettlementMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundSplitSettlementMapper.java index 5004229d3..ae099411f 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundSplitSettlementMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundSplitSettlementMapper.java @@ -149,6 +149,20 @@ public interface InboundSplitSettlementMapper { @Param("childMeasurementItemId") long childMeasurementItemId, @Param("lineNo") int lineNo, @Param("packageCount") int packageCount); + @Update(""" + UPDATE fl_inbound_carton_label + SET inbound_id=#{childInboundId}, order_id=#{childOrderId}, inbound_code=#{childInboundCode} + WHERE tenant_id=#{accountId} AND inbound_id=#{sourceInboundId} AND batch_status='ACTIVE' + ORDER BY carton_index DESC + LIMIT #{remainingPackageCount} + """) + int transferRemainingCartonLabels(@Param("accountId") long accountId, + @Param("sourceInboundId") long sourceInboundId, + @Param("childInboundId") long childInboundId, + @Param("childOrderId") long childOrderId, + @Param("childInboundCode") String childInboundCode, + @Param("remainingPackageCount") int remainingPackageCount); + @Update(""" UPDATE fl_inbound_order SET package_count = GREATEST(COALESCE(package_count, total_package_count) - #{packageCount}, 0), diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/PdaPickSettlementService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/PdaPickSettlementService.java index 7207d64e0..68815baa3 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/PdaPickSettlementService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/PdaPickSettlementService.java @@ -185,6 +185,8 @@ public class PdaPickSettlementService { requireAffected(splits.cloneChildWithPurpose(parent.tenantId(), parentInboundId, rootInboundId, remainderChildId, orderSplit.remainderChild().orderId(), orderSplit.remainderChild().orderNo(), remainderChildCode, childPackageCount, sequence, "REMAINDER", operatorId, operatorName), "余量子入库单"); + splits.transferRemainingCartonLabels(parent.tenantId(), parentInboundId, remainderChildId, + orderSplit.remainderChild().orderId(), remainderChildCode, childPackageCount); } requireAffected(splits.insertBatchWithChildren(batchId, parent.tenantId(), rootInboundId, operationId, parentInboundId, idempotencyKey, rootVersion, parentAvailable, null, remainderChildId == 0L ? null : remainderChildId, diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java index 306cbe216..7d601312b 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java @@ -66,6 +66,8 @@ class PdaPickSettlementServiceTest { verify(splits).cloneChildWithPurpose(eq(10001L), eq(31L), eq(31L), anyLong(), eq(1002L), eq("ORDER-REMAINDER"), eq("IN-001-1"), eq(12), eq(1), eq("REMAINDER"), eq(9L), eq("operator")); + verify(splits).transferRemainingCartonLabels(eq(10001L), eq(31L), anyLong(), + eq(1002L), eq("IN-001-1"), eq(12)); verify(splits).markSourceLoaded(10001L, 31L, 8, 9L, 3); verify(allocations).consume(10001L, 31L, 1L, 8); verify(warehouse).settle(eq("internal-token"), org.mockito.ArgumentMatchers.argThat(request -> diff --git a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs index 44a0d7fed..46089b6f2 100644 --- a/scripts/admin-test-env-modules/manual-state-loading-pda.mjs +++ b/scripts/admin-test-env-modules/manual-state-loading-pda.mjs @@ -425,6 +425,21 @@ export async function run(context, matrix = context.createdResources.manualState const completed = await complete(context, await pickAll(context, await claim(context, await createPlanTask(context, 'DONE'), 'DONE'), 'DONE'), 'DONE') + const loadingOrderId = id(inProgress.source?.detail?.summary?.orderId) + const loadingTenantId = id(inProgress.source?.tenantId) + const loadingOrderResponse = await context.request('GET', + `/api/platform/v1/orders/${encodeURIComponent(loadingOrderId)}?tenantId=${encodeURIComponent(loadingTenantId)}`) + const loadingOrder = context.apiSuccess(MODULE, '读取人工 PROGRESS 对应 LOADING 统一订单', loadingOrderResponse, '', OPTIONS) + context.assert(MODULE, '人工 PROGRESS 稳定提供统一订单 LOADING 样本', loadingOrderResponse, + id(loadingOrder?.summary?.orderId) === loadingOrderId && loadingOrder?.summary?.mainStatus === 'LOADING', + `orderId=${loadingOrderId || 'missing'}, status=${loadingOrder?.summary?.mainStatus || 'missing'}`, OPTIONS) + if (loadingOrderId && loadingOrder?.summary?.mainStatus === 'LOADING') { + matrix.pages.unifiedOrders.fixtures.LOADING = { + id: loadingOrderId, status: 'LOADING', inboundId: inProgress.source.inboundId, + detailPath: `/orders/${loadingOrderId}`, + } + } + const chains = { EDITING: editing, LOADING: inProgress, COMPLETED: completed } for (const [status, chain] of Object.entries(chains)) { matrix.pages.containerLoading.fixtures[status] = fixture(chain.containerId, status, diff --git a/scripts/admin-test-env-modules/manual-state-matrix-contract.test.mjs b/scripts/admin-test-env-modules/manual-state-matrix-contract.test.mjs index 37da55c15..096592364 100644 --- a/scripts/admin-test-env-modules/manual-state-matrix-contract.test.mjs +++ b/scripts/admin-test-env-modules/manual-state-matrix-contract.test.mjs @@ -6,6 +6,7 @@ import { normalizeModuleOrder, parseArguments } from '../admin-test-env-acceptan const entry = readFileSync(new URL('../admin-test-env-acceptance.mjs', import.meta.url), 'utf8') const source = readFileSync(new URL('./manual-state-matrix.mjs', import.meta.url), 'utf8') +const loadingSource = readFileSync(new URL('./manual-state-loading-pda.mjs', import.meta.url), 'utf8') test('default full acceptance includes the retained manual state matrix after lifecycle modules', () => { assert.match(entry, /'manual-state-matrix'/) @@ -19,8 +20,8 @@ test('focused matrix execution injects real container lifecycle prerequisites an 'phase3-container-loading', 'phase3-loading', 'ocean-bookings', - 'phase3-customs', 'phase3-loading-finalize', + 'phase3-customs', 'manual-state-matrix', ] assert.deepEqual(normalizeModuleOrder(['order-inbound-status-fixtures', 'manual-state-matrix']), expected) @@ -36,8 +37,8 @@ test('focused splitting and manual groups resolve producers before consumers wit 'phase3-loading', 'phase3-order-splitting', 'ocean-bookings', - 'phase3-customs', 'phase3-loading-finalize', + 'phase3-customs', 'manual-state-matrix', ] assert.deepEqual(normalizeModuleOrder([ @@ -60,7 +61,7 @@ test('matrix delegates bounded groups and asserts every declared status has a re test('fixture-unavailable statuses stay supported without becoming hard requirements', () => { const matrix = { pages: { - inbounds: { requiredStatuses: ['WAIT_MEASURE', 'WAIT_PRICING', 'FEE_CONFIRMING', 'INBOUNDING', 'VOIDED'], fixtures: { + inbounds: { requiredStatuses: ['WAIT_MEASURE', 'MEASURE_REJECTED', 'WAIT_PRICING', 'FEE_CONFIRMING', 'INBOUNDING', 'VOIDED'], fixtures: { WAIT_MEASURE: { id: '1' }, } }, unifiedOrders: { requiredStatuses: ['WAIT_SPLIT', 'LOADING', 'LOADED', 'DELIVERED'], fixtures: { @@ -71,7 +72,7 @@ test('fixture-unavailable statuses stay supported without becoming hard requirem normalizeStateRequirements(matrix) assert.deepEqual(matrix.pages.inbounds.supportedStatuses, - ['WAIT_MEASURE', 'WAIT_PRICING', 'FEE_CONFIRMING', 'INBOUNDING', 'VOIDED']) + ['WAIT_MEASURE', 'MEASURE_REJECTED', 'WAIT_PRICING', 'FEE_CONFIRMING', 'INBOUNDING', 'VOIDED']) assert.deepEqual(matrix.pages.inbounds.requiredStatuses, ['WAIT_MEASURE']) assert.deepEqual(matrix.pages.unifiedOrders.requiredStatuses, ['WAIT_SPLIT', 'LOADING', 'LOADED']) assert.equal(matrix.pages.inbounds.coverageGaps.WAIT_PRICING.category, 'fixture-unavailable') @@ -93,7 +94,8 @@ test('unknown missing status is never swallowed by the known coverage-gap allowl }) test('LOADING and LOADED fixtures are resolved through inbound and unified-order details', () => { - assert.match(source, /attachUnifiedOrderFixture\(context, matrix, 'phase3ContainerLoading', 'LOADING'\)/) + assert.match(loadingSource, /matrix\.pages\.unifiedOrders\.fixtures\.LOADING/) + assert.match(loadingSource, /loadingOrder\?\.summary\?\.mainStatus === 'LOADING'/) assert.match(source, /attachUnifiedOrderFixture\(context, matrix, 'phase3Loading', 'LOADED'\)/) assert.match(source, /\/api\/platform\/inbounds\/\$\{encodeURIComponent\(inboundId\)\}/) assert.match(source, /id\(inbound\?\.summary\?\.id\) === inboundId/) diff --git a/scripts/admin-test-env-modules/manual-state-matrix.mjs b/scripts/admin-test-env-modules/manual-state-matrix.mjs index 2bf5d0ff5..63045f85a 100644 --- a/scripts/admin-test-env-modules/manual-state-matrix.mjs +++ b/scripts/admin-test-env-modules/manual-state-matrix.mjs @@ -9,6 +9,7 @@ const GROUPS = [ const FIXTURE_UNAVAILABLE = { inbounds: { + MEASURE_REJECTED: '当前稳定 Admin 契约未开放测量审核驳回接口,无法稳定构造该状态', WAIT_PRICING: '自动计费会继续推进,无法稳定保留 WAIT_PRICING', FEE_CONFIRMING: '当前无可稳定保留的费用确认前公开生成链路', INBOUNDING: '自动入库在同一调用内继续推进 WAIT_SPLIT,无可暂停 API', @@ -112,7 +113,6 @@ export async function run(context) { await extension.run(context, matrix) } - await attachUnifiedOrderFixture(context, matrix, 'phase3ContainerLoading', 'LOADING') await attachUnifiedOrderFixture(context, matrix, 'phase3Loading', 'LOADED') normalizeStateRequirements(matrix) registerFixtureUnavailableGaps(context, matrix)