fix(dropship): preserve carton quantities and shared pallet outbound ownership
This commit is contained in:
+10
-1
@@ -157,11 +157,20 @@ public record FirstLegLoadingPlanCompletedV2(
|
||||
/** 托盘下来源入库明细、SKU、箱数、数量及箱号冻结快照;同 SKU 多明细按 sourceInboundDetailId 精确区分。 */
|
||||
public record SkuPackageSnapshot(String sourceInboundDetailId, String skuCode, String productName,
|
||||
int packageCount, int productQuantity,
|
||||
List<String> cartonMarks) {
|
||||
List<String> cartonMarks, java.util.Map<String, Integer> cartonQuantities) {
|
||||
public SkuPackageSnapshot {
|
||||
if (sourceInboundDetailId == null || sourceInboundDetailId.isBlank()) {
|
||||
throw new IllegalArgumentException("sourceInboundDetailId is required for each pallet SKU snapshot");
|
||||
}
|
||||
if (cartonMarks == null || cartonQuantities == null || cartonMarks.size() != packageCount
|
||||
|| new java.util.HashSet<>(cartonMarks).size() != packageCount
|
||||
|| !cartonQuantities.keySet().equals(new java.util.HashSet<>(cartonMarks))
|
||||
|| cartonQuantities.values().stream().anyMatch(quantity -> quantity == null || quantity <= 0)
|
||||
|| cartonQuantities.values().stream().mapToLong(Integer::longValue).sum() != productQuantity) {
|
||||
throw new IllegalArgumentException("cartonQuantities must exactly cover cartonMarks and productQuantity");
|
||||
}
|
||||
cartonMarks = List.copyOf(cartonMarks);
|
||||
cartonQuantities = java.util.Map.copyOf(cartonQuantities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ components:
|
||||
items: {$ref: '#/components/schemas/FirstLegSkuPackageSnapshotV2'}
|
||||
FirstLegSkuPackageSnapshotV2:
|
||||
type: object
|
||||
required: [sourceInboundDetailId, skuCode, productName, packageCount, productQuantity, cartonMarks]
|
||||
required: [sourceInboundDetailId, skuCode, productName, packageCount, productQuantity, cartonMarks, cartonQuantities]
|
||||
properties:
|
||||
sourceInboundDetailId: {type: string, minLength: 1, description: Exact source inbound detail identity matching the order item; never infer from SKU because multiple details may share a SKU}
|
||||
skuCode: {type: string}
|
||||
@@ -74,6 +74,10 @@ components:
|
||||
packageCount: {type: integer, minimum: 1}
|
||||
productQuantity: {type: integer, minimum: 1}
|
||||
cartonMarks: {type: array, items: {type: string}, minItems: 1}
|
||||
cartonQuantities:
|
||||
type: object
|
||||
description: 箱唛到逐箱实收件数的完整映射;键与 cartonMarks 相同,件数合计等于 productQuantity
|
||||
additionalProperties: {type: integer, minimum: 1}
|
||||
OverseasContainerUnloadedV1:
|
||||
type: object
|
||||
required: [sourceContainerBookingId, containerNo, unloadingCompletedAt, unloadingPhotoUploadedAt]
|
||||
|
||||
+18
-4
@@ -11,15 +11,29 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class FirstLegLoadingPlanCompletedV2Test {
|
||||
@Test
|
||||
void exactCartonQuantitiesRejectMissingMarksAndIncorrectTotals() {
|
||||
var marks = List.of("BOX-A", "BOX-B");
|
||||
var snapshot = new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot("101", "SKU", "产品", 2, 20,
|
||||
marks, java.util.Map.of("BOX-A", 7, "BOX-B", 13));
|
||||
assertThat(snapshot.cartonQuantities().get("BOX-B")).isEqualTo(13);
|
||||
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot("101", "SKU", "产品", 2, 20,
|
||||
marks, java.util.Map.of("BOX-A", 20))).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot("101", "SKU", "产品", 2, 20,
|
||||
marks, java.util.Map.of("BOX-A", 7, "BOX-B", 12))).isInstanceOf(IllegalArgumentException.class);
|
||||
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot("101", "SKU", "产品", 2, 20,
|
||||
marks, java.util.Map.of("BOX-A", 0, "BOX-B", 20))).isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void palletSkuRequiresExplicitSourceDetailIncludingJsonDeserialization() throws Exception {
|
||||
var mapper = new ObjectMapper();
|
||||
var first = new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot("101", "SAME", "产品", 1, 10, List.of("BOX-101"));
|
||||
var second = new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot("102", "SAME", "产品", 1, 20, List.of("BOX-102"));
|
||||
var first = new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot("101", "SAME", "产品", 1, 10, List.of("BOX-101"), java.util.Map.of("BOX-101", 10));
|
||||
var second = new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot("102", "SAME", "产品", 1, 20, List.of("BOX-102"), java.util.Map.of("BOX-102", 20));
|
||||
assertThat(mapper.readValue(mapper.writeValueAsString(first), FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot.class))
|
||||
.isEqualTo(first).isNotEqualTo(second);
|
||||
for (String invalid : new String[]{null, "", " "}) {
|
||||
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot(invalid, "SAME", "产品", 1, 10, List.of("BOX-101")))
|
||||
assertThatThrownBy(() -> new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot(invalid, "SAME", "产品", 1, 10, List.of("BOX-101"), java.util.Map.of("BOX-101", 10)))
|
||||
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("sourceInboundDetailId");
|
||||
}
|
||||
assertThatThrownBy(() -> mapper.readValue("{\"skuCode\":\"SAME\",\"packageCount\":1,\"productQuantity\":10,\"cartonMarks\":[\"BOX-101\"]}",
|
||||
@@ -81,7 +95,7 @@ class FirstLegLoadingPlanCompletedV2Test {
|
||||
@Test
|
||||
void carriesDropshipPalletAndCartonSnapshots() throws Exception {
|
||||
var sku = new FirstLegLoadingPlanCompletedV2.SkuPackageSnapshot(
|
||||
"101", "SKU", "产品", 2, 20, List.of("BOX-001", "BOX-002"));
|
||||
"101", "SKU", "产品", 2, 20, List.of("BOX-001", "BOX-002"), java.util.Map.of("BOX-001", 7, "BOX-002", 13));
|
||||
var pallet = new FirstLegLoadingPlanCompletedV2.PalletSnapshot("PLT-01", List.of(sku));
|
||||
var order = new FirstLegLoadingPlanCompletedV2.Order(
|
||||
"2", "2", "3", "ORD", "3", "RK", null, null, null,
|
||||
|
||||
Reference in New Issue
Block a user