From 9d492cb5edbe7b58855d3ac34ff816d5e5b5971c Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 9 Sep 2026 04:00:24 +0800 Subject: [PATCH] fix(overseas): restore dispatch delivery returns --- .../service/ExceptionOrderService.java | 45 +++++++ ...xceptionOrderDispatchReturnSourceTest.java | 113 ++++++++++++++++++ ...-overseas-pda-operations-contract.test.mjs | 3 +- .../phase4-overseas-pda-operations.mjs | 15 ++- 4 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 lastleg-service/src/test/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderDispatchReturnSourceTest.java diff --git a/lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderService.java b/lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderService.java index cb1be9ebc..4bb787b6c 100644 --- a/lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderService.java +++ b/lastleg-service/src/main/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderService.java @@ -14,6 +14,14 @@ import com.baiyue.lastleg.delivery.entity.OutboundPdaPick; import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper; import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper; import com.baiyue.lastleg.delivery.mapper.OutboundPdaPickMapper; +import com.baiyue.lastleg.dispatch.entity.DispatchItem; +import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper; +import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaPick; +import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaPickOrder; +import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaTask; +import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickMapper; +import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper; +import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaTaskMapper; import com.baiyue.lastleg.delivery.model.DeliveryStatusPolicy; import com.baiyue.lastleg.entity.LastlegInboundOrder; import com.baiyue.lastleg.entity.LastlegOrder; @@ -66,6 +74,10 @@ public class ExceptionOrderService { @Resource private OutboundOrderMapper outboundOrders; @Resource private OutboundPalletItemMapper outboundPalletItems; @Resource private OutboundPdaPickMapper outboundPdaPicks; + @Resource private DispatchItemMapper dispatchItems; + @Resource private DispatchPdaTaskMapper dispatchPdaTasks; + @Resource private DispatchPdaPickMapper dispatchPdaPicks; + @Resource private DispatchPdaPickOrderMapper dispatchPdaPickOrders; @Resource private ExceptionReturnItemMapper returnItems; @Resource private ExceptionOrderCommandMapper commands; @Resource private InventoryCommandService inventoryCommands; @@ -424,9 +436,42 @@ public class ExceptionOrderService { item.getSkuCode(), item.getInboundCode(), Objects.requireNonNullElse(pick.getPackages(), 0L)), (left, right) -> left.withPackages(left.maxReturnPackages() + right.maxReturnPackages())); } + appendDispatchPickSources(delivery, outbound, itemById, aggregated); return new ArrayList<>(aggregated.values()); } + private void appendDispatchPickSources(DeliveryOrder delivery, OutboundOrder outbound, + Map outboundItemById, + Map aggregated) { + if (outbound.getDispatchOrderId() == null) return; + String customerId = delivery.getCustomerId(); + DispatchPdaTask task = dispatchPdaTasks.findByDispatch(customerId, outbound.getDispatchOrderId()); + if (task == null) return; + Map dispatchItemById = new LinkedHashMap<>(); + dispatchItems.findByDispatchOrder(customerId, outbound.getDispatchOrderId()) + .forEach(item -> dispatchItemById.put(item.getId(), item)); + for (DispatchPdaPick pick : dispatchPdaPicks.listTask(customerId, task.getId())) { + DispatchItem dispatchItem = dispatchItemById.get(pick.getDispatchItemId()); + if (dispatchItem == null || pick.getInventoryPositionId() == null) continue; + List attributions = dispatchPdaPickOrders.listPick(customerId, pick.getId()); + for (DispatchPdaPickOrder attribution : attributions) { + LastlegOrder tail = lastMileOrders.selectById(attribution.getOrderId()); + if (tail == null || !customerId.equals(tail.getCustomerId())) continue; + OutboundPalletItem outboundItem = outboundItemById.values().stream() + .filter(item -> Objects.equals(item.getWarehousePalletId(), dispatchItem.getWarehousePalletId())) + .filter(item -> !StringUtils.hasText(item.getInboundCode()) + || Objects.equals(item.getInboundCode(), tail.getInboundCode())) + .findFirst().orElse(null); + if (outboundItem == null) continue; + String key = outboundItem.getId() + ":" + pick.getInventoryPositionId() + ":" + tail.getInboundCode(); + long packages = Objects.requireNonNullElse(attribution.getPackages(), 0L); + aggregated.merge(key, new Source(outboundItem.getId(), pick.getInventoryPositionId(), + outboundItem.getPalletNo(), outboundItem.getSkuCode(), tail.getInboundCode(), packages), + (left, right) -> left.withPackages(left.maxReturnPackages() + right.maxReturnPackages())); + } + } + } + private List storedReturnSources(ExceptionOrder order) { return returnItems.findByException(order.getCustomerId(), order.getId()).stream() .map(item -> new ExceptionOrderResponses.ReturnSource(item.getOutboundPalletItemId(), diff --git a/lastleg-service/src/test/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderDispatchReturnSourceTest.java b/lastleg-service/src/test/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderDispatchReturnSourceTest.java new file mode 100644 index 000000000..2d2063fbb --- /dev/null +++ b/lastleg-service/src/test/java/com/baiyue/lastleg/exceptionorder/service/ExceptionOrderDispatchReturnSourceTest.java @@ -0,0 +1,113 @@ +package com.baiyue.lastleg.exceptionorder.service; + +import com.baiyue.lastleg.delivery.entity.DeliveryOrder; +import com.baiyue.lastleg.delivery.entity.OutboundOrder; +import com.baiyue.lastleg.delivery.entity.OutboundPalletItem; +import com.baiyue.lastleg.delivery.mapper.DeliveryOrderMapper; +import com.baiyue.lastleg.delivery.mapper.OutboundOrderMapper; +import com.baiyue.lastleg.delivery.mapper.OutboundPalletItemMapper; +import com.baiyue.lastleg.delivery.mapper.OutboundPdaPickMapper; +import com.baiyue.lastleg.dispatch.entity.DispatchItem; +import com.baiyue.lastleg.dispatch.mapper.DispatchItemMapper; +import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaPick; +import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaPickOrder; +import com.baiyue.lastleg.dispatch.pda.entity.DispatchPdaTask; +import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickMapper; +import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper; +import com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaTaskMapper; +import com.baiyue.lastleg.entity.LastlegInboundOrder; +import com.baiyue.lastleg.entity.LastlegOrder; +import com.baiyue.lastleg.mapper.LastlegInboundOrderMapper; +import com.baiyue.lastleg.mapper.LastlegOrderMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ExceptionOrderDispatchReturnSourceTest { + @Mock private DeliveryOrderMapper deliveryOrders; + @Mock private OutboundOrderMapper outboundOrders; + @Mock private OutboundPalletItemMapper outboundPalletItems; + @Mock private OutboundPdaPickMapper outboundPdaPicks; + @Mock private DispatchItemMapper dispatchItems; + @Mock private DispatchPdaTaskMapper dispatchPdaTasks; + @Mock private DispatchPdaPickMapper dispatchPdaPicks; + @Mock private DispatchPdaPickOrderMapper dispatchPdaPickOrders; + @Mock private LastlegOrderMapper lastMileOrders; + @Mock private LastlegInboundOrderMapper inboundOrders; + private ExceptionOrderService service; + + @BeforeEach + void setUp() { + service = new ExceptionOrderService(); + ReflectionTestUtils.setField(service, "deliveryOrders", deliveryOrders); + ReflectionTestUtils.setField(service, "outboundOrders", outboundOrders); + ReflectionTestUtils.setField(service, "outboundPalletItems", outboundPalletItems); + ReflectionTestUtils.setField(service, "outboundPdaPicks", outboundPdaPicks); + ReflectionTestUtils.setField(service, "dispatchItems", dispatchItems); + ReflectionTestUtils.setField(service, "dispatchPdaTasks", dispatchPdaTasks); + ReflectionTestUtils.setField(service, "dispatchPdaPicks", dispatchPdaPicks); + ReflectionTestUtils.setField(service, "dispatchPdaPickOrders", dispatchPdaPickOrders); + ReflectionTestUtils.setField(service, "lastMileOrders", lastMileOrders); + ReflectionTestUtils.setField(service, "inboundOrders", inboundOrders); + } + + @Test + void returnsTheAttributedInventorySourceForDispatchDeliveryPicks() { + DeliveryOrder delivery = new DeliveryOrder(); + delivery.setId(400L); delivery.setCustomerId("1001"); delivery.setDeliveryNo("DL-1"); + delivery.setOutboundNo("OUT-1"); delivery.setStatus("DELIVERED"); + OutboundOrder outbound = new OutboundOrder(); + outbound.setId(300L); outbound.setCustomerId("1001"); outbound.setOutboundNo("OUT-1"); + outbound.setDispatchOrderId(200L); + OutboundPalletItem outboundItem = new OutboundPalletItem(); + outboundItem.setId(301L); outboundItem.setCustomerId("1001"); outboundItem.setOutboundOrderId(300L); + outboundItem.setWarehousePalletId(501L); outboundItem.setPalletNo("PLT-1"); outboundItem.setInboundCode("IN-1"); + DispatchPdaTask task = new DispatchPdaTask(); + task.setId(201L); task.setCustomerId("1001"); task.setDispatchOrderId(200L); + DispatchItem dispatchItem = new DispatchItem(); + dispatchItem.setId(202L); dispatchItem.setCustomerId("1001"); dispatchItem.setDispatchOrderId(200L); + dispatchItem.setWarehousePalletId(501L); + DispatchPdaPick pick = new DispatchPdaPick(); + pick.setId(203L); pick.setCustomerId("1001"); pick.setTaskId(201L); + pick.setDispatchItemId(202L); pick.setInventoryPositionId(601L); pick.setPackages(2L); + DispatchPdaPickOrder attribution = new DispatchPdaPickOrder(); + attribution.setId(204L); attribution.setCustomerId("1001"); attribution.setPickId(203L); + attribution.setOrderId(101L); attribution.setPackages(2L); + LastlegOrder tail = new LastlegOrder(); + tail.setId(101L); tail.setCustomerId("1001"); tail.setOrderNo("LM-1"); tail.setInboundCode("IN-1"); + tail.setDeliveryOrderNo("DL-1"); tail.setOutboundOrderNo("OUT-1"); tail.setPackageCount(2); + LastlegInboundOrder inbound = new LastlegInboundOrder(); + inbound.setId(102L); inbound.setCustomerId("1001"); inbound.setInboundCode("IN-1"); inbound.setPackageCount(2); + + when(deliveryOrders.findByNo("1001", "DL-1")).thenReturn(delivery); + when(lastMileOrders.selectList(any())).thenReturn(List.of(tail)); + when(inboundOrders.selectList(any())).thenReturn(List.of(inbound)); + when(outboundOrders.findByNo("1001", "OUT-1")).thenReturn(outbound); + when(outboundPalletItems.findByOutbound("1001", 300L)).thenReturn(List.of(outboundItem)); + when(outboundPdaPicks.findByOutbound("1001", 300L)).thenReturn(List.of()); + when(dispatchPdaTasks.findByDispatch("1001", 200L)).thenReturn(task); + when(dispatchItems.findByDispatchOrder("1001", 200L)).thenReturn(List.of(dispatchItem)); + when(dispatchPdaPicks.listTask("1001", 201L)).thenReturn(List.of(pick)); + when(dispatchPdaPickOrders.listPick("1001", 203L)).thenReturn(List.of(attribution)); + when(lastMileOrders.selectById(101L)).thenReturn(tail); + + var candidates = service.returnCandidates("1001", "DL-1"); + + assertThat(candidates).hasSize(1); + assertThat(candidates.get(0).returnSources()).singleElement().satisfies(source -> { + assertThat(source.outboundPalletItemId()).isEqualTo(301L); + assertThat(source.inventoryPositionId()).isEqualTo(601L); + assertThat(source.maxReturnPackages()).isEqualTo(2L); + }); + } +} diff --git a/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs b/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs index ffc75242a..c977ff5fe 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-operations-contract.test.mjs @@ -50,7 +50,8 @@ test('P2 to P5 cover all frozen endpoints, replays and conflict boundaries witho 'chargeAmount: 0.01', 'cartonMark:', 'fbaCartonMark:', 'inventory-candidates', 'sourceInventoryBalanceId:', 'sourceInventoryPositionId:', 'sourceInboundOrderId:', 'operationWarehouseId:', 'relabelItemForUnit', 'sourceLines.map', - 'const packageCount = 1', 'oldLabel: item.cartonMark', + 'const packageCount = 1', 'line.packageCount += additionalPackages', 'oldLabel: item.cartonMark', + 'id(candidate.itemId) !== id(item.itemId)', 'oldLabel: null', 'newLabel: item.fbaCartonMark || item.newSkuCode', "operationType: changesCartonLabel ? 'CARTON_AND_SKU' : 'SKU_LABEL'", 'skuItems:', 'P5换标费用冻结后开放PDA作业', 'P5换标完成后钱包结算收敛']) { 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 88acd628c..4db39f3ba 100644 --- a/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs +++ b/scripts/admin-test-env-modules/phase4-overseas-pda-operations.mjs @@ -371,6 +371,14 @@ async function verifyRelabel(context, token, merchantToken, fixture) { } if (requiredPackages === 0) break } + for (const line of sourceLines) { + if (requiredPackages === 0) break + const remainingCapacity = Number(line.position.availablePackages) - line.packageCount + if (remainingCapacity <= 0) continue + const additionalPackages = Math.min(requiredPackages, remainingCapacity) + line.packageCount += additionalPackages + requiredPackages -= additionalPackages + } context.addStep(MODULE, 'P5客户库存候选返回换标权威来源', inventoryCandidatesResponse, requiredPackages === 0, `sourceLines=${sourceLines.length}, selectedPackages=${3 - requiredPackages}; ${trace(inventoryCandidatesResponse)}`, OPTIONS) @@ -456,7 +464,8 @@ async function verifyRelabel(context, token, merchantToken, fixture) { await expectConflict(context, token, 'POST', 'P5正常换标旧版本A0409', unitPath, { ...scanBody, oldLabel: `${context.marker}-OLD-STALE`, idempotencyKey: key(context, 'RELABEL-STALE') }) detail = scanned.data - const skipItem = relabelItemForUnit(detail, 1) + const skipItem = Array.isArray(detail?.items) + ? detail.items.find((candidate) => id(candidate?.itemId) && id(candidate.itemId) !== id(item.itemId)) : null const skipBody = { itemId: id(skipItem?.itemId), oldLabel: skipItem?.cartonMark, reason: `${context.marker}-skip`, expectedVersion: Number(detail.version), idempotencyKey: key(context, 'RELABEL-SKIP') } @@ -465,8 +474,8 @@ async function verifyRelabel(context, token, merchantToken, fixture) { context.assert(MODULE, 'P5跳过换标重放不重复计数', skipReplay.response, Number(skipReplay.data?.skippedPackages) === 1, trace(skipReplay.response), OPTIONS) detail = skipped.data - const exceptionItem = relabelItemForUnit(detail, 2) - const exceptionBody = { itemId: id(exceptionItem?.itemId), oldLabel: exceptionItem?.cartonMark, + const exceptionItem = item + const exceptionBody = { itemId: id(exceptionItem?.itemId), oldLabel: null, exceptionCode: 'DAMAGED', reason: `${context.marker}-damaged`, expectedVersion: Number(detail.version), idempotencyKey: key(context, 'RELABEL-EXCEPTION') } const exception = await request(context, token, 'POST', 'P5登记换标异常一箱',