Merge branch 'dev' into dev-lyhfive
# Conflicts: # firstleg/src/main/java/com/baiyue/firstleg/service/LoadingCompletedOutboxService.java # firstleg/src/test/java/com/baiyue/firstleg/service/LoadingCompletedOutboxServiceTest.java # lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/controller/InboundPdaController.java # lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/dto/PdaResponses.java # lastleg-service/src/main/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowService.java # lastleg-service/src/main/java/com/baiyue/lastleg/service/LoadingCompletedConsumer.java # lastleg-service/src/test/java/com/baiyue/lastleg/inboundplan/pda/service/InboundPdaWorkflowServiceTest.java # lastleg-service/src/test/java/com/baiyue/lastleg/service/LoadingCompletedConsumerTest.java
This commit is contained in:
@@ -10,6 +10,8 @@ public final class EventTypes {
|
||||
public static final String ORDER_CANCELLED = "OrderCancelled";
|
||||
public static final String FIRST_LEG_SHIPMENT_DEPARTED = "FirstLegShipmentDeparted";
|
||||
public static final String FIRST_LEG_LOADING_PLAN_COMPLETED = "FirstLegLoadingPlanCompleted";
|
||||
public static final String FIRST_LEG_CONTAINER_SHIPPING_NODE_RECORDED =
|
||||
"FirstLegContainerShippingNodeRecorded";
|
||||
public static final String OVERSEAS_CONTAINER_UNLOADED = "OverseasContainerUnloaded";
|
||||
public static final String IAM_USER_STATUS_CHANGED = "IamUserStatusChanged";
|
||||
public static final String IAM_TENANT_STATUS_CHANGED = "IamTenantStatusChanged";
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.baiyue.common.event;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
/** Actual carrier milestone recorded by Firstleg for overseas receiving readiness. */
|
||||
public record FirstLegContainerShippingNodeRecordedV1(
|
||||
String queryId,
|
||||
String customerId,
|
||||
String oceanBookingId,
|
||||
String containerBookingId,
|
||||
String containerNo,
|
||||
String standardNode,
|
||||
int nodeSequence,
|
||||
Instant observedAt,
|
||||
Instant actualAt,
|
||||
String locationCode,
|
||||
String locationName,
|
||||
String providerCode,
|
||||
String providerEventId,
|
||||
String providerNodeCode,
|
||||
String sourceReference,
|
||||
String rawPayloadHash) {
|
||||
private static final Map<String, Integer> STANDARD_NODES = Map.of(
|
||||
"DEPARTED_WAREHOUSE", 10, "AT_ORIGIN_PORT", 20, "LOADED_ON_VESSEL", 30,
|
||||
"DEPARTED_ORIGIN_PORT", 40, "IN_TRANSIT", 50, "ARRIVED_DESTINATION_PORT", 60,
|
||||
"DISCHARGED_FROM_VESSEL", 70, "PICKUP_READY", 80, "PICKED_UP", 90,
|
||||
"EMPTY_RETURNED", 100);
|
||||
|
||||
public FirstLegContainerShippingNodeRecordedV1 {
|
||||
if (queryId == null || queryId.isBlank() || customerId == null || customerId.isBlank()
|
||||
|| oceanBookingId == null || oceanBookingId.isBlank()
|
||||
|| containerBookingId == null || containerBookingId.isBlank()
|
||||
|| containerNo == null || containerNo.isBlank()) {
|
||||
throw new IllegalArgumentException("船节点交接业务标识不能为空");
|
||||
}
|
||||
if (standardNode == null || !Integer.valueOf(nodeSequence).equals(STANDARD_NODES.get(standardNode))
|
||||
|| observedAt == null || actualAt == null) {
|
||||
throw new IllegalArgumentException("船节点交接只接受带实际时间的标准节点事实");
|
||||
}
|
||||
if (providerCode == null || providerCode.isBlank() || providerEventId == null
|
||||
|| providerEventId.isBlank() || providerNodeCode == null || providerNodeCode.isBlank()
|
||||
|| rawPayloadHash == null || !rawPayloadHash.matches("(?i)^[0-9a-f]{64}$")) {
|
||||
throw new IllegalArgumentException("船节点交接供应商审计字段不完整");
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-2
@@ -16,12 +16,25 @@ public record FirstLegLoadingPlanCompletedV1(
|
||||
String destinationWarehouseCode, String destinationWarehouseName,
|
||||
int packageCount, Integer productQuantity, Integer palletCount,
|
||||
BigDecimal grossWeightKg, BigDecimal netWeightKg, BigDecimal volumeCbm,
|
||||
List<Item> items, String customerId, String orderType, List<PalletSnapshot> pallets) {
|
||||
List<Item> items, String customerId, String orderType, List<PalletSnapshot> pallets,
|
||||
String destinationType) {
|
||||
public Order {
|
||||
items = items == null ? List.of() : items;
|
||||
orderType = orderType == null || orderType.isBlank() ? "FIRST_LEG" : orderType;
|
||||
pallets = pallets == null ? List.of() : pallets;
|
||||
}
|
||||
public Order(String sourceInboundId, String rootInboundId,
|
||||
String sourceOrderId, String sourceOrderNo, String rootOrderId,
|
||||
String inboundCode, String destinationWarehouseId,
|
||||
String destinationWarehouseCode, String destinationWarehouseName,
|
||||
int packageCount, Integer productQuantity, Integer palletCount,
|
||||
BigDecimal grossWeightKg, BigDecimal netWeightKg, BigDecimal volumeCbm,
|
||||
List<Item> items, String customerId, String orderType, List<PalletSnapshot> pallets) {
|
||||
this(sourceInboundId, rootInboundId, sourceOrderId, sourceOrderNo, rootOrderId,
|
||||
inboundCode, destinationWarehouseId, destinationWarehouseCode,
|
||||
destinationWarehouseName, packageCount, productQuantity, palletCount,
|
||||
grossWeightKg, netWeightKg, volumeCbm, items, customerId, orderType, pallets, null);
|
||||
}
|
||||
public Order(String sourceInboundId, String rootInboundId,
|
||||
String sourceOrderId, String sourceOrderNo, String rootOrderId,
|
||||
String inboundCode, String destinationWarehouseId,
|
||||
@@ -32,7 +45,7 @@ public record FirstLegLoadingPlanCompletedV1(
|
||||
this(sourceInboundId, rootInboundId, sourceOrderId, sourceOrderNo, rootOrderId,
|
||||
inboundCode, destinationWarehouseId, destinationWarehouseCode,
|
||||
destinationWarehouseName, packageCount, productQuantity, palletCount,
|
||||
grossWeightKg, netWeightKg, volumeCbm, items, null, "FIRST_LEG", List.of());
|
||||
grossWeightKg, netWeightKg, volumeCbm, items, null, "FIRST_LEG", List.of(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+17
-2
@@ -15,13 +15,28 @@ public record FirstLegLoadingPlanCompletedV2(
|
||||
String destinationWarehouseCode, String destinationWarehouseName,
|
||||
int packageCount, Integer productQuantity, Integer palletCount,
|
||||
BigDecimal grossWeightKg, BigDecimal netWeightKg, BigDecimal volumeCbm,
|
||||
List<Item> items, String customerId, String orderType, List<PalletSnapshot> pallets) {
|
||||
List<Item> items, String customerId, String orderType, List<PalletSnapshot> pallets,
|
||||
/** 目的类型:FBA 可按箱数收货,OVERSEAS 必须逐箱扫码。 */
|
||||
String destinationType) {
|
||||
public Order {
|
||||
items = items == null ? List.of() : items;
|
||||
orderType = orderType == null || orderType.isBlank() ? "FIRST_LEG" : orderType;
|
||||
pallets = pallets == null ? List.of() : pallets;
|
||||
}
|
||||
|
||||
public Order(String sourceInboundId, String rootInboundId,
|
||||
String sourceOrderId, String sourceOrderNo, String rootOrderId,
|
||||
String inboundCode, String destinationWarehouseId,
|
||||
String destinationWarehouseCode, String destinationWarehouseName,
|
||||
int packageCount, Integer productQuantity, Integer palletCount,
|
||||
BigDecimal grossWeightKg, BigDecimal netWeightKg, BigDecimal volumeCbm,
|
||||
List<Item> items, String customerId, String orderType, List<PalletSnapshot> pallets) {
|
||||
this(sourceInboundId, rootInboundId, sourceOrderId, sourceOrderNo, rootOrderId,
|
||||
inboundCode, destinationWarehouseId, destinationWarehouseCode,
|
||||
destinationWarehouseName, packageCount, productQuantity, palletCount,
|
||||
grossWeightKg, netWeightKg, volumeCbm, items, customerId, orderType, pallets, null);
|
||||
}
|
||||
|
||||
public Order(String sourceInboundId, String rootInboundId,
|
||||
String sourceOrderId, String sourceOrderNo, String rootOrderId,
|
||||
String inboundCode, String destinationWarehouseId,
|
||||
@@ -32,7 +47,7 @@ public record FirstLegLoadingPlanCompletedV2(
|
||||
this(sourceInboundId, rootInboundId, sourceOrderId, sourceOrderNo, rootOrderId,
|
||||
inboundCode, destinationWarehouseId, destinationWarehouseCode,
|
||||
destinationWarehouseName, packageCount, productQuantity, palletCount,
|
||||
grossWeightKg, netWeightKg, volumeCbm, items, null, "FIRST_LEG", List.of());
|
||||
grossWeightKg, netWeightKg, volumeCbm, items, null, "FIRST_LEG", List.of(), null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -2,7 +2,10 @@ package com.baiyue.common.event;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/** 海外仓卸柜完成事实;国内订舱 ID 是稳定聚合标识,containerNo 仅用于核对物理柜号。 */
|
||||
/**
|
||||
* 海外仓柜级卸柜完成事实;国内订舱 ID 是稳定聚合与幂等标识,containerNo 用于核对物理柜号。
|
||||
* unloadingCompletedAt 是全柜 complete 时刻,不得使用签收拍照上传时刻代替。
|
||||
*/
|
||||
public record OverseasContainerUnloadedV1(
|
||||
String sourceContainerBookingId,
|
||||
String containerNo,
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package com.baiyue.common.event;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
|
||||
class FirstLegContainerShippingNodeRecordedV1Test {
|
||||
@Test
|
||||
void roundTripsLongBusinessIdsAndActualPickupReadyFact() throws Exception {
|
||||
var payload = payload();
|
||||
var mapper = new ObjectMapper().findAndRegisterModules();
|
||||
|
||||
var restored = mapper.readValue(mapper.writeValueAsString(payload),
|
||||
FirstLegContainerShippingNodeRecordedV1.class);
|
||||
|
||||
assertThat(restored).isEqualTo(payload);
|
||||
assertThat(restored.containerBookingId()).isEqualTo("2088000000000000003");
|
||||
assertThat(restored.standardNode()).isEqualTo("PICKUP_READY");
|
||||
assertThat(restored.nodeSequence()).isEqualTo(80);
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsEstimatedOnlyOrIncompleteProviderFacts() {
|
||||
assertThatThrownBy(() -> new FirstLegContainerShippingNodeRecordedV1(
|
||||
"1", "2", "3", "4", "ZCSU7881970", "PICKUP_READY", 80,
|
||||
Instant.parse("2026-08-25T01:00:00Z"), null, null, null,
|
||||
"ZIM", "evt-1", "Carrier Release", "ZIM:evt-1", "a".repeat(64)))
|
||||
.isInstanceOf(IllegalArgumentException.class).hasMessageContaining("实际时间");
|
||||
|
||||
assertThatThrownBy(() -> new FirstLegContainerShippingNodeRecordedV1(
|
||||
"1", "2", "3", "4", "ZCSU7881970", "ARRIVED", 60,
|
||||
Instant.parse("2026-08-25T01:00:00Z"), Instant.parse("2026-08-25T01:00:00Z"),
|
||||
null, null, "ZIM", "evt-2", "ARRIVED", "ZIM:evt-2", "b".repeat(64)))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
}
|
||||
|
||||
private static FirstLegContainerShippingNodeRecordedV1 payload() {
|
||||
return new FirstLegContainerShippingNodeRecordedV1(
|
||||
"2088000000000000001", "2088000000000000002", "2088000000000000004",
|
||||
"2088000000000000003", "ZCSU7881970", "PICKUP_READY", 80,
|
||||
Instant.parse("2026-08-25T01:02:00Z"), Instant.parse("2026-08-25T01:00:00Z"),
|
||||
"USLAX", "Los Angeles", "ZIM", "evt-1", "AVAILABLE",
|
||||
"ZIM:evt-1", "a".repeat(64));
|
||||
}
|
||||
}
|
||||
+13
@@ -47,4 +47,17 @@ class FirstLegLoadingPlanCompletedV1Test {
|
||||
assertThat(restored).isEqualTo(payload);
|
||||
assertThat(json).contains("ONE_CLICK_DROPSHIP", "PLT-01", "BOX-003", "customerId");
|
||||
}
|
||||
|
||||
@Test
|
||||
void legacyParserRetainsDestinationTypeWhenV2OrderCarriesIt() throws Exception {
|
||||
String json = """
|
||||
{"loadingPlanId":"1","planNo":"LP-1","containerBookingId":"2","containerNo":"CONT-1",
|
||||
"warehouseId":"3","warehouseName":"海外仓","orders":[{"sourceInboundId":"100",
|
||||
"destinationWarehouseId":"3","packageCount":1,"items":[],"destinationType":"OVERSEAS"}]}
|
||||
""";
|
||||
|
||||
FirstLegLoadingPlanCompletedV1 restored = mapper.readValue(json, FirstLegLoadingPlanCompletedV1.class);
|
||||
|
||||
assertThat(restored.orders().get(0).destinationType()).isEqualTo("OVERSEAS");
|
||||
}
|
||||
}
|
||||
|
||||
+12
@@ -40,6 +40,18 @@ class FirstLegLoadingPlanCompletedV2Test {
|
||||
assertThat(json.at("/orders/0/pallets/0/skuPackages/0/cartonMarks")).hasSize(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void carriesFrozenDestinationType() {
|
||||
var order = new FirstLegLoadingPlanCompletedV2.Order(
|
||||
"2", "2", "3", "ORD", "3", "RK", null, "ONT8", "FBA ONT8",
|
||||
2, 20, 0, null, null, null, List.of(), "11", "FIRST_LEG", List.of(), "FBA");
|
||||
|
||||
var json = new ObjectMapper().valueToTree(new FirstLegLoadingPlanCompletedV2(
|
||||
"4", "P", "5", "CONT", "CAB", "6", "WH", List.of(order)));
|
||||
|
||||
assertThat(json.at("/orders/0/destinationType").asText()).isEqualTo("FBA");
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapsEachOrdinaryCartonNumberToItsSku() {
|
||||
var item = new FirstLegLoadingPlanCompletedV2.Item("1", "SKU-PSC2", "产品", "Product", 2,
|
||||
|
||||
+2
@@ -20,5 +20,7 @@ class OverseasContainerUnloadedV1Test {
|
||||
|
||||
assertThat(mapper.readValue(json, OverseasContainerUnloadedV1.class)).isEqualTo(payload);
|
||||
assertThat(json).contains("sourceContainerBookingId", "1001", "MSCU1234567", "unloadingPhotoUploadedAt");
|
||||
assertThat(payload.unloadingCompletedAt()).isAfter(payload.unloadingPhotoUploadedAt());
|
||||
assertThat(payload.unloadingCompletedAt()).isNotEqualTo(payload.unloadingPhotoUploadedAt());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user