优化
This commit is contained in:
+1
-1
@@ -39,7 +39,7 @@ class InboundMeasurementEvidenceOpenApiContractTest {
|
||||
.containsEntry("$ref", "#/components/schemas/InboundMeasurementItemRequest");
|
||||
assertThat(properties(schemas, "InboundMeasurementItemRequest")).containsKeys(
|
||||
"inboundDetailId", "itemNo", "boxSpecification", "packageCount", "lengthCm",
|
||||
"widthCm", "heightCm", "unitWeightKg", "intelligentImageFileId", "remark");
|
||||
"widthCm", "heightCm", "intelligentImageFileId", "remark");
|
||||
|
||||
Map<String, Object> itemProperties = properties(schemas, "InboundMeasurementItem");
|
||||
assertThat(itemProperties).containsKeys("id", "intelligentImageFileId");
|
||||
|
||||
+4
-4
@@ -51,12 +51,12 @@ class PlatformCartonScanControllerTest {
|
||||
.thenReturn(new CartonMeasurementResult(30001L, 1, 40001L, "RK-001#5-1",
|
||||
"FBA-SCAN-001", 1,
|
||||
new BigDecimal("60.1"), new BigDecimal("40.2"), new BigDecimal("30.3"),
|
||||
new BigDecimal("12.5"), 99001L, "SIM-01",
|
||||
99001L, "SIM-01",
|
||||
LocalDateTime.parse("2026-08-07T10:30:00"), true));
|
||||
MockPart data = new MockPart("data", "data", ("""
|
||||
{"boxCode":"RK-001#5-1","scannedShipmentId":"FBA-SCAN-001",
|
||||
"length":60.1,"width":40.2,"height":30.3,
|
||||
"weight":12.5,"machineNo":"SIM-01","measuredAt":"2026-08-07T10:30:00"}
|
||||
"machineNo":"SIM-01","measuredAt":"2026-08-07T10:30:00"}
|
||||
""").getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||
data.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
MockMultipartFile image = new MockMultipartFile(
|
||||
@@ -91,11 +91,11 @@ class PlatformCartonScanControllerTest {
|
||||
when(cartonMeasurements.upsertManualForInbound(eq(10001L), eq(90001L), eq(70001L), any()))
|
||||
.thenReturn(new CartonMeasurementResult(30001L, 1, 40001L, "RK-001#5-1",
|
||||
null, 1, new BigDecimal("60.1"), new BigDecimal("40.2"), new BigDecimal("30.3"),
|
||||
new BigDecimal("12.5"), 99001L, "MANUAL-ADMIN",
|
||||
99001L, "MANUAL-ADMIN",
|
||||
LocalDateTime.parse("2026-08-12T14:00:00"), true));
|
||||
MockPart data = new MockPart("data", "data", ("""
|
||||
{"boxCode":"RK-001#5-1","length":60.1,"width":40.2,"height":30.3,
|
||||
"weight":12.5,"machineNo":"MANUAL-ADMIN","measuredAt":"2026-08-12T14:00:00"}
|
||||
"machineNo":"MANUAL-ADMIN","measuredAt":"2026-08-12T14:00:00"}
|
||||
""").getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||||
data.getHeaders().setContentType(MediaType.APPLICATION_JSON);
|
||||
MockMultipartFile image = new MockMultipartFile(
|
||||
|
||||
+2
-2
@@ -17,10 +17,10 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TruckAppointmentControllerContractTest {
|
||||
@Test void exposesDedicatedEndpointAndPermissions() {String p=PlatformTruckAppointmentController.class.getAnnotation(org.springframework.web.bind.annotation.RequestMapping.class).value()[0];assertThat(p).isEqualTo("/api/platform/v1/truck-appointments"); for(String n:new String[]{"page","detail","history","attachments","attach","upload","deleteAttachment","create","edit","action","reschedule"}) assertThat(java.util.Arrays.stream(PlatformTruckAppointmentController.class.getDeclaredMethods()).anyMatch(m->m.getName().equals(n))).isTrue();}
|
||||
@Test void schemaHasAggregateTablesStatesAndActualEventTimes() throws Exception {String s=new ClassPathResource("db/phase3-truck-appointment-schema.sql").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("fl_truck_appointment","fl_truck_appointment_history","fl_truck_appointment_attachment","fl_truck_appointment_operation","PENDING_DISPATCH","PICKUP_IN_PROGRESS","ARRIVED_WAREHOUSE","RESCHEDULED","actual_arrived_warehouse_at","actual_loading_at","actual_departed_at","CALL phase3_truck_add_column");}
|
||||
@Test void schemaHasAggregateTablesStatesAndActualEventTimes() throws Exception {String s=new ClassPathResource("db/phase3-truck-appointment-schema.sql").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("fl_truck_appointment","fl_truck_appointment_history","fl_truck_appointment_attachment","fl_truck_appointment_operation","bill_of_lading_no VARCHAR(128)","chk_truck_attachment_type","BILL_OF_LADING","PENDING_DISPATCH","PICKUP_IN_PROGRESS","ARRIVED_WAREHOUSE","RESCHEDULED","actual_arrived_warehouse_at","actual_loading_at","actual_departed_at","CALL phase3_truck_add_column");}
|
||||
@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 openApiFreezesIndependentCreateAndOptionalAssociations() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("required: [customerId, carrierId, plannedLoadingAt, expectedVersion]","independent operational records","no RELEASED-state prerequisite","It may be supplied only with containerBookingId","ACTIVE IAM customer","ACTIVE Warehouse supplier ID validated as TRUCKING","Fulfillment associations remain null");}
|
||||
@Test void openApiFreezesAttachmentRequestAndOwnershipContract() throws Exception {String s=new ClassPathResource("openapi/truck-appointments-v1.yaml").getContentAsString(StandardCharsets.UTF_8);assertThat(s).contains("AttachmentRequest:","required: [fileId]","maxLength: 64","TRUCK_APPOINTMENT_ATTACHMENT","Deletes the appointment reference only","DetailStableReferences:","oceanBookingNo:","shippingCompanyName:","bookingAgentName:","cabinetNo:");}
|
||||
@Test void openApiFreezesAttachmentRequestAndOwnershipContract() 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]","BILL_OF_LADING","TRUCK_APPOINTMENT_ATTACHMENT","Deletes the appointment reference only","DetailStableReferences:","oceanBookingNo:","shippingCompanyName:","bookingAgentName:","cabinetNo:","billOfLadingNo:");}
|
||||
@Test void methodsHavePlatformPermissionsAndAuthenticatedScope() {
|
||||
Map<String,String> expected=Map.ofEntries(
|
||||
Map.entry("page","platform:firstleg:truck-appointment:list"), Map.entry("detail","platform:firstleg:truck-appointment:list"),
|
||||
|
||||
+8
-1
@@ -76,7 +76,14 @@ class PdaSplitSettlementSqlContractTest {
|
||||
.getAnnotation(Insert.class).value()[0];
|
||||
assertThat(clone)
|
||||
.contains("#{childOrderId},#{childOrderNo}")
|
||||
.contains("CASE WHEN #{splitPurpose}='LOADED' THEN 'LOADING' ELSE 'WAIT_SPLIT' END");
|
||||
.contains("CASE WHEN #{splitPurpose}='LOADED' THEN 'LOADING' ELSE 'WAIT_SPLIT' END")
|
||||
.contains("actual_gross_weight_kg * #{packageCount}")
|
||||
.contains("actual_net_weight_kg * #{packageCount}");
|
||||
|
||||
String cloneItem = InboundSplitSettlementMapper.class.getMethod("cloneMeasurementItem",
|
||||
long.class, long.class, long.class, long.class, long.class, long.class, int.class, int.class)
|
||||
.getAnnotation(Insert.class).value()[0];
|
||||
assertThat(cloneItem).doesNotContain("unit_weight_kg").doesNotContain("total_weight_kg");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+8
@@ -22,6 +22,11 @@ class SmartLoadingCandidateMapperContractTest {
|
||||
.contains("i.inbound_detail_id IS NOT NULL")
|
||||
.contains("o.warehouse_id=#{warehouseId}")
|
||||
.contains("o.process_status='WAIT_SPLIT'")
|
||||
.contains("unitGrossWeightKg")
|
||||
.contains("unitNetWeightKg")
|
||||
.contains("o.actual_gross_weight_kg")
|
||||
.contains("o.actual_net_weight_kg")
|
||||
.doesNotContain("i.unit_weight_kg")
|
||||
.contains("SELECT SUM(li.package_count)")
|
||||
.contains("li.deleted=0")
|
||||
.contains("ORDER BY o.inbounded_at ASC, o.id ASC, i.id ASC");
|
||||
@@ -39,6 +44,9 @@ class SmartLoadingCandidateMapperContractTest {
|
||||
.contains("SELECT MAX(m2.id)")
|
||||
.contains("o.warehouse_id=#{warehouseId}")
|
||||
.contains("o.process_status='WAIT_SPLIT'")
|
||||
.contains("unitGrossWeightKg")
|
||||
.contains("unitNetWeightKg")
|
||||
.doesNotContain("i.unit_weight_kg")
|
||||
.contains("SELECT SUM(li.package_count)")
|
||||
.contains("FOR UPDATE");
|
||||
}
|
||||
|
||||
+3
-1
@@ -17,6 +17,8 @@ class TruckAppointmentMapperContractTest {
|
||||
"fl_ocean_booking", "fl_container_booking", "record_status='ACTIVE'", "cabinet_no", "container_no", "fl_truck_appointment_attachment", "attachment_count");
|
||||
String transition = TruckAppointmentMapper.class.getMethod("transition", long.class, String.class, String.class, int.class).getAnnotation(Update.class).value()[0];
|
||||
assertThat(transition).contains("status=#{toStatus}", "status=#{fromStatus}", "version=#{version}", "revision=revision+1", "version=version+1");
|
||||
String dispatch = TruckAppointmentMapper.class.getMethod("dispatch", long.class, String.class, String.class, int.class).getAnnotation(Update.class).value()[0];
|
||||
assertThat(dispatch).contains("status='DISPATCHED'", "bill_of_lading_no=#{billOfLadingNo}", "status=#{fromStatus}", "version=#{version}", "revision=revision+1");
|
||||
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'");
|
||||
@@ -28,7 +30,7 @@ class TruckAppointmentMapperContractTest {
|
||||
String edit = TruckAppointmentMapper.class.getMethod("updateEdit", TruckAppointmentEntity.class, int.class).getAnnotation(Update.class).value()[0];
|
||||
assertThat(edit).contains("ocean_booking_id=#{e.oceanBookingId}", "container_booking_id=#{e.containerBookingId}",
|
||||
"loading_plan_id=#{e.loadingPlanId}", "customer_id=#{e.customerId}", "carrier_id=#{e.carrierId}",
|
||||
"planned_loading_at=#{e.plannedLoadingAt}", "WHERE id=#{e.id} AND version=#{version}",
|
||||
"planned_loading_at=#{e.plannedLoadingAt}", "bill_of_lading_no=#{e.billOfLadingNo}", "WHERE id=#{e.id} AND version=#{version}",
|
||||
"version=version+1", "revision=revision+1");
|
||||
}
|
||||
}
|
||||
|
||||
+4
-6
@@ -172,7 +172,7 @@ class CartonMeasurementServiceTest {
|
||||
when(repository.updateMeasurement(measurement)).thenReturn(true);
|
||||
CartonMeasurementCommand oversize = new CartonMeasurementCommand(
|
||||
"RK-001#5-1", new BigDecimal("60.100"), new BigDecimal("50.100"),
|
||||
new BigDecimal("50.100"), new BigDecimal("12.500"), "MJ-01",
|
||||
new BigDecimal("50.100"), "MJ-01",
|
||||
LocalDateTime.parse("2026-08-07T10:30:00"),
|
||||
new MockMultipartFile("image", "measure.jpg", "image/jpeg", "image".getBytes()),
|
||||
"FBA-SCAN-001");
|
||||
@@ -282,7 +282,6 @@ class CartonMeasurementServiceTest {
|
||||
measurement.setTotalVolumeCbm(new BigDecimal("0.0600"));
|
||||
measurement.setVolumeWeightKg(new BigDecimal("10.020"));
|
||||
InboundMeasurementItemEntity item = item(40001L, 30001L, 1, 99001L);
|
||||
item.setUnitWeightKg(new BigDecimal("10.880"));
|
||||
item.setOuterLengthCm(new BigDecimal("50"));
|
||||
item.setOuterWidthCm(new BigDecimal("40"));
|
||||
item.setOuterHeightCm(new BigDecimal("30"));
|
||||
@@ -317,7 +316,6 @@ class CartonMeasurementServiceTest {
|
||||
measurement.setTotalVolumeCbm(new BigDecimal("0.0600"));
|
||||
measurement.setVolumeWeightKg(new BigDecimal("10.020"));
|
||||
InboundMeasurementItemEntity item = item(40001L, 30001L, 1, 99001L);
|
||||
item.setUnitWeightKg(new BigDecimal("10.880"));
|
||||
item.setOuterLengthCm(new BigDecimal("50"));
|
||||
item.setOuterWidthCm(new BigDecimal("40"));
|
||||
item.setOuterHeightCm(new BigDecimal("30"));
|
||||
@@ -347,7 +345,7 @@ class CartonMeasurementServiceTest {
|
||||
|
||||
private CartonMeasurementCommand command() {
|
||||
return new CartonMeasurementCommand("RK-001#5-1", new BigDecimal("59.100"),
|
||||
new BigDecimal("40.200"), new BigDecimal("30.300"), new BigDecimal("12.500"),
|
||||
new BigDecimal("40.200"), new BigDecimal("30.300"),
|
||||
"MJ-01", LocalDateTime.parse("2026-08-07T10:30:00"),
|
||||
new MockMultipartFile("image", "measure.jpg", "image/jpeg", "image".getBytes()),
|
||||
"FBA-SCAN-001");
|
||||
@@ -356,7 +354,7 @@ class CartonMeasurementServiceTest {
|
||||
private CartonMeasurementCommand acceptanceCommand() {
|
||||
return new CartonMeasurementCommand(
|
||||
"RK-001#5-1", new BigDecimal("59.500"), new BigDecimal("41.200"),
|
||||
new BigDecimal("31.300"), new BigDecimal("13.600"), "MJ-ADMIN-E2E-02",
|
||||
new BigDecimal("31.300"), "MJ-ADMIN-E2E-02",
|
||||
LocalDateTime.parse("2026-08-08T10:01:00"),
|
||||
new MockMultipartFile("image", "measure.jpg", "image/jpeg", "image".getBytes()),
|
||||
"FBA-SCAN-002");
|
||||
@@ -381,7 +379,7 @@ class CartonMeasurementServiceTest {
|
||||
item.setId(id); item.setTenantId(10001L); item.setMeasurementId(measurementId);
|
||||
item.setBoxCode("RK-001#5-1"); item.setPackageCount(packageCount);
|
||||
item.setLengthCm(new BigDecimal("50")); item.setWidthCm(new BigDecimal("40"));
|
||||
item.setHeightCm(new BigDecimal("30")); item.setUnitWeightKg(new BigDecimal("10"));
|
||||
item.setHeightCm(new BigDecimal("30"));
|
||||
item.setSourceImageFileId(imageId); item.setItemNo("B1"); item.setBoxSpecification("50x40x30cm");
|
||||
return item;
|
||||
}
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ class InboundMeasurementOpenApiServiceTest {
|
||||
{"eventNo":"M202608010001","warehouseCode":"CN-YW-01","inboundCode":"RK202608010001",
|
||||
"externalDetailNo":"BOX-001","inboundDetailId":"80001","deviceCode":"DEVICE-01",
|
||||
"measuredAt":"2026-08-01T10:30:00+08:00","lengthCm":50.20,"widthCm":40.10,
|
||||
"heightCm":30.00,"packageCount":5,"unitWeightKg":12.5,
|
||||
"heightCm":30.00,"packageCount":5,
|
||||
"boxSpecification":"50.2x40.1x30cm"}
|
||||
""";
|
||||
private static final byte[] IMAGE = "image-content".getBytes(java.nio.charset.StandardCharsets.UTF_8);
|
||||
|
||||
+2
-2
@@ -783,7 +783,7 @@ class InboundMeasurementServiceTest {
|
||||
new InboundMeasurementRequests.SaveDraftRequest(null, 0, null,
|
||||
List.of(new InboundMeasurementRequests.ItemRequest(80001L, "001", "A", 2,
|
||||
new BigDecimal("40"), new BigDecimal("30"), new BigDecimal("20"),
|
||||
new BigDecimal("3.250"), null, null, null, null, null, null))));
|
||||
null, null, null, null, null, null))));
|
||||
|
||||
assertThat(result.measurementId()).isEqualTo(30L);
|
||||
ArgumentCaptor<MeasurementBatchCommand> command = ArgumentCaptor.forClass(MeasurementBatchCommand.class);
|
||||
@@ -801,7 +801,7 @@ class InboundMeasurementServiceTest {
|
||||
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
|
||||
InboundMeasurementRequests.ItemRequest item = new InboundMeasurementRequests.ItemRequest(
|
||||
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
|
||||
new BigDecimal("20"), new BigDecimal("3.250"), null, null, null, null, null, null);
|
||||
new BigDecimal("20"), null, null, null, null, null, null);
|
||||
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
|
||||
when(orderCargoClient.detail("internal-token", 1L, 80L, 80001L)).thenReturn(
|
||||
ApiResponse.success(new OrderCargoClient.CargoBinding(
|
||||
|
||||
@@ -70,12 +70,11 @@ class MeasurementBatchServiceTest {
|
||||
assertThat(items.getValue().get(0).getCalculationRuleVersion())
|
||||
.isEqualTo("CUSTOMER_DIMENSION_MEDIUM_V0");
|
||||
assertThat(items.getValue().get(0).getGirthCm()).isEqualByComparingTo("140.000");
|
||||
assertThat(items.getValue().get(0).getUnitWeightKg()).isNull();
|
||||
verify(repository, never()).updateInbound(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void manualDraftCalculatesWeightVolumeAndVolumeWeightOnServer() {
|
||||
void manualDraftCalculatesVolumeAndVolumeWeightOnServer() {
|
||||
MeasurementBatchCommand command = new MeasurementBatchCommand(
|
||||
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
|
||||
LocalDateTime.now(), null, "CKW-1", new BigDecimal("20"),
|
||||
@@ -90,7 +89,6 @@ class MeasurementBatchServiceTest {
|
||||
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
|
||||
org.mockito.ArgumentMatchers.eq(100L), items.capture());
|
||||
assertThat(items.getValue().get(0).getInboundDetailId()).isEqualTo(80001L);
|
||||
assertThat(items.getValue().get(0).getTotalWeightKg()).isEqualByComparingTo("6.500");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -143,7 +141,7 @@ class MeasurementBatchServiceTest {
|
||||
private MeasurementBatchCommand.Item item(BigDecimal unitWeight) {
|
||||
return new MeasurementBatchCommand.Item(
|
||||
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
|
||||
new BigDecimal("20"), unitWeight, 500L, null,
|
||||
new BigDecimal("20"), 500L, null,
|
||||
new BigDecimal("41"), new BigDecimal("31"), new BigDecimal("21"), "备注");
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,6 @@ class MeasurementReviewServiceTest {
|
||||
measurement.setStatus("SUBMITTED");
|
||||
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
|
||||
item.setPackageCount(5);
|
||||
item.setTotalWeightKg(new BigDecimal("54.400"));
|
||||
item.setTotalVolumeCbm(new BigDecimal("0.7480"));
|
||||
item.setVolumeWeightKg(new BigDecimal("124.916"));
|
||||
when(repository.findOwned(10001L, 10L)).thenReturn(order);
|
||||
|
||||
+2
-2
@@ -64,7 +64,7 @@ class MojiaMeasurementOpenApiServiceTest {
|
||||
when(cartons.upsert(eq(10001L), eq(90001L), any())).thenReturn(new CartonMeasurementResult(
|
||||
1L, 1, 2L, "PCHK2608140001#5-1", "FBA19L5KMY2CU000002", 1,
|
||||
new BigDecimal("60.1"), new BigDecimal("40.2"),
|
||||
new BigDecimal("30.3"), new BigDecimal("12.5"), 3L, "MJ-01", LocalDateTime.now(), true));
|
||||
new BigDecimal("30.3"), 3L, "MJ-01", LocalDateTime.now(), true));
|
||||
|
||||
UploadResponse result = service.receive(headers, json());
|
||||
|
||||
@@ -119,7 +119,7 @@ class MojiaMeasurementOpenApiServiceTest {
|
||||
return """
|
||||
{"boxCode":"PCHK2608140001#5-1","shipment_id":"FBA19L5KMY2CU000002",
|
||||
"length":60.1,"width":40.2,"height":30.3,
|
||||
"weight":12.5,"machineNo":"MJ-01","picture":"data:image/jpeg;base64,/9j/2Q=="}
|
||||
"machineNo":"MJ-01","picture":"data:image/jpeg;base64,/9j/2Q=="}
|
||||
""";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -676,7 +676,7 @@ class SmartLoadingServiceTest {
|
||||
|
||||
private static Candidate candidate(int boxes) {
|
||||
return new Candidate(9L, 10L, 20L, 30L, "O", "I", 100L, "FBA", "FBA渠道", 200L,
|
||||
"FBA1", "FBA仓", "客户", "货物", boxes, new BigDecimal("2"), new BigDecimal("0.5"));
|
||||
"FBA1", "FBA仓", "客户", "货物", boxes, new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.5"));
|
||||
}
|
||||
|
||||
private static ContainerBookingEntity booking() {
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.baiyue.firstleg.domain.TruckAppointmentStatus;
|
||||
import com.baiyue.firstleg.dto.TruckAppointmentDtos.ActionRequest;
|
||||
import com.baiyue.firstleg.dto.TruckAppointmentDtos.SaveRequest;
|
||||
import com.baiyue.firstleg.entity.TruckAppointmentEntity;
|
||||
import com.baiyue.firstleg.entity.TruckAppointmentAttachmentEntity;
|
||||
import com.baiyue.firstleg.entity.TruckAppointmentHistoryEntity;
|
||||
import com.baiyue.firstleg.entity.TruckAppointmentOperationEntity;
|
||||
import com.baiyue.firstleg.entity.LoadingPlanEntity;
|
||||
@@ -16,7 +17,9 @@ import com.baiyue.firstleg.oceanbooking.repository.OceanBookingRepository;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingEntity;
|
||||
import com.baiyue.firstleg.oceanbooking.entity.OceanBookingContainerAllocationEntity;
|
||||
import com.baiyue.firstleg.client.IamCustomerSnapshotClient;
|
||||
import com.baiyue.firstleg.client.TruckAppointmentFileClient;
|
||||
import com.baiyue.firstleg.client.WarehouseSupplierClient;
|
||||
import com.baiyue.common.core.api.ApiResponse;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
@@ -43,10 +46,12 @@ class TruckAppointmentServiceTest {
|
||||
@Mock ContainerRepository containers;
|
||||
@Mock WarehouseSupplierSnapshotService suppliers;
|
||||
@Mock IamCustomerSnapshotService customers;
|
||||
@Mock TruckAppointmentFileClient files;
|
||||
@Spy ObjectMapper objectMapper = JsonMapper.builder().findAndAddModules().build();
|
||||
@InjectMocks TruckAppointmentService service;
|
||||
|
||||
@BeforeEach void setUp() { MockitoAnnotations.openMocks(this);
|
||||
service.internalToken = "test-internal-token";
|
||||
when(customers.active(anyLong(), any())).thenAnswer(invocation ->
|
||||
new IamCustomerSnapshotClient.Customer(invocation.getArgument(0), "C-7",
|
||||
"Canonical Customer", "ACTIVE", null, null));
|
||||
@@ -105,6 +110,38 @@ class TruckAppointmentServiceTest {
|
||||
verifyNoInteractions(oceanBookings, containers, loadingPlans);
|
||||
}
|
||||
|
||||
@Test void createAllowsDraftWithoutBillOfLadingOrAttachment() {
|
||||
SaveRequest request = validRequest();
|
||||
|
||||
service.create(7L, "admin", request);
|
||||
|
||||
ArgumentCaptor<TruckAppointmentEntity> entity = ArgumentCaptor.forClass(TruckAppointmentEntity.class);
|
||||
verify(repository).insert(entity.capture());
|
||||
assertThat(entity.getValue().getBillOfLadingNo()).isNull();
|
||||
verifyNoInteractions(files);
|
||||
}
|
||||
|
||||
@Test void createFreezesReleasedBookingBillAndRejectsClientMismatch() {
|
||||
SaveRequest request = validRequest();
|
||||
when(request.oceanBookingId()).thenReturn(99L);
|
||||
OceanBookingEntity booking = new OceanBookingEntity();
|
||||
booking.setId(99L); booking.setStatus("RELEASED"); booking.setCustomerId(7L);
|
||||
booking.setBillOfLadingNo(" BL-BOOKING-1 ");
|
||||
when(oceanBookings.find(99L)).thenReturn(booking);
|
||||
|
||||
service.create(7L, "admin", request);
|
||||
|
||||
ArgumentCaptor<TruckAppointmentEntity> entity = ArgumentCaptor.forClass(TruckAppointmentEntity.class);
|
||||
verify(repository).insert(entity.capture());
|
||||
assertThat(entity.getValue().getBillOfLadingNo()).isEqualTo("BL-BOOKING-1");
|
||||
|
||||
SaveRequest mismatch = validRequest();
|
||||
when(mismatch.oceanBookingId()).thenReturn(99L);
|
||||
when(mismatch.billOfLadingNo()).thenReturn("BL-OTHER");
|
||||
assertThatThrownBy(() -> service.create(7L, "admin", mismatch))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("billOfLadingNo").hasMessageContaining("不一致");
|
||||
}
|
||||
|
||||
@Test void createAcceptsConfirmedBookingWithoutContainerOrPlan() {
|
||||
SaveRequest request = mock(SaveRequest.class);
|
||||
when(request.expectedVersion()).thenReturn(0);
|
||||
@@ -251,6 +288,57 @@ class TruckAppointmentServiceTest {
|
||||
verify(repository, never()).history(any());
|
||||
}
|
||||
|
||||
@Test void dispatchRequiresBillOfLadingNumberAndActiveOwnedAttachment() {
|
||||
TruckAppointmentEntity missingBill = entity(TruckAppointmentStatus.PENDING_DISPATCH, 1);
|
||||
when(repository.claimOperation(anyLong(), eq(1L), eq("DISPATCH"), eq("dispatch-no-bill"), anyString())).thenReturn(true);
|
||||
when(repository.find(1L)).thenReturn(missingBill);
|
||||
|
||||
assertThatThrownBy(() -> service.action(1L, "DISPATCH", new ActionRequest(1, null),
|
||||
"dispatch-no-bill", null, 7L, "admin"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("提单号");
|
||||
|
||||
TruckAppointmentEntity missingAttachment = entity(TruckAppointmentStatus.PENDING_DISPATCH, 1);
|
||||
missingAttachment.setBillOfLadingNo("BL-1");
|
||||
missingAttachment.setCustomerId(7L);
|
||||
when(repository.claimOperation(anyLong(), eq(1L), eq("DISPATCH"), eq("dispatch-no-file"), anyString())).thenReturn(true);
|
||||
when(repository.find(1L)).thenReturn(missingAttachment);
|
||||
when(repository.attachments(1L)).thenReturn(List.of());
|
||||
|
||||
assertThatThrownBy(() -> service.action(1L, "DISPATCH", new ActionRequest(1, null),
|
||||
"dispatch-no-file", null, 7L, "admin"))
|
||||
.isInstanceOf(BizException.class).hasMessageContaining("有效提单附件");
|
||||
verify(repository, never()).dispatch(anyLong(), anyString(), anyString(), anyInt());
|
||||
}
|
||||
|
||||
@Test void dispatchFreezesReleasedBookingBillAndTransitionsOnceWithActiveAttachment() {
|
||||
TruckAppointmentEntity before = entity(TruckAppointmentStatus.PENDING_DISPATCH, 1);
|
||||
before.setCustomerId(7L); before.setOceanBookingId(99L); before.setBillOfLadingNo(null);
|
||||
TruckAppointmentEntity after = entity(TruckAppointmentStatus.DISPATCHED, 2);
|
||||
after.setCustomerId(7L); after.setOceanBookingId(99L); after.setBillOfLadingNo("BL-BOOKING-1");
|
||||
OceanBookingEntity booking = new OceanBookingEntity();
|
||||
booking.setId(99L); booking.setStatus("RELEASED"); booking.setBillOfLadingNo("BL-BOOKING-1");
|
||||
TruckAppointmentAttachmentEntity attachment = new TruckAppointmentAttachmentEntity();
|
||||
attachment.setFileId(88L); attachment.setAttachmentType("BILL_OF_LADING");
|
||||
when(repository.claimOperation(anyLong(), eq(1L), eq("DISPATCH"), eq("dispatch-ok"), anyString())).thenReturn(true);
|
||||
when(repository.find(1L)).thenReturn(before, after);
|
||||
when(oceanBookings.find(99L)).thenReturn(booking);
|
||||
when(repository.attachments(1L)).thenReturn(List.of(attachment));
|
||||
when(files.metadata("test-internal-token", 7L, "TRUCK_APPOINTMENT_ATTACHMENT", "1", 88L))
|
||||
.thenReturn(ApiResponse.success(new TruckAppointmentFileClient.Metadata(88L, 7L,
|
||||
"TRUCK_APPOINTMENT_ATTACHMENT", "1", "bill.pdf", "hash", "application/pdf", 10L)));
|
||||
when(repository.dispatch(1L, "PENDING_DISPATCH", "BL-BOOKING-1", 1)).thenReturn(1);
|
||||
when(repository.completeOperation(eq(1L), eq("DISPATCH"), eq("dispatch-ok"), anyString())).thenReturn(1);
|
||||
|
||||
TruckAppointmentEntity result = service.action(1L, "DISPATCH", new ActionRequest(1, null),
|
||||
"dispatch-ok", null, 7L, "admin");
|
||||
|
||||
assertThat(result.getStatus()).isEqualTo(TruckAppointmentStatus.DISPATCHED);
|
||||
assertThat(result.getBillOfLadingNo()).isEqualTo("BL-BOOKING-1");
|
||||
verify(repository).dispatch(1L, "PENDING_DISPATCH", "BL-BOOKING-1", 1);
|
||||
verify(repository, never()).transition(anyLong(), anyString(), anyString(), anyInt());
|
||||
verify(repository).history(any(TruckAppointmentHistoryEntity.class));
|
||||
}
|
||||
|
||||
@Test void actionRejectsMissingKeyAndMismatchedClientHash() {
|
||||
var request = new ActionRequest(0, "提交");
|
||||
assertThatThrownBy(() -> service.action(1L, "SUBMIT", request, null, null, 7L, "admin"))
|
||||
|
||||
Reference in New Issue
Block a user