This commit is contained in:
2026-08-18 11:32:55 +08:00
parent ea26eecc50
commit 3e1010fa26
57 changed files with 1133 additions and 247 deletions
@@ -23,21 +23,33 @@ class InboundMeasurementEvidenceOpenApiContractTest {
Map<String, Object> schemas = stringMap(stringMap(document.get("components")).get("schemas"));
Map<String, Object> detailProperties = properties(schemas, "InboundMeasurementDetail");
assertThat(detailProperties).containsKeys("actualGrossWeightKg", "items", "weightResults");
assertThat(detailProperties).containsKeys(
"actualGrossWeightKg", "actualNetWeightKg", "items", "weightResults");
assertThat(stringMap(detailProperties.get("items")))
.containsEntry("description", "DIMENSION 尺寸测量明细及其图片证据");
assertThat(stringMap(detailProperties.get("weightResults")))
.containsEntry("description", "WEIGHT 称重结果及其图片、确认状态和服务端重量事实");
assertThat(detailProperties).doesNotContainKey("images");
Map<String, Object> draftProperties = properties(schemas, "InboundMeasurementSaveDraftRequest");
assertThat(draftProperties).containsKeys("measurementId", "version", "actualGoodsName", "items")
.doesNotContainKeys("originalDocumentNo", "originalGrossWeightKg", "originalVolumeCbm");
assertThat(paths).containsKey(
"/api/platform/v1/first-leg/inbounds/{inboundId}/measurement/draft");
assertThat(stringMap(stringMap(draftProperties.get("items")).get("items")))
.containsEntry("$ref", "#/components/schemas/InboundMeasurementItemRequest");
assertThat(properties(schemas, "InboundMeasurementItemRequest")).containsKeys(
"inboundDetailId", "itemNo", "boxSpecification", "packageCount", "lengthCm",
"widthCm", "heightCm", "unitWeightKg", "intelligentImageFileId", "remark");
Map<String, Object> itemProperties = properties(schemas, "InboundMeasurementItem");
assertThat(itemProperties).containsKeys("id", "intelligentImageFileId");
Map<String, Object> weightProperties = properties(schemas, "InboundWeightResult");
assertThat(weightProperties).containsKeys(
"id", "fileId", "recognitionStatus", "grossWeightKg", "netWeightKg", "confirmedAt");
assertThat(stringMap(properties(schemas, "UnifiedInboundOrderSummary")
.get("actualGrossWeightKg")).get("description").toString())
.contains("服务端权威货物总重量", "CONFIRMED netWeightKg", "不得从图片重算");
assertThat(stringMap(detailProperties.get("actualGrossWeightKg")).get("description").toString())
.contains("CONFIRMED", "grossWeightKg", "含托盘毛重");
assertThat(stringMap(detailProperties.get("actualNetWeightKg")).get("description").toString())
.contains("CONFIRMED", "netWeightKg", "审核与计费以此为准");
}
private static Map<String, Object> properties(Map<String, Object> schemas, String schemaName) {
@@ -1,7 +1,6 @@
package com.baiyue.firstleg.controller;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dto.ExceptionReleaseRequest;
import com.baiyue.firstleg.dto.MeasurementApprovalResponse;
import com.baiyue.firstleg.dto.MeasurementReviewRequest;
import com.baiyue.firstleg.service.InitialInboundPricingService;
@@ -24,14 +23,10 @@ import static org.mockito.Mockito.when;
@DisplayName("入库测量审核控制器契约")
class InboundMeasurementReviewContractTest {
@Test
@DisplayName("审核、放行和历史接口声明独立按钮权限")
@DisplayName("审核和历史接口声明独立按钮权限")
void shouldDeclareButtonPermissions() throws Exception {
assertAuthority("approve", "hasAuthority('firstleg:inbound:measurement-review')",
AuthenticatedUser.class, long.class, long.class, String.class, MeasurementReviewRequest.class);
assertAuthority("reject", "hasAuthority('firstleg:inbound:measurement-review')",
AuthenticatedUser.class, long.class, long.class, String.class, MeasurementReviewRequest.class);
assertAuthority("exceptionRelease", "hasAuthority('firstleg:inbound:exception-release')",
AuthenticatedUser.class, long.class, long.class, String.class, ExceptionReleaseRequest.class);
assertAuthority("history", "hasAuthority('firstleg:inbound:measurement-review-history')",
AuthenticatedUser.class, long.class, long.class, long.class, long.class);
}
@@ -43,14 +38,14 @@ class InboundMeasurementReviewContractTest {
.getContentAsString(StandardCharsets.UTF_8);
assertThat(openapi)
.contains("/api/platform/v1/inbounds/{inboundId}/measurements/approve:")
.contains("/api/platform/v1/inbounds/{inboundId}/measurements/reject:")
.contains("firstleg:inbound:exception-release")
.contains("Idempotency-Key")
.contains("初算异常只返回非阻断提示")
.contains("data.processStatus 固定为 WAIT_SPLIT")
.contains("feeStatus=UNPRICED 且主流程仍成功")
.contains("name: inboundId")
.contains("schema: {type: string, pattern:")
.doesNotContain("/api/platform/v1/inbounds/{inboundId}/measurements/reject:")
.doesNotContain("/api/platform/v1/inbounds/{inboundId}/measurements/exception-release:")
.doesNotContain("待客户确认");
}
@@ -51,7 +51,9 @@ class InboundOrderControllerContractTest {
.contains("required: false")
.contains("InboundOrderSummary:")
.contains("required: [id, tenantId]")
.contains("actualNetWeightKg: {type: number, nullable: true}")
.contains("actualNetWeightKg: {type: number, nullable: true")
.contains("只读取 fl_inbound_order.actual_gross_weight_kg,不以净重回退")
.contains("只读取 fl_inbound_order.actual_net_weight_kg,不以毛重回退")
.contains("chargeableVolumeCbm: {type: number, nullable: true}")
.contains("measureCompletedAt: {type: string, format: date-time, nullable: true}")
.contains("pricingTaxMode: {type: string, enum: [TAX_INCLUDED, TAX_EXCLUDED]")
@@ -2,17 +2,38 @@ package com.baiyue.firstleg.controller;
import com.baiyue.common.security.AuthenticatedUser;
import com.baiyue.firstleg.dto.InboundMeasurementRequests;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.multipart.MultipartFile;
import java.lang.reflect.Method;
import java.lang.reflect.RecordComponent;
import java.util.Arrays;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DisplayName("平台入库测量接口权限契约")
class PlatformInboundMeasurementControllerContractTest {
@Test
void draftRequestOnlyAcceptsEditableGoodsNameAndMeasurementItems() {
assertThat(Arrays.stream(InboundMeasurementRequests.SaveDraftRequest.class.getRecordComponents())
.map(RecordComponent::getName)).containsExactly(
"measurementId", "version", "actualGoodsName", "items");
}
@Test
void draftRequestRejectsRemovedDeclarationFieldsAtDeserialization() {
String json = """
{"version":0,"originalGrossWeightKg":2000,"actualGoodsName":"货物","items":[]}
""";
assertThatThrownBy(() -> new ObjectMapper().readValue(
json, InboundMeasurementRequests.SaveDraftRequest.class))
.hasMessageContaining("originalGrossWeightKg");
}
@Test
void everyMeasurementOperationUsesDedicatedButtonPermission() throws Exception {
assertAuthority("detail", "hasAuthority('platform:firstleg:inbound:measure:view')",
@@ -41,7 +41,8 @@ class UnifiedInboundOpenApiContractTest {
"holdFlag", "actualGrossWeightKg", "actualNetWeightKg", "actualVolumeCbm",
"volumeWeightKg", "chargeableWeightKg", "chargeableVolumeCbm", "palletCount",
"inboundedAt", "requestedPickupAt", "expressCompanyCode", "expressCompanyName", "containerNo",
"cabinetNo", "sailingAt", "portArrivalAt", "createdByName", "createdAt", "updatedAt");
"cabinetNo", "estimatedSailingAt", "actualSailingAt", "estimatedPortArrivalAt",
"actualPortArrivalAt", "createdByName", "createdAt", "updatedAt");
}
@SuppressWarnings("unchecked")
@@ -0,0 +1,35 @@
package com.baiyue.firstleg.dto;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import static org.assertj.core.api.Assertions.assertThat;
class InboundOrderWeightSemanticsTest {
@Test
void summaryKeepsGrossAndNetWeightAsIndependentDatabaseFacts() {
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setActualGrossWeightKg(new BigDecimal("1787.500"));
inbound.setActualNetWeightKg(new BigDecimal("1747.500"));
InboundOrderSummary result = InboundOrderSummary.from(
inbound, (CurrentContainerTransport) null, null, null);
assertThat(result.actualGrossWeightKg()).isEqualByComparingTo("1787.500");
assertThat(result.actualNetWeightKg()).isEqualByComparingTo("1747.500");
}
@Test
void summaryDoesNotFallbackBetweenGrossAndNetWeight() {
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setActualGrossWeightKg(new BigDecimal("1787.500"));
InboundOrderSummary result = InboundOrderSummary.from(
inbound, (CurrentContainerTransport) null, null, null);
assertThat(result.actualGrossWeightKg()).isEqualByComparingTo("1787.500");
assertThat(result.actualNetWeightKg()).isNull();
}
}
@@ -23,13 +23,14 @@ class InboundOrderStatusSqlContractTest {
}
@Test
void keepsConfirmedWeightNetSumAuthoritativeWhenMeasurementIsApproved() {
void keepsConfirmedGrossAndNetSumsSeparateWhenMeasurementIsApproved() {
assertThat(sql("approveMeasurement"))
.contains("FROM fl_inbound_weight_result weight")
.contains("weight.recognition_status = 'CONFIRMED'")
.contains("weight.deleted = 0")
.contains("SUM(weight.net_weight_kg)")
.contains("ELSE (SELECT COALESCE(SUM(item.total_weight_kg), 0)");
.contains("actual_gross_weight_kg = (SELECT SUM(weight.gross_weight_kg)")
.contains("actual_net_weight_kg = (SELECT SUM(weight.net_weight_kg)")
.doesNotContain("item.total_weight_kg");
}
private static String sql(String methodName) {
@@ -0,0 +1,71 @@
package com.baiyue.firstleg.repository;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.dto.CurrentContainerTransport;
import com.baiyue.firstleg.mapper.UnifiedInboundTransportMapper;
import org.junit.jupiter.api.Test;
import org.springframework.test.util.ReflectionTestUtils;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.ArgumentMatchers.anyList;
class UnifiedInboundTransportRepositoryTest {
private final UnifiedInboundTransportMapper mapper = mock(UnifiedInboundTransportMapper.class);
private final UnifiedInboundReadRepository repository = repository();
@Test
void batchKeepsParentChildAndDepartedUnitOnTheirOwnContainer() {
var oldDeparted = fact(11L, 101L, 501L, "OLD", null, LocalDateTime.parse("2026-08-01T08:00"));
var splitChild = fact(12L, 101L, 502L, "NEW", LocalDateTime.parse("2026-08-20T08:00"), null);
when(mapper.findCurrentByInboundIds(anyList())).thenReturn(List.of(oldDeparted, splitChild));
var result = repository.findTransports(Map.of(11L, 101L, 12L, 101L));
assertThat(result.get(11L).containerBookingId()).isEqualTo(501L);
assertThat(result.get(11L).actualSailingAt()).isEqualTo(LocalDateTime.parse("2026-08-01T08:00"));
assertThat(result.get(12L).containerBookingId()).isEqualTo(502L);
assertThat(result.get(12L).estimatedSailingAt()).isEqualTo(LocalDateTime.parse("2026-08-20T08:00"));
verify(mapper).findCurrentByInboundIds(anyList());
}
@Test
void noContainerReturnsNoFact() {
when(mapper.findCurrentByInboundIds(List.of(11L))).thenReturn(List.of());
assertThat(repository.findTransports(Map.of(11L, 101L))).isEmpty();
}
@Test
void multipleEffectiveContainersAreAnInvariantConflict() {
when(mapper.findCurrentByInboundIds(List.of(11L))).thenReturn(List.of(
fact(11L, 101L, 501L, "A", null, null), fact(11L, 101L, 502L, "B", null, null)));
assertThatThrownBy(() -> repository.findTransports(Map.of(11L, 101L)))
.isInstanceOf(BizException.class).hasMessageContaining("多个有效柜次");
}
@Test
void filtersRowsOutsideInboundTenantOwnership() {
when(mapper.findCurrentByInboundIds(List.of(11L))).thenReturn(List.of(
fact(11L, 999L, 501L, "OTHER", null, null)));
assertThat(repository.findTransports(Map.of(11L, 101L))).isEmpty();
}
private UnifiedInboundReadRepository repository() {
var value = new UnifiedInboundReadRepository();
ReflectionTestUtils.setField(value, "transports", mapper);
return value;
}
private CurrentContainerTransport fact(Long inboundId, Long accountId, Long bookingId, String containerNo,
LocalDateTime estimatedSailingAt, LocalDateTime actualSailingAt) {
return new CurrentContainerTransport(inboundId, accountId, bookingId, containerNo, "CAB", "VESSEL", "V1",
estimatedSailingAt, actualSailingAt, null, null);
}
}
@@ -0,0 +1,20 @@
package com.baiyue.firstleg.repository;
import com.baiyue.firstleg.mapper.UnifiedInboundTransportMapper;
import org.apache.ibatis.annotations.Select;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class UnifiedInboundTransportSqlContractTest {
@Test
void queriesCurrentBusinessUnitContainersInOneBatchFromAuthoritativeBooking() throws Exception {
String sql = String.join(" ", UnifiedInboundTransportMapper.class
.getMethod("findCurrentByInboundIds", java.util.List.class).getAnnotation(Select.class).value());
assertThat(sql).contains("fl_loading_plan_item", "i.deleted = 0",
"p.plan_status IN ('LOADING', 'COMPLETED')", "fl_container_booking",
"estimated_sailing_at", "actual_sailing_at", "estimated_port_arrival_at",
"actual_port_arrival_at", "<foreach");
assertThat(sql).doesNotContain("fl_inbound_transport_snapshot", "LIMIT 1");
}
}
@@ -275,7 +275,7 @@ class CartonMeasurementServiceTest {
}
@Test
void confirmedPackageCountUsesCartonWeightAsFallbackWhenNoConfirmedImageExists() {
void confirmedPackageCountDoesNotUseCartonWeightAsWeightFallback() {
InboundOrderEntity inbound = inbound("MEASURE_PENDING");
InboundMeasurementEntity measurement = measurement(30001L, "SUBMITTED", 2);
measurement.setTotalPackageCount(1);
@@ -300,7 +300,8 @@ class CartonMeasurementServiceTest {
service.updateConfirmedPackageCount(10001L, 70001L, 40001L, 2, 5);
assertThat(inbound.getTotalPackageCount()).isEqualTo(5);
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("54.400");
assertThat(inbound.getActualGrossWeightKg()).isNull();
assertThat(inbound.getActualNetWeightKg()).isNull();
assertThat(inbound.getActualVolumeCbm()).isEqualByComparingTo("0.7480");
assertThat(inbound.getVolumeWeightKg()).isEqualByComparingTo("124.916");
verify(repository).updateInbound(inbound);
@@ -309,7 +310,8 @@ class CartonMeasurementServiceTest {
@Test
void cartonMeasurementAfterConfirmedWeightKeepsNetWeightAndUpdatesVolume() {
InboundOrderEntity inbound = inbound("MEASURE_PENDING");
inbound.setActualGrossWeightKg(new BigDecimal("11.500"));
inbound.setActualGrossWeightKg(new BigDecimal("30.000"));
inbound.setActualNetWeightKg(new BigDecimal("11.500"));
InboundMeasurementEntity measurement = measurement(30001L, "SUBMITTED", 2);
measurement.setTotalPackageCount(1);
measurement.setTotalVolumeCbm(new BigDecimal("0.0600"));
@@ -323,6 +325,7 @@ class CartonMeasurementServiceTest {
item.setCalculationRuleVersion("ACTUAL_DIMENSION_V1");
InboundWeightResultEntity confirmedWeight = new InboundWeightResultEntity();
confirmedWeight.setRecognitionStatus("CONFIRMED");
confirmedWeight.setGrossWeightKg(new BigDecimal("30.000"));
confirmedWeight.setNetWeightKg(new BigDecimal("11.500"));
when(repository.findInbound(10001L, 70001L)).thenReturn(inbound);
when(repository.findItem(10001L, 40001L)).thenReturn(item);
@@ -335,7 +338,8 @@ class CartonMeasurementServiceTest {
service.updateConfirmedPackageCount(10001L, 70001L, 40001L, 2, 5);
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("11.500");
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("30.000");
assertThat(inbound.getActualNetWeightKg()).isEqualByComparingTo("11.500");
assertThat(inbound.getActualVolumeCbm()).isEqualByComparingTo("0.7480");
assertThat(inbound.getVolumeWeightKg()).isEqualByComparingTo("124.916");
verify(repository).updateInbound(inbound);
@@ -421,7 +421,8 @@ class FbaPricingServiceTest {
value.setChannelCode("MATSON_TRUCK"); value.setCarrierId(31L); value.setCarrierName("船司A");
value.setTaxMode("BUY_DECLARATION"); value.setPricingTaxMode("TAX_INCLUDED");
value.setDestinationType("FBA"); value.setDestinationCode("ONT8");
value.setActualGrossWeightKg(new BigDecimal(weight)); value.setActualVolumeCbm(new BigDecimal(volume));
value.setActualGrossWeightKg(new BigDecimal(weight).add(new BigDecimal("8.000")));
value.setActualNetWeightKg(new BigDecimal(weight)); value.setActualVolumeCbm(new BigDecimal(volume));
return value;
}
@@ -459,6 +459,7 @@ class FeeManagementServiceTest {
inbound.setDestinationName("ONT8");
inbound.setTotalPackageCount(10);
inbound.setActualGrossWeightKg(new BigDecimal("10.5000"));
inbound.setActualNetWeightKg(new BigDecimal("10.5000"));
inbound.setActualVolumeCbm(new BigDecimal("1.250000"));
inbound.setVolumeWeightKg(new BigDecimal("208.3333"));
inbound.setChargeableWeightKg(new BigDecimal("208.3333"));
@@ -56,8 +56,12 @@ class InboundMeasurementServiceTest {
@BeforeEach
void stubWarehousePalletSnapshots() {
when(repository.updateInboundWeightSummary(any())).thenReturn(true);
when(warehousePallets.palletSnapshots("internal-token", 100L)).thenReturn(
ApiResponse.success(List.of(palletSnapshot())));
when(orderCargoClient.measurementDeclaration("internal-token", 1L, 80L)).thenReturn(
ApiResponse.success(new OrderCargoClient.MeasurementDeclarationSnapshot(
"REF-50", new BigDecimal("2000"), new BigDecimal("20"), "123123")));
}
@Test
@@ -98,6 +102,7 @@ class InboundMeasurementServiceTest {
void correctingConfirmedWeightImmediatelyRemovesItFromInboundTotal() {
InboundOrderEntity inbound = inbound("MEASURING");
inbound.setActualGrossWeightKg(new BigDecimal("12.500"));
inbound.setActualNetWeightKg(new BigDecimal("12.500"));
InboundWeightResultEntity weight = weight(20L, "12.500", "KG", "CONFIRMED", 4);
weight.setGrossWeightKg(new BigDecimal("12.500"));
weight.setNetWeightKg(new BigDecimal("12.500"));
@@ -115,6 +120,7 @@ class InboundMeasurementServiceTest {
assertThat(weight.getGrossWeightKg()).isNull();
assertThat(weight.getNetWeightKg()).isNull();
assertThat(inbound.getActualGrossWeightKg()).isNull();
assertThat(inbound.getActualNetWeightKg()).isNull();
verify(repository).updateInboundWeightSummary(inbound);
}
@@ -134,13 +140,18 @@ class InboundMeasurementServiceTest {
assertThat(result.recommendedPalletType()).isEqualTo("STANDARD");
assertThat(result.inboundVersion()).isEqualTo(7);
assertThat(result.version()).isEqualTo(4);
assertThat(result.originalDocumentNo()).isEqualTo("REF-50");
assertThat(result.originalGrossWeightKg()).isEqualByComparingTo("2000");
assertThat(result.originalVolumeCbm()).isEqualByComparingTo("20");
assertThat(result.actualGoodsName()).isEqualTo("123123");
verify(repository, never()).updateInbound(any());
}
@Test
void detailKeepsDimensionAndWeightEvidenceOnTheirStableBusinessRecords() {
InboundOrderEntity inbound = inbound("MEASURE_PENDING");
inbound.setActualGrossWeightKg(new BigDecimal("11.500"));
inbound.setActualGrossWeightKg(new BigDecimal("30.000"));
inbound.setActualNetWeightKg(new BigDecimal("11.500"));
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
measurement.setId(30L);
measurement.setVersion(4);
@@ -161,7 +172,8 @@ class InboundMeasurementServiceTest {
InboundMeasurementResponses.Detail result = service.detail(1L, 10L);
assertThat(result.actualGrossWeightKg()).isEqualByComparingTo("11.500");
assertThat(result.actualGrossWeightKg()).isEqualByComparingTo("30.000");
assertThat(result.actualNetWeightKg()).isEqualByComparingTo("11.500");
assertThat(result.items()).singleElement().satisfies(dimension -> {
assertThat(dimension.id()).isEqualTo(40L);
assertThat(dimension.intelligentImageFileId()).isEqualTo(140L);
@@ -232,6 +244,7 @@ class InboundMeasurementServiceTest {
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
InboundWeightResultEntity current = weight(20L, "10.000", "LB", "PENDING_CONFIRM", 0);
InboundWeightResultEntity previous = weight(21L, "2.000", "KG", "CONFIRMED", 1);
previous.setGrossWeightKg(new BigDecimal("2.000"));
previous.setNetWeightKg(new BigDecimal("2.000"));
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(repository.findWeight(1L, 10L, 20L)).thenReturn(current);
@@ -246,6 +259,7 @@ class InboundMeasurementServiceTest {
assertThat(response.grossWeightKg()).isEqualByComparingTo("4.536");
assertThat(response.netWeightKg()).isEqualByComparingTo("4.536");
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("6.536");
assertThat(inbound.getActualNetWeightKg()).isEqualByComparingTo("6.536");
assertThat(inbound.getProcessStatus()).isEqualTo("MEASURING");
verify(repository).insertRevision(any(InboundWeightRevisionEntity.class));
}
@@ -268,7 +282,8 @@ class InboundMeasurementServiceTest {
assertThat(response.grossWeightKg()).isEqualByComparingTo("30.000");
assertThat(response.netWeightKg()).isEqualByComparingTo("11.500");
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("11.500");
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("30.000");
assertThat(inbound.getActualNetWeightKg()).isEqualByComparingTo("11.500");
ArgumentCaptor<InboundWeightRevisionEntity> revision =
ArgumentCaptor.forClass(InboundWeightRevisionEntity.class);
verify(repository).insertRevision(revision.capture());
@@ -276,6 +291,31 @@ class InboundMeasurementServiceTest {
assertThat(revision.getValue().getNetWeightKg()).isEqualByComparingTo("11.500");
}
@Test
void confirmingFifthPalletSeparatelyAggregatesGrossAndCargoNetWeight() {
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
InboundWeightResultEntity current = weight(20L, "357.500", "KG", "PENDING_CONFIRM", 0);
current.setPalletTareWeightKg(new BigDecimal("8.000"));
List<InboundWeightResultEntity> weights = new java.util.ArrayList<>();
weights.add(current);
for (long id = 21L; id <= 24L; id++) {
InboundWeightResultEntity confirmed = weight(id, "357.500", "KG", "CONFIRMED", 1);
confirmed.setGrossWeightKg(new BigDecimal("357.500"));
confirmed.setNetWeightKg(new BigDecimal("349.500"));
weights.add(confirmed);
}
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(repository.findWeight(1L, 10L, 20L)).thenReturn(current);
when(repository.updateWeight(current)).thenReturn(true);
when(repository.listWeights(1L, 10L)).thenReturn(weights);
service.confirmWeightResult(1L, 10L, 20L, 9L, "操作员",
new InboundMeasurementRequests.ConfirmWeightResultRequest(0, null, null, null));
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("1787.500");
assertThat(inbound.getActualNetWeightKg()).isEqualByComparingTo("1747.500");
}
@Test
void confirmingWeightRejectsGrossWeightNotAboveFrozenTare() {
InboundOrderEntity inbound = inbound("WAIT_MEASURE");
@@ -545,7 +585,7 @@ class InboundMeasurementServiceTest {
verify(repository).deleteWeight(1L, 10L, 20L, 1);
verify(imageClient).delete("internal-token", 9L, 1L, 10L, 120L);
verify(repository, never()).updateInboundWeightSummary(any());
verify(repository).updateInboundWeightSummary(inbound);
}
@Test
@@ -566,6 +606,7 @@ class InboundMeasurementServiceTest {
service.deleteWeightResult(1L, 10L, 20L, 2, 9L);
assertThat(inbound.getActualGrossWeightKg()).isNull();
assertThat(inbound.getActualNetWeightKg()).isNull();
verify(repository).updateInboundWeightSummary(inbound);
}
@@ -574,8 +615,10 @@ class InboundMeasurementServiceTest {
InboundOrderEntity inbound = inbound("MEASURING");
inbound.setActualGrossWeightKg(new BigDecimal("15.000"));
InboundWeightResultEntity deleted = weight(20L, "10.000", "KG", "CONFIRMED", 2);
deleted.setGrossWeightKg(new BigDecimal("10.000"));
deleted.setNetWeightKg(new BigDecimal("10.000"));
InboundWeightResultEntity remaining = weight(21L, "5.000", "KG", "CONFIRMED", 1);
remaining.setGrossWeightKg(new BigDecimal("5.000"));
remaining.setNetWeightKg(new BigDecimal("5.000"));
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(repository.findWeight(1L, 10L, 20L)).thenReturn(deleted);
@@ -588,6 +631,7 @@ class InboundMeasurementServiceTest {
service.deleteWeightResult(1L, 10L, 20L, 2, 9L);
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("5.000");
assertThat(inbound.getActualNetWeightKg()).isEqualByComparingTo("5.000");
verify(repository).updateInboundWeightSummary(inbound);
}
@@ -622,6 +666,7 @@ class InboundMeasurementServiceTest {
item.setPackageCount(3);
item.setSourceImageFileId(500L);
InboundWeightResultEntity weight = weight(20L, "20.000", "KG", "CONFIRMED", 1);
weight.setGrossWeightKg(new BigDecimal("20.000"));
weight.setNetWeightKg(new BigDecimal("20.000"));
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(repository.findMeasurement(1L, 10L, 30L)).thenReturn(measurement);
@@ -638,6 +683,7 @@ class InboundMeasurementServiceTest {
assertThat(inbound.getMeasureReviewStatus()).isEqualTo("PENDING");
assertThat(measurement.getStatus()).isEqualTo("SUBMITTED");
assertThat(inbound.getActualGrossWeightKg()).isEqualByComparingTo("20.000");
assertThat(inbound.getActualNetWeightKg()).isEqualByComparingTo("20.000");
assertThat(inbound.getPalletCount()).isEqualTo(1);
assertThat(inbound.getActualVolumeCbm()).isEqualByComparingTo("1.2345");
}
@@ -711,7 +757,7 @@ class InboundMeasurementServiceTest {
when(batchService.saveBatch(any())).thenReturn(saved);
InboundMeasurementResponses.DraftSaved result = service.saveDraft(1L, 10L, 9L,
new InboundMeasurementRequests.SaveDraftRequest(null, 0, null, null, null, null,
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))));
@@ -720,13 +766,38 @@ class InboundMeasurementServiceTest {
ArgumentCaptor<MeasurementBatchCommand> command = ArgumentCaptor.forClass(MeasurementBatchCommand.class);
verify(batchService).saveBatch(command.capture());
assertThat(command.getValue().items().get(0).inboundDetailId()).isEqualTo(80001L);
assertThat(command.getValue().originalDocumentNo()).isEqualTo("REF-50");
assertThat(command.getValue().originalGrossWeightKg()).isEqualByComparingTo("2000");
assertThat(command.getValue().originalVolumeCbm()).isEqualByComparingTo("20");
assertThat(command.getValue().actualGoodsName()).isEqualTo("123123");
verify(orderCargoClient).detail("internal-token", 1L, 80L, 80001L);
}
@Test
void savingDraftRejectsOverlongDefaultGoodsNameBeforeDatabaseWrite() {
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);
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(orderCargoClient.detail("internal-token", 1L, 80L, 80001L)).thenReturn(
ApiResponse.success(new OrderCargoClient.CargoBinding(
80001L, 80L, 1, "Test", 2, new BigDecimal("6.5"), new BigDecimal("0.1"))));
when(orderCargoClient.measurementDeclaration("internal-token", 1L, 80L)).thenReturn(
ApiResponse.success(new OrderCargoClient.MeasurementDeclarationSnapshot(
"REF", new BigDecimal("6.5"), new BigDecimal("0.1"), "A".repeat(256))));
assertThatThrownBy(() -> service.saveDraft(1L, 10L, 9L,
new InboundMeasurementRequests.SaveDraftRequest(null, 0, null, List.of(item))))
.isInstanceOf(BizException.class).hasMessageContaining("超过255字符");
verify(batchService, never()).saveBatch(any());
}
private InboundOrderEntity inbound(String status) {
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setId(10L);
inbound.setTenantId(1L);
inbound.setOrderId(80L);
inbound.setWarehouseId(100L);
inbound.setProcessStatus(status);
inbound.setTotalPackageCount(3);
@@ -9,6 +9,8 @@ import com.baiyue.firstleg.entity.InboundMeasurementEntity;
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.entity.InboundStatusHistoryEntity;
import com.baiyue.firstleg.entity.InboundWeightResultEntity;
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
import com.baiyue.firstleg.repository.InboundWorkflowRepository;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.DisplayName;
@@ -41,6 +43,7 @@ import static org.mockito.Mockito.when;
@SpringJUnitConfig(MeasurementReviewService.class)
class MeasurementReviewServiceTest {
@MockBean private InboundWorkflowRepository repository;
@MockBean private InboundMeasurementRepository measurements;
@MockBean private InboundIdempotencyService idempotency;
@MockBean private InboundProperties properties;
@Resource private MeasurementReviewService service;
@@ -212,7 +215,8 @@ class MeasurementReviewServiceTest {
order.setProcessStatus("MEASURE_PENDING");
order.setMeasureReviewStatus("PENDING");
order.setTotalPackageCount(5);
order.setActualGrossWeightKg(new BigDecimal("11.500"));
order.setActualGrossWeightKg(new BigDecimal("1787.500"));
order.setActualNetWeightKg(new BigDecimal("1747.500"));
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
measurement.setId(20L);
measurement.setStatus("SUBMITTED");
@@ -224,9 +228,23 @@ class MeasurementReviewServiceTest {
when(repository.findOwned(10001L, 10L)).thenReturn(order);
when(repository.findMeasurement(10001L, 10L, 20L)).thenReturn(measurement);
when(repository.listMeasurementItems(10001L, 20L)).thenReturn(List.of(item));
when(measurements.listWeights(10001L, 10L)).thenReturn(List.of(
confirmedWeight("357.500", "349.500"),
confirmedWeight("357.500", "349.500"),
confirmedWeight("357.500", "349.500"),
confirmedWeight("357.500", "349.500"),
confirmedWeight("357.500", "349.500")));
return new Fixture(service, repository, idempotency, order, measurement);
}
private InboundWeightResultEntity confirmedWeight(String grossWeightKg, String netWeightKg) {
InboundWeightResultEntity weight = new InboundWeightResultEntity();
weight.setRecognitionStatus("CONFIRMED");
weight.setGrossWeightKg(new BigDecimal(grossWeightKg));
weight.setNetWeightKg(new BigDecimal(netWeightKg));
return weight;
}
private record Fixture(MeasurementReviewService service, InboundWorkflowRepository repository,
InboundIdempotencyService idempotency, InboundOrderEntity order,
InboundMeasurementEntity measurement) {