Merge remote-tracking branch 'origin/test' into dev
This commit is contained in:
@@ -534,18 +534,36 @@ public class InboundMeasurementService {
|
||||
}
|
||||
|
||||
private InboundMeasurementResponses.Item toItem(InboundMeasurementItemEntity item) {
|
||||
String overrideVersion = item.getCalculationRuleVersion();
|
||||
boolean legacyFullOverride = "WAREHOUSE_OUTER_OVERRIDE_V1".equals(overrideVersion);
|
||||
boolean lengthOverridden = legacyFullOverride || warehouseOverrideContains(overrideVersion, 'L');
|
||||
boolean widthOverridden = legacyFullOverride || warehouseOverrideContains(overrideVersion, 'W');
|
||||
boolean heightOverridden = legacyFullOverride || warehouseOverrideContains(overrideVersion, 'H');
|
||||
return new InboundMeasurementResponses.Item(
|
||||
item.getId(), item.getInboundDetailId(), item.getItemNo(), item.getBoxSpecification(), item.getPackageCount(),
|
||||
item.getLengthCm(), item.getWidthCm(), item.getHeightCm(), item.getGirthCm(),
|
||||
item.getUnitVolumeCbm(),
|
||||
item.getTotalVolumeCbm(), item.getVolumeWeightKg(), item.getSourceImageFileId(),
|
||||
item.getOverLimitWarning(), item.getOuterLengthCm(), item.getOuterWidthCm(),
|
||||
item.getOuterHeightCm(), item.getChargeRuleId(), item.getCoefficientMode(),
|
||||
item.getOverLimitWarning(), lengthOverridden ? item.getOuterLengthCm() : null,
|
||||
widthOverridden ? item.getOuterWidthCm() : null,
|
||||
heightOverridden ? item.getOuterHeightCm() : null,
|
||||
item.getChargeRuleId(), item.getCoefficientMode(),
|
||||
item.getRemark(), item.getBoxCode(), item.getScannedShipmentId(),
|
||||
item.getPackageCount(),
|
||||
item.getMachineNo(), item.getDeviceMeasuredAt(), item.getUpdatedAt());
|
||||
}
|
||||
|
||||
private boolean warehouseOverrideContains(String calculationRuleVersion, char dimension) {
|
||||
if (calculationRuleVersion == null
|
||||
|| !calculationRuleVersion.startsWith("WAREHOUSE_OUTER_OVERRIDE_")
|
||||
|| !calculationRuleVersion.endsWith("_V1")) {
|
||||
return false;
|
||||
}
|
||||
String dimensions = calculationRuleVersion.substring(
|
||||
"WAREHOUSE_OUTER_OVERRIDE_".length(), calculationRuleVersion.length() - 3);
|
||||
return dimensions.indexOf(dimension) >= 0;
|
||||
}
|
||||
|
||||
private void requireConfirmedPackageTotal(InboundOrderEntity inbound,
|
||||
List<InboundMeasurementItemEntity> items) {
|
||||
if (inbound.getTotalPackageCount() == null || inbound.getTotalPackageCount() < 1) {
|
||||
|
||||
@@ -201,26 +201,26 @@ public class MeasurementBatchService {
|
||||
boolean hasOuterOverride = source.outerLengthCm() != null
|
||||
|| source.outerWidthCm() != null
|
||||
|| source.outerHeightCm() != null;
|
||||
if (hasOuterOverride && (source.outerLengthCm() == null
|
||||
|| source.outerWidthCm() == null || source.outerHeightCm() == null
|
||||
|| source.outerLengthCm().signum() <= 0 || source.outerWidthCm().signum() <= 0
|
||||
|| source.outerHeightCm().signum() <= 0)) {
|
||||
if ((source.outerLengthCm() != null && source.outerLengthCm().signum() <= 0)
|
||||
|| (source.outerWidthCm() != null && source.outerWidthCm().signum() <= 0)
|
||||
|| (source.outerHeightCm() != null && source.outerHeightCm().signum() <= 0)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"仓库修改外箱长、宽、高必须同时填写且大于0,明细 " + source.itemNo());
|
||||
"仓库修改外箱尺寸必须大于0,明细 " + source.itemNo());
|
||||
}
|
||||
if (hasOuterOverride) {
|
||||
// Warehouse-confirmed outer dimensions are authoritative. Derived
|
||||
// values below (girth, volume and volumetric weight) use them directly.
|
||||
outerLength = source.outerLengthCm();
|
||||
outerWidth = source.outerWidthCm();
|
||||
outerHeight = source.outerHeightCm();
|
||||
} else if (coefficientRule != null) {
|
||||
if (coefficientRule != null) {
|
||||
BigDecimal specificationCoefficient = resolveSpecificationCoefficient(
|
||||
coefficientRule, source.lengthCm(), source.widthCm(), source.heightCm());
|
||||
outerLength = applyCoefficient(outerLength, specificationCoefficient);
|
||||
outerWidth = applyCoefficient(outerWidth, specificationCoefficient);
|
||||
outerHeight = applyCoefficient(outerHeight, specificationCoefficient);
|
||||
}
|
||||
if (hasOuterOverride) {
|
||||
// Each warehouse-confirmed dimension is authoritative on its own.
|
||||
// Unspecified dimensions keep their strategy-calculated values.
|
||||
if (source.outerLengthCm() != null) outerLength = source.outerLengthCm();
|
||||
if (source.outerWidthCm() != null) outerWidth = source.outerWidthCm();
|
||||
if (source.outerHeightCm() != null) outerHeight = source.outerHeightCm();
|
||||
}
|
||||
BigDecimal unitVolumeRaw = outerLength.multiply(outerWidth).multiply(outerHeight)
|
||||
.divide(new BigDecimal("1000000"), 6, RoundingMode.HALF_UP);
|
||||
BigDecimal unitVolume = hasMatchedAddition && !hasOuterOverride
|
||||
@@ -256,7 +256,7 @@ public class MeasurementBatchService {
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
item.setChargeRuleId(coefficientRule == null ? null : coefficientRule.getId());
|
||||
item.setCoefficientMode(coefficientMode);
|
||||
item.setCalculationRuleVersion(hasOuterOverride ? "WAREHOUSE_OUTER_OVERRIDE_V1"
|
||||
item.setCalculationRuleVersion(hasOuterOverride ? warehouseOverrideVersion(source)
|
||||
: coefficientRule != null
|
||||
? "CUSTOMER_COEFFICIENT_V" + coefficientRule.getRuleVersion() + "_MULTIPLY"
|
||||
: useDimensionRule ? weighted.calculationRuleVersion() : "ACTUAL_DIMENSION_V1");
|
||||
@@ -271,6 +271,18 @@ public class MeasurementBatchService {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
private String warehouseOverrideVersion(MeasurementBatchCommand.Item source) {
|
||||
if (source.outerLengthCm() != null && source.outerWidthCm() != null
|
||||
&& source.outerHeightCm() != null) {
|
||||
return "WAREHOUSE_OUTER_OVERRIDE_V1";
|
||||
}
|
||||
StringBuilder dimensions = new StringBuilder();
|
||||
if (source.outerLengthCm() != null) dimensions.append('L');
|
||||
if (source.outerWidthCm() != null) dimensions.append('W');
|
||||
if (source.outerHeightCm() != null) dimensions.append('H');
|
||||
return "WAREHOUSE_OUTER_OVERRIDE_" + dimensions + "_V1";
|
||||
}
|
||||
|
||||
private String normalizeMode(String mode) {
|
||||
if (mode == null || mode.isBlank()) return null;
|
||||
String normalized = mode.trim().toUpperCase(java.util.Locale.ROOT);
|
||||
|
||||
@@ -1526,9 +1526,9 @@ components:
|
||||
nullable: true
|
||||
description: 当前尺寸测量明细的 File 文件 ID;图片类型固定为 DIMENSION
|
||||
overLimitWarning: {type: string, nullable: true}
|
||||
outerLengthCm: {type: number, format: double, nullable: true}
|
||||
outerWidthCm: {type: number, format: double, nullable: true}
|
||||
outerHeightCm: {type: number, format: double, nullable: true}
|
||||
outerLengthCm: {type: number, format: double, nullable: true, description: 仓库手工保存的外箱长度;未保存人工修正时为空}
|
||||
outerWidthCm: {type: number, format: double, nullable: true, description: 仓库手工保存的外箱宽度;未保存人工修正时为空}
|
||||
outerHeightCm: {type: number, format: double, nullable: true, description: 仓库手工保存的外箱高度;未保存人工修正时为空}
|
||||
chargeRuleId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||
coefficientMode: {type: string, enum: [ADD, MULTIPLY], nullable: true}
|
||||
remark: {type: string, nullable: true}
|
||||
@@ -1642,9 +1642,9 @@ components:
|
||||
heightCm: {type: number, minimum: 0.001}
|
||||
intelligentImageFileId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||
overLimitWarning: {type: string, maxLength: 500, nullable: true}
|
||||
outerLengthCm: {type: number, minimum: 0, nullable: true}
|
||||
outerWidthCm: {type: number, minimum: 0, nullable: true}
|
||||
outerHeightCm: {type: number, minimum: 0, nullable: true}
|
||||
outerLengthCm: {type: number, minimum: 0, nullable: true, description: 可单独提交的仓库外箱长度修正;保存后参与联动计算}
|
||||
outerWidthCm: {type: number, minimum: 0, nullable: true, description: 可单独提交的仓库外箱宽度修正;保存后参与联动计算}
|
||||
outerHeightCm: {type: number, minimum: 0, nullable: true, description: 可单独提交的仓库外箱高度修正;保存后参与联动计算}
|
||||
chargeRuleId: {type: string, pattern: '^[1-9][0-9]*$', nullable: true}
|
||||
coefficientMode: {type: string, enum: [ADD, MULTIPLY], nullable: true}
|
||||
remark: {type: string, maxLength: 500, nullable: true}
|
||||
|
||||
@@ -190,6 +190,10 @@ class InboundMeasurementServiceTest {
|
||||
item.setTenantId(1L);
|
||||
item.setMeasurementId(30L);
|
||||
item.setSourceImageFileId(140L);
|
||||
item.setOuterLengthCm(new BigDecimal("64.000"));
|
||||
item.setOuterWidthCm(new BigDecimal("54.000"));
|
||||
item.setOuterHeightCm(new BigDecimal("54.000"));
|
||||
item.setCalculationRuleVersion("CUSTOMER_DIMENSION_EXTRA_LARGE_V1");
|
||||
InboundWeightResultEntity weight = weight(50L, "30.000", "KG", "CONFIRMED", 2);
|
||||
weight.setGrossWeightKg(new BigDecimal("30.000"));
|
||||
weight.setPalletTareWeightKg(new BigDecimal("18.500"));
|
||||
@@ -207,6 +211,9 @@ class InboundMeasurementServiceTest {
|
||||
assertThat(result.items()).singleElement().satisfies(dimension -> {
|
||||
assertThat(dimension.id()).isEqualTo(40L);
|
||||
assertThat(dimension.intelligentImageFileId()).isEqualTo(140L);
|
||||
assertThat(dimension.outerLengthCm()).isNull();
|
||||
assertThat(dimension.outerWidthCm()).isNull();
|
||||
assertThat(dimension.outerHeightCm()).isNull();
|
||||
});
|
||||
assertThat(result.weightResults()).singleElement().satisfies(confirmedWeight -> {
|
||||
assertThat(confirmedWeight.id()).isEqualTo(50L);
|
||||
@@ -218,6 +225,56 @@ class InboundMeasurementServiceTest {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailReturnsWarehouseOuterDimensionsOnlyAfterManualOverrideWasSaved() {
|
||||
InboundOrderEntity inbound = inbound("MEASURING");
|
||||
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
|
||||
measurement.setId(30L);
|
||||
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
|
||||
item.setId(40L);
|
||||
item.setOuterLengthCm(new BigDecimal("65.000"));
|
||||
item.setOuterWidthCm(new BigDecimal("55.000"));
|
||||
item.setOuterHeightCm(new BigDecimal("55.000"));
|
||||
item.setCalculationRuleVersion("WAREHOUSE_OUTER_OVERRIDE_V1");
|
||||
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());
|
||||
|
||||
InboundMeasurementResponses.Detail result = service.detail(1L, 10L);
|
||||
|
||||
assertThat(result.items()).singleElement().satisfies(dimension -> {
|
||||
assertThat(dimension.outerLengthCm()).isEqualByComparingTo("65.000");
|
||||
assertThat(dimension.outerWidthCm()).isEqualByComparingTo("55.000");
|
||||
assertThat(dimension.outerHeightCm()).isEqualByComparingTo("55.000");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailReturnsOnlyTheIndividuallyOverriddenOuterDimension() {
|
||||
InboundOrderEntity inbound = inbound("MEASURING");
|
||||
InboundMeasurementEntity measurement = new InboundMeasurementEntity();
|
||||
measurement.setId(30L);
|
||||
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
|
||||
item.setId(40L);
|
||||
item.setOuterLengthCm(new BigDecimal("65.000"));
|
||||
item.setOuterWidthCm(new BigDecimal("54.000"));
|
||||
item.setOuterHeightCm(new BigDecimal("54.000"));
|
||||
item.setCalculationRuleVersion("WAREHOUSE_OUTER_OVERRIDE_L_V1");
|
||||
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());
|
||||
|
||||
InboundMeasurementResponses.Detail result = service.detail(1L, 10L);
|
||||
|
||||
assertThat(result.items()).singleElement().satisfies(dimension -> {
|
||||
assertThat(dimension.outerLengthCm()).isEqualByComparingTo("65.000");
|
||||
assertThat(dimension.outerWidthCm()).isNull();
|
||||
assertThat(dimension.outerHeightCm()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailComparesLatestDeclarationWithoutMutatingFrozenMeasurementFacts() {
|
||||
InboundOrderEntity inbound = inbound("WAIT_SPLIT");
|
||||
|
||||
@@ -246,6 +246,29 @@ class MeasurementBatchServiceTest {
|
||||
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_V1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void warehouseCanOverrideOnlyOneOuterDimension() {
|
||||
MeasurementBatchCommand.Item source = new MeasurementBatchCommand.Item(
|
||||
80001L, "001", "A", 2, new BigDecimal("40"), new BigDecimal("30"),
|
||||
new BigDecimal("20"), 500L, null, new BigDecimal("50"),
|
||||
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("50.000");
|
||||
assertThat(calculated.getOuterWidthCm()).isEqualByComparingTo("32.000");
|
||||
assertThat(calculated.getOuterHeightCm()).isEqualByComparingTo("22.000");
|
||||
assertThat(calculated.getUnitVolumeCbm()).isEqualByComparingTo("0.0352");
|
||||
assertThat(calculated.getCalculationRuleVersion()).isEqualTo("WAREHOUSE_OUTER_OVERRIDE_L_V1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void staleDraftVersionIsRejectedBeforeReplacingItems() {
|
||||
InboundMeasurementEntity existing = new InboundMeasurementEntity();
|
||||
|
||||
Reference in New Issue
Block a user