优化
This commit is contained in:
+10
@@ -86,6 +86,16 @@ class PlatformOceanBookingControllerContractTest {
|
||||
.contains("Sum of all container requirement quantities; independent of allocation, linking or unlinking.");
|
||||
}
|
||||
|
||||
@Test
|
||||
void openApiFreezesStandardCarrierCodeOnBooking() throws Exception {
|
||||
String yaml = Files.readString(Path.of("src/main/resources/openapi/ocean-bookings-v1.yaml"));
|
||||
assertThat(yaml)
|
||||
.contains("carrierCode:")
|
||||
.contains("enum: [MATSON, SML, ZIM, COSCO, EMC, HEDE, OTHER]")
|
||||
.contains("Required before RELEASE")
|
||||
.contains("OTHER means manual carrier-node tracking");
|
||||
}
|
||||
|
||||
private static Method method(String name) {
|
||||
return Arrays.stream(PlatformOceanBookingController.class.getDeclaredMethods())
|
||||
.filter(method -> method.getName().equals(name)).findFirst().orElseThrow();
|
||||
|
||||
+2
@@ -24,6 +24,8 @@ class OceanBookingSqlContractTest {
|
||||
.contains("MODIFY COLUMN customer_name VARCHAR(128) NULL COMMENT '可选客户名称快照'")
|
||||
.contains("service_term VARCHAR(32) NULL COMMENT '运输服务条款快照;发布前必填'")
|
||||
.contains("freight_term VARCHAR(32) NULL COMMENT '海运费支付条款快照;发布前必填'")
|
||||
.contains("carrier_code VARCHAR(16) NULL COMMENT '船司标准编码")
|
||||
.contains("carrier_code IS NULL OR carrier_code IN ('MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER')")
|
||||
.contains("container_booking_id BIGINT UNSIGNED NOT NULL COMMENT '明确引用现有柜次ID'")
|
||||
.contains("REFERENCES fl_container_booking(id)")
|
||||
.contains("REFERENCES fl_ocean_booking(id) ON DELETE CASCADE")
|
||||
|
||||
+3
-2
@@ -259,12 +259,12 @@ class OceanBookingServiceTest {
|
||||
}
|
||||
|
||||
private static DraftData partialDraft(String remark) {
|
||||
return new DraftData(null, null, null, null, null, null, null, null, null,
|
||||
return new DraftData(null, null, null, null, null, null, null, null, null, null,
|
||||
null, null, null, null, null, null, null, null, null, null, null, remark, null, null);
|
||||
}
|
||||
|
||||
private static DraftData completeDraft() {
|
||||
return new DraftData(100L, "客户", "SEA-US", 701L, "BL-001", 71L, "船公司",
|
||||
return new DraftData(100L, "客户", "SEA-US", 701L, "BL-001", 71L, "船公司", "MATSON",
|
||||
81L, null, "VESSEL", "V001", "CNSZX", "深圳", "USLAX", "洛杉矶",
|
||||
"LAX", "CY-CY", "PREPAID", LocalDateTime.of(2026, 9, 1, 0, 0),
|
||||
LocalDateTime.of(2026, 9, 20, 0, 0), null,
|
||||
@@ -279,6 +279,7 @@ class OceanBookingServiceTest {
|
||||
OceanBookingEntity value = booking(status, version);
|
||||
value.setCustomerId(100L); value.setCustomerName("客户"); value.setChannelCode("SEA-US");
|
||||
value.setWarehouseId(701L); value.setBillOfLadingNo("BL-001"); value.setShippingCompanyId(71L);
|
||||
value.setCarrierCode("MATSON");
|
||||
value.setBookingAgentId(81L); value.setVesselName("VESSEL"); value.setVoyageNo("V001");
|
||||
value.setServiceTerm("CY-CY"); value.setFreightTerm("PREPAID");
|
||||
value.setEstimatedSailingAt(LocalDateTime.of(2026, 9, 1, 0, 0));
|
||||
|
||||
+5
@@ -63,6 +63,8 @@ class CarrierScheduleQuerySqlContractTest {
|
||||
.contains("'EXCEPTION_OBSERVED','PROVIDER_QUERY','STOP'")
|
||||
.contains("provider_event_id")
|
||||
.contains("'PUBLIC_FORM','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API'")
|
||||
.contains("'MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER'")
|
||||
.contains("'OFFICIAL_API','MANUAL'")
|
||||
.contains("lease_owner")
|
||||
.contains("consecutive_failures")
|
||||
.doesNotContain("status IN ('READY','MANUAL_REQUIRED','ARRIVED'");
|
||||
@@ -102,11 +104,14 @@ class CarrierScheduleQuerySqlContractTest {
|
||||
.contains("ADD COLUMN next_allowed_at DATETIME(3) NULL")
|
||||
.contains("ADD KEY idx_carrier_schedule_query_due (status,next_allowed_at,id)")
|
||||
.contains("DROP CHECK chk_carrier_schedule_query_access")
|
||||
.contains("DROP CHECK chk_carrier_schedule_query_carrier")
|
||||
.contains("DROP CHECK chk_carrier_schedule_query_status")
|
||||
.contains("DROP INDEX uk_carrier_schedule_query_scope")
|
||||
.contains("ADD UNIQUE KEY uk_carrier_schedule_query_scope")
|
||||
.contains("(customer_id,ocean_booking_id,container_scope_id)")
|
||||
.contains("'PUBLIC_FORM','PUBLIC_WEB_PROXY','PUBLIC_HTML','HCAPTCHA','OFFICIAL_API'")
|
||||
.contains("'MATSON','SML','ZIM','COSCO','EMC','HEDE','OTHER'")
|
||||
.contains("'OFFICIAL_API','MANUAL'")
|
||||
.contains("status IN ('READY','MANUAL_REQUIRED','STOPPED')")
|
||||
.contains("ARRIVED_DESTINATION_PORT")
|
||||
.contains("EMPTY_RETURNED")
|
||||
|
||||
+12
@@ -39,4 +39,16 @@ class CarrierScheduleAdapterRegistryTest {
|
||||
assertThatThrownBy(() -> registry.prepare("UNKNOWN", "BOOKING_NO", "X"))
|
||||
.hasMessageContaining("仅支持MATSON");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundUnknownCarrierFallsBackToManualWithoutPretendingToBeAnotherProvider() {
|
||||
var result = registry.prepareOutbound("OTHER", "CONTAINER_NO", "CSNU1234567");
|
||||
|
||||
assertThat(result.carrierCode()).isEqualTo("OTHER");
|
||||
assertThat(result.accessMode()).isEqualTo("MANUAL");
|
||||
assertThat(result.status()).isEqualTo("MANUAL_REQUIRED");
|
||||
assertThat(result.coverageGapCode()).isEqualTo("UNSUPPORTED_CARRIER");
|
||||
assertThat(result.officialUrl()).isEmpty();
|
||||
assertThat(registry.prepareOutbound(null, "BOOKING_NO", "OB-1").carrierCode()).isEqualTo("OTHER");
|
||||
}
|
||||
}
|
||||
|
||||
+66
-2
@@ -47,12 +47,14 @@ class CarrierScheduleQueryServiceTest {
|
||||
@Mock private PortScheduleRepository portSchedules;
|
||||
@Mock private CarrierScheduleNodeOutboxService nodeOutbox;
|
||||
@InjectMocks private CarrierScheduleQueryService service;
|
||||
private CarrierScheduleAdapterRegistry adapters;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
set("objectMapper", new ObjectMapper().findAndRegisterModules());
|
||||
set("adapters", new CarrierScheduleAdapterRegistry());
|
||||
adapters = new CarrierScheduleAdapterRegistry();
|
||||
set("adapters", adapters);
|
||||
set("clock", Clock.fixed(Instant.parse("2026-08-10T04:00:00Z"), ZoneOffset.UTC));
|
||||
when(repository.claimOperation(any(), anyLong(), anyString(), anyString(), anyString())).thenReturn(true);
|
||||
when(repository.completeOperation(anyLong(), anyLong(), anyString(), anyString())).thenReturn(1);
|
||||
@@ -85,7 +87,7 @@ class CarrierScheduleQueryServiceTest {
|
||||
@Test
|
||||
void outboundConfirmationCreatesTaskWithContainerTrackingAndDepartureNode() {
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setCustomerId(10001L);
|
||||
booking.setShippingCompanyName("中远海运"); booking.setBookingNo("OB-260810-0001");
|
||||
booking.setShippingCompanyName("中远海运"); booking.setCarrierCode("COSCO"); booking.setBookingNo("OB-260810-0001");
|
||||
var container = new com.baiyue.firstleg.entity.ContainerBookingEntity();
|
||||
container.setContainerNo("CSNU1234567");
|
||||
when(portSchedules.oceanBooking(10L)).thenReturn(booking);
|
||||
@@ -96,12 +98,74 @@ class CarrierScheduleQueryServiceTest {
|
||||
|
||||
assertThat(result.getTriggerSource()).isEqualTo("OUTBOUND_CONFIRM");
|
||||
assertThat(result.getCarrierCode()).isEqualTo("COSCO");
|
||||
assertThat(result.getStatus()).isEqualTo("MANUAL_REQUIRED");
|
||||
assertThat(result.getAccessMode()).isEqualTo("OFFICIAL_API");
|
||||
assertThat(result.getManualReason()).contains("Cargo Tracking API");
|
||||
assertThat(result.getTrackingType()).isEqualTo("CONTAINER_NO");
|
||||
assertThat(result.getLatestStandardNode()).isEqualTo("DEPARTED_WAREHOUSE");
|
||||
assertThat(result.getNextAllowedAt()).isEqualTo(LocalDateTime.of(2026, 8, 10, 12, 30));
|
||||
verify(repository).insertIfAbsent(any(com.baiyue.firstleg.portschedule.entity.CarrierScheduleNodeFactEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundUnknownCarrierCreatesManualTaskAndPreservesBookingCarrierIdentity() {
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setCustomerId(10001L);
|
||||
booking.setShippingCompanyId(9988L); booking.setShippingCompanyName("示例船公司"); booking.setCarrierCode("OTHER");
|
||||
var container = new com.baiyue.firstleg.entity.ContainerBookingEntity();
|
||||
container.setContainerNo("CSNU1234567");
|
||||
when(portSchedules.oceanBooking(10L)).thenReturn(booking);
|
||||
when(portSchedules.containerBooking(20L)).thenReturn(container);
|
||||
when(portSchedules.allocation(10L, 20L)).thenReturn(new OceanBookingContainerAllocationEntity());
|
||||
|
||||
CarrierScheduleQueryEntity result = service.ensureFromOutboundConfirm(10001L, 10L, 20L, 7L, "admin");
|
||||
|
||||
assertThat(result.getCarrierCode()).isEqualTo("OTHER");
|
||||
assertThat(result.getAccessMode()).isEqualTo("MANUAL");
|
||||
assertThat(result.getStatus()).isEqualTo("MANUAL_REQUIRED");
|
||||
assertThat(result.getManualReason()).contains("carrierCode=OTHER", "shippingCompanyId=9988", "shippingCompanyName=示例船公司");
|
||||
assertThat(result.getNextAllowedAt()).isNull();
|
||||
assertThat(result.getLatestStandardNode()).isEqualTo("DEPARTED_WAREHOUSE");
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundSupportedCarrierCreatesReadyTask() {
|
||||
ReflectionTestUtils.setField(adapters, "matsonAutomated", true);
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setCustomerId(10001L);
|
||||
booking.setShippingCompanyName("美森"); booking.setCarrierCode("MATSON");
|
||||
var container = new com.baiyue.firstleg.entity.ContainerBookingEntity();
|
||||
container.setContainerNo("MATU4646247");
|
||||
when(portSchedules.oceanBooking(10L)).thenReturn(booking);
|
||||
when(portSchedules.containerBooking(20L)).thenReturn(container);
|
||||
when(portSchedules.allocation(10L, 20L)).thenReturn(new OceanBookingContainerAllocationEntity());
|
||||
|
||||
CarrierScheduleQueryEntity result = service.ensureFromOutboundConfirm(10001L, 10L, 20L, 7L, "admin");
|
||||
|
||||
assertThat(result.getCarrierCode()).isEqualTo("MATSON");
|
||||
assertThat(result.getStatus()).isEqualTo("READY");
|
||||
assertThat(result.getAccessMode()).isEqualTo("PUBLIC_WEB_PROXY");
|
||||
assertThat(result.getManualReason()).isNull();
|
||||
assertThat(result.getNextAllowedAt()).isEqualTo(LocalDateTime.of(2026, 8, 10, 12, 30));
|
||||
}
|
||||
|
||||
@Test
|
||||
void outboundReplayKeepsSingleTaskAndSingleDepartureFact() {
|
||||
OceanBookingEntity booking = new OceanBookingEntity(); booking.setCustomerId(10001L);
|
||||
booking.setShippingCompanyName("示例船公司"); booking.setCarrierCode("OTHER");
|
||||
var container = new com.baiyue.firstleg.entity.ContainerBookingEntity();
|
||||
container.setContainerNo("CSNU1234567");
|
||||
when(portSchedules.oceanBooking(10L)).thenReturn(booking);
|
||||
when(portSchedules.containerBooking(20L)).thenReturn(container);
|
||||
when(portSchedules.allocation(10L, 20L)).thenReturn(new OceanBookingContainerAllocationEntity());
|
||||
|
||||
CarrierScheduleQueryEntity first = service.ensureFromOutboundConfirm(10001L, 10L, 20L, 7L, "admin");
|
||||
when(repository.findByBusinessScope(10001L, 10L, 20L)).thenReturn(first);
|
||||
CarrierScheduleQueryEntity replay = service.ensureFromOutboundConfirm(10001L, 10L, 20L, 7L, "admin");
|
||||
|
||||
assertThat(replay).isSameAs(first);
|
||||
verify(repository, times(1)).insert(any(CarrierScheduleQueryEntity.class));
|
||||
verify(repository, times(1)).insertIfAbsent(any(CarrierScheduleNodeFactEntity.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void providerActualObservationAdvancesBusinessNodeWithoutChangingTaskStatus() {
|
||||
CarrierScheduleQueryEntity task = task(0, 0);
|
||||
|
||||
Reference in New Issue
Block a user