fix(delivery): project current dispatched cargo instead of whole order SKU quantities

This commit is contained in:
2026-10-04 02:54:52 +08:00
parent 9cd709edee
commit c46a68e32b
4 changed files with 180 additions and 3 deletions
@@ -103,6 +103,7 @@ class DeliveryTransferServiceTest {
@Mock LastlegOrderMapper tailOrders;
@Mock LastlegOrderStatusHistoryMapper histories;
@Mock LastlegOrderDetailMapper tailOrderDetails;
@Mock com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper dispatchPickOrders;
@Mock OutboundPdaTaskMapper pdaTasks;
@Mock OutboundPdaPickMapper pdaPicks;
@Mock InventoryPositionMapper inventoryPositions;
@@ -141,6 +142,7 @@ class DeliveryTransferServiceTest {
ReflectionTestUtils.setField(service, "tailOrders", tailOrders);
ReflectionTestUtils.setField(service, "histories", histories);
ReflectionTestUtils.setField(service, "tailOrderDetails", tailOrderDetails);
ReflectionTestUtils.setField(service, "dispatchPickOrders", dispatchPickOrders);
ReflectionTestUtils.setField(service, "pdaTasks", pdaTasks);
ReflectionTestUtils.setField(service, "pdaPicks", pdaPicks);
ReflectionTestUtils.setField(service, "inventoryPositions", inventoryPositions);
@@ -536,11 +538,17 @@ class DeliveryTransferServiceTest {
lax9Item.setOrderId(902L);
when(deliveryOrders.findByNo("TENANT-A", "DEL-ONT8")).thenReturn(ont8);
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(ont8Item, lax9Item));
when(dispatchPickOrders.deliveryPickedSkus("TENANT-A", 101L)).thenReturn(List.of(
new com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper.DeliveryPickedSku(201L, 901L, "SKU-A", "A", 12L),
new com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper.DeliveryPickedSku(202L, 902L, "SKU-B", "B", 8L)));
when(tailOrders.selectBatchIds(List.of(901L))).thenReturn(List.of());
DeliveryResponses.DeliveryDetail detail = service.deliveryDetail("TENANT-A", "DEL-ONT8");
assertEquals("ONT8", detail.order().destinationWarehouseCode());
assertEquals(1, detail.skus().size());
assertEquals(12, detail.skus().get(0).packageCount());
assertEquals("SKU-A", detail.skus().get(0).skuCode());
verify(tailOrders).selectBatchIds(List.of(901L));
}
@@ -556,6 +564,93 @@ class DeliveryTransferServiceTest {
}
}
@Test
void redispatchDetailUsesActualTwoMixedBoxesInsteadOfOriginalThreeDeclaredSkuBoxes() {
OutboundOrder outbound = outbound(301L, 101L, "DSP-101", "OUT-301", "transfer-101");
DeliveryOrder delivery = delivery(401L, outbound, "DEL-RETURN"); delivery.setPackageCount(2);
DispatchItem selected = item(201L, 501L, "RETURN-PALLET", "ONT8", "Ontario", "IN-A", 2);
selected.setOrderId(901L);
var order = new com.baiyue.lastleg.entity.LastlegOrder();
order.setId(901L); order.setCustomerId("TENANT-A"); order.setInboundCode("IN-A");
var small = new com.baiyue.lastleg.entity.LastlegOrderDetail();
small.setCustomerId("TENANT-A"); small.setOrderId(901L); small.setSkuCode("SMALL"); small.setPackageCount(2);
var large = new com.baiyue.lastleg.entity.LastlegOrderDetail();
large.setCustomerId("TENANT-A"); large.setOrderId(901L); large.setSkuCode("LARGE"); large.setPackageCount(1);
when(deliveryOrders.findByNo("TENANT-A", "DEL-RETURN")).thenReturn(delivery);
when(dispatchItems.findByDispatchOrder("TENANT-A", 101L)).thenReturn(List.of(selected));
when(dispatchPickOrders.deliveryPickedSkus("TENANT-A", 101L)).thenReturn(List.of(
new com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper.DeliveryPickedSku(201L, 901L, "MIXED", "SMALL", 2L)));
when(tailOrders.selectBatchIds(List.of(901L))).thenReturn(List.of(order));
when(tailOrderDetails.selectList(any())).thenReturn(List.of(small, large));
var detail = service.deliveryDetail("TENANT-A", "DEL-RETURN");
assertEquals(1, detail.skus().size()); assertEquals(2, detail.skus().get(0).packageCount());
assertEquals(null, detail.skus().get(0).skuCode()); assertEquals(null, detail.skus().get(0).productName());
assertEquals(null, detail.skus().get(0).weightKg()); assertEquals(null, detail.skus().get(0).volumeCbm());
assertEquals("IN-A", detail.skus().get(0).inboundCode());
}
@Test
void currentDeliveryKnownSkuUsesOnlyActualPickedQuantityAndKeepsUnpickedSkuUnknown() {
DispatchItem selected = item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-A", 3);
var picked = List.of(new com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper.DeliveryPickedSku(
201L, 901L, "SMALL", "Actual SMALL", 1L));
var metadata = new com.baiyue.lastleg.entity.LastlegOrderDetail();
metadata.setOrderId(901L); metadata.setSkuCode("SMALL"); metadata.setPackageCount(99);
metadata.setShipmentNo("SHIP-A"); metadata.setGrossWeightKg(new BigDecimal("999"));
List<DeliveryResponses.DeliverySku> rows = ReflectionTestUtils.invokeMethod(service, "deliveryCargoSkus",
List.of(selected), picked, Map.of(), List.of(metadata));
assertEquals(2, rows.size()); assertEquals(1, rows.get(0).packageCount());
assertEquals("SMALL", rows.get(0).skuCode()); assertEquals("SHIP-A", rows.get(0).shipmentNo());
assertEquals(null, rows.get(0).weightKg()); assertEquals(2, rows.get(1).packageCount());
assertEquals(null, rows.get(1).skuCode());
}
@Test
void mixedPalletUsesActualAttributionSourceRatherThanPrimaryDispatchOrder() {
DispatchItem selected = item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-B", 3);
var a = new com.baiyue.lastleg.entity.LastlegOrder(); a.setId(901L); a.setInboundCode("IN-A");
var b = new com.baiyue.lastleg.entity.LastlegOrder(); b.setId(902L); b.setInboundCode("IN-B");
var picked = List.of(
new com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper.DeliveryPickedSku(201L, 901L, "A-SKU", "A", 2L),
new com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper.DeliveryPickedSku(201L, 902L, "B-SKU", "B", 1L));
List<DeliveryResponses.DeliverySku> rows = ReflectionTestUtils.invokeMethod(service, "deliveryCargoSkus",
List.of(selected), picked, Map.of(901L, a, 902L, b), List.of());
assertEquals(List.of("IN-A", "IN-B"), rows.stream().map(DeliveryResponses.DeliverySku::inboundCode).toList());
assertEquals(List.of(2, 1), rows.stream().map(DeliveryResponses.DeliverySku::packageCount).toList());
}
@Test
void selectedDispatchSnapshotBeforePickingDoesNotInventSkuOrDeclaredMeasurements() {
DispatchItem selected = item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-A", 2);
List<DeliveryResponses.DeliverySku> rows = ReflectionTestUtils.invokeMethod(service, "deliveryCargoSkus",
List.of(selected), List.of(), Map.of(), List.of());
assertEquals(1, rows.size()); assertEquals(2, rows.get(0).packageCount());
assertEquals(null, rows.get(0).skuCode()); assertEquals(null, rows.get(0).weightKg());
}
@Test
void actualPickedQuantityAboveFrozenDispatchOrNonPositiveQuantityIsRejected() {
DispatchItem selected = item(201L, 501L, "PLT-501", "ONT8", "Ontario", "IN-A", 2);
for (long quantity : new long[] {0, -1, 3}) {
var picked = List.of(new com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper.DeliveryPickedSku(
201L, 901L, "SMALL", "SMALL", quantity));
assertThrows(ResponseStatusException.class, () -> ReflectionTestUtils.invokeMethod(service, "deliveryCargoSkus",
List.of(selected), picked, Map.of(), List.of()));
}
}
@Test
void deliveryPickedSkuQueryIsScopedToActualDispatchAndFrozenReceiptSkuWithoutDeclaredPackageJoin() throws Exception {
var method = com.baiyue.lastleg.dispatch.pda.mapper.DispatchPdaPickOrderMapper.class
.getMethod("deliveryPickedSkus", String.class, Long.class);
String sql = String.join(" ", method.getAnnotation(org.apache.ibatis.annotations.Select.class).value());
assertTrue(sql.contains("t.dispatch_order_id=#{dispatchId}"));
assertTrue(sql.contains("BINARY t.customer_id=BINARY #{customerId}"));
assertTrue(sql.contains("CASE WHEN a.receipt_id IS NOT NULL THEN i.sku_code ELSE b.sku_code END"));
assertTrue(sql.contains("COALESCE(a.packages,p.packages)"));
assertTrue(!sql.contains("ll_order_detail"));
}
@Test
void globalOutboundCancelIsIdempotentForAnUnexecutedOrder() {
OutboundOrder outbound = outbound(301L, null, null, "OUT-301", "one-click-key");