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

# Conflicts:
#	firstleg/src/test/java/com/baiyue/firstleg/service/CartonMeasurementServiceTest.java
This commit is contained in:
2026-09-03 21:23:35 +08:00
38 changed files with 643 additions and 206 deletions
@@ -35,14 +35,19 @@ class MerchantInboundBillingControllerContractTest {
.isEqualTo("hasAuthority('merchant:order:view')");
assertThat(method.getAnnotation(Operation.class).description())
.contains("WAIT_SPLIT", "LOADING", "LOADED", "DELIVERED", "APPROVED + SENT", "已发布入库运费明细");
assertThat(MerchantInboundBillingController.class.getDeclaredMethods())
.noneMatch(candidate -> candidate.getName().toLowerCase().contains("confirm"));
var confirm = MerchantInboundBillingController.class.getDeclaredMethod(
"confirm", AuthenticatedUser.class, long.class, String.class,
com.baiyue.firstleg.dto.MerchantInboundBillingResponse.ConfirmFeeRequest.class);
assertThat(confirm.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('merchant:order:edit')");
String openapi = new ClassPathResource("openapi/firstleg-v1.yaml")
.getContentAsString(StandardCharsets.UTF_8);
assertThat(openapi).contains("/api/merchant/v1/first-leg/orders/{orderId}/inbound-billing:")
.contains("WAIT_SPLIT", "LOADING、LOADED、DELIVERED", "measureReviewStatus=APPROVED",
"feeStatus=SENT", "PUBLISHED 入库运费明细", "CONFIRMED 只表示发送前确认完成");
"feeStatus=SENT", "PUBLISHED 入库运费明细", "customerConfirmationStatus")
.contains("/api/merchant/v1/first-leg/orders/{orderId}/inbound-billing/confirm:",
"确认成功后记录当前成员与确认时间", "平台不得再修改");
}
@Test
@@ -90,10 +90,10 @@ class CartonMeasurementServiceTest {
assertThat(result.scannedShipmentId()).isEqualTo("FBA-SCAN-002");
assertThat(insertedItem.get().getScannedShipmentId()).isEqualTo("FBA-SCAN-002");
assertThat(insertedItem.get().getMeasurementId()).isEqualTo(30001L);
assertThat(insertedItem.get().getTotalVolumeCbm()).isEqualByComparingTo("0.1680");
assertThat(insertedItem.get().getOuterLengthCm()).isEqualByComparingTo("62.000");
assertThat(insertedItem.get().getOuterWidthCm()).isEqualByComparingTo("52.000");
assertThat(insertedItem.get().getOuterHeightCm()).isEqualByComparingTo("52.000");
assertThat(insertedItem.get().getTotalVolumeCbm()).isEqualByComparingTo("0.1010");
assertThat(insertedItem.get().getOuterLengthCm()).isEqualByComparingTo("63.500");
assertThat(insertedItem.get().getOuterWidthCm()).isEqualByComparingTo("45.200");
assertThat(insertedItem.get().getOuterHeightCm()).isEqualByComparingTo("35.300");
assertThat(measurement.getSourceType()).isEqualTo(MeasurementBatchService.SOURCE_THIRD_PARTY);
assertThat(measurement.getSourceCode()).isEqualTo("DEVICE");
}
@@ -155,9 +155,9 @@ class CartonMeasurementServiceTest {
assertThat(result.created()).isFalse();
assertThat(result.confirmedPackageCount()).isEqualTo(4);
assertThat(existing.getLengthCm()).isEqualByComparingTo("59.500");
assertThat(existing.getUnitVolumeCbm()).isEqualByComparingTo("0.168");
assertThat(existing.getTotalVolumeCbm()).isEqualByComparingTo("0.6720");
assertThat(existing.getVolumeWeightKg()).isEqualByComparingTo("112.224");
assertThat(existing.getUnitVolumeCbm()).isEqualByComparingTo("0.101");
assertThat(existing.getTotalVolumeCbm()).isEqualByComparingTo("0.4040");
assertThat(existing.getVolumeWeightKg()).isEqualByComparingTo("67.468");
assertThat(existing.getSourceImageFileId()).isEqualTo(99001L);
assertThat(existing.getScannedShipmentId()).isEqualTo("FBA-SCAN-002");
verify(images).delete("internal-token", 90001L, 10001L, 70001L, 88001L);
@@ -95,10 +95,10 @@ class CustomerDimensionConfigServiceTest {
assertThat(result.rules()).extracting(rule -> rule.boxType())
.containsExactly(CustomerDimensionBoxType.values());
assertThat(result.rules().get(0).boxTypeName()).isEqualTo("小箱");
assertThat(result.rules().get(0).outerLengthCm()).isEqualByComparingTo("32.000");
assertThat(result.rules().get(0).outerWidthCm()).isEqualByComparingTo("22.000");
assertThat(result.rules().get(0).outerHeightCm()).isEqualByComparingTo("22.000");
assertThat(result.rules().get(0).volumeCbm()).isEqualByComparingTo("0.015");
assertThat(result.rules().get(0).outerLengthCm()).isEqualByComparingTo("34.000");
assertThat(result.rules().get(0).outerWidthCm()).isEqualByComparingTo("24.000");
assertThat(result.rules().get(0).outerHeightCm()).isEqualByComparingTo("24.000");
assertThat(result.rules().get(0).volumeCbm()).isEqualByComparingTo("0.020");
}
@Test
@@ -206,7 +206,7 @@ class CustomerDimensionConfigServiceTest {
value.setInnerWidthCm(type.defaultWidthCm());
value.setInnerHeightCm(type.defaultHeightCm());
value.setEdgeAdditionCm(new BigDecimal("2.000"));
value.setDefaultCalculationMode("ADD");
value.setDefaultCalculationMode(CustomerDimensionConfigService.DEFAULT_CALCULATION_MODE);
value.setRemark(type.defaultRemark());
value.setVersion(0);
value.setInitializationKey(initializationKey);
@@ -32,7 +32,7 @@ class CustomerDimensionPricingFlowTest {
var kgEstimate = pricing.estimate(rate("KG", "8.700000"), actualWeight, twoCartonsVolume);
var cbmEstimate = pricing.estimate(rate("CBM", "1400.000000"), actualWeight, twoCartonsVolume);
assertThat(twoCartonsVolume).isEqualByComparingTo("0.030");
assertThat(twoCartonsVolume).isEqualByComparingTo("0.006");
assertThat(kgEstimate.quantity()).isEqualByComparingTo("24.000000");
assertThat(kgEstimate.amount()).isEqualByComparingTo("208.80");
assertThat(cbmEstimate.quantity()).isEqualByComparingTo("0.066116");
@@ -26,16 +26,16 @@ class CustomerDimensionWeightingServiceTest {
}
@Test
void measuredDimensionsUseSmallestContainingRuleAndAddConfiguredValueOncePerDimension() {
void measuredDimensionsUseSmallestContainingRuleAndAddEachSide() {
var weighted = service.load(10001L).resolve(
new BigDecimal("10"), new BigDecimal("10"), new BigDecimal("10"));
assertThat(weighted.boxType()).isEqualTo(CustomerDimensionBoxType.SMALL);
assertThat(weighted.outerLengthCm()).isEqualByComparingTo("32.000");
assertThat(weighted.outerWidthCm()).isEqualByComparingTo("22.000");
assertThat(weighted.outerHeightCm()).isEqualByComparingTo("22.000");
assertThat(weighted.rawVolumeCbm()).isEqualByComparingTo("0.015488");
assertThat(weighted.unitVolumeCbm()).isEqualByComparingTo("0.015");
assertThat(weighted.outerLengthCm()).isEqualByComparingTo("14.000");
assertThat(weighted.outerWidthCm()).isEqualByComparingTo("14.000");
assertThat(weighted.outerHeightCm()).isEqualByComparingTo("14.000");
assertThat(weighted.rawVolumeCbm()).isEqualByComparingTo("0.002744");
assertThat(weighted.unitVolumeCbm()).isEqualByComparingTo("0.003");
}
@Test
@@ -52,7 +52,7 @@ class CustomerDimensionWeightingServiceTest {
new BigDecimal("30.001"), new BigDecimal("20"), new BigDecimal("20"));
assertThat(weighted.boxType()).isEqualTo(CustomerDimensionBoxType.MEDIUM);
assertThat(weighted.unitVolumeCbm()).isEqualByComparingTo("0.043");
assertThat(weighted.unitVolumeCbm()).isEqualByComparingTo("0.020");
}
@Test
@@ -62,9 +62,9 @@ class CustomerDimensionWeightingServiceTest {
assertThat(weighted.boxType()).isEqualTo(CustomerDimensionBoxType.EXTRA_LARGE);
assertThat(weighted.edgeAdditionCm()).isEqualByComparingTo("2.000");
assertThat(weighted.outerLengthCm()).isEqualByComparingTo("62.001");
assertThat(weighted.outerWidthCm()).isEqualByComparingTo("52.000");
assertThat(weighted.outerHeightCm()).isEqualByComparingTo("52.000");
assertThat(weighted.outerLengthCm()).isEqualByComparingTo("64.001");
assertThat(weighted.outerWidthCm()).isEqualByComparingTo("54.000");
assertThat(weighted.outerHeightCm()).isEqualByComparingTo("54.000");
assertThat(weighted.calculationRuleVersion()).isEqualTo("CUSTOMER_DIMENSION_EXTRA_LARGE_V3");
}
@@ -24,13 +24,13 @@ class CustomerDimensionWeightingTerminalRulesTest {
var ruleSet = service.load(10001L);
assertWeighted(ruleSet, "10", "10", "10", CustomerDimensionBoxType.SMALL,
"32", "22", "22", "0.015");
"14", "14", "14", "0.003");
assertWeighted(ruleSet, "35", "25", "25", CustomerDimensionBoxType.MEDIUM,
"42", "32", "32", "0.043");
"39", "29", "29", "0.033");
assertWeighted(ruleSet, "45", "35", "35", CustomerDimensionBoxType.LARGE,
"52", "42", "42", "0.092");
"49", "39", "39", "0.075");
assertWeighted(ruleSet, "55", "45", "45", CustomerDimensionBoxType.EXTRA_LARGE,
"62", "52", "52", "0.168");
"59", "49", "49", "0.142");
}
private static void assertWeighted(CustomerDimensionWeightingService.RuleSet rules,
@@ -9,6 +9,8 @@ import com.baiyue.firstleg.entity.InboundFeeEntity;
import com.baiyue.firstleg.entity.InboundFeeItemEntity;
import com.baiyue.firstleg.entity.InboundPricingEntity;
import com.baiyue.firstleg.entity.InboundPricingIdempotencyEntity;
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
import com.baiyue.firstleg.pricing.ActivePriceBookEstimator;
import com.baiyue.firstleg.repository.FbaPricingRepository;
import com.baiyue.firstleg.pricebookquery.application.model.PriceBookQueryRows.RateRow;
@@ -27,6 +29,7 @@ import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.concurrent.atomic.AtomicReference;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -49,6 +52,7 @@ class FbaPricingServiceTest {
private static final long INBOUND = 30001L;
private static final LocalDateTime NOW = LocalDateTime.of(2026, 8, 3, 10, 0);
@MockBean private FbaPricingRepository repository;
@MockBean private com.baiyue.firstleg.repository.InboundMeasurementRepository measurementRepository;
@MockBean private InboundFeeDetailService feeDetails;
@MockBean private ActivePriceBookEstimator priceBookEstimator;
@Resource private FbaPricingService service;
@@ -100,6 +104,35 @@ class FbaPricingServiceTest {
assertThat(fee.getValue().getRoundingMode()).isEqualTo("HALF_UP");
}
@Test
@DisplayName("外箱规计费保存测量批次和尺寸来源快照")
void bindsSavedOuterMeasurementWhenCalculatingCustomerFee() {
readyRepository();
InboundPricingEntity inbound = inbound("62.500", "1.0019");
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
measurement.setId(301L);
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
item.setOuterLengthCm(new BigDecimal("34"));
item.setOuterWidthCm(new BigDecimal("24"));
item.setOuterHeightCm(new BigDecimal("64"));
item.setCalculationRuleVersion("CUSTOMER_DIMENSION_SMALL_V3");
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
when(measurementRepository.findLatestMeasurement(TENANT, INBOUND)).thenReturn(measurement);
when(measurementRepository.listItems(TENANT, 301L)).thenReturn(List.of(item));
when(priceBookEstimator.estimate(any())).thenReturn(
estimate("CBM", "1.0019", "100", "100.19", "0"));
service.calculate(TENANT, OPERATOR, INBOUND, NOW,
new BigDecimal("62.500"), new BigDecimal("1.0019"),
"OUTER", "OUTER", "calc-outer");
ArgumentCaptor<InboundFeeEntity> fee = ArgumentCaptor.forClass(InboundFeeEntity.class);
verify(repository).insertFee(fee.capture());
assertThat(fee.getValue().getMeasurementId()).isEqualTo(301L);
assertThat(fee.getValue().getMeasurementSource()).isEqualTo("OUTER");
assertThat(fee.getValue().getActualVolumeCbm()).isEqualByComparingTo("1.0019");
}
@Test
@DisplayName("缺少订单客户归属时拒绝生成对客费用")
void rejectsMissingOrderCustomerOwnership() {
@@ -182,44 +215,31 @@ class FbaPricingServiceTest {
}
@Test
@DisplayName("装柜完成后的自动费用仍可确认")
void confirmsCalculatedFeeAfterLoadingCompleted() {
@DisplayName("装柜完成后禁止确认费用")
void rejectsConfirmAfterLoadingCompleted() {
InboundPricingEntity inbound = inbound("10.000", "1.0000");
inbound.setProcessStatus("LOADED");
inbound.setFeeStatus("CALCULATED");
InboundFeeEntity fee = savedFee("CALCULATED");
when(repository.findLatestFee(TENANT, INBOUND)).thenReturn(fee);
when(repository.findIdempotency(TENANT, OPERATOR, "FEE_CONFIRM", "confirm-loaded"))
.thenReturn(null);
doAnswer(invocation -> {
((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(900L);
return null;
}).when(repository).insertIdempotency(any());
when(repository.confirmFee(TENANT, 501L, OPERATOR, NOW)).thenReturn(1);
when(repository.markInboundConfirmed(TENANT, INBOUND)).thenReturn(1);
when(repository.completeIdempotency(any(), eq(501L), eq(NOW))).thenReturn(1);
when(repository.findFee(TENANT, 501L)).thenReturn(fee);
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
FeeSnapshotResponse response = service.confirm(
TENANT, OPERATOR, INBOUND, "confirm-loaded");
assertThat(response.nextProcessStatus()).isEqualTo("LOADED");
verify(repository).markInboundConfirmed(TENANT, INBOUND);
assertThatThrownBy(() -> service.confirm(TENANT, OPERATOR, INBOUND, "confirm-loaded"))
.isInstanceOf(BizException.class)
.hasMessageContaining("装柜流程");
verify(repository, never()).markInboundConfirmed(TENANT, INBOUND);
}
@Test
@DisplayName("审核未通过的测量快照禁止计费")
void rejectsUnapprovedMeasurementSnapshot() {
@DisplayName("审核未通过的测量快照也允许计费")
void allowsUnapprovedMeasurementSnapshot() {
readyRepository();
InboundPricingEntity inbound = inbound("10", "1");
inbound.setMeasureReviewStatus("REJECTED");
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
when(priceBookEstimator.estimate(any())).thenReturn(
estimate("CBM", "1.0000", "10", "10", "0"));
assertThatThrownBy(() -> service.calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-3"))
.isInstanceOf(BizException.class)
.hasMessageContaining("审核通过");
verify(priceBookEstimator, never()).estimate(any());
assertThat(service.calculate(TENANT, OPERATOR, INBOUND, NOW, "calc-3")).isNotNull();
verify(priceBookEstimator).estimate(any());
}
@Test
@@ -368,6 +388,55 @@ class FbaPricingServiceTest {
eq(TENANT), eq(INBOUND), eq(502L), eq(2), eq(NOW));
}
@Test
@DisplayName("客户确认已发送费用后记录确认人并锁定版本")
void customerConfirmsSentFeeAndLocksVersion() {
InboundFeeEntity current = savedFee("SENT");
when(repository.findLatestFee(TENANT, INBOUND)).thenReturn(current);
when(repository.findIdempotency(TENANT, OPERATOR, "CUSTOMER_FEE_CONFIRM", "customer-confirm-1"))
.thenReturn(null);
doAnswer(invocation -> { ((InboundPricingIdempotencyEntity) invocation.getArgument(0)).setId(910L); return null; })
.when(repository).insertIdempotency(any());
when(repository.confirmFeeByCustomer(TENANT, 501L, 1, OPERATOR, "客户成员", NOW))
.thenAnswer(invocation -> {
current.setCustomerConfirmedBy(OPERATOR);
current.setCustomerConfirmedByName("客户成员");
current.setCustomerConfirmedAt(NOW);
return 1;
});
when(repository.completeIdempotency(any(), eq(501L), eq(NOW))).thenReturn(1);
when(repository.findFee(TENANT, 501L)).thenReturn(current);
FeeSnapshotResponse response = service.confirmByCustomer(
TENANT, OPERATOR, "客户成员", INBOUND, 1, "customer-confirm-1");
assertThat(response.customerConfirmationStatus()).isEqualTo("CONFIRMED");
assertThat(response.customerConfirmedBy()).isEqualTo(OPERATOR);
assertThat(response.customerConfirmedByName()).isEqualTo("客户成员");
assertThat(response.customerConfirmedAt()).isEqualTo(NOW);
}
@Test
@DisplayName("客户已确认费用时拒绝平台再次修改")
void rejectsAdjustmentAfterCustomerConfirmation() {
InboundPricingEntity inbound = inbound("10", "1");
inbound.setProcessStatus("WAIT_SPLIT");
InboundFeeEntity current = savedFee("SENT");
current.setCustomerConfirmedAt(NOW);
when(repository.findInbound(TENANT, INBOUND)).thenReturn(inbound);
when(repository.findLatestFee(TENANT, INBOUND)).thenReturn(current);
assertThatThrownBy(() -> service.adjustPublished(TENANT, OPERATOR, INBOUND,
new com.baiyue.firstleg.dto.FbaPricingDtos.AdjustFeeRequest(
1, new BigDecimal("12.5"), new BigDecimal("8.00"),
BigDecimal.ZERO, "客服修正"), "locked-adjust"))
.isInstanceOf(BizException.class)
.hasMessageContaining("客户已确认");
verify(repository, never()).insertIdempotency(any());
verify(repository, never()).supersedeCurrent(anyLong(), anyLong(), any());
}
@Test
@DisplayName("初算费用调整后保持初算状态且不发布财务来源")
void adjustsCalculatedFeeWithoutPublishingIt() {
@@ -41,6 +41,8 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
@@ -235,6 +237,9 @@ class InboundMeasurementServiceTest {
item.setOuterLengthCm(new BigDecimal("65.000"));
item.setOuterWidthCm(new BigDecimal("55.000"));
item.setOuterHeightCm(new BigDecimal("55.000"));
item.setManualOuterLengthCm(new BigDecimal("65.000"));
item.setManualOuterWidthCm(new BigDecimal("55.000"));
item.setManualOuterHeightCm(new BigDecimal("55.000"));
item.setCalculationRuleVersion("WAREHOUSE_OUTER_OVERRIDE_V1");
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(repository.findLatestMeasurement(1L, 10L)).thenReturn(measurement);
@@ -260,6 +265,7 @@ class InboundMeasurementServiceTest {
item.setOuterLengthCm(new BigDecimal("65.000"));
item.setOuterWidthCm(new BigDecimal("54.000"));
item.setOuterHeightCm(new BigDecimal("54.000"));
item.setManualOuterLengthCm(new BigDecimal("65.000"));
item.setCalculationRuleVersion("WAREHOUSE_OUTER_OVERRIDE_L_V1");
when(repository.findInbound(1L, 10L)).thenReturn(inbound);
when(repository.findLatestMeasurement(1L, 10L)).thenReturn(measurement);
@@ -894,7 +900,8 @@ class InboundMeasurementServiceTest {
@Test
void submitAdvancesOnlyToMeasurePendingAfterAllWeightsConfirmed() {
InboundOrderEntity inbound = inbound("MEASURING");
InboundOrderEntity inbound = spy(inbound("MEASURING"));
clearInvocations(inbound);
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
measurement.setId(30L);
measurement.setTenantId(1L);
@@ -928,6 +935,8 @@ class InboundMeasurementServiceTest {
assertThat(inbound.getActualNetWeightKg()).isEqualByComparingTo("20.000");
assertThat(inbound.getPalletCount()).isEqualTo(1);
assertThat(inbound.getActualVolumeCbm()).isEqualByComparingTo("1.2345");
assertThat(inbound.getTotalPackageCount()).isEqualTo(3);
verify(inbound, never()).setTotalPackageCount(any());
}
@Test
@@ -212,12 +212,12 @@ class MeasurementBatchServiceTest {
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
InboundMeasurementItemEntity calculated = items.getValue().get(0);
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("61.500");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("43.200");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("33.300");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("214.500");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.088");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.1760");
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("63.500");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("45.200");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("35.300");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("224.500");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.101");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.2020");
}
@Test
@@ -235,12 +235,12 @@ class MeasurementBatchServiceTest {
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
InboundMeasurementItemEntity calculated = items.getValue().get(0);
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("62.001");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("52.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("52.000");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("270.001");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.168");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.3360");
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("64.001");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("54.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("54.000");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("280.001");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.187");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.3740");
assertThat(calculated.getCalculationRuleVersion())
.isEqualTo("CUSTOMER_DIMENSION_EXTRA_LARGE_V0");
}
@@ -261,13 +261,16 @@ class MeasurementBatchServiceTest {
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
InboundMeasurementItemEntity calculated = items.getValue().get(0);
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("50.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("40.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("30.000");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("200.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0699");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.1398");
assertThat(calculated.getVolumeWeightKg()).isEqualByComparingTo("23.347");
assertThat(calculated.getManualOuterLengthCm()).isEqualByComparingTo("50");
assertThat(calculated.getManualOuterWidthCm()).isEqualByComparingTo("40");
assertThat(calculated.getManualOuterHeightCm()).isEqualByComparingTo("30");
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("54.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("44.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("34.000");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("210.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0808");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.1616");
assertThat(calculated.getVolumeWeightKg()).isEqualByComparingTo("26.988");
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_V1");
}
@@ -287,15 +290,44 @@ class MeasurementBatchServiceTest {
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
InboundMeasurementItemEntity calculated = items.getValue().get(0);
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("50.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("32.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("22.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0367");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.0734");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("160.000");
assertThat(calculated.getManualOuterLengthCm()).isEqualByComparingTo("50");
assertThat(calculated.getManualOuterWidthCm()).isNull();
assertThat(calculated.getManualOuterHeightCm()).isNull();
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("54.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("34.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("24.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0441");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.0882");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("170.000");
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_L_V1");
}
@Test
void actualStrategyUsesEachWarehouseValueAndFillsBlankDimensionsFromMeasurement() {
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
80001L, "001", "A", 1, new BigDecimal("61.1"), new BigDecimal("41.2"),
new BigDecimal("31.3"), 500L, null, new BigDecimal("11"),
null, new BigDecimal("11"), null, "ACTUAL", "宽未填写");
MeasurementBatchCommand command = new MeasurementBatchCommand(
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(source));
service.saveBatch(command);
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
InboundMeasurementItemEntity calculated = items.getValue().get(0);
assertThat(calculated.getManualOuterLengthCm()).isEqualByComparingTo("11");
assertThat(calculated.getManualOuterWidthCm()).isNull();
assertThat(calculated.getManualOuterHeightCm()).isEqualByComparingTo("11");
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("11.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("41.200");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("11.000");
assertThat(calculated.getCoefficientMode()).isEqualTo("ACTUAL");
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_LH_V1");
}
@Test
void warehouseOverrideUsesTheTierMatchedByItsBaseDimensionsBeforeMultiplyStrategy() {
CustomerChargeRuleEntity rule = new CustomerChargeRuleEntity();
@@ -325,7 +357,10 @@ class MeasurementBatchServiceTest {
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
InboundMeasurementItemEntity calculated = items.getValue().get(0);
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("31.000");
assertThat(calculated.getManualOuterLengthCm()).isEqualByComparingTo("31");
assertThat(calculated.getManualOuterWidthCm()).isNull();
assertThat(calculated.getManualOuterHeightCm()).isNull();
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("37.200");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("24.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("24.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0215");
@@ -154,6 +154,59 @@ class MerchantInboundBillingServiceTest {
verify(pricing, never()).latest(10001L, 88L);
}
@Test
void shouldPublishSavedOuterDimensionsAndOuterTotalsInsteadOfMeasuredDimensions() {
InboundOrderEntity inbound = inbound("WAIT_SPLIT", "APPROVED", "SENT");
InboundOrderDetail detail = new InboundOrderDetail(
mock(com.baiyue.firstleg.dto.InboundOrderSummary.class), null,
List.of(), List.of(), List.of(), List.of());
InboundMeasurementResponses.Detail measurement = mock(InboundMeasurementResponses.Detail.class);
InboundMeasurementResponses.Item item = mock(InboundMeasurementResponses.Item.class);
FeeSnapshotResponse fee = mock(FeeSnapshotResponse.class);
InboundFeeDetailEntity published = new InboundFeeDetailEntity();
published.setId(601L);
when(orders.findByOrderId(10001L, 77L)).thenReturn(inbound);
when(orders.detail(10001L, inbound)).thenReturn(detail);
when(measurements.detail(10001L, 88L)).thenReturn(measurement);
when(measurement.items()).thenReturn(List.of(item));
when(measurement.weightResults()).thenReturn(List.of());
when(measurement.actualGrossWeightKg()).thenReturn(new BigDecimal("62.5"));
when(item.itemNo()).thenReturn("1");
when(item.packageCount()).thenReturn(2);
when(item.lengthCm()).thenReturn(new BigDecimal("30"));
when(item.widthCm()).thenReturn(new BigDecimal("20"));
when(item.heightCm()).thenReturn(new BigDecimal("60"));
when(item.calculatedOuterLengthCm()).thenReturn(new BigDecimal("34"));
when(item.calculatedOuterWidthCm()).thenReturn(new BigDecimal("24"));
when(item.calculatedOuterHeightCm()).thenReturn(new BigDecimal("64"));
when(item.totalVolumeCbm()).thenReturn(new BigDecimal("0.1046"));
when(item.volumeWeightKg()).thenReturn(new BigDecimal("17.469"));
when(item.coefficientMode()).thenReturn("ADD");
when(item.calculationRuleVersion()).thenReturn("CUSTOMER_DIMENSION_SMALL_V3");
when(pricing.latest(10001L, 88L)).thenReturn(fee);
when(feeDetails.publishedFreight(10001L, 88L)).thenReturn(published);
when(fee.chargeMode()).thenReturn("CBM");
when(fee.measurementId()).thenReturn(301L);
when(fee.chargeUnit()).thenReturn("CBM");
when(fee.actualVolumeCbm()).thenReturn(new BigDecimal("0.1046"));
when(fee.convertedQuantity()).thenReturn(new BigDecimal("0.1000"));
when(measurements.detail(10001L, 88L, 301L)).thenReturn(measurement);
var result = service.find(10001L, 77L);
assertThat(result.measurementItems()).singleElement().satisfies(value -> {
assertThat(value.lengthCm()).isEqualByComparingTo("34");
assertThat(value.widthCm()).isEqualByComparingTo("24");
assertThat(value.heightCm()).isEqualByComparingTo("64");
assertThat(value.calculationRuleVersion()).isEqualTo("CUSTOMER_DIMENSION_SMALL_V3");
});
assertThat(result.measurementSummary().actualPackageCount()).isEqualTo(2);
assertThat(result.measurementSummary().actualVolumeCbm()).isEqualByComparingTo("0.1046");
assertThat(result.measurementSummary().volumeWeightKg()).isEqualByComparingTo("17.469");
assertThat(result.measurementSummary().calculationSource()).isEqualTo("OUTER");
verify(measurements).detail(10001L, 88L, 301L);
}
@ParameterizedTest
@ValueSource(strings = {"FEE_CONFIRMING", "VOIDED"})
void shouldRejectProcessStatusesOutsidePublishedFulfillmentLifecycle(String processStatus) {