fix: align phase4 receiving and carrier fixtures

This commit is contained in:
2026-08-24 23:12:04 +08:00
parent 1e8d85c714
commit bf4fd95841
6 changed files with 37 additions and 11 deletions
@@ -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.
}
}
}
}
@@ -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");
@@ -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'/)
})
@@ -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',
@@ -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)
@@ -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', () => {