修复客户尺寸加权与入库计费验收
This commit is contained in:
@@ -35,15 +35,14 @@ public class CartonMeasurementService {
|
||||
private static final Set<String> DEVICE_WRITABLE_STATUSES = Set.of("WAIT_MEASURE", "MEASURE_REJECTED");
|
||||
private static final Set<String> MANUAL_COUNT_EDITABLE_STATUSES = Set.of(
|
||||
"WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED");
|
||||
private static final BigDecimal CUBIC_CENTIMETERS_PER_CBM = new BigDecimal("1000000");
|
||||
private static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
||||
private static final String CALCULATION_RULE_VERSION = "V1_CBM_CEILING_4_GIRTH_L2WH";
|
||||
private static final long MAX_IMAGE_BYTES = 5L * 1024 * 1024;
|
||||
|
||||
private final InboundMeasurementRepository repository;
|
||||
private final ObjectProvider<CartonLabelBindingResolver> resolverProvider;
|
||||
private final MeasurementImageClient imageClient;
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
private final CustomerDimensionWeightingService dimensionWeighting;
|
||||
|
||||
@Value("${baiyue.security.internal-service-token}")
|
||||
private String internalToken;
|
||||
@@ -51,11 +50,13 @@ public class CartonMeasurementService {
|
||||
public CartonMeasurementService(InboundMeasurementRepository repository,
|
||||
ObjectProvider<CartonLabelBindingResolver> resolverProvider,
|
||||
MeasurementImageClient imageClient,
|
||||
TransactionTemplate transactionTemplate) {
|
||||
TransactionTemplate transactionTemplate,
|
||||
CustomerDimensionWeightingService dimensionWeighting) {
|
||||
this.repository = repository;
|
||||
this.resolverProvider = resolverProvider;
|
||||
this.imageClient = imageClient;
|
||||
this.transactionTemplate = transactionTemplate;
|
||||
this.dimensionWeighting = dimensionWeighting;
|
||||
}
|
||||
|
||||
public CartonMeasurementResult upsert(long tenantId, long operatorId, CartonMeasurementCommand command) {
|
||||
@@ -118,7 +119,7 @@ public class CartonMeasurementService {
|
||||
}
|
||||
requireVersion(expectedMeasurementVersion, measurement.getVersion());
|
||||
item.setPackageCount(confirmedPackageCount);
|
||||
applyCalculatedValues(item);
|
||||
applyCalculatedValuesFromSnapshot(item);
|
||||
item.setUpdatedAt(LocalDateTime.now());
|
||||
if (!repository.updateItem(item)) {
|
||||
throw concurrentModification();
|
||||
@@ -187,7 +188,7 @@ public class CartonMeasurementService {
|
||||
item.setMachineNo(command.machineNo().trim());
|
||||
item.setDeviceMeasuredAt(command.measuredAt());
|
||||
item.setUpdatedAt(LocalDateTime.now());
|
||||
applyCalculatedValues(item);
|
||||
applyCalculatedValues(item, dimensionWeighting.load(tenantId));
|
||||
|
||||
if (created) {
|
||||
repository.insertItem(item);
|
||||
@@ -223,13 +224,15 @@ public class CartonMeasurementService {
|
||||
return measurement;
|
||||
}
|
||||
|
||||
private void applyCalculatedValues(InboundMeasurementItemEntity item) {
|
||||
BigDecimal unitRaw = item.getLengthCm().multiply(item.getWidthCm()).multiply(item.getHeightCm())
|
||||
.divide(CUBIC_CENTIMETERS_PER_CBM, 6, RoundingMode.HALF_UP);
|
||||
BigDecimal unitVolume = unitRaw.setScale(4, RoundingMode.CEILING);
|
||||
private void applyCalculatedValues(InboundMeasurementItemEntity item,
|
||||
CustomerDimensionWeightingService.RuleSet dimensionRules) {
|
||||
CustomerDimensionWeightingService.WeightedDimensions weighted = dimensionRules.resolve(
|
||||
item.getLengthCm(), item.getWidthCm(), item.getHeightCm());
|
||||
BigDecimal unitRaw = weighted.rawVolumeCbm();
|
||||
BigDecimal unitVolume = weighted.unitVolumeCbm();
|
||||
BigDecimal count = BigDecimal.valueOf(item.getPackageCount());
|
||||
BigDecimal totalRaw = unitRaw.multiply(count).setScale(6, RoundingMode.HALF_UP);
|
||||
BigDecimal totalVolume = unitVolume.multiply(count).setScale(4, RoundingMode.CEILING);
|
||||
BigDecimal totalVolume = unitVolume.multiply(count).setScale(4, RoundingMode.UNNECESSARY);
|
||||
item.setGirthCm(item.getLengthCm().add(item.getWidthCm().add(item.getHeightCm())
|
||||
.multiply(BigDecimal.valueOf(2))).setScale(3, RoundingMode.CEILING));
|
||||
item.setUnitWeightKg(item.getUnitWeightKg() == null ? null
|
||||
@@ -241,7 +244,31 @@ public class CartonMeasurementService {
|
||||
: item.getUnitWeightKg().multiply(count).setScale(3, RoundingMode.CEILING));
|
||||
item.setTotalVolumeCbm(totalVolume);
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
item.setCalculationRuleVersion(CALCULATION_RULE_VERSION);
|
||||
item.setCalculationRuleVersion(weighted.calculationRuleVersion());
|
||||
item.setOuterLengthCm(weighted.outerLengthCm());
|
||||
item.setOuterWidthCm(weighted.outerWidthCm());
|
||||
item.setOuterHeightCm(weighted.outerHeightCm());
|
||||
}
|
||||
|
||||
private void applyCalculatedValuesFromSnapshot(InboundMeasurementItemEntity item) {
|
||||
if (!positive(item.getOuterLengthCm()) || !positive(item.getOuterWidthCm())
|
||||
|| !positive(item.getOuterHeightCm()) || !positive(item.getUnitVolumeCbm())
|
||||
|| !StringUtils.hasText(item.getCalculationRuleVersion())) {
|
||||
applyCalculatedValues(item, dimensionWeighting.load(item.getTenantId()));
|
||||
return;
|
||||
}
|
||||
BigDecimal count = BigDecimal.valueOf(item.getPackageCount());
|
||||
BigDecimal totalRaw = item.getOuterLengthCm().multiply(item.getOuterWidthCm())
|
||||
.multiply(item.getOuterHeightCm()).divide(new BigDecimal("1000000"), 6, RoundingMode.HALF_UP)
|
||||
.multiply(count).setScale(6, RoundingMode.HALF_UP);
|
||||
BigDecimal totalVolume = item.getUnitVolumeCbm().multiply(count)
|
||||
.setScale(4, RoundingMode.UNNECESSARY);
|
||||
item.setVolumeRawCbm(totalRaw);
|
||||
item.setVolumeCbm(totalVolume);
|
||||
item.setTotalWeightKg(item.getUnitWeightKg() == null ? null
|
||||
: item.getUnitWeightKg().multiply(count).setScale(3, RoundingMode.CEILING));
|
||||
item.setTotalVolumeCbm(totalVolume);
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
}
|
||||
|
||||
private void recalculateMeasurement(InboundMeasurementEntity measurement,
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ import java.util.EnumMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** 管理每个客户固定四种箱规的尺寸配置,不参与当前订单或计费计算。 */
|
||||
/** 管理每个客户固定四种箱规;规则供实测尺寸归档和计费体积加权使用,不包含价格。 */
|
||||
@Service
|
||||
public class CustomerDimensionConfigService {
|
||||
public static final BigDecimal DEFAULT_EDGE_ADDITION_CM = new BigDecimal("2.000");
|
||||
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package com.baiyue.firstleg.service;
|
||||
|
||||
import com.baiyue.common.core.api.ResultCode;
|
||||
import com.baiyue.common.core.exception.BizException;
|
||||
import com.baiyue.firstleg.domain.CustomerDimensionBoxType;
|
||||
import com.baiyue.firstleg.entity.CustomerDimensionConfigEntity;
|
||||
import com.baiyue.firstleg.repository.CustomerDimensionConfigRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/** Resolves measured carton dimensions to the customer's smallest containing dimension rule. */
|
||||
@Service
|
||||
public class CustomerDimensionWeightingService {
|
||||
private static final BigDecimal TWO = new BigDecimal("2");
|
||||
private static final BigDecimal CUBIC_CENTIMETERS_PER_CBM = new BigDecimal("1000000");
|
||||
|
||||
private final CustomerDimensionConfigRepository repository;
|
||||
|
||||
public CustomerDimensionWeightingService(CustomerDimensionConfigRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public RuleSet load(long tenantId) {
|
||||
List<CustomerDimensionConfigEntity> values = repository.findAll(tenantId);
|
||||
if (values == null || values.size() != CustomerDimensionBoxType.values().length
|
||||
|| values.stream().anyMatch(CustomerDimensionWeightingService::invalidRule)
|
||||
|| values.stream().map(CustomerDimensionConfigEntity::getBoxType)
|
||||
.collect(java.util.stream.Collectors.toSet()).size() != CustomerDimensionBoxType.values().length) {
|
||||
throw new BizException(ResultCode.CONFLICT, "客户箱规尺寸配置不完整,不能计算计费体积");
|
||||
}
|
||||
return new RuleSet(values.stream().map(Rule::from).toList());
|
||||
}
|
||||
|
||||
private static boolean invalidRule(CustomerDimensionConfigEntity value) {
|
||||
return value == null || value.getBoxType() == null || value.getVersion() == null
|
||||
|| !positive(value.getInnerLengthCm()) || !positive(value.getInnerWidthCm())
|
||||
|| !positive(value.getInnerHeightCm()) || !positive(value.getEdgeAdditionCm());
|
||||
}
|
||||
|
||||
private static boolean positive(BigDecimal value) {
|
||||
return value != null && value.signum() > 0;
|
||||
}
|
||||
|
||||
public static final class RuleSet {
|
||||
private final List<Rule> rules;
|
||||
|
||||
private RuleSet(List<Rule> rules) {
|
||||
this.rules = rules.stream()
|
||||
.sorted(Comparator.comparing(Rule::innerVolume)
|
||||
.thenComparing(rule -> rule.boxType().ordinal()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
public WeightedDimensions resolve(BigDecimal lengthCm, BigDecimal widthCm, BigDecimal heightCm) {
|
||||
List<BigDecimal> measured = sortedDimensions(lengthCm, widthCm, heightCm);
|
||||
return rules.stream()
|
||||
.filter(rule -> rule.contains(measured))
|
||||
.findFirst()
|
||||
.map(Rule::weighted)
|
||||
.orElseThrow(() -> new BizException(ResultCode.VALIDATION_FAILED,
|
||||
"实测尺寸超出客户最大箱规,请先调整客户箱规后再提交测量"));
|
||||
}
|
||||
}
|
||||
|
||||
public record WeightedDimensions(
|
||||
CustomerDimensionBoxType boxType,
|
||||
Integer ruleVersion,
|
||||
BigDecimal outerLengthCm,
|
||||
BigDecimal outerWidthCm,
|
||||
BigDecimal outerHeightCm,
|
||||
BigDecimal rawVolumeCbm,
|
||||
BigDecimal unitVolumeCbm,
|
||||
String calculationRuleVersion
|
||||
) {
|
||||
}
|
||||
|
||||
private record Rule(
|
||||
CustomerDimensionBoxType boxType,
|
||||
Integer version,
|
||||
BigDecimal innerLengthCm,
|
||||
BigDecimal innerWidthCm,
|
||||
BigDecimal innerHeightCm,
|
||||
BigDecimal edgeAdditionCm
|
||||
) {
|
||||
private static Rule from(CustomerDimensionConfigEntity value) {
|
||||
return new Rule(value.getBoxType(), value.getVersion(), value.getInnerLengthCm(),
|
||||
value.getInnerWidthCm(), value.getInnerHeightCm(), value.getEdgeAdditionCm());
|
||||
}
|
||||
|
||||
private BigDecimal innerVolume() {
|
||||
return innerLengthCm.multiply(innerWidthCm).multiply(innerHeightCm);
|
||||
}
|
||||
|
||||
private boolean contains(List<BigDecimal> measured) {
|
||||
List<BigDecimal> inner = sortedDimensions(innerLengthCm, innerWidthCm, innerHeightCm);
|
||||
return measured.get(0).compareTo(inner.get(0)) <= 0
|
||||
&& measured.get(1).compareTo(inner.get(1)) <= 0
|
||||
&& measured.get(2).compareTo(inner.get(2)) <= 0;
|
||||
}
|
||||
|
||||
private WeightedDimensions weighted() {
|
||||
BigDecimal edgePair = edgeAdditionCm.multiply(TWO);
|
||||
BigDecimal outerLength = innerLengthCm.add(edgePair);
|
||||
BigDecimal outerWidth = innerWidthCm.add(edgePair);
|
||||
BigDecimal outerHeight = innerHeightCm.add(edgePair);
|
||||
BigDecimal rawVolume = outerLength.multiply(outerWidth).multiply(outerHeight)
|
||||
.divide(CUBIC_CENTIMETERS_PER_CBM, 6, RoundingMode.HALF_UP);
|
||||
BigDecimal unitVolume = rawVolume.setScale(3, RoundingMode.HALF_UP);
|
||||
return new WeightedDimensions(boxType, version, outerLength, outerWidth, outerHeight,
|
||||
rawVolume, unitVolume, "CUSTOMER_DIMENSION_" + boxType.name() + "_V" + version);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<BigDecimal> sortedDimensions(BigDecimal length, BigDecimal width, BigDecimal height) {
|
||||
if (!positive(length) || !positive(width) || !positive(height)) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "长、宽、高必须大于0");
|
||||
}
|
||||
List<BigDecimal> dimensions = new ArrayList<>(List.of(length, width, height));
|
||||
dimensions.sort(Comparator.reverseOrder());
|
||||
return dimensions;
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,10 @@ public class MeasurementBatchService {
|
||||
/** 已接收第三方测量数据,待平台提交审核。 */
|
||||
static final String STATUS_RECEIVED = "RECEIVED";
|
||||
static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
||||
static final String CALCULATION_RULE_VERSION = "V1_CBM_CEILING_4_GIRTH_L2WH";
|
||||
|
||||
@Resource
|
||||
private InboundMeasurementRepository repository;
|
||||
@Resource
|
||||
private CustomerDimensionWeightingService dimensionWeighting;
|
||||
|
||||
@Transactional
|
||||
public InboundMeasurementEntity saveBatch(MeasurementBatchCommand command) {
|
||||
@@ -47,7 +47,8 @@ public class MeasurementBatchService {
|
||||
throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
}
|
||||
validateSource(command);
|
||||
List<InboundMeasurementItemEntity> calculatedItems = calculateItems(command);
|
||||
List<InboundMeasurementItemEntity> calculatedItems = calculateItems(
|
||||
command, dimensionWeighting.load(command.tenantId()));
|
||||
|
||||
InboundMeasurementEntity measurement;
|
||||
if (command.measurementId() == null) {
|
||||
@@ -126,18 +127,20 @@ public class MeasurementBatchService {
|
||||
measurement.setActualGoodsName(command.actualGoodsName());
|
||||
}
|
||||
|
||||
private List<InboundMeasurementItemEntity> calculateItems(MeasurementBatchCommand command) {
|
||||
private List<InboundMeasurementItemEntity> calculateItems(
|
||||
MeasurementBatchCommand command, CustomerDimensionWeightingService.RuleSet dimensionRules) {
|
||||
Set<String> itemNumbers = new HashSet<>();
|
||||
return command.items().stream().map(source -> {
|
||||
if (!itemNumbers.add(source.itemNo())) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "测量明细号不能重复");
|
||||
}
|
||||
BigDecimal unitVolumeRaw = source.lengthCm().multiply(source.widthCm())
|
||||
.multiply(source.heightCm()).divide(new BigDecimal("1000000"), 6, RoundingMode.HALF_UP);
|
||||
BigDecimal unitVolume = unitVolumeRaw.setScale(4, RoundingMode.CEILING);
|
||||
CustomerDimensionWeightingService.WeightedDimensions weighted = dimensionRules.resolve(
|
||||
source.lengthCm(), source.widthCm(), source.heightCm());
|
||||
BigDecimal unitVolumeRaw = weighted.rawVolumeCbm();
|
||||
BigDecimal unitVolume = weighted.unitVolumeCbm();
|
||||
BigDecimal packageCount = BigDecimal.valueOf(source.packageCount());
|
||||
BigDecimal totalVolumeRaw = unitVolumeRaw.multiply(packageCount).setScale(6, RoundingMode.HALF_UP);
|
||||
BigDecimal totalVolume = unitVolume.multiply(packageCount).setScale(4, RoundingMode.CEILING);
|
||||
BigDecimal totalVolume = unitVolume.multiply(packageCount).setScale(4, RoundingMode.UNNECESSARY);
|
||||
|
||||
InboundMeasurementItemEntity item = new InboundMeasurementItemEntity();
|
||||
item.setInboundDetailId(source.inboundDetailId());
|
||||
@@ -158,12 +161,12 @@ public class MeasurementBatchService {
|
||||
: source.unitWeightKg().multiply(packageCount).setScale(3, RoundingMode.CEILING));
|
||||
item.setTotalVolumeCbm(totalVolume);
|
||||
item.setVolumeWeightKg(totalVolume.multiply(VOLUME_WEIGHT_FACTOR).setScale(3, RoundingMode.CEILING));
|
||||
item.setCalculationRuleVersion(CALCULATION_RULE_VERSION);
|
||||
item.setCalculationRuleVersion(weighted.calculationRuleVersion());
|
||||
item.setSourceImageFileId(source.intelligentImageFileId());
|
||||
item.setOverLimitWarning(source.overLimitWarning());
|
||||
item.setOuterLengthCm(source.outerLengthCm());
|
||||
item.setOuterWidthCm(source.outerWidthCm());
|
||||
item.setOuterHeightCm(source.outerHeightCm());
|
||||
item.setOuterLengthCm(weighted.outerLengthCm());
|
||||
item.setOuterWidthCm(weighted.outerWidthCm());
|
||||
item.setOuterHeightCm(weighted.outerHeightCm());
|
||||
item.setRemark(source.remark());
|
||||
item.setCreatedAt(LocalDateTime.now());
|
||||
return item;
|
||||
|
||||
Reference in New Issue
Block a user