fix(firstleg): surface existing truck appointment
This commit is contained in:
+1
@@ -30,6 +30,7 @@ class TruckAppointmentControllerContractTest {
|
||||
}
|
||||
}
|
||||
@Test void openApiDocumentsLifecycleIdempotencyAndActualTimes() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("/api/platform/v1/truck-appointments/{id}/{action}:","/api/platform/v1/truck-appointments/{id}/reschedule:","name: Idempotency-Key","required: true","actual event timestamps");}
|
||||
@Test void openApiDocumentsActionableDuplicateCreateConflict() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("A0409","existing appointment number","continue editing","before booking-document validation","database unique key");}
|
||||
@Test void openApiRequiresReleasedBookingAndAllocatedRealContainerWithoutCustomerScope() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("required: [oceanBookingId, containerBookingId, carrierId, plannedLoadingAt, expectedVersion]","RELEASED ocean booking","ACTIVE real-numbered container","OCEAN_BOOKING_DOCUMENT","valid unique ISO 6346 container number","schema: {$ref: '#/components/schemas/DetailEnvelope'}","required: [code, message, data, timestamp]","allOf: [{$ref: '#/components/schemas/DetailStableReferences'}]","nullable: true").doesNotContain("customerId", "customerName", "Filter by customer");}
|
||||
@Test void openApiKeepsOnlyNonBookingTruckAttachments() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("AttachmentRequest:","required: [fileId, attachmentType]","required: [attachmentType, file]","TRUCK_APPOINTMENT_ATTACHMENT","Deletes the appointment reference only","DetailStableReferences:","oceanBookingNo:","shippingCompanyName:","bookingAgentName:","cabinetNo:","billOfLadingNo:").doesNotContain("enum: [BILL_OF_LADING, DISPATCH_ORDER");}
|
||||
@Test void openApiExposesOnlyPayableWithFixedCurrencies() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("payableAmount:","enum: [RMB, USD]","default: RMB").doesNotContain("receivableAmount");}
|
||||
|
||||
+3
@@ -23,6 +23,9 @@ class TruckAppointmentMapperContractTest {
|
||||
String active = TruckAppointmentMapper.class.getMethod("activeForUpdate", long.class).getAnnotation(Select.class).value()[0];
|
||||
assertThat(active).contains("container_booking_id=#{containerBookingId}", "FOR UPDATE")
|
||||
.contains("'COMPLETED','CANCELLED','EXCEPTION'");
|
||||
String firstActive = TruckAppointmentMapper.class.getMethod("firstActiveForUpdate", long.class).getAnnotation(Select.class).value()[0];
|
||||
assertThat(firstActive).contains("container_booking_id=#{containerBookingId}", "ORDER BY created_at,id", "LIMIT 1 FOR UPDATE")
|
||||
.contains("'COMPLETED','CANCELLED','EXCEPTION'");
|
||||
String pdaComplete = TruckAppointmentMapper.class.getMethod("completeLoadingFromPda", long.class, String.class, int.class).getAnnotation(Update.class).value()[0];
|
||||
assertThat(pdaComplete).contains("status='COMPLETED'", "actual_loading_at=COALESCE", "status=#{fromStatus}",
|
||||
"version=#{version}", "revision=revision+1", "version=version+1");
|
||||
|
||||
+14
-3
@@ -125,24 +125,35 @@ class TruckAppointmentServiceTest {
|
||||
|
||||
@Test void createRejectsAnotherActiveAppointmentForTheSameRealContainerWithoutWriting() {
|
||||
SaveRequest request = validRequest();
|
||||
when(repository.activeForUpdate(88L)).thenReturn(List.of(entity(TruckAppointmentStatus.DRAFT, 0)));
|
||||
TruckAppointmentEntity existing = entity(TruckAppointmentStatus.DRAFT, 0);
|
||||
existing.setAppointmentNo("TA-260901-0003");
|
||||
when(repository.firstActiveForUpdate(88L)).thenReturn(existing);
|
||||
|
||||
assertThatThrownBy(() -> service.create(7L, "admin", request))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("已有有效拖车预约");
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("该柜已存在拖车预约 TA-260901-0003,请继续编辑");
|
||||
|
||||
verifyNoInteractions(oceanBookingDocuments);
|
||||
verify(repository, never()).insert(any());
|
||||
verify(repository, never()).history(any());
|
||||
assertThat(existing.getStatus()).isEqualTo(TruckAppointmentStatus.DRAFT);
|
||||
assertThat(existing.getVersion()).isZero();
|
||||
}
|
||||
|
||||
@Test void concurrentCreateUniqueCollisionBecomesBusinessConflictWithoutHistory() {
|
||||
SaveRequest request = validRequest();
|
||||
TruckAppointmentEntity concurrent = entity(TruckAppointmentStatus.DRAFT, 0);
|
||||
concurrent.setAppointmentNo("TA-260901-0003");
|
||||
when(repository.firstActiveForUpdate(88L)).thenReturn(null, concurrent);
|
||||
doThrow(new DuplicateKeyException("Duplicate entry for key 'uk_truck_active_container'"))
|
||||
.when(repository).insert(any());
|
||||
|
||||
assertThatThrownBy(() -> service.create(7L, "admin", request))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("已有有效拖车预约");
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("该柜已存在拖车预约 TA-260901-0003,请继续编辑");
|
||||
|
||||
verify(repository, times(2)).firstActiveForUpdate(88L);
|
||||
verify(repository, never()).history(any());
|
||||
assertThat(concurrent.getStatus()).isEqualTo(TruckAppointmentStatus.DRAFT);
|
||||
assertThat(concurrent.getVersion()).isZero();
|
||||
}
|
||||
|
||||
@Test void createPreservesMaximumChineseRemarkAndBoundaryValues() {
|
||||
|
||||
Reference in New Issue
Block a user