fix(dropship): preserve carton quantities and shared pallet outbound ownership

This commit is contained in:
2026-09-14 20:55:09 +08:00
parent 6dbcb2128c
commit 4f13ee2ec3
24 changed files with 302 additions and 99 deletions
@@ -141,6 +141,18 @@ class LoadingCompletedConsumerTest {
}
verify(dropshipOrders).receive(any());
}
@Test void preservesNonUniformCartonQuantitiesAcrossTheEventBoundary() {
EventEnvelope original=overseasDropshipEvent(false);
var node=(com.fasterxml.jackson.databind.node.ObjectNode)original.payload().deepCopy();
var sku=(com.fasterxml.jackson.databind.node.ObjectNode)node.path("orders").get(0).path("pallets").get(0).path("skuPackages").get(0);
var quantities=sku.putObject("cartonQuantities");
quantities.put("RK1#5-1",6).put("RK1#5-2",7).put("RK1#5-3",8).put("RK1#5-4",9).put("RK1#5-5",20);
service.consume(rehash(original,node));
var captured=ArgumentCaptor.forClass(DropshipRequests.Receive.class);
verify(dropshipOrders).receive(captured.capture());
assertEquals(Map.of("RK1#5-1",6L,"RK1#5-2",7L,"RK1#5-3",8L,"RK1#5-4",9L,"RK1#5-5",20L),
captured.getValue().pallets().get(0).skuPackages().get(0).cartonQuantities());
}
@Test void rejectsAmbiguousDropshipSkuSourceBeforeWritingReceivingLabels() {
var failure=assertThrows(IllegalArgumentException.class,()->service.consume(overseasDropshipEvent(true)));
assertTrue(failure.getMessage().contains("来源明细"));
@@ -159,6 +171,7 @@ class LoadingCompletedConsumerTest {
var secondSku=(com.fasterxml.jackson.databind.node.ObjectNode)secondPallet.path("skuPackages").get(0);
var marks=secondSku.putArray("cartonMarks"); for(int n=6;n<=10;n++)marks.add("RK1#10-"+n);
((com.fasterxml.jackson.databind.node.ArrayNode)order.path("pallets")).add(secondPallet);
addManifest(secondPallet, 5);
service.consume(rehash(original,node));
@@ -230,5 +243,15 @@ class LoadingCompletedConsumerTest {
private EventEnvelope event(){ var item1=new FirstLegLoadingPlanCompletedV1.Item("d-1","SKU-1","产品","Product",5,50,1,new BigDecimal("10"),new BigDecimal("9"),new BigDecimal("12"),new BigDecimal("1.2"),"wh-1","ONT8","FBA ONT8","FBA-1","SHIP-1","MARK-1"); var item2=new FirstLegLoadingPlanCompletedV1.Item("d-2","SKU-2","产品2","Product2",3,30,2,new BigDecimal("8"),new BigDecimal("7"),new BigDecimal("9"),new BigDecimal("0.8"),"wh-2","LAX9","FBA LAX9","FBA-2","SHIP-2","MARK-2"); var p=new FirstLegLoadingPlanCompletedV1("plan-1","LP1","book-1","MSCU6639870","CAB1","ow-1","洛杉矶海外仓",List.of(new FirstLegLoadingPlanCompletedV1.Order("in-1","root-1","ord-1","FL1","root-o1","RK1","wh-1","ONT8","FBA ONT8",5,50,1,new BigDecimal("10"),new BigDecimal("9"),new BigDecimal("1.2"),List.of(item1),"100","Customer 100","FIRST_LEG",List.of()),new FirstLegLoadingPlanCompletedV1.Order("in-2","root-2","ord-2","FL2","root-o2","RK2","wh-2","LAX9","FBA LAX9",3,30,2,new BigDecimal("8"),new BigDecimal("7"),new BigDecimal("0.8"),List.of(item2),"100","Customer 100","FIRST_LEG",List.of()))); JsonNode node=json.valueToTree(p); String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node)); return new EventEnvelope("123e4567-e89b-12d3-a456-426614174000",EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED,1,Instant.now(),RegionCode.CN,RegionCode.OVERSEAS,"100","LOADING_PLAN","plan-1",2,1L,"123e4567-e89b-12d3-a456-426614174001","FIRSTLEG_LOADING_COMPLETED:plan-1:100:1",null,node,hash); }
private JsonNode v2Payload(EventEnvelope source){ com.fasterxml.jackson.databind.node.ObjectNode node=source.payload().deepCopy(); node.set("sourceContainerBookingId",node.remove("containerBookingId")); return node; }
private EventEnvelope eventForPayload(FirstLegLoadingPlanCompletedV1 payload, long version){ JsonNode node=json.valueToTree(payload); for(JsonNode order:node.path("orders")) for(JsonNode pallet:order.path("pallets")) addManifest(pallet,order.path("packageCount").asInt()); String hash=EventPayloadHash.sha256(EventPayloadCanonicalizer.canonicalize(node)); return new EventEnvelope("123e4567-e89b-12d3-a456-426614174099",EventTypes.FIRST_LEG_LOADING_PLAN_COMPLETED,1,Instant.now(),RegionCode.CN,RegionCode.OVERSEAS,"100","LOADING_PLAN",payload.loadingPlanId(),2,version,"123e4567-e89b-12d3-a456-426614174098","FIRSTLEG_LOADING_COMPLETED:"+payload.loadingPlanId()+":100:1",null,node,hash); }
private void addManifest(JsonNode pallet,int packageCount){ var object=(com.fasterxml.jackson.databind.node.ObjectNode)pallet; object.put("manifestVersion",1).put("manifestPackageCount",packageCount).put("manifestFragmentCount",1).put("manifestHash","a".repeat(64)); }
private void addManifest(JsonNode pallet,int packageCount){
var object=(com.fasterxml.jackson.databind.node.ObjectNode)pallet;
object.put("manifestVersion",1).put("manifestPackageCount",packageCount).put("manifestFragmentCount",1).put("manifestHash","a".repeat(64));
// These fixtures explicitly represent uniform cartons; production must never infer this ratio.
for(JsonNode sku:object.path("skuPackages")) {
int count=sku.path("packageCount").asInt(), quantity=sku.path("productQuantity").asInt();
assertTrue(count>0 && quantity%count==0);
var quantities=((com.fasterxml.jackson.databind.node.ObjectNode)sku).putObject("cartonQuantities");
for(JsonNode mark:sku.path("cartonMarks")) quantities.put(mark.asText(),quantity/count);
}
}
}