fix: settle whole dropship pallets by sku
This commit is contained in:
@@ -22,6 +22,9 @@ public interface InboundSplitSettlementMapper {
|
|||||||
|
|
||||||
@Select("SELECT container_booking_id FROM fl_loading_plan WHERE id=#{loadingPlanId}")
|
@Select("SELECT container_booking_id FROM fl_loading_plan WHERE id=#{loadingPlanId}")
|
||||||
Long containerBookingId(@Param("loadingPlanId") long 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("""
|
||||||
SELECT id, tenant_id AS tenantId, order_id AS orderId, order_no AS orderNo,
|
SELECT id, tenant_id AS tenantId, order_id AS orderId, order_no AS orderNo,
|
||||||
inbound_code AS inboundCode, inbound_role AS inboundRole,
|
inbound_code AS inboundCode, inbound_role AS inboundRole,
|
||||||
|
|||||||
@@ -180,7 +180,14 @@ public class PdaPickSettlementService {
|
|||||||
if (pickedByMeasurement != pickedPackageCount) {
|
if (pickedByMeasurement != pickedPackageCount) {
|
||||||
throw conflict("PDA 已拣明细与入库单已拣箱数不一致");
|
throw conflict("PDA 已拣明细与入库单已拣箱数不一致");
|
||||||
}
|
}
|
||||||
List<MeasurementSplitLine> measurementLines = measurementLines(measurements, pickedByDetail);
|
boolean dropshipWholePallet = "ONE_CLICK_DROPSHIP".equals(
|
||||||
|
splits.fulfillmentType(parent.tenantId(), parentInboundId));
|
||||||
|
if (dropshipWholePallet && childPackageCount != 0) {
|
||||||
|
throw conflict("一件代发现场托盘必须整票装柜,不能生成余量分片");
|
||||||
|
}
|
||||||
|
List<MeasurementSplitLine> measurementLines = dropshipWholePallet
|
||||||
|
? wholePalletMeasurementLines(measurements, pickedPackageCount, parentAvailable)
|
||||||
|
: measurementLines(measurements, pickedByDetail);
|
||||||
int measuredRemaining = measurementLines.stream().mapToInt(MeasurementSplitLine::remainingPackageCount).sum();
|
int measuredRemaining = measurementLines.stream().mapToInt(MeasurementSplitLine::remainingPackageCount).sum();
|
||||||
if (measuredRemaining != childPackageCount) {
|
if (measuredRemaining != childPackageCount) {
|
||||||
throw conflict("主入库单测量明细剩余箱数与未装柜箱数不一致");
|
throw conflict("主入库单测量明细剩余箱数与未装柜箱数不一致");
|
||||||
@@ -191,7 +198,9 @@ public class PdaPickSettlementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (root == null || !"MASTER".equals(root.inboundRole())) throw conflict("根主入库单不存在");
|
if (root == null || !"MASTER".equals(root.inboundRole())) throw conflict("根主入库单不存在");
|
||||||
OrderFulfillmentSplitClient.Response orderSplit = createOrderSplit(parent, root, measurementLines,
|
Map<Long, Integer> orderQuantities = dropshipWholePallet
|
||||||
|
? new LinkedHashMap<>(pickedByDetail) : measurementQuantities(measurementLines);
|
||||||
|
OrderFulfillmentSplitClient.Response orderSplit = createOrderSplit(parent, root, orderQuantities,
|
||||||
pickedPackageCount, childPackageCount, loadingPlanId, containerBookingId, operationId,
|
pickedPackageCount, childPackageCount, loadingPlanId, containerBookingId, operationId,
|
||||||
"pda-task-" + taskId, operatorId, operatorName);
|
"pda-task-" + taskId, operatorId, operatorName);
|
||||||
String settlementKey = "PDA-SETTLE-" + taskId + '-' + parentInboundId;
|
String settlementKey = "PDA-SETTLE-" + taskId + '-' + parentInboundId;
|
||||||
@@ -282,17 +291,14 @@ public class PdaPickSettlementService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private OrderFulfillmentSplitClient.Response createOrderSplit(
|
private OrderFulfillmentSplitClient.Response createOrderSplit(
|
||||||
ParentRow source, ParentRow root, List<MeasurementSplitLine> lines,
|
ParentRow source, ParentRow root, Map<Long, Integer> quantitiesByDetail,
|
||||||
int loadedPackageCount, int remainingPackageCount, long loadingPlanId,
|
int loadedPackageCount, int remainingPackageCount, long loadingPlanId,
|
||||||
long containerBookingId, String operationId, String idempotencyKey,
|
long containerBookingId, String operationId, String idempotencyKey,
|
||||||
long operatorId, String operatorName) {
|
long operatorId, String operatorName) {
|
||||||
if (root.orderId() == null || source.orderId() == null) {
|
if (root.orderId() == null || source.orderId() == null) {
|
||||||
throw conflict("入仓单缺少真实订单引用");
|
throw conflict("入仓单缺少真实订单引用");
|
||||||
}
|
}
|
||||||
List<OrderFulfillmentSplitClient.QuantityLine> quantities = lines.stream()
|
List<OrderFulfillmentSplitClient.QuantityLine> quantities = quantitiesByDetail.entrySet().stream()
|
||||||
.collect(Collectors.groupingBy(line -> line.item().inboundDetailId(), LinkedHashMap::new,
|
|
||||||
Collectors.summingInt(line -> count(line.item().packageCount(), "测量明细箱数"))))
|
|
||||||
.entrySet().stream()
|
|
||||||
.map(entry -> new OrderFulfillmentSplitClient.QuantityLine(entry.getKey(), entry.getValue()))
|
.map(entry -> new OrderFulfillmentSplitClient.QuantityLine(entry.getKey(), entry.getValue()))
|
||||||
.toList();
|
.toList();
|
||||||
OrderFulfillmentSplitClient.CreateRequest request = new OrderFulfillmentSplitClient.CreateRequest(
|
OrderFulfillmentSplitClient.CreateRequest request = new OrderFulfillmentSplitClient.CreateRequest(
|
||||||
@@ -386,6 +392,24 @@ public class PdaPickSettlementService {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 一件代发逐箱封托已经冻结 SKU 归属;测量批次只承担整票体积重量,不再冒充 SKU 明细。 */
|
||||||
|
private static List<MeasurementSplitLine> wholePalletMeasurementLines(List<MeasurementItemRow> 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<Long, Integer> measurementQuantities(List<MeasurementSplitLine> 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,
|
private void consumePickedAllocations(long accountId, long inboundId,
|
||||||
List<InboundPalletAllocationEntity> confirmed, int pickedCount) {
|
List<InboundPalletAllocationEntity> confirmed, int pickedCount) {
|
||||||
int toConsume = pickedCount;
|
int toConsume = pickedCount;
|
||||||
|
|||||||
@@ -202,6 +202,35 @@ class PdaPickSettlementServiceTest {
|
|||||||
&& request.remainingItems().isEmpty()));
|
&& request.remainingItems().isEmpty()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void settlesWholeDropshipPalletWithSkuFactsInsteadOfAggregateMeasurementDetail() {
|
||||||
|
List<LoadingPickItemEntity> 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
|
@Test
|
||||||
void onlyPureSkippedInboundRestoresCandidate() {
|
void onlyPureSkippedInboundRestoresCandidate() {
|
||||||
LoadingPickItemEntity item = item(12L, 22L, 32L, 42L, 8, 0, "SKIPPED");
|
LoadingPickItemEntity item = item(12L, 22L, 32L, 42L, 8, 0, "SKIPPED");
|
||||||
|
|||||||
Reference in New Issue
Block a user