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 d6da45553..3a0594bf1 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 @@ -485,12 +485,17 @@ public class ExceptionOrderService { for (DispatchPdaPickOrder attribution : attributions) { LastlegOrder tail = lastMileOrders.selectById(attribution.getOrderId()); if (tail == null || !customerId.equals(tail.getCustomerId())) continue; - OutboundPalletItem outboundItem = outboundItemById.values().stream() + List matchingItems = 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; + .filter(item -> Objects.equals(item.getInboundCode(), dispatchItem.getInboundCode())) + .filter(item -> Objects.equals(item.getDestinationWarehouseId(), dispatchItem.getDestinationWarehouseId())) + .filter(item -> Objects.equals(item.getDestinationWarehouseCode(), dispatchItem.getDestinationWarehouseCode())) + .filter(item -> Objects.equals(item.getDestinationWarehouseName(), dispatchItem.getDestinationWarehouseName())) + .filter(item -> Objects.equals(item.getOrderRefs(), dispatchItem.getOrderRefs())).toList(); + if (matchingItems.size() > 1) throw conflict("排车明细对应的原出库托盘项不唯一,拒绝退回来源"); + if (matchingItems.isEmpty()) continue; + OutboundPalletItem outboundItem = matchingItems.get(0); + // A mixed pallet has one physical outbound item; each source is identified by the actual pick attribution. String key = outboundItem.getId() + ":" + pick.getInventoryPositionId() + ":" + tail.getInboundCode(); long packages = Objects.requireNonNullElse(attribution.getPackages(), 0L); aggregated.merge(key, new Source(outboundItem.getId(), pick.getInventoryPositionId(), 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 index 4e7ef7760..76b1eeaa5 100644 --- 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 @@ -79,6 +79,7 @@ class ExceptionOrderDispatchReturnSourceTest { DispatchItem dispatchItem = new DispatchItem(); dispatchItem.setId(202L); dispatchItem.setCustomerId("1001"); dispatchItem.setDispatchOrderId(200L); dispatchItem.setWarehousePalletId(501L); + dispatchItem.setInboundCode("IN-1"); DispatchPdaPick pick = new DispatchPdaPick(); pick.setId(203L); pick.setCustomerId("1001"); pick.setTaskId(201L); pick.setDispatchItemId(202L); pick.setInventoryPositionId(601L); pick.setPackages(2L); @@ -112,4 +113,116 @@ class ExceptionOrderDispatchReturnSourceTest { assertThat(source.maxReturnPackages()).isEqualTo(2L); }); } + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.ValueSource(strings = {"SINGLE", "MULTI", "MULTI_REVERSED", "AMBIGUOUS"}) + void mixedPalletRetainsSecondInboundSourceAndSumsOnlyItsActualPickAttributions(String layout) { + var metadata = new org.apache.ibatis.builder.MapperBuilderAssistant( + new com.baomidou.mybatisplus.core.MybatisConfiguration(), "mixed-return-source-test"); + com.baomidou.mybatisplus.core.metadata.TableInfoHelper.initTableInfo(metadata, LastlegOrder.class); + com.baomidou.mybatisplus.core.metadata.TableInfoHelper.initTableInfo(metadata, LastlegInboundOrder.class); + 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); + dispatchItem.setInboundCode("IN-1"); + DispatchPdaPick pick = new DispatchPdaPick(); + pick.setId(203L); pick.setCustomerId("1001"); pick.setTaskId(201L); + pick.setDispatchItemId(202L); pick.setInventoryPositionId(601L); pick.setPackages(1L); + DispatchPdaPickOrder attribution = new DispatchPdaPickOrder(); + attribution.setId(204L); attribution.setCustomerId("1001"); attribution.setPickId(203L); + attribution.setOrderId(101L); attribution.setPackages(1L); + 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(1); + LastlegInboundOrder inbound = new LastlegInboundOrder(); + inbound.setId(102L); inbound.setCustomerId("1001"); inbound.setInboundCode("IN-1"); inbound.setPackageCount(1); + + LastlegOrder secondTail = new LastlegOrder(); + secondTail.setId(111L); secondTail.setCustomerId("1001"); secondTail.setOrderNo("LM-2"); secondTail.setInboundCode("IN-2"); + secondTail.setDeliveryOrderNo("DL-1"); secondTail.setOutboundOrderNo("OUT-1"); secondTail.setPackageCount(3); + LastlegInboundOrder secondInbound = new LastlegInboundOrder(); + secondInbound.setId(112L); secondInbound.setCustomerId("1001"); secondInbound.setInboundCode("IN-2"); secondInbound.setPackageCount(3); + DispatchPdaPick secondPick = new DispatchPdaPick(); + secondPick.setId(213L); secondPick.setCustomerId("1001"); secondPick.setTaskId(201L); + secondPick.setDispatchItemId(202L); secondPick.setInventoryPositionId(602L); secondPick.setPackages(3L); + DispatchPdaPickOrder secondOne = new DispatchPdaPickOrder(); + secondOne.setId(214L); secondOne.setCustomerId("1001"); secondOne.setPickId(213L); secondOne.setOrderId(111L); secondOne.setPackages(1L); + DispatchPdaPickOrder secondTwo = new DispatchPdaPickOrder(); + secondTwo.setId(215L); secondTwo.setCustomerId("1001"); secondTwo.setPickId(213L); secondTwo.setOrderId(111L); secondTwo.setPackages(2L); + + when(deliveryOrders.findByNo("1001", "DL-1")).thenReturn(delivery); + when(lastMileOrders.selectList(any())).thenAnswer(call -> { + var query = (com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper) call.getArgument(0); + query.getSqlSegment(); + var parameters = query.getParamNameValuePairs().values(); + assertThat(parameters).contains("1001"); + if (parameters.contains("DL-1")) return List.of(tail, secondTail); + if (parameters.contains("LM-1") && parameters.contains("IN-1")) return List.of(tail); + if (parameters.contains("LM-2") && parameters.contains("IN-2")) return List.of(secondTail); + throw new AssertionError("Unexpected tail source query: " + parameters); + }); + when(inboundOrders.selectList(any())).thenAnswer(call -> { + var query = (com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper) call.getArgument(0); + query.getSqlSegment(); + var parameters = query.getParamNameValuePairs().values(); + assertThat(parameters).contains("1001"); + if (parameters.contains("IN-1")) return List.of(inbound); + if (parameters.contains("IN-2")) return List.of(secondInbound); + throw new AssertionError("Unexpected inbound source query: " + parameters); + }); + when(outboundOrders.findByNo("1001", "OUT-1")).thenReturn(outbound); + OutboundPalletItem otherItem = new OutboundPalletItem(); + otherItem.setId(302L); otherItem.setCustomerId("1001"); otherItem.setOutboundOrderId(300L); + otherItem.setWarehousePalletId(501L); otherItem.setPalletNo("PLT-1"); otherItem.setInboundCode("IN-1"); + if (!"AMBIGUOUS".equals(layout)) { + otherItem.setDestinationWarehouseId("OTHER"); otherItem.setDestinationWarehouseCode("OTHER"); + otherItem.setDestinationWarehouseName("OTHER"); otherItem.setOrderRefs("OTHER"); + } + var outboundSnapshots = "SINGLE".equals(layout) ? List.of(outboundItem) + : "MULTI_REVERSED".equals(layout) ? List.of(otherItem, outboundItem) : List.of(outboundItem, otherItem); + when(outboundPalletItems.findByOutbound("1001", 300L)).thenReturn(outboundSnapshots); + 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, secondPick)); + when(dispatchPdaPickOrders.listPick("1001", 203L)).thenReturn(List.of(attribution)); + when(lastMileOrders.selectById(101L)).thenReturn(tail); + if (!"AMBIGUOUS".equals(layout)) { + when(dispatchPdaPickOrders.listPick("1001", 213L)).thenReturn(List.of(secondOne, secondTwo)); + when(lastMileOrders.selectById(111L)).thenReturn(secondTail); + } + + if ("AMBIGUOUS".equals(layout)) { + org.assertj.core.api.Assertions.assertThatThrownBy(() -> service.returnCandidates("1001", "DL-1")) + .isInstanceOf(org.springframework.web.server.ResponseStatusException.class).hasMessageContaining("原出库托盘项不唯一"); + return; + } + + var candidates = service.returnCandidates("1001", "DL-1"); + + assertThat(candidates).hasSize(2); + var first = candidates.stream().filter(row -> "IN-1".equals(row.inboundCode())).findFirst().orElseThrow(); + var second = candidates.stream().filter(row -> "IN-2".equals(row.inboundCode())).findFirst().orElseThrow(); + assertThat(first.returnSources()).singleElement().satisfies(source -> { + assertThat(source.outboundPalletItemId()).isEqualTo(301L); + assertThat(source.inventoryPositionId()).isEqualTo(601L); + assertThat(source.maxReturnPackages()).isEqualTo(1L); + }); + assertThat(second.returnSources()).singleElement().satisfies(source -> { + assertThat(source.outboundPalletItemId()).isEqualTo(301L); + assertThat(source.inventoryPositionId()).isEqualTo(602L); + assertThat(source.maxReturnPackages()).isEqualTo(3L); + }); + } + }