From cb8e5fb1450f0f0afa68ca13cd926404aa2916e2 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 29 Sep 2026 14:39:11 +0800 Subject: [PATCH] fix(overseas): preserve shared overflow locations during pallet moves --- .../phase4-overseas-pda-operations.mjs | 16 +++++++++----- .../WarehousePalletTransitionService.java | 14 ++++++------- .../WarehousePalletTransitionServiceTest.java | 21 +++++++++++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs index 3b2505e99..abcba0290 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs @@ -101,8 +101,11 @@ async function resolvePallet(context, token, palletCode, name = '扫码解析托 async function resolvePublicLocation(context, fixture, sourceLocationCode) { const response = await context.requestOverseas('GET', `/api/platform/v1/warehouse-admin/${encodeURIComponent(fixture.overseasWarehouseId)}/locations?page=1&size=200`) - const location = records(response?.body?.data).find((item) => ['STORAGE', 'NORMAL'].includes(item?.locationType) - && item?.status === 'IDLE' && item?.locationCode !== sourceLocationCode) + const locations = records(response?.body?.data) + const location = locations.find((item) => item?.locationType === 'SHARED_OVERFLOW' + && item?.locationCode === 'A0' && item?.status === 'ACTIVE' && item.locationCode !== sourceLocationCode) + || locations.find((item) => ['STORAGE', 'NORMAL'].includes(item?.locationType) + && item?.status === 'IDLE' && item?.locationCode !== sourceLocationCode) context.addStep(MODULE, '复用当前海外仓公共库位', response, success(response) && longId(id(location?.id)) && nonBlank(location?.locationCode), `locationId=${id(location?.id) || 'missing'}, locationCode=${location?.locationCode || 'missing'}; ${trace(response)}`, OPTIONS) @@ -546,10 +549,13 @@ async function verifyRelabel(context, token, merchantToken, fixture) { const reworked = await request(context, context.overseasToken, 'POST', 'P5平台复核退回REWORK', reviewPath, reworkBody) if (!success(reworked.response)) return false const reworkReplay = await request(context, context.overseasToken, 'POST', 'P5 REWORK同键重放', reviewPath, reworkBody) - context.assert(MODULE, 'P5 REWORK重放保持PROCESSING', reworkReplay.response, - reworkReplay.data?.taskStatus === 'PROCESSING' && Number(reworkReplay.data.relabelledPackages) === 0, + context.assert(MODULE, 'P5 REWORK重放保持AVAILABLE等待重新认领', reworkReplay.response, + reworkReplay.data?.taskStatus === 'AVAILABLE' && Number(reworkReplay.data.relabelledPackages) === 0, trace(reworkReplay.response), OPTIONS) - detail = reworked.data + const reclaimed = await request(context, token, 'POST', 'P5重新认领返工任务', + `${pdaBase}/${encodeURIComponent(relabelNo)}/claim`, { idempotencyKey: key(context, 'RELABEL-REWORK-CLAIM') }) + if (!success(reclaimed.response) || reclaimed.data?.taskStatus !== 'PROCESSING') return false + detail = reclaimed.data for (const redoItem of detail.items || []) { if (redoItem.operationType === 'CARTON_AND_SKU') { const carton = await request(context, token, 'POST', 'P5返工重新确认箱标阶段', diff --git a/warehouse-overseas-service/src/main/java/com/baiyue/warehouse/service/WarehousePalletTransitionService.java b/warehouse-overseas-service/src/main/java/com/baiyue/warehouse/service/WarehousePalletTransitionService.java index 5c65171cf..7659ae918 100644 --- a/warehouse-overseas-service/src/main/java/com/baiyue/warehouse/service/WarehousePalletTransitionService.java +++ b/warehouse-overseas-service/src/main/java/com/baiyue/warehouse/service/WarehousePalletTransitionService.java @@ -157,7 +157,7 @@ public class WarehousePalletTransitionService { } private void requireAvailableTarget(WarehouseLocationEntity location, String targetStatus) { - String expected = isVirtual(location) ? "ACTIVE" : "IDLE"; + String expected = isNonExclusive(location) ? "ACTIVE" : "IDLE"; if (!expected.equals(location.getStatus())) { throw new BizException(ResultCode.CONFLICT, "目标库位不可用"); } @@ -168,18 +168,18 @@ public class WarehousePalletTransitionService { private void transitionSameLocation(long warehouseId, WarehouseLocationEntity location, String beforeStatus, String afterStatus) { - if (beforeStatus.equals(afterStatus) || isVirtual(location)) return; + if (beforeStatus.equals(afterStatus) || isNonExclusive(location)) return; changeLocationStatus(warehouseId, location.getId(), expectedLocationStatus(location, beforeStatus), expectedLocationStatus(location, afterStatus)); } private void releaseSourceLocation(long warehouseId, WarehouseLocationEntity source, String palletStatus) { - if (isVirtual(source) || "IDLE".equals(palletStatus)) return; + if (isNonExclusive(source) || "IDLE".equals(palletStatus)) return; changeLocationStatus(warehouseId, source.getId(), "OCCUPIED", "IDLE"); } private void occupyTargetLocation(long warehouseId, WarehouseLocationEntity target, String palletStatus) { - if (isVirtual(target) || "IDLE".equals(palletStatus)) return; + if (isNonExclusive(target) || "IDLE".equals(palletStatus)) return; changeLocationStatus(warehouseId, target.getId(), "IDLE", "OCCUPIED"); } @@ -191,11 +191,11 @@ public class WarehousePalletTransitionService { } private static String expectedLocationStatus(WarehouseLocationEntity location, String palletStatus) { - return isVirtual(location) ? "ACTIVE" : palletStatus; + return isNonExclusive(location) ? "ACTIVE" : palletStatus; } - private static boolean isVirtual(WarehouseLocationEntity location) { - return "VIRTUAL".equals(location.getLocationType()); + private static boolean isNonExclusive(WarehouseLocationEntity location) { + return "VIRTUAL".equals(location.getLocationType()) || WarehouseResourceService.isSharedOverflow(location); } private PalletTransitionResponse replay(WarehousePalletOperationEntity operation, String hash) { diff --git a/warehouse-overseas-service/src/test/java/com/baiyue/warehouse/service/WarehousePalletTransitionServiceTest.java b/warehouse-overseas-service/src/test/java/com/baiyue/warehouse/service/WarehousePalletTransitionServiceTest.java index a63f0731e..4607692d2 100644 --- a/warehouse-overseas-service/src/test/java/com/baiyue/warehouse/service/WarehousePalletTransitionServiceTest.java +++ b/warehouse-overseas-service/src/test/java/com/baiyue/warehouse/service/WarehousePalletTransitionServiceTest.java @@ -64,6 +64,27 @@ class WarehousePalletTransitionServiceTest { verify(mapper).transitionLocation(88L, locationId, "OCCUPIED", "IDLE"); } + @Test void movingIntoAndOutOfSharedOverflowKeepsItActive() { + WarehouseLocationEntity overflow = location(20L, "SHARED_OVERFLOW", "ACTIVE"); + overflow.setLocationCode("A0"); overflow.setSharedOverflow(true); + WarehousePalletEntity pallet = pallet("P-1", "OCCUPIED", 4); + when(mapper.lockPublicPallet(88L, 10L)).thenReturn(pallet); + when(mapper.lockPublicLocation(88L, 11L)).thenReturn(location(11L, "STORAGE", "OCCUPIED")); + when(mapper.lockPublicLocation(88L, 20L)).thenReturn(overflow); + when(mapper.transitionLocation(88L, 11L, "OCCUPIED", "IDLE")).thenReturn(1); + when(mapper.transition(88L, 10L, "OCCUPIED", 20L, 4)).thenReturn(1); + service.apply(10L, new PalletTransitionRequests.Apply(88L, 4, "OCCUPIED", 20L, "into-overflow", "move")); + + pallet.setLocationId(20L); pallet.setVersion(5); + when(mapper.lockPublicLocation(88L, 11L)).thenReturn(location(11L, "STORAGE", "IDLE")); + when(mapper.transitionLocation(88L, 11L, "IDLE", "OCCUPIED")).thenReturn(1); + when(mapper.transition(88L, 10L, "OCCUPIED", 11L, 5)).thenReturn(1); + service.apply(10L, new PalletTransitionRequests.Apply(88L, 5, "OCCUPIED", 11L, "out-of-overflow", "restore")); + + verify(mapper, never()).transitionLocation(eq(88L), eq(20L), anyString(), anyString()); + assertEquals("ACTIVE", overflow.getStatus()); + } + @Test void exactReplayDoesNotMutatePhysicalPalletAgain() { WarehousePalletOperationEntity existing = operation(); when(mapper.findOperation("operation-1")).thenReturn(existing);