fix(dropship): preserve carton quantities and shared pallet outbound ownership
This commit is contained in:
+19
-54
@@ -293,61 +293,28 @@ public class LoadingCompletedOutboxService {
|
||||
|
||||
private List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> palletSnapshots(List<Row> values, List<PalletRow> source,
|
||||
Map<String, PalletManifest> manifests) {
|
||||
if (source.isEmpty()) return List.of();
|
||||
if (source.stream().anyMatch(item -> Boolean.TRUE.equals(item.getActualBinding()))) {
|
||||
return actualPalletSnapshots(values, source, manifests);
|
||||
if (source.isEmpty() || source.stream().anyMatch(item -> !Boolean.TRUE.equals(item.getActualBinding()))) {
|
||||
throw new BizException(ResultCode.CONFLICT, "一件代发必须提供真实逐箱实收托盘快照");
|
||||
}
|
||||
Map<String, List<PalletRow>> byPallet = source.stream().collect(java.util.stream.Collectors.groupingBy(
|
||||
p -> (p.getAllocationNo() == null ? "" : p.getAllocationNo()) + "|" + p.getPalletCode(),
|
||||
LinkedHashMap::new, java.util.stream.Collectors.toList()));
|
||||
int loadedPackages = byPallet.values().stream().map(list -> list.get(0)).map(PalletRow::getAllocatedPackageCount)
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
|
||||
if (loadedPackages != sumInt(values, Row::getPackageCount)) {
|
||||
throw new BizException(ResultCode.CONFLICT, "一键代发实装箱数与托盘已拣箱数不一致");
|
||||
}
|
||||
Map<Long, DetailSnapshot> details = new LinkedHashMap<>();
|
||||
values.stream().sorted(Comparator.comparing(Row::getSourceInboundDetailId)).forEach(row ->
|
||||
details.putIfAbsent(row.getSourceInboundDetailId(), new DetailSnapshot(row.getSourceInboundDetailId(),
|
||||
row.getSkuCode(), row.getProductNameZh(), row.getPackageCount(), row.getProductQuantity(), new ArrayList<>())));
|
||||
source.stream().filter(item -> item.getBoxCode() != null).forEach(item -> {
|
||||
DetailSnapshot detail = details.get(item.getSourceInboundDetailId());
|
||||
if (detail != null && !detail.marks().contains(item.getBoxCode())) detail.marks().add(item.getBoxCode());
|
||||
});
|
||||
Map<Long, Integer> remainingPackages = details.values().stream().collect(java.util.stream.Collectors.toMap(
|
||||
DetailSnapshot::detailId, DetailSnapshot::packageCount, (a,b)->a, LinkedHashMap::new));
|
||||
Map<Long, Integer> remainingMarks = details.values().stream().collect(java.util.stream.Collectors.toMap(
|
||||
DetailSnapshot::detailId, ignored -> 0, (a,b)->a, LinkedHashMap::new));
|
||||
List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> result = new ArrayList<>();
|
||||
for (List<PalletRow> palletRows : byPallet.values()) {
|
||||
PalletRow first = palletRows.get(0);
|
||||
int capacity = first.getAllocatedPackageCount() == null ? 0 : first.getAllocatedPackageCount();
|
||||
List<FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot> sku = new ArrayList<>();
|
||||
for (DetailSnapshot detail : details.values()) {
|
||||
int remaining = remainingPackages.getOrDefault(detail.detailId(), 0);
|
||||
if (capacity <= 0 || remaining <= 0) continue;
|
||||
int count = Math.min(capacity, remaining);
|
||||
int totalQuantity = detail.productQuantity() == null ? count
|
||||
: (int) Math.round((double) detail.productQuantity() * count / Math.max(1, detail.packageCount()));
|
||||
int markStart = remainingMarks.getOrDefault(detail.detailId(), 0);
|
||||
int markEnd = Math.min(detail.marks().size(), markStart + count);
|
||||
List<String> marks = new ArrayList<>(detail.marks().subList(markStart, markEnd));
|
||||
if (marks.size() != count) throw new BizException(ResultCode.CONFLICT, "一键代发箱唛数量与托盘箱数不一致");
|
||||
sku.add(new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot(detail.detailId().toString(), detail.skuCode(),
|
||||
detail.productName(), count, totalQuantity, marks));
|
||||
remainingPackages.put(detail.detailId(), remaining - count);
|
||||
remainingMarks.put(detail.detailId(), markEnd);
|
||||
capacity -= count;
|
||||
}
|
||||
if (!sku.isEmpty()) result.add(new FirstLegLoadingPlanCompletedV2.PalletSnapshot(
|
||||
first.getPalletCode() == null ? "PALLET-" + first.getAllocationNo() : first.getPalletCode(), sku));
|
||||
}
|
||||
if (remainingPackages.values().stream().mapToInt(Integer::intValue).sum() != 0) {
|
||||
throw new BizException(ResultCode.CONFLICT, "一键代发托盘未覆盖全部实装货物明细");
|
||||
}
|
||||
return result;
|
||||
return actualPalletSnapshots(values, source, manifests);
|
||||
}
|
||||
|
||||
/** 现场逐箱扫描已给出精确箱到托关系,不再使用旧的按托盘容量顺序推算。 */
|
||||
private Map<String, Integer> cartonQuantities(List<PalletRow> rows, List<String> marks) {
|
||||
Map<String, Integer> quantities = new LinkedHashMap<>();
|
||||
for (PalletRow row : rows) {
|
||||
if (!marks.contains(row.getBoxCode())) continue;
|
||||
Integer quantity = row.getActualSkuQuantity();
|
||||
if (quantity == null || quantity <= 0 || quantities.putIfAbsent(row.getBoxCode(), quantity) != null) {
|
||||
throw new BizException(ResultCode.CONFLICT, "一件代发箱唛缺少有效的逐箱实点 SKU 数量");
|
||||
}
|
||||
}
|
||||
if (quantities.size() != marks.size()) {
|
||||
throw new BizException(ResultCode.CONFLICT, "一件代发逐箱实点 SKU 数量未覆盖全部箱唛");
|
||||
}
|
||||
return quantities;
|
||||
}
|
||||
|
||||
private List<FirstLegLoadingPlanCompletedV2.PalletSnapshot> actualPalletSnapshots(
|
||||
List<Row> values, List<PalletRow> source, Map<String, PalletManifest> manifests) {
|
||||
Map<Long, Row> details = values.stream().collect(java.util.stream.Collectors.toMap(
|
||||
@@ -373,7 +340,7 @@ public class LoadingCompletedOutboxService {
|
||||
.filter(Objects::nonNull).mapToInt(Integer::intValue).sum();
|
||||
if (quantity <= 0) throw new BizException(ResultCode.CONFLICT, "一件代发现场托盘缺少实点 SKU 数量");
|
||||
skus.add(new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot(detail.getSourceInboundDetailId().toString(), detail.getSkuCode(),
|
||||
detail.getProductNameZh(), marks.size(), quantity, marks));
|
||||
detail.getProductNameZh(), marks.size(), quantity, marks, cartonQuantities(detailRows.getValue(), marks)));
|
||||
actualPackages += marks.size();
|
||||
}
|
||||
PalletManifest manifest = manifests.get(pallet.getKey());
|
||||
@@ -415,8 +382,6 @@ public class LoadingCompletedOutboxService {
|
||||
return "ONE_CLICK_DROPSHIP".equals(value);
|
||||
}
|
||||
|
||||
private record DetailSnapshot(Long detailId, String skuCode, String productName,
|
||||
Integer packageCount, Integer productQuantity, List<String> marks) {}
|
||||
private record PalletManifest(int version, int packageCount, int fragmentCount, String hash) {}
|
||||
|
||||
private void insert(EventEnvelope envelope) {
|
||||
|
||||
+14
-5
@@ -290,7 +290,7 @@ class LoadingCompletedOutboxServiceTest {
|
||||
|
||||
@org.junit.jupiter.params.ParameterizedTest
|
||||
@org.junit.jupiter.params.provider.ValueSource(booleans = {true, false})
|
||||
void freezesRepeatedSkuCartonsBySourceDetailForBothPalletSources(boolean actualBinding) throws Exception {
|
||||
void freezesActualSkuCartonsAndRejectsInferredPalletSources(boolean actualBinding) throws Exception {
|
||||
Row first = row(11, 100, 101); first.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
first.setSkuCode("SAME-SKU"); first.setProductQuantity(10);
|
||||
Row second = row(11, 100, 102); second.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
@@ -305,6 +305,10 @@ class LoadingCompletedOutboxServiceTest {
|
||||
when(snapshots.palletRows(90L)).thenReturn(List.of(
|
||||
pallet(11,100,1,"DP-260909-0001",2,102,"SAME-SKU",1,20,"BOX-102"),
|
||||
pallet(11,100,1,"DP-260909-0001",2,101,"SAME-SKU",1,10,"BOX-101")));
|
||||
assertThatThrownBy(() -> service.publish(90L)).isInstanceOf(com.baiyue.common.core.exception.BizException.class)
|
||||
.hasMessageContaining("真实逐箱实收");
|
||||
verify(outbox, org.mockito.Mockito.never()).insert(org.mockito.ArgumentMatchers.any(InboundOutboxEntity.class));
|
||||
return;
|
||||
}
|
||||
service.publish(90L);
|
||||
service.publish(90L);
|
||||
@@ -320,6 +324,7 @@ class LoadingCompletedOutboxServiceTest {
|
||||
assertThat(sku.path("cartonMarks")).hasSize(1);
|
||||
assertThat(sku.path("cartonMarks").get(0).asText()).isEqualTo("BOX-" + detail);
|
||||
assertThat(sku.path("productQuantity").asInt()).isEqualTo("101".equals(detail) ? 10 : 20);
|
||||
assertThat(sku.path("cartonQuantities").path("BOX-" + detail).asInt()).isEqualTo("101".equals(detail) ? 10 : 20);
|
||||
}
|
||||
assertThat(frozen.get(0).path("sourceInboundDetailId"))
|
||||
.isNotEqualTo(frozen.get(1).path("sourceInboundDetailId"));
|
||||
@@ -332,10 +337,11 @@ class LoadingCompletedOutboxServiceTest {
|
||||
Row second = row(11, 100, 102); second.setFulfillmentType("ONE_CLICK_DROPSHIP");
|
||||
second.setPackageCount(1); second.setProductQuantity(5); second.setSkuCode("SKU-B");
|
||||
when(snapshots.rows(90L)).thenReturn(List.of(first, second));
|
||||
when(snapshots.palletRows(90L)).thenReturn(List.of(
|
||||
pallet(11,100,1,"PLT-01",2,101,"SKU-A",2,20,"BOX-001"),
|
||||
pallet(11,100,1,"PLT-01",2,101,"SKU-A",2,20,"BOX-002"),
|
||||
pallet(11,100,2,"PLT-02",1,102,"SKU-B",1,5,"BOX-003")));
|
||||
when(snapshots.workPalletCount(90L)).thenReturn(2);
|
||||
when(snapshots.actualPalletRows(90L)).thenReturn(List.of(
|
||||
actualPallet(11,100,"PLT-01",101,"SKU-A","BOX-001",7),
|
||||
actualPallet(11,100,"PLT-01",101,"SKU-A","BOX-002",13),
|
||||
actualPallet(11,100,"PLT-02",102,"SKU-B","BOX-003",5)));
|
||||
|
||||
service.publish(90L);
|
||||
|
||||
@@ -346,6 +352,8 @@ class LoadingCompletedOutboxServiceTest {
|
||||
assertThat(event.payload().at("/orders/0/customerId").asText()).isEqualTo("11");
|
||||
assertThat(event.payload().at("/orders/0/pallets")).hasSize(2);
|
||||
assertThat(event.payload().at("/orders/0/pallets/0/skuPackages/0/cartonMarks")).hasSize(2);
|
||||
assertThat(event.payload().at("/orders/0/pallets/0/skuPackages/0/cartonQuantities/BOX-001").asInt()).isEqualTo(7);
|
||||
assertThat(event.payload().at("/orders/0/pallets/0/skuPackages/0/cartonQuantities/BOX-002").asInt()).isEqualTo(13);
|
||||
assertThat(event.payload().at("/orders/0/palletCount").asInt()).isEqualTo(2);
|
||||
}
|
||||
|
||||
@@ -463,6 +471,7 @@ class LoadingCompletedOutboxServiceTest {
|
||||
String sku,String boxCode,int actualSkuQuantity) {
|
||||
PalletRow row = pallet(account,inbound,0,palletNo,1,detail,sku,1,1,boxCode);
|
||||
row.setActualBinding(true);
|
||||
row.setPalletId(Integer.toUnsignedLong(palletNo.hashCode()) + 1L);
|
||||
row.setActualSkuQuantity(actualSkuQuantity);
|
||||
return row;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user