diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java index a49339834..f76eaf789 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerService.java @@ -449,15 +449,6 @@ public class ReceivingContainerService { if (pallet.fbaWarehouseCode() != null && !pallet.fbaWarehouseCode().isBlank()) { throw conflict("FBA-designated pallet cannot receive an OVERSEAS destination carton"); } - if (ref.line().getDestinationWarehouseId() != null && !ref.line().getDestinationWarehouseId().isBlank()) { - try { - if (Long.parseLong(ref.line().getDestinationWarehouseId()) != pallet.warehouseId()) { - throw conflict("Pallet destination warehouse does not match the inbound code"); - } - } catch (NumberFormatException ignored) { - // Destination codes that are not resource IDs are still protected by the actual overseas warehouse check. - } - } } } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerServiceBehaviorTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerServiceBehaviorTest.java index 0241f0153..61b54e06d 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerServiceBehaviorTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/ReceivingContainerServiceBehaviorTest.java @@ -224,6 +224,22 @@ class ReceivingContainerServiceBehaviorTest { verify(workflow, never()).assignResolvedPallet(any(), any(), any(), any()); } + @Test + void overseasPalletUsesActualReceivingWarehouseInsteadOfBusinessDestinationId() { + stubCurrent("OVERSEAS", "CONT-A"); + InboundPlanLine overseasLine = line("OVERSEAS"); + overseasLine.setDestinationWarehouseId("900"); + when(lines.selectList(any())).thenReturn(List.of(overseasLine)); + when(warehouse.resolvePallet("PAL-1")).thenReturn(resource(100L, null)); + + var resolved = service.resolvePallet("7", + new ReceivingContainerRequests.PalletResolve("PAL-1", "IN-1")); + + assertThat(resolved.compatible()).isTrue(); + assertThat(resolved.warehouseId()).isEqualTo(100L); + assertThat(resolved.destinationType()).isEqualTo("OVERSEAS"); + } + @Test void containerCompletionRequiresEveryInboundCodeToBalanceExactly() { stubCurrent("FBA", "CONT-A"); diff --git a/scripts/admin-test-env-modules/phase3-http-fail-closed-contract.test.mjs b/scripts/admin-test-env-modules/phase3-http-fail-closed-contract.test.mjs index 9f2107dbf..ae644258e 100644 --- a/scripts/admin-test-env-modules/phase3-http-fail-closed-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase3-http-fail-closed-contract.test.mjs @@ -47,4 +47,6 @@ test('outbound lifecycle checks replay, hash conflict, stale version and final r '负向与幂等验证后回查出仓聚合', '出仓聚合保持 READY_FOR_DISPATCH、LOADED 和确认撤销历史', ]) assert.match(source, new RegExp(step)) + assert.match(source, /if \(!confirmed\?\.aggregate\) return/) + assert.match(source, /hashConflict\.http === 200 && codeOf\(hashConflict\) === 'A0409'/) }) diff --git a/scripts/admin-test-env-modules/phase3-loading.mjs b/scripts/admin-test-env-modules/phase3-loading.mjs index 4280e8609..a5e5357b5 100644 --- a/scripts/admin-test-env-modules/phase3-loading.mjs +++ b/scripts/admin-test-env-modules/phase3-loading.mjs @@ -138,12 +138,22 @@ export async function run(context) { context.assert(MODULE, '存在可用于发布订舱的 ACTIVE 订舱代理', supplierResponse, Boolean(supplier?.id && supplier?.name), `supplierId=${id(supplier?.id) || 'missing'}`, OPTIONS) if (!supplier?.id) return + const carrierResponse = await context.request('GET', + '/api/platform/v1/carrier-schedule-queries/carriers') + const carriers = context.apiSuccess(MODULE, '读取船节点canonical船司目录', carrierResponse, '', OPTIONS) + const shippingCarrier = Array.isArray(carriers) + ? carriers.find((value) => value?.carrierCode === 'MATSON' && value?.carrierName) + : null + context.assert(MODULE, '订舱船司与订舱代理使用各自真实事实源', carrierResponse, + Boolean(shippingCarrier?.carrierName), + `carrierCode=${shippingCarrier?.carrierCode || 'missing'}, bookingAgentId=${id(supplier.id)}`, OPTIONS) + if (!shippingCarrier?.carrierName) return const bookingDraft = { customerId: tenantId, customerName: initial?.summary?.customerName, warehouseId, channelCode: initial?.summary?.channelCode, billOfLadingNo: `P3BL-${suffix}`.slice(0, 128), - shippingCompanyId: id(supplier.id), shippingCompanyName: supplier.name, + shippingCompanyId: null, shippingCompanyName: shippingCarrier.carrierName, bookingAgentId: id(supplier.id), bookingAgentName: supplier.name, vesselName: `P3VESSEL-${suffix}`.slice(0, 128), voyageNo: `P3V-${suffix}`.slice(0, 64), departurePortCode: 'CNSZX', departurePortName: '深圳', destinationPortCode: 'USLAX', diff --git a/scripts/admin-test-env-modules/phase3-outbound-dispatch.mjs b/scripts/admin-test-env-modules/phase3-outbound-dispatch.mjs index 30d553185..94611f91e 100644 --- a/scripts/admin-test-env-modules/phase3-outbound-dispatch.mjs +++ b/scripts/admin-test-env-modules/phase3-outbound-dispatch.mjs @@ -107,6 +107,7 @@ export async function run(context) { && confirmed?.aggregate?.inboundStatus === 'LOADED' && Number(confirmed?.aggregate?.version) === expectedVersion + 1, `dispatch=${confirmed?.aggregate?.dispatchStatus || 'missing'}, inbound=${confirmed?.aggregate?.inboundStatus || 'missing'}, version=${confirmed?.aggregate?.version ?? 'missing'}`, OPTIONS) + if (!confirmed?.aggregate) return const replayResponse = await request(context, 'POST', `${BASE}/confirm-dispatch`, actionBody, { 'Idempotency-Key': confirmKey }) @@ -118,7 +119,8 @@ export async function run(context) { const hashConflict = await request(context, 'POST', `${BASE}/confirm-dispatch`, { ...actionBody, remark: 'forged' }, { 'Idempotency-Key': confirmKey }) - context.addStep(MODULE, '相同 Idempotency-Key 不同请求拒绝', hashConflict, rejected(hashConflict), + context.addStep(MODULE, '相同 Idempotency-Key 不同请求拒绝', hashConflict, + hashConflict.http === 200 && codeOf(hashConflict) === 'A0409', `HTTP=${hashConflict.http}, code=${codeOf(hashConflict)}`, OPTIONS) const reverseKey = `${context.marker}-outbound-reverse`.slice(0, 128) diff --git a/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs b/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs index 9d9038c6c..84aa0b25e 100644 --- a/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase3-producer-chain-contract.test.mjs @@ -395,6 +395,11 @@ test('preload and loading versions use immediate operation baselines and exact p assert.equal((loading.match(/request\(context, 'POST', pushRequest\.path, pushRequest\.body, pushRequest\.headers\)/g) || []).length, 2) assert.match(loading, /push exact replay 对计划版本历史关联和任务零漂移/) assert.match(loading, /Number\(claimedPlan\?\.version\) > beforeClaimPlanVersion/) + assert.match(loading, /carrier-schedule-queries\/carriers/) + assert.match(loading, /carrierCode === 'MATSON'/) + assert.match(loading, /shippingCompanyId: null, shippingCompanyName: shippingCarrier\.carrierName/) + assert.match(loading, /bookingAgentId: id\(supplier\.id\), bookingAgentName: supplier\.name/) + assert.doesNotMatch(loading, /shippingCompanyId: id\(supplier\.id\), shippingCompanyName: supplier\.name/) }) test('PDA completion acceptance verifies immutable BATCH and COMPLETE_LOADING audit with operator metadata', () => {