fix(overseas): preserve shared overflow locations during pallet moves
This commit is contained in:
@@ -101,8 +101,11 @@ async function resolvePallet(context, token, palletCode, name = '扫码解析托
|
|||||||
async function resolvePublicLocation(context, fixture, sourceLocationCode) {
|
async function resolvePublicLocation(context, fixture, sourceLocationCode) {
|
||||||
const response = await context.requestOverseas('GET',
|
const response = await context.requestOverseas('GET',
|
||||||
`/api/platform/v1/warehouse-admin/${encodeURIComponent(fixture.overseasWarehouseId)}/locations?page=1&size=200`)
|
`/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)
|
const locations = records(response?.body?.data)
|
||||||
&& item?.status === 'IDLE' && item?.locationCode !== sourceLocationCode)
|
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,
|
context.addStep(MODULE, '复用当前海外仓公共库位', response,
|
||||||
success(response) && longId(id(location?.id)) && nonBlank(location?.locationCode),
|
success(response) && longId(id(location?.id)) && nonBlank(location?.locationCode),
|
||||||
`locationId=${id(location?.id) || 'missing'}, locationCode=${location?.locationCode || 'missing'}; ${trace(response)}`, OPTIONS)
|
`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)
|
const reworked = await request(context, context.overseasToken, 'POST', 'P5平台复核退回REWORK', reviewPath, reworkBody)
|
||||||
if (!success(reworked.response)) return false
|
if (!success(reworked.response)) return false
|
||||||
const reworkReplay = await request(context, context.overseasToken, 'POST', 'P5 REWORK同键重放', reviewPath, reworkBody)
|
const reworkReplay = await request(context, context.overseasToken, 'POST', 'P5 REWORK同键重放', reviewPath, reworkBody)
|
||||||
context.assert(MODULE, 'P5 REWORK重放保持PROCESSING', reworkReplay.response,
|
context.assert(MODULE, 'P5 REWORK重放保持AVAILABLE等待重新认领', reworkReplay.response,
|
||||||
reworkReplay.data?.taskStatus === 'PROCESSING' && Number(reworkReplay.data.relabelledPackages) === 0,
|
reworkReplay.data?.taskStatus === 'AVAILABLE' && Number(reworkReplay.data.relabelledPackages) === 0,
|
||||||
trace(reworkReplay.response), OPTIONS)
|
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 || []) {
|
for (const redoItem of detail.items || []) {
|
||||||
if (redoItem.operationType === 'CARTON_AND_SKU') {
|
if (redoItem.operationType === 'CARTON_AND_SKU') {
|
||||||
const carton = await request(context, token, 'POST', 'P5返工重新确认箱标阶段',
|
const carton = await request(context, token, 'POST', 'P5返工重新确认箱标阶段',
|
||||||
|
|||||||
+7
-7
@@ -157,7 +157,7 @@ public class WarehousePalletTransitionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void requireAvailableTarget(WarehouseLocationEntity location, String targetStatus) {
|
private void requireAvailableTarget(WarehouseLocationEntity location, String targetStatus) {
|
||||||
String expected = isVirtual(location) ? "ACTIVE" : "IDLE";
|
String expected = isNonExclusive(location) ? "ACTIVE" : "IDLE";
|
||||||
if (!expected.equals(location.getStatus())) {
|
if (!expected.equals(location.getStatus())) {
|
||||||
throw new BizException(ResultCode.CONFLICT, "目标库位不可用");
|
throw new BizException(ResultCode.CONFLICT, "目标库位不可用");
|
||||||
}
|
}
|
||||||
@@ -168,18 +168,18 @@ public class WarehousePalletTransitionService {
|
|||||||
|
|
||||||
private void transitionSameLocation(long warehouseId, WarehouseLocationEntity location,
|
private void transitionSameLocation(long warehouseId, WarehouseLocationEntity location,
|
||||||
String beforeStatus, String afterStatus) {
|
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),
|
changeLocationStatus(warehouseId, location.getId(), expectedLocationStatus(location, beforeStatus),
|
||||||
expectedLocationStatus(location, afterStatus));
|
expectedLocationStatus(location, afterStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseSourceLocation(long warehouseId, WarehouseLocationEntity source, String palletStatus) {
|
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");
|
changeLocationStatus(warehouseId, source.getId(), "OCCUPIED", "IDLE");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void occupyTargetLocation(long warehouseId, WarehouseLocationEntity target, String palletStatus) {
|
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");
|
changeLocationStatus(warehouseId, target.getId(), "IDLE", "OCCUPIED");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,11 +191,11 @@ public class WarehousePalletTransitionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static String expectedLocationStatus(WarehouseLocationEntity location, String palletStatus) {
|
private static String expectedLocationStatus(WarehouseLocationEntity location, String palletStatus) {
|
||||||
return isVirtual(location) ? "ACTIVE" : palletStatus;
|
return isNonExclusive(location) ? "ACTIVE" : palletStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isVirtual(WarehouseLocationEntity location) {
|
private static boolean isNonExclusive(WarehouseLocationEntity location) {
|
||||||
return "VIRTUAL".equals(location.getLocationType());
|
return "VIRTUAL".equals(location.getLocationType()) || WarehouseResourceService.isSharedOverflow(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PalletTransitionResponse replay(WarehousePalletOperationEntity operation, String hash) {
|
private PalletTransitionResponse replay(WarehousePalletOperationEntity operation, String hash) {
|
||||||
|
|||||||
+21
@@ -64,6 +64,27 @@ class WarehousePalletTransitionServiceTest {
|
|||||||
verify(mapper).transitionLocation(88L, locationId, "OCCUPIED", "IDLE");
|
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() {
|
@Test void exactReplayDoesNotMutatePhysicalPalletAgain() {
|
||||||
WarehousePalletOperationEntity existing = operation();
|
WarehousePalletOperationEntity existing = operation();
|
||||||
when(mapper.findOperation("operation-1")).thenReturn(existing);
|
when(mapper.findOperation("operation-1")).thenReturn(existing);
|
||||||
|
|||||||
Reference in New Issue
Block a user