一件代发拣货

This commit is contained in:
2026-09-03 10:44:34 +08:00
parent cf77e19765
commit a3715f618f
40 changed files with 804 additions and 103 deletions
@@ -58,5 +58,11 @@ class CustomerDimensionConfigControllerContractTest {
long.class, com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.UpdateRequest.class)
.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('firstleg:dimension-config:edit')");
assertThat(PlatformCustomerDimensionConfigController.class
.getDeclaredMethod("updateDefaultCalculationMode",
com.baiyue.common.security.AuthenticatedUser.class, long.class,
com.baiyue.firstleg.dto.CustomerDimensionConfigDtos.DefaultCalculationModeUpdateRequest.class)
.getAnnotation(PreAuthorize.class).value())
.isEqualTo("hasAuthority('firstleg:dimension-config:edit')");
}
}
@@ -3,6 +3,7 @@ package com.baiyue.firstleg.controller;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.RequestParam;
import org.yaml.snakeyaml.Yaml;
@@ -15,6 +16,15 @@ import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("FBA计费OpenAPI契约")
class FbaPricingContractTest {
@Test
@DisplayName("确认和发送费用兼容新的组合权限与原有权限")
void allowsCombinedConfirmSendPermissionAndLegacyPermissions() {
assertPricingAuthority("confirm", "hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
+ " 'firstleg:fba-pricing:confirm')");
assertPricingAuthority("send", "hasAnyAuthority('firstleg:inbound:confirm-send-fee',"
+ " 'firstleg:inbound:send-fee')");
}
@Test
@DisplayName("客户规则保留客户参数,船司价格使用平台公共作用域")
void keepsCustomerRuleScopeAndRemovesCarrierRateTenantScope() {
@@ -25,7 +35,7 @@ class FbaPricingContractTest {
Method rates = method("rates");
assertThat(rates.getParameterTypes()).doesNotContain(Long.class);
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule"}) {
for (String scopedMethod : new String[] {"rule", "createRule", "retireRule", "setDefaultRule"}) {
Method method = method(scopedMethod);
assertThat(method.getParameterTypes()[1]).isEqualTo(long.class);
assertThat(method.getParameters()[1].getAnnotation(RequestParam.class).required()).isTrue();
@@ -45,6 +55,7 @@ class FbaPricingContractTest {
.getContentAsString(StandardCharsets.UTF_8);
assertThat(openapi)
.contains("/api/platform/v1/fba-pricing/customer-rules/versions:")
.contains("/api/platform/v1/fba-pricing/customer-rules/{id}/default:")
.contains("/api/platform/v1/fba-pricing/carrier-rates/versions:")
.contains("/api/platform/v1/inbounds/{inboundId}/fees/calculate:")
.contains("/api/platform/v1/inbounds/{inboundId}/fees/confirm:")
@@ -146,6 +157,13 @@ class FbaPricingContractTest {
.findFirst().orElseThrow();
}
private static void assertPricingAuthority(String name, String expected) {
Method method = java.util.Arrays.stream(PlatformFbaPricingController.class.getDeclaredMethods())
.filter(candidate -> candidate.getName().equals(name))
.findFirst().orElseThrow();
assertThat(method.getAnnotation(PreAuthorize.class).value()).isEqualTo(expected);
}
@SuppressWarnings("unchecked")
private static Map<String, Object> map(Object value) {
return (Map<String, Object>) value;
@@ -58,7 +58,8 @@ class PlatformInboundMeasurementControllerContractTest {
"hasAnyAuthority('platform:firstleg:inbound:measure:view',"
+ " 'platform:firstleg:inbound:weight:edit')",
AuthenticatedUser.class, long.class, long.class, long.class, long.class, String.class);
assertAuthority("saveDraft", "hasAuthority('platform:firstleg:inbound:measure:edit')",
assertAuthority("saveDraft", "hasAnyAuthority('platform:firstleg:inbound:warehouse-suggestion:edit',"
+ " 'platform:firstleg:inbound:measure:edit')",
AuthenticatedUser.class, long.class, long.class,
InboundMeasurementRequests.SaveDraftRequest.class);
assertAuthority("submit", "hasAuthority('platform:firstleg:inbound:measure:submit')",
@@ -5,6 +5,7 @@ import com.baiyue.firstleg.mapper.CustomerDimensionConfigMapper;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
@@ -34,6 +35,7 @@ class CustomerDimensionConfigSqlContractTest {
.contains("inner_width_cm DECIMAL(9,3) NOT NULL")
.contains("inner_height_cm DECIMAL(9,3) NOT NULL")
.contains("edge_addition_cm DECIMAL(6,3) NOT NULL DEFAULT 2.000")
.contains("default_calculation_mode VARCHAR(16) NOT NULL DEFAULT 'ADD'")
.contains("initialization_key VARCHAR(128) NOT NULL")
.contains("created_by BIGINT UNSIGNED NOT NULL")
.contains("updated_by BIGINT UNSIGNED NOT NULL")
@@ -88,6 +90,19 @@ class CustomerDimensionConfigSqlContractTest {
.contains("remark = CASE box_type");
}
@Test
void defaultCalculationModeUpdateCoversTheWholeCustomerRuleSet() throws Exception {
Update annotation = CustomerDimensionConfigMapper.class
.getDeclaredMethod("updateDefaultCalculationMode", long.class, String.class, long.class)
.getAnnotation(Update.class);
String sql = String.join(" ", annotation.value());
assertThat(sql)
.contains("default_calculation_mode = #{mode}")
.contains("tenant_id = #{tenantId}")
.contains("version = version + 1");
}
@Test
void unifiedMigrationKeepsPermissionGuardsAndAssertsFinalFourRowSchema() throws Exception {
String sql = Files.readString(Path.of("..", "deploy", "sql", "头程数据库统一增量-MySQL8.sql"),
@@ -140,6 +140,20 @@ class CustomerDimensionConfigServiceTest {
verify(repository, times(2)).update(any());
}
@Test
void savesAndReturnsTheCustomerDefaultCalculationMode() {
List<CustomerDimensionConfigEntity> existing = defaultConfigs("registration-1");
List<CustomerDimensionConfigEntity> saved = defaultConfigs("registration-1");
saved.forEach(value -> value.setDefaultCalculationMode("MULTIPLY"));
when(repository.findAll(8L)).thenReturn(existing, saved);
when(repository.updateDefaultCalculationMode(8L, "MULTIPLY", 100L)).thenReturn(4);
GroupResponse result = service.updateDefaultCalculationMode(8L, 100L, "MULTIPLY");
assertThat(result.defaultCalculationMode()).isEqualTo("MULTIPLY");
verify(repository).updateDefaultCalculationMode(8L, "MULTIPLY", 100L);
}
@Test
void compensationLocksAndDeletesExactlyFourUnchangedDefaults() {
when(repository.findDefaultsForCompensation(8L, "registration-1"))
@@ -192,6 +206,7 @@ class CustomerDimensionConfigServiceTest {
value.setInnerWidthCm(type.defaultWidthCm());
value.setInnerHeightCm(type.defaultHeightCm());
value.setEdgeAdditionCm(new BigDecimal("2.000"));
value.setDefaultCalculationMode("ADD");
value.setRemark(type.defaultRemark());
value.setVersion(0);
value.setInitializationKey(initializationKey);
@@ -1,6 +1,5 @@
package com.baiyue.firstleg.service;
import com.baiyue.common.core.exception.BizException;
import com.baiyue.firstleg.dto.FbaPricingDtos.CustomerRuleVersionRequest;
import com.baiyue.firstleg.dto.FbaPricingDtos.CarrierRateVersionRequest;
import com.baiyue.firstleg.entity.CarrierFbaRateEntity;
@@ -17,10 +16,10 @@ import java.time.LocalDateTime;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -47,10 +46,9 @@ class FbaMasterDataServiceTest {
}
@Test
@DisplayName("半开有效期首尾相接不视为重叠")
void acceptsAdjacentHalfOpenPeriods() {
@DisplayName("有效期合法时创建客户规则")
void createsRuleWithValidPeriod() {
CustomerChargeRuleEntity old = activeRule(AUGUST.minusMonths(1), AUGUST);
when(repository.listRules(1L, null)).thenReturn(List.of(old));
when(repository.listRules(1L, "CR-1")).thenReturn(List.of(old));
when(repository.nextRuleVersion(1L, "CR-1")).thenReturn(2);
doAnswer(invocation -> { ((CustomerChargeRuleEntity) invocation.getArgument(0)).setId(10L); return null; })
@@ -61,18 +59,25 @@ class FbaMasterDataServiceTest {
assertThat(created.getId()).isEqualTo(10L);
assertThat(created.getRuleVersion()).isEqualTo(2);
assertThat(created.getEffectiveFrom()).isEqualTo(AUGUST);
assertThat(created.getEffectiveTo()).isEqualTo(AUGUST.plusMonths(1));
}
@Test
@DisplayName("任意重叠有效期在保存阶段拒绝避免多规则匹配")
void rejectsOverlappingPeriods() {
when(repository.listRules(1L, null)).thenReturn(List.of(
activeRule(AUGUST, AUGUST.plusMonths(1))));
@DisplayName("重叠配置允许新增且新增时固定不启用")
void createsOverlappingRuleAsNonDefault() {
when(repository.nextRuleVersion(1L, "CR-1")).thenReturn(2);
doAnswer(invocation -> { ((CustomerChargeRuleEntity) invocation.getArgument(0)).setId(11L); return null; })
.when(repository).insertRule(any());
assertThatThrownBy(() -> service.createRuleVersion(
1L, 2L, request(AUGUST.plusDays(1), AUGUST.plusMonths(2))))
.isInstanceOf(BizException.class)
.hasMessageContaining("有效期重叠");
CustomerChargeRuleEntity created = service.createRuleVersion(
1L, 2L, request(AUGUST.plusDays(1), AUGUST.plusMonths(2), true));
assertThat(created.getId()).isEqualTo(11L);
assertThat(created.getDefaultRule()).isFalse();
verify(repository, never()).clearDefaultRule(anyLong(), anyLong(), anyLong());
verify(repository, never()).setDefaultRule(anyLong(), anyLong(), anyLong());
verify(repository, never()).listRules(1L, null);
}
@Test
@@ -92,8 +97,13 @@ class FbaMasterDataServiceTest {
}
private CustomerRuleVersionRequest request(LocalDateTime from, LocalDateTime to) {
return request(from, to, null);
}
private CustomerRuleVersionRequest request(LocalDateTime from, LocalDateTime to, Boolean defaultRule) {
return new CustomerRuleVersionRequest("CR-1", 100L, "客户A", "FBA_SEA", "KG",
"TAX_INCLUDED", new BigDecimal("167"), 3, "CEILING", "USD", from, to, null);
"TAX_INCLUDED", new BigDecimal("167"), 3, "CEILING", "USD", from, to, null,
BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, defaultRule);
}
private CarrierRateVersionRequest carrierRateRequest() {
@@ -7,6 +7,8 @@ import com.baiyue.firstleg.entity.CustomerDimensionConfigEntity;
import com.baiyue.firstleg.entity.InboundMeasurementEntity;
import com.baiyue.firstleg.entity.InboundMeasurementItemEntity;
import com.baiyue.firstleg.entity.InboundOrderEntity;
import com.baiyue.firstleg.entity.CustomerChargeRuleEntity;
import com.baiyue.firstleg.repository.FbaPricingRepository;
import com.baiyue.firstleg.repository.InboundMeasurementRepository;
import com.baiyue.firstleg.repository.CustomerDimensionConfigRepository;
import jakarta.annotation.Resource;
@@ -34,6 +36,7 @@ import static org.mockito.Mockito.when;
class MeasurementBatchServiceTest {
@MockBean private InboundMeasurementRepository repository;
@MockBean private CustomerDimensionConfigRepository dimensionConfigs;
@MockBean private FbaPricingRepository pricingRepository;
@Resource private MeasurementBatchService service;
@BeforeEach
@@ -62,14 +65,17 @@ class MeasurementBatchServiceTest {
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
assertThat(items.getValue().get(0).getUnitVolumeCbm()).isEqualByComparingTo("0.0510");
assertThat(items.getValue().get(0).getTotalVolumeCbm()).isEqualByComparingTo("0.1020");
assertThat(items.getValue().get(0).getOuterLengthCm()).isEqualByComparingTo("44.000");
assertThat(items.getValue().get(0).getOuterWidthCm()).isEqualByComparingTo("34.000");
assertThat(items.getValue().get(0).getOuterHeightCm()).isEqualByComparingTo("34.000");
assertThat(items.getValue().get(0).getUnitVolumeCbm()).isEqualByComparingTo("0.0267");
assertThat(items.getValue().get(0).getTotalVolumeCbm()).isEqualByComparingTo("0.0534");
assertThat(items.getValue().get(0).getOuterLengthCm()).isEqualByComparingTo("41.000");
assertThat(items.getValue().get(0).getOuterWidthCm()).isEqualByComparingTo("31.000");
assertThat(items.getValue().get(0).getOuterHeightCm()).isEqualByComparingTo("21.000");
assertThat(items.getValue().get(0).getCalculationRuleVersion())
.isEqualTo("CUSTOMER_DIMENSION_MEDIUM_V0");
assertThat(items.getValue().get(0).getGirthCm()).isEqualByComparingTo("140.000");
.isEqualTo("WAREHOUSE_OUTER_OVERRIDE_V1");
assertThat(items.getValue().get(0).getCoefficientMode()).isNull();
assertThat(items.getValue().get(0).getChargeRuleId()).isNull();
assertThat(items.getValue().get(0).getGirthCm()).isEqualByComparingTo("145.000");
verify(pricingRepository).findDefaultRule(1L, 1L);
verify(repository, never()).updateInbound(any());
}
@@ -83,14 +89,163 @@ class MeasurementBatchServiceTest {
InboundMeasurementEntity saved = service.saveBatch(command);
assertThat(saved.getStatus()).isEqualTo("DRAFT");
assertThat(saved.getTotalVolumeCbm()).isEqualByComparingTo("0.1020");
assertThat(saved.getVolumeWeightKg()).isEqualByComparingTo("17.034");
assertThat(saved.getTotalVolumeCbm()).isEqualByComparingTo("0.0534");
assertThat(saved.getVolumeWeightKg()).isEqualByComparingTo("8.918");
ArgumentCaptor<List<InboundMeasurementItemEntity>> items = ArgumentCaptor.forClass(List.class);
verify(repository).replaceItems(org.mockito.ArgumentMatchers.eq(1L),
org.mockito.ArgumentMatchers.eq(100L), items.capture());
assertThat(items.getValue().get(0).getInboundDetailId()).isEqualTo(80001L);
}
@Test
void automaticallyAppliesCustomerDefaultRuleToActualMeasurement() {
CustomerChargeRuleEntity rule = new CustomerChargeRuleEntity();
rule.setId(77L);
rule.setTenantId(1L);
rule.setCustomerId(1L);
rule.setRuleCode("DEFAULT");
rule.setRuleVersion(2);
rule.setStatus("ACTIVE");
rule.setDefaultRule(true);
rule.setLengthFactor(new BigDecimal("1.1"));
rule.setWidthFactor(new BigDecimal("1.1"));
rule.setHeightFactor(new BigDecimal("1.1"));
when(pricingRepository.findDefaultRule(1L, 1L)).thenReturn(rule);
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
new BigDecimal("20"), 500L, null, null, null, null, null,
null, "备注");
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.getChargeRuleId()).isEqualTo(77L);
assertThat(calculated.getCoefficientMode()).isEqualTo("MULTIPLY");
assertThat(calculated.getOuterLengthCm()).isEqualByComparingTo("44.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("33.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("22.000");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("154.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0320");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.0640");
}
@Test
void usesOneMatchedTierCoefficientForAllDimensionsOfEachSpecification() {
CustomerChargeRuleEntity rule = new CustomerChargeRuleEntity();
rule.setId(78L);
rule.setTenantId(1L);
rule.setCustomerId(1L);
rule.setRuleCode("TIERED");
rule.setRuleVersion(3);
rule.setStatus("ACTIVE");
rule.setDefaultRule(true);
rule.setLengthFactor(new BigDecimal("1.05"));
rule.setWidthFactor(new BigDecimal("1.25"));
rule.setHeightFactor(new BigDecimal("1.35"));
rule.setRemark("SMALL:30,20,20;系数:1.1;小箱:—;MEDIUM:40,30,30;系数:1.2;中箱:—;"
+ "LARGE:50,40,40;系数:1.3;大箱:—;EXTRA_LARGE:60,50,50;系数:1.4;特大箱:—");
when(pricingRepository.findDefaultRule(1L, 1L)).thenReturn(rule);
MeasurementBatchCommand.Item small = new MeasurementBatchCommand.Item(
80001L, "001", "A", 1, new BigDecimal("30"), new BigDecimal("20"),
new BigDecimal("20"), 500L, null, null, null, null, null, null, "小箱");
MeasurementBatchCommand.Item medium = new MeasurementBatchCommand.Item(
80002L, "002", "B", 1, new BigDecimal("31"), new BigDecimal("20"),
new BigDecimal("20"), 501L, null, null, null, null, null, null, "中箱");
MeasurementBatchCommand command = new MeasurementBatchCommand(
1L, 10L, null, 0, "MANUAL", "PLATFORM", null, null, null,
LocalDateTime.now(), null, null, null, null, null, 9L, List.of(small, medium));
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());
assertThat(items.getValue().get(0).getOuterLengthCm()).isEqualByComparingTo("33.000");
assertThat(items.getValue().get(0).getOuterWidthCm()).isEqualByComparingTo("22.000");
assertThat(items.getValue().get(0).getOuterHeightCm()).isEqualByComparingTo("22.000");
assertThat(items.getValue().get(1).getOuterLengthCm()).isEqualByComparingTo("37.200");
assertThat(items.getValue().get(1).getOuterWidthCm()).isEqualByComparingTo("24.000");
assertThat(items.getValue().get(1).getOuterHeightCm()).isEqualByComparingTo("24.000");
}
@Test
void keepsActualMeasurementUnchangedWhenCustomerHasNoDefaultRule() {
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
new BigDecimal("20"), 500L, null, null, null, null, null, null, "备注");
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.getOuterLengthCm()).isEqualByComparingTo("40.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("30.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("20.000");
assertThat(calculated.getChargeRuleId()).isNull();
assertThat(calculated.getCoefficientMode()).isNull();
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("ACTUAL_DIMENSION_V1");
}
@Test
void addsMatchedEdgeValueToMeasuredDimensionsInsteadOfRuleBoundary() {
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
80001L, "001", "A", 2, new BigDecimal("59.5"), new BigDecimal("41.2"),
new BigDecimal("31.3"), 500L, null, null, null, null, null, "ADD", "备注");
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.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");
}
@Test
void warehouseOuterDimensionsOverrideCalculatedCartonAndAllDerivedValues() {
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
new BigDecimal("20"), 500L, null, new BigDecimal("50"),
new BigDecimal("40"), new BigDecimal("30"), null, "ADD", "仓库修正");
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.getOuterLengthCm()).isEqualByComparingTo("50.000");
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("40.000");
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("30.000");
assertThat(calculated.getGirthCm()).isEqualByComparingTo("190.000");
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0600");
assertThat(calculated.getTotalVolumeCbm()).isEqualByComparingTo("0.1200");
assertThat(calculated.getVolumeWeightKg()).isEqualByComparingTo("20.040");
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_V1");
}
@Test
void staleDraftVersionIsRejectedBeforeReplacingItems() {
InboundMeasurementEntity existing = new InboundMeasurementEntity();
@@ -149,6 +304,7 @@ class MeasurementBatchServiceTest {
InboundOrderEntity inbound = new InboundOrderEntity();
inbound.setId(10L);
inbound.setTenantId(1L);
inbound.setCustomerId(1L);
inbound.setProcessStatus("WAIT_MEASURE");
inbound.setVersion(0);
return inbound;