diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java index 35bcdee74..38ebc22c4 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/palletops/service/PdaPalletOperationService.java @@ -267,7 +267,8 @@ public class PdaPalletOperationService { InventoryPosition position, String idempotencyKey, long before, long delta, long after, String sourcePallet, String targetPallet, String reason) { InventoryLedger ledger = new InventoryLedger(); ledger.setId(IdWorker.getId()); - ledger.setCustomerId(balance.getCustomerId()); ledger.setBalanceId(balance.getId()); + ledger.setCustomerId(balance.getCustomerId()); ledger.setCustomerName(balance.getCustomerName()); + ledger.setBalanceId(balance.getId()); ledger.setPositionId(position.getId()); ledger.setOverseasWarehouseId(balance.getOverseasWarehouseId()); ledger.setDestinationWarehouseCode(balance.getDestinationWarehouseCode()); ledger.setInboundOrderId(balance.getInboundOrderId()); ledger.setPalletId(position.getPalletId()); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveService.java index ab7b71d58..2b5c7cdbf 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveService.java @@ -151,6 +151,7 @@ public class PdaInventoryMoveService { private void ledger(PdaInventoryMove operation, InventoryPosition position, InventoryBalance balance, String operator, String key, String reason, LocalDateTime now) { InventoryLedger row = new InventoryLedger(); row.setId(IdWorker.getId()); row.setCustomerId(position.getCustomerId()); + row.setCustomerName(balance.getCustomerName()); row.setBalanceId(balance.getId()); row.setPositionId(position.getId()); row.setOverseasWarehouseId(String.valueOf(operation.getOverseasWarehouseId())); row.setDestinationWarehouseCode(balance.getDestinationWarehouseCode()); row.setInboundOrderId(balance.getInboundOrderId()); diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeService.java index 101e03b74..5fd08def6 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeService.java @@ -281,7 +281,8 @@ public class PdaStocktakeService { InventoryBalance balance, String operator, String key, String reason, long before, long delta, long after, LocalDateTime now) { InventoryLedger row = new InventoryLedger(); - row.setId(IdWorker.getId()); row.setCustomerId(item.getCustomerId()); row.setBalanceId(balance.getId()); + row.setId(IdWorker.getId()); row.setCustomerId(item.getCustomerId()); row.setCustomerName(balance.getCustomerName()); + row.setBalanceId(balance.getId()); row.setPositionId(position.getId()); row.setOverseasWarehouseId(String.valueOf(task.getOverseasWarehouseId())); row.setDestinationWarehouseCode(balance.getDestinationWarehouseCode()); row.setInboundOrderId(balance.getInboundOrderId()); row.setPalletId(position.getPalletId()); row.setPalletNo(position.getPalletNo()); row.setBatchNo(position.getBatchNo()); 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 ed82fcf29..59d6428f8 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 @@ -101,6 +101,7 @@ class PdaPalletOperationServiceTest { @Test void inventoryLedgerStoresStateSemanticsInsteadOfLongPalletCodes() { InventoryBalance balance = new InventoryBalance(); balance.setId(200L); balance.setCustomerId("100"); + balance.setCustomerName("客户甲"); balance.setOverseasWarehouseId("88"); balance.setInboundOrderId(300L); balance.setInboundCode("IN-1"); InventoryPosition position = new InventoryPosition(); position.setId(100L); position.setPalletId(20L); position.setPalletNo("PDAOPS-ADMIN-API-20260825095854036-SWAP-TARGET"); @@ -118,6 +119,7 @@ class PdaPalletOperationServiceTest { assertEquals("OCCUPIED", ledger.getValue().getStatusFrom()); assertEquals("OCCUPIED", ledger.getValue().getStatusTo()); assertEquals("PDAOPS-LONG-LOCATION-CODE", ledger.getValue().getWarehouseLocationCode()); + assertEquals("客户甲", ledger.getValue().getCustomerName()); } @Test void exactReplayReturnsStoredResultBeforeValidatingChangedPalletVersions() throws Exception { diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveServiceTest.java index 7207e9c2f..8f78f38c3 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/pda/move/service/PdaInventoryMoveServiceTest.java @@ -71,6 +71,7 @@ class PdaInventoryMoveServiceTest { position.setPickedPackages(0L); position.setReceivingPackages(0L); position.setVersion(4L); when(moves.lockPositions("900", 30L)).thenReturn(List.of(position)); InventoryBalance balance = new InventoryBalance(); balance.setId(10L); balance.setCustomerId("100"); + balance.setCustomerName("客户甲"); balance.setOverseasWarehouseId("900"); balance.setInboundOrderId(50L); balance.setInboundCode("IN-1"); when(balances.selectById(10L)).thenReturn(balance); when(physical.apply(eq(900L), any(PalletPhysicalGateway.PalletReference.class), eq("OCCUPIED"), @@ -94,6 +95,7 @@ class PdaInventoryMoveServiceTest { assertThat(ledger.getValue().getStatusFrom()).isEqualTo("OCCUPIED"); assertThat(ledger.getValue().getStatusTo()).isEqualTo("OCCUPIED"); assertThat(ledger.getValue().getWarehouseLocationCode()).isEqualTo(targetLocationCode); + assertThat(ledger.getValue().getCustomerName()).isEqualTo("客户甲"); verify(moves).updateById(any(PdaInventoryMove.class)); } diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeServiceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeServiceTest.java index 255e8180c..e2caf9ea9 100644 --- a/lastleg-service/src/test/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeServiceTest.java +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/pda/stocktake/service/PdaStocktakeServiceTest.java @@ -123,6 +123,7 @@ class PdaStocktakeServiceTest { position.setAllocatedPackages(0L); position.setPickedPackages(0L); position.setReceivingPackages(0L); position.setVersion(3L); InventoryBalance balance = new InventoryBalance(); balance.setId(10L); balance.setCustomerId("100"); + balance.setCustomerName("客户甲"); balance.setOverseasWarehouseId("900"); balance.setAvailablePackages(5L); balance.setVersion(8L); when(inventory.lockPosition(11L, "100", "900")).thenReturn(position); when(inventory.lockBalance(10L, "100", "900")).thenReturn(balance); @@ -141,6 +142,7 @@ class PdaStocktakeServiceTest { assertThat(ledger.getValue().getDeltaPackages()).isEqualTo(2L); assertThat(ledger.getValue().getAfterPackages()).isEqualTo(7L); assertThat(ledger.getValue().getBusinessType()).isEqualTo("STOCKTAKE_ADJUST"); + assertThat(ledger.getValue().getCustomerName()).isEqualTo("客户甲"); assertThat(response.task().status()).isEqualTo("SUBMITTED"); } } diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda-contract.test.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda-contract.test.mjs index 4cd77d951..69ff2bf6d 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-contract.test.mjs @@ -4,7 +4,7 @@ import path from 'node:path' import test from 'node:test' import { fileURLToPath } from 'node:url' -import { run } from './phase4-overseas-pda.mjs' +import { expandOverseasCartonCandidates, run } from './phase4-overseas-pda.mjs' const here = path.dirname(fileURLToPath(import.meta.url)) const source = fs.readFileSync(path.join(here, 'phase4-overseas-pda.mjs'), 'utf8') @@ -61,7 +61,8 @@ test('PDA module covers the container-number receiving contract and excludes out assert.match(source, /const cartonCandidates =/) assert.doesNotMatch(source, /current\/skus\/resolve/) assert.doesNotMatch(source, /skuCode:/) - assert.match(source, /FBA和OVERSEAS箱码resolve都必须HTTP 200\/code 200并返回当前柜唯一入仓码/) + assert.match(source, /FBA箱码与OVERSEAS真实manifest箱码必须HTTP 200\/code 200并返回当前柜唯一入仓码/) + assert.match(source, /排除未实装箱码候选/) assert.match(source, /const matches = inbounds\.filter/) assert.match(source, /!Array\.isArray\(resolved\?\.inboundCode\)/) assert.match(source, /matches\.length === 1/) @@ -102,6 +103,19 @@ test('PDA module covers the container-number receiving contract and excludes out assert.doesNotMatch(source, /awaitProducerCandidates/) }) +test('OVERSEAS carton discovery probes the frozen sequence and lets the server exclude skipped cartons', () => { + assert.deepEqual(expandOverseasCartonCandidates([ + { inboundCode: 'NYEXX2609020042', destinationType: 'OVERSEAS' }, + { inboundCode: 'FBA2609020001', destinationType: 'FBA' }, + ], ['NYEXX2609020042#8-5', 'NYEXX2609020042#8-2', 'NYEXX2609020042#8-1']), [ + 'NYEXX2609020042#8-1', 'NYEXX2609020042#8-2', 'NYEXX2609020042#8-3', 'NYEXX2609020042#8-4', + 'NYEXX2609020042#8-5', 'NYEXX2609020042#8-6', 'NYEXX2609020042#8-7', 'NYEXX2609020042#8-8', + ]) + assert.deepEqual(expandOverseasCartonCandidates([ + { inboundCode: 'NYEXX2609020042', destinationType: 'OVERSEAS' }, + ], ['NYEXX2609020042#2001-1']), []) +}) + test('real-device legacy FBA carton rejection is retained as regression evidence', () => { const evidence = { http: 200, diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda.mjs index 37d93876a..4d71110c8 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda.mjs @@ -34,6 +34,19 @@ function receivingSnapshot(data) { }) } +export function expandOverseasCartonCandidates(inbounds, producerCartonMarks = []) { + const marks = (Array.isArray(producerCartonMarks) ? producerCartonMarks : [producerCartonMarks]) + .map(id).filter(nonBlank) + return inbounds.filter((item) => item?.destinationType === 'OVERSEAS').flatMap((inbound) => { + const patterns = marks.map((cartonMark) => ({ cartonMark, match: /^(.*)#(\d+)-(\d+)$/.exec(cartonMark) })) + .filter(({ match }) => match && match[1] === inbound.inboundCode) + const totals = [...new Set(patterns.map(({ match }) => Number(match[2])) + .filter((total) => Number.isSafeInteger(total) && total > 0 && total <= 2000))] + if (totals.length !== 1) return [] + return Array.from({ length: totals[0] }, (_, index) => `${inbound.inboundCode}#${totals[0]}-${index + 1}`) + }) +} + function blockerGap(context, name, reason, category = 'fixture-unavailable', response = null) { context.addCoverageGap(MODULE, SCENARIO, name, reason, category, { deploymentBlocker: true }) context.assert(MODULE, `${name}覆盖前置`, response, false, reason, OPTIONS) @@ -364,7 +377,7 @@ async function resolveCartonFixtures(context, token, inbounds, producerCartonMar .map(id).filter(nonBlank) const fbaCartons = inbounds.filter((item) => item.destinationType === 'FBA') .map((item, index) => `${item.inboundCode}#PDA-CONTRACT-${index + 1}`) - const overseasCartons = inbounds.some((item) => item.destinationType === 'OVERSEAS') ? manifestCartons : [] + const overseasCartons = expandOverseasCartonCandidates(inbounds, manifestCartons) const cartonCandidates = [...new Set([...fbaCartons, ...overseasCartons])] if (!cartonCandidates.length) { blockerGap(context, '统一箱码解析链', 'FBA缺少当前柜入仓码或OVERSEAS缺少真实manifest箱码') @@ -374,6 +387,12 @@ async function resolveCartonFixtures(context, token, inbounds, producerCartonMar for (const [index, cartonMark] of cartonCandidates.entries()) { const response = await context.requestOverseas('POST', '/api/pda/v1/last-leg/receiving-containers/current/cartons/resolve', { cartonMark }, {}, token) + if (overseasCartons.includes(cartonMark) + && a0404Message(response, /^Carton mark was not found$/)) { + context.addStep(MODULE, `排除未实装箱码候选${index + 1}`, response, true, + `cartonMark=${cartonMark}; ${trace(response)}`, OPTIONS) + continue + } const resolved = response.body?.data const matches = inbounds.filter((item) => item.inboundCode === resolved?.inboundCode) const destinations = new Set(matches.map((item) => item?.destinationType).filter(nonBlank)) @@ -385,7 +404,7 @@ async function resolveCartonFixtures(context, token, inbounds, producerCartonMar unique, `cartonMark=${cartonMark}, inboundCode=${resolved?.inboundCode || 'missing'}, matches=${matches.length}; ${trace(response)}`, OPTIONS) if (!unique) { blockerGap(context, `箱码${cartonMark}唯一归属`, - `FBA和OVERSEAS箱码resolve都必须HTTP 200/code 200并返回当前柜唯一入仓码;inboundCode=${resolved?.inboundCode || 'missing'}, matches=${matches.length}`, + `FBA箱码与OVERSEAS真实manifest箱码必须HTTP 200/code 200并返回当前柜唯一入仓码;inboundCode=${resolved?.inboundCode || 'missing'}, matches=${matches.length}`, 'fixture-mismatch', response) return null }