From fd9a070efc2fd260e52e77c943265e950de93c40 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Wed, 2 Sep 2026 00:27:04 +0800 Subject: [PATCH] fix: settle whole dropship pallets by sku --- .../mapper/InboundSplitSettlementMapper.java | 3 ++ .../service/PdaPickSettlementService.java | 38 +++++++++++++++---- .../service/PdaPickSettlementServiceTest.java | 29 ++++++++++++++ 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundSplitSettlementMapper.java b/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundSplitSettlementMapper.java index bb29c8c8c..683c17012 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundSplitSettlementMapper.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/mapper/InboundSplitSettlementMapper.java @@ -22,6 +22,9 @@ public interface InboundSplitSettlementMapper { @Select("SELECT container_booking_id FROM fl_loading_plan WHERE id=#{loadingPlanId}") Long containerBookingId(@Param("loadingPlanId") long loadingPlanId); + + @Select("SELECT fulfillment_type FROM fl_inbound_order WHERE id=#{inboundId} AND tenant_id=#{accountId} AND deleted=0") + String fulfillmentType(@Param("accountId") long accountId, @Param("inboundId") long inboundId); @Select(""" SELECT id, tenant_id AS tenantId, order_id AS orderId, order_no AS orderNo, inbound_code AS inboundCode, inbound_role AS inboundRole, diff --git a/firstleg/src/main/java/com/baiyue/firstleg/service/PdaPickSettlementService.java b/firstleg/src/main/java/com/baiyue/firstleg/service/PdaPickSettlementService.java index bf8847085..950b02d0f 100644 --- a/firstleg/src/main/java/com/baiyue/firstleg/service/PdaPickSettlementService.java +++ b/firstleg/src/main/java/com/baiyue/firstleg/service/PdaPickSettlementService.java @@ -180,7 +180,14 @@ public class PdaPickSettlementService { if (pickedByMeasurement != pickedPackageCount) { throw conflict("PDA 已拣明细与入库单已拣箱数不一致"); } - List measurementLines = measurementLines(measurements, pickedByDetail); + boolean dropshipWholePallet = "ONE_CLICK_DROPSHIP".equals( + splits.fulfillmentType(parent.tenantId(), parentInboundId)); + if (dropshipWholePallet && childPackageCount != 0) { + throw conflict("一件代发现场托盘必须整票装柜,不能生成余量分片"); + } + List measurementLines = dropshipWholePallet + ? wholePalletMeasurementLines(measurements, pickedPackageCount, parentAvailable) + : measurementLines(measurements, pickedByDetail); int measuredRemaining = measurementLines.stream().mapToInt(MeasurementSplitLine::remainingPackageCount).sum(); if (measuredRemaining != childPackageCount) { throw conflict("主入库单测量明细剩余箱数与未装柜箱数不一致"); @@ -191,7 +198,9 @@ public class PdaPickSettlementService { } if (root == null || !"MASTER".equals(root.inboundRole())) throw conflict("根主入库单不存在"); - OrderFulfillmentSplitClient.Response orderSplit = createOrderSplit(parent, root, measurementLines, + Map orderQuantities = dropshipWholePallet + ? new LinkedHashMap<>(pickedByDetail) : measurementQuantities(measurementLines); + OrderFulfillmentSplitClient.Response orderSplit = createOrderSplit(parent, root, orderQuantities, pickedPackageCount, childPackageCount, loadingPlanId, containerBookingId, operationId, "pda-task-" + taskId, operatorId, operatorName); String settlementKey = "PDA-SETTLE-" + taskId + '-' + parentInboundId; @@ -282,17 +291,14 @@ public class PdaPickSettlementService { } private OrderFulfillmentSplitClient.Response createOrderSplit( - ParentRow source, ParentRow root, List lines, + ParentRow source, ParentRow root, Map quantitiesByDetail, int loadedPackageCount, int remainingPackageCount, long loadingPlanId, long containerBookingId, String operationId, String idempotencyKey, long operatorId, String operatorName) { if (root.orderId() == null || source.orderId() == null) { throw conflict("入仓单缺少真实订单引用"); } - List quantities = lines.stream() - .collect(Collectors.groupingBy(line -> line.item().inboundDetailId(), LinkedHashMap::new, - Collectors.summingInt(line -> count(line.item().packageCount(), "测量明细箱数")))) - .entrySet().stream() + List quantities = quantitiesByDetail.entrySet().stream() .map(entry -> new OrderFulfillmentSplitClient.QuantityLine(entry.getKey(), entry.getValue())) .toList(); OrderFulfillmentSplitClient.CreateRequest request = new OrderFulfillmentSplitClient.CreateRequest( @@ -386,6 +392,24 @@ public class PdaPickSettlementService { return result; } + /** 一件代发逐箱封托已经冻结 SKU 归属;测量批次只承担整票体积重量,不再冒充 SKU 明细。 */ + private static List wholePalletMeasurementLines(List measurements, + int pickedPackageCount, + int parentAvailable) { + int measuredPackages = measurements.stream() + .mapToInt(item -> count(item.packageCount(), "测量明细箱数")).sum(); + if (pickedPackageCount != parentAvailable || measuredPackages != parentAvailable) { + throw conflict("一件代发整托箱数与入库单、测量批次不守恒"); + } + return measurements.stream().map(item -> new MeasurementSplitLine(item, + count(item.packageCount(), "测量明细箱数"), 0)).toList(); + } + + private static Map measurementQuantities(List lines) { + return lines.stream().collect(Collectors.groupingBy(line -> line.item().inboundDetailId(), + LinkedHashMap::new, Collectors.summingInt(line -> count(line.item().packageCount(), "测量明细箱数")))); + } + private void consumePickedAllocations(long accountId, long inboundId, List confirmed, int pickedCount) { int toConsume = pickedCount; diff --git a/firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java b/firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java index 9bf638824..92cbb20ff 100644 --- a/firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java +++ b/firstleg/src/test/java/com/baiyue/firstleg/service/PdaPickSettlementServiceTest.java @@ -202,6 +202,35 @@ class PdaPickSettlementServiceTest { && request.remainingItems().isEmpty())); } + @Test + void settlesWholeDropshipPalletWithSkuFactsInsteadOfAggregateMeasurementDetail() { + List items = List.of( + item(11L, 21L, 31L, 41L, 1, 1, "PICKED"), + item(12L, 22L, 31L, 42L, 1, 1, "PICKED"), + item(13L, 23L, 31L, 43L, 1, 1, "PICKED"), + item(14L, 24L, 31L, 44L, 1, 1, "PICKED")); + resolved(items); + when(splits.lockParent(31L)).thenReturn(parent(4)); + when(splits.fulfillmentType(10001L, 31L)).thenReturn("ONE_CLICK_DROPSHIP"); + when(splits.lockLatestApprovedItemsForInbound(10001L, 31L)) + .thenReturn(List.of(new MeasurementItemRow(51L, 61L, 41L, 4))); + when(allocations.lockConfirmed(10001L, 31L)) + .thenReturn(List.of(allocation(1L, 1, 501L, 4))); + when(warehouse.settle(eq("internal-token"), any())).thenReturn(ApiResponse.success( + new WarehousePalletLocationClient.SettlementResult(List.of(released(91L, 501L)), List.of()))); + writes(0, 4); + + service.settle(1L, 9L, "operator"); + + verify(orderSplits).split(eq("internal-token"), eq(9L), + org.mockito.ArgumentMatchers.argThat(request -> request.lines().size() == 4 + && request.lines().stream().allMatch(line -> line.packageCount() == 1) + && request.lines().stream().map(OrderFulfillmentSplitClient.QuantityLine::sourceDetailId) + .collect(java.util.stream.Collectors.toSet()).equals(java.util.Set.of(41L, 42L, 43L, 44L)))); + verify(splits).updateMeasurementItemForLoaded(10001L, 51L, 4); + verify(splits).markSourceLoaded(10001L, 31L, 4, 9L, 3); + } + @Test void onlyPureSkippedInboundRestoresCandidate() { LoadingPickItemEntity item = item(12L, 22L, 32L, 42L, 8, 0, "SKIPPED");