Merge remote-tracking branch 'origin/test' into dev

This commit is contained in:
2026-09-01 21:51:52 +08:00
42 changed files with 1598 additions and 67 deletions
@@ -24,7 +24,14 @@ class InboundMeasurementEvidenceOpenApiContractTest {
Map<String, Object> schemas = stringMap(stringMap(document.get("components")).get("schemas"));
Map<String, Object> detailProperties = properties(schemas, "InboundMeasurementDetail");
assertThat(detailProperties).containsKeys(
"actualGrossWeightKg", "actualNetWeightKg", "items", "weightResults");
"actualGrossWeightKg", "actualNetWeightKg", "latestDeclaration", "items", "weightResults");
assertThat(stringMap(detailProperties.get("latestDeclaration")))
.containsEntry("$ref", "#/components/schemas/InboundMeasurementLatestDeclaration");
assertThat(properties(schemas, "InboundMeasurementLatestDeclaration"))
.containsKeys("referenceId", "totalPackageCount", "grossWeightKg", "volumeCbm", "goodsName", "items");
assertThat(properties(schemas, "InboundMeasurementDeclarationItem"))
.containsKeys("inboundDetailId", "itemNo", "cargoName", "packageCount",
"lengthCm", "widthCm", "heightCm");
assertThat(stringMap(detailProperties.get("items")))
.containsEntry("description", "DIMENSION 尺寸测量明细及其图片证据");
assertThat(stringMap(detailProperties.get("weightResults")))
@@ -13,7 +13,7 @@ class SmartLoadingCandidateMapperContractTest {
@Test
void candidateHasOnlyTheCanonicalMybatisConstructor() {
assertThat(Candidate.class.getDeclaredConstructors()).singleElement()
.satisfies(constructor -> assertThat(constructor.getParameterCount()).isEqualTo(24));
.satisfies(constructor -> assertThat(constructor.getParameterCount()).isEqualTo(26));
}
@Test
@@ -23,6 +23,8 @@ class SmartLoadingCandidateMapperContractTest {
assertThat(sql)
.contains("o.destination_warehouse_id destinationWarehouseId")
.contains("o.order_status_snapshot orderStatusSnapshot")
.contains("o.expected_arrival_at expectedArrivalAt")
.contains("m.status='APPROVED'")
.contains("SELECT MAX(m2.id)")
.contains("m2.status='APPROVED'")
@@ -51,6 +53,8 @@ class SmartLoadingCandidateMapperContractTest {
assertThat(sql)
.contains("o.destination_warehouse_id destinationWarehouseId")
.contains("o.order_status_snapshot orderStatusSnapshot")
.contains("o.expected_arrival_at expectedArrivalAt")
.contains("m.status='APPROVED'")
.contains("SELECT MAX(m2.id)")
.contains("o.warehouse_id=#{warehouseId}")
@@ -65,6 +65,10 @@ class InboundMeasurementServiceTest {
when(orderCargoClient.measurementDeclaration("internal-token", 1L, 80L)).thenReturn(
ApiResponse.success(new OrderCargoClient.MeasurementDeclarationSnapshot(
"REF-50", new BigDecimal("2000"), new BigDecimal("20"), "123123")));
when(orderCargoClient.listForCartonLabels("internal-token", 1L, 80L)).thenReturn(
ApiResponse.success(List.of(new OrderCargoClient.CargoLabelBinding(
80001L, 80L, 1, "123123", 3, new BigDecimal("40"),
new BigDecimal("30"), new BigDecimal("20")))));
}
@Test
@@ -213,6 +217,71 @@ class InboundMeasurementServiceTest {
});
}
@Test
void detailComparesLatestDeclarationWithoutMutatingFrozenMeasurementFacts() {
InboundOrderEntity inbound = inbound("WAIT_SPLIT");
inbound.setMeasureReviewStatus("APPROVED");
inbound.setActualGrossWeightKg(new BigDecimal("30.000"));
inbound.setActualNetWeightKg(new BigDecimal("11.500"));
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
measurement.setId(30L);
measurement.setStatus("APPROVED");
measurement.setOriginalDocumentNo("OLD-REF");
measurement.setOriginalGrossWeightKg(new BigDecimal("25.000"));
measurement.setOriginalVolumeCbm(new BigDecimal("0.5000"));
measurement.setTotalPackageCount(2);
measurement.setTotalVolumeCbm(new BigDecimal("0.6000"));
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
item.setId(40L);
item.setTenantId(1L);
item.setMeasurementId(30L);
item.setInboundDetailId(80001L);
item.setBoxCode("RK-001#2-1");
item.setPackageCount(2);
item.setLengthCm(new BigDecimal("45"));
item.setWidthCm(new BigDecimal("35"));
item.setHeightCm(new BigDecimal("25"));
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(repository.findLatestMeasurement(1L, 10L)).thenReturn(measurement);
when(repository.listItems(1L, 30L)).thenReturn(List.of(item));
when(repository.listWeights(1L, 10L)).thenReturn(List.of());
when(orderCargoClient.measurementDeclaration("internal-token", 1L, 80L)).thenReturn(
ApiResponse.success(new OrderCargoClient.MeasurementDeclarationSnapshot(
"NEW-REF", new BigDecimal("40.000"), new BigDecimal("0.8000"), "最新申报品名")));
when(orderCargoClient.listForCartonLabels("internal-token", 1L, 80L)).thenReturn(
ApiResponse.success(List.of(new OrderCargoClient.CargoLabelBinding(
80001L, 80L, 1, "最新申报品名", 4, new BigDecimal("50"),
new BigDecimal("40"), new BigDecimal("30")))));
InboundMeasurementResponses.Detail result = service.detail(1L, 10L);
assertThat(result.originalDocumentNo()).isEqualTo("OLD-REF");
assertThat(result.originalGrossWeightKg()).isEqualByComparingTo("25.000");
assertThat(result.originalVolumeCbm()).isEqualByComparingTo("0.5000");
assertThat(result.actualPackageCount()).isEqualTo(2);
assertThat(result.latestDeclaration()).satisfies(latest -> {
assertThat(latest.referenceId()).isEqualTo("NEW-REF");
assertThat(latest.totalPackageCount()).isEqualTo(4);
assertThat(latest.grossWeightKg()).isEqualByComparingTo("40.000");
assertThat(latest.volumeCbm()).isEqualByComparingTo("0.8000");
assertThat(latest.items()).singleElement().satisfies(declared -> {
assertThat(declared.inboundDetailId()).isEqualTo(80001L);
assertThat(declared.packageCount()).isEqualTo(4);
assertThat(declared.lengthCm()).isEqualByComparingTo("50");
});
});
assertThat(result.items()).singleElement().satisfies(measured -> {
assertThat(measured.id()).isEqualTo(40L);
assertThat(measured.boxCode()).isEqualTo("RK-001#2-1");
assertThat(measured.packageCount()).isEqualTo(2);
assertThat(measured.lengthCm()).isEqualByComparingTo("45");
});
verify(repository, never()).updateInbound(any());
verify(repository, never()).updateMeasurement(any());
verify(repository, never()).updateItem(any());
verify(repository, never()).deleteWeight(anyLong(), anyLong(), anyLong(), anyInt());
}
@Test
void creatingWeightResultNormalizesProviderConfidenceAtBusinessBoundary() {
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
@@ -122,11 +122,11 @@ class SmartLoadingServiceTest {
Candidate first = new Candidate(9L, 10L, 20L, 30L, "O", "I", 100L,
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "服装", 3,
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.2"),
null, null, null, null, null, null);
null, null, null, null, null, null, "WAIT_SPLIT", LocalDateTime.parse("2026-09-10T09:30:00"));
Candidate second = new Candidate(9L, 10L, 21L, 30L, "O", "I", 100L,
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "鞋类", 7,
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.5"),
null, null, null, null, null, null);
null, null, null, null, null, null, "WAIT_SPLIT", LocalDateTime.parse("2026-09-10T09:30:00"));
when(repository.booking(99L)).thenReturn(booking());
when(repository.candidates(100L)).thenReturn(List.of(first, second));
@@ -139,6 +139,8 @@ class SmartLoadingServiceTest {
assertThat(candidate.averageUnitVolumeCbm()).isEqualByComparingTo("0.410000");
assertThat(candidate.availableVolumeCbm()).isEqualByComparingTo("4.1000");
assertThat(candidate.unitGrossWeightKg()).isEqualByComparingTo("2");
assertThat(candidate.orderStatusSnapshot()).isEqualTo("WAIT_SPLIT");
assertThat(candidate.expectedArrivalAt()).isEqualTo(LocalDateTime.parse("2026-09-10T09:30:00"));
});
}
@@ -440,15 +442,15 @@ class SmartLoadingServiceTest {
Candidate first = new Candidate(9L, 10L, 20L, 30L, "O", "I", 100L,
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "A", 30,
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.2"),
null, null, null, null, null, null);
null, null, null, null, null, null, null, null);
Candidate second = new Candidate(9L, 10L, 21L, 30L, "O", "I", 100L,
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "B", 40,
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.5"),
null, null, null, null, null, null);
null, null, null, null, null, null, null, null);
Candidate third = new Candidate(9L, 10L, 22L, 30L, "O", "I", 100L,
"FBA", "FBA渠道", 200L, "FBA1", "FBA仓", "客户", "C", 30,
new BigDecimal("2"), new BigDecimal("1.5"), new BigDecimal("0.8"),
null, null, null, null, null, null);
null, null, null, null, null, null, null, null);
var result = service.proportionalAllocations(List.of(first, second, third), 60);
@@ -1109,7 +1111,7 @@ class SmartLoadingServiceTest {
return new Candidate(tenantId, inboundId, detailId, orderId, "O" + orderId, "I" + inboundId,
100L, "FBA", "FBA渠道", 200L, destinationCode, "FBA仓", "客户", "货物", boxes,
new BigDecimal("2"), new BigDecimal("1.5"),
new BigDecimal("0.5"), null, null, null, null, null, null);
new BigDecimal("0.5"), null, null, null, null, null, null, null, null);
}
private static Candidate withChannel(Candidate value, String channelCode, String channelName) {
@@ -1118,7 +1120,8 @@ class SmartLoadingServiceTest {
value.destinationWarehouseId(), value.destinationWarehouseCode(), value.destinationWarehouseName(),
value.customerName(), value.goodsName(), value.availablePackageCount(), value.unitGrossWeightKg(),
value.unitNetWeightKg(), value.unitVolumeCbm(), value.coLoadGroupId(), value.groupNo(),
value.memberCount(), value.groupTotalBoxes(), value.locked(), value.blockReason());
value.memberCount(), value.groupTotalBoxes(), value.locked(), value.blockReason(),
value.orderStatusSnapshot(), value.expectedArrivalAt());
}
private static ChannelRow channel(String channelCode, String channelName) {