fix(firstleg): update submitted outer dimensions safely
This commit is contained in:
+18
@@ -109,6 +109,24 @@ public class PlatformInboundMeasurementController {
|
||||
return ApiResponse.success(service.updateCalculationStrategy(tenantId, inboundId, request));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASUREMENT_OUTER_DIMENSIONS_UPDATE",
|
||||
resourceType = "INBOUND_MEASUREMENT_ITEM", resourceId = "#itemId",
|
||||
description = "更新仓库外箱长宽高")
|
||||
@Operation(summary = "更新仓库外箱长宽高",
|
||||
description = "独立更新当前测量批次指定明细的仓库外箱尺寸并重算体积;不改变测量提交或审核状态。"
|
||||
+ "仅当客户已确认费用且该票已装柜完成时冻结。")
|
||||
@PutMapping("/measurement/items/{itemId}/outer-dimensions")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:measure:edit')")
|
||||
public ApiResponse<InboundMeasurementResponses.OuterDimensionsUpdated> updateOuterDimensions(
|
||||
@AuthenticationPrincipal AuthenticatedUser user,
|
||||
@RequestParam long tenantId,
|
||||
@PathVariable long inboundId,
|
||||
@PathVariable long itemId,
|
||||
@Valid @RequestBody InboundMeasurementRequests.UpdateOuterDimensionsRequest request) {
|
||||
requirePlatformTenant(user, tenantId);
|
||||
return ApiResponse.success(service.updateOuterDimensions(tenantId, inboundId, itemId, request));
|
||||
}
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASUREMENT_SUBMIT", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "确认测量并提交审核")
|
||||
@Operation(summary = "确认测量并提交审核",
|
||||
|
||||
@@ -95,6 +95,18 @@ public final class InboundMeasurementRequests {
|
||||
) {
|
||||
}
|
||||
|
||||
public record UpdateOuterDimensionsRequest(
|
||||
@NotNull @Positive Long measurementId,
|
||||
@NotNull @PositiveOrZero Integer version,
|
||||
@DecimalMin("0.001") @DecimalMax("999999999.999")
|
||||
@Digits(integer = 9, fraction = 3) BigDecimal outerLengthCm,
|
||||
@DecimalMin("0.001") @DecimalMax("999999999.999")
|
||||
@Digits(integer = 9, fraction = 3) BigDecimal outerWidthCm,
|
||||
@DecimalMin("0.001") @DecimalMax("999999999.999")
|
||||
@Digits(integer = 9, fraction = 3) BigDecimal outerHeightCm
|
||||
) {
|
||||
}
|
||||
|
||||
public record ConfirmedPackageCountRequest(
|
||||
@NotNull @PositiveOrZero Integer measurementVersion,
|
||||
@NotNull @Positive @Max(1000000) Integer confirmedPackageCount
|
||||
|
||||
@@ -188,6 +188,11 @@ public final class InboundMeasurementResponses {
|
||||
) {
|
||||
}
|
||||
|
||||
public record OuterDimensionsUpdated(
|
||||
Long measurementId, Integer version, String status, Long itemId
|
||||
) {
|
||||
}
|
||||
|
||||
public record MeasurementSubmitted(Long measurementId, Integer version, String processStatus,
|
||||
String measureReviewStatus,
|
||||
@Schema(description = "测量完成后的推荐托盘类型,仅为建议,不代表已分配托盘",
|
||||
|
||||
@@ -171,13 +171,7 @@ public class InboundMeasurementService {
|
||||
InboundMeasurementRequests.UpdateCalculationStrategyRequest request) {
|
||||
InboundOrderEntity inbound = repository.lockInbound(tenantId, inboundId);
|
||||
if (inbound == null) throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
var currentFee = pricingRepository.findLatestFeeForUpdate(tenantId, inboundId);
|
||||
boolean customerConfirmed = currentFee != null && currentFee.getCustomerConfirmedAt() != null;
|
||||
boolean loadingCompleted = Set.of("LOADED", "DELIVERED").contains(inbound.getProcessStatus());
|
||||
if (customerConfirmed && loadingCompleted) {
|
||||
throw new BizException(ResultCode.CONFLICT,
|
||||
"客户已确认且该票已装柜完成,不能修改箱规计算规则");
|
||||
}
|
||||
requireMeasurementRevisionEditable(tenantId, inboundId, inbound, "箱规计算规则");
|
||||
if ("MULTIPLY".equals(request.coefficientMode()) && request.chargeRuleId() == null) {
|
||||
throw new BizException(ResultCode.VALIDATION_FAILED, "箱规系数模式必须选择计费规则");
|
||||
}
|
||||
@@ -199,6 +193,40 @@ public class InboundMeasurementService {
|
||||
request.coefficientMode(), appliedRuleId);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InboundMeasurementResponses.OuterDimensionsUpdated updateOuterDimensions(
|
||||
long tenantId, long inboundId, long itemId,
|
||||
InboundMeasurementRequests.UpdateOuterDimensionsRequest request) {
|
||||
InboundOrderEntity inbound = repository.lockInbound(tenantId, inboundId);
|
||||
if (inbound == null) throw new BizException(ResultCode.NOT_FOUND, "入库单不存在");
|
||||
requireMeasurementRevisionEditable(tenantId, inboundId, inbound, "仓库外箱尺寸");
|
||||
InboundMeasurementEntity measurement = repository.lockMeasurement(
|
||||
tenantId, inboundId, request.measurementId());
|
||||
if (measurement == null) throw new BizException(ResultCode.NOT_FOUND, "测量批次不存在");
|
||||
requireVersion(request.version(), measurement.getVersion());
|
||||
List<InboundMeasurementItemEntity> items = repository.listItems(tenantId, measurement.getId());
|
||||
batchService.recalculateOuterDimensions(tenantId, measurement, items, itemId,
|
||||
request.outerLengthCm(), request.outerWidthCm(), request.outerHeightCm());
|
||||
measurement.setUpdatedAt(LocalDateTime.now());
|
||||
if (!repository.updateMeasurement(measurement)) throw concurrentModification();
|
||||
inbound.setActualVolumeCbm(measurement.getTotalVolumeCbm());
|
||||
inbound.setVolumeWeightKg(measurement.getVolumeWeightKg());
|
||||
if (!repository.updateInbound(inbound)) throw concurrentModification();
|
||||
return new InboundMeasurementResponses.OuterDimensionsUpdated(
|
||||
measurement.getId(), measurement.getVersion(), measurement.getStatus(), itemId);
|
||||
}
|
||||
|
||||
private void requireMeasurementRevisionEditable(long tenantId, long inboundId,
|
||||
InboundOrderEntity inbound, String subject) {
|
||||
var currentFee = pricingRepository.findLatestFeeForUpdate(tenantId, inboundId);
|
||||
boolean customerConfirmed = currentFee != null && currentFee.getCustomerConfirmedAt() != null;
|
||||
boolean loadingCompleted = Set.of("LOADED", "DELIVERED").contains(inbound.getProcessStatus());
|
||||
if (customerConfirmed && loadingCompleted) {
|
||||
throw new BizException(ResultCode.CONFLICT,
|
||||
"客户已确认且该票已装柜完成,不能修改" + subject);
|
||||
}
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public InboundMeasurementResponses.MeasurementSubmitted submit(
|
||||
long tenantId, long inboundId, long operatorId,
|
||||
|
||||
@@ -116,26 +116,64 @@ public class MeasurementBatchService {
|
||||
for (int index = 0; index < existingItems.size(); index++) {
|
||||
InboundMeasurementItemEntity target = existingItems.get(index);
|
||||
InboundMeasurementItemEntity source = recalculated.get(index);
|
||||
target.setGirthCm(source.getGirthCm());
|
||||
target.setUnitVolumeCbm(source.getUnitVolumeCbm());
|
||||
target.setVolumeRawCbm(source.getVolumeRawCbm());
|
||||
target.setVolumeCbm(source.getVolumeCbm());
|
||||
target.setTotalVolumeCbm(source.getTotalVolumeCbm());
|
||||
target.setVolumeWeightKg(source.getVolumeWeightKg());
|
||||
target.setChargeRuleId(source.getChargeRuleId());
|
||||
target.setCoefficientMode(source.getCoefficientMode());
|
||||
target.setCalculationRuleVersion(source.getCalculationRuleVersion());
|
||||
target.setOuterLengthCm(source.getOuterLengthCm());
|
||||
target.setOuterWidthCm(source.getOuterWidthCm());
|
||||
target.setOuterHeightCm(source.getOuterHeightCm());
|
||||
target.setUpdatedAt(LocalDateTime.now());
|
||||
if (!repository.updateItem(target)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
applyRecalculatedItem(target, source, false);
|
||||
}
|
||||
applyTotals(measurement, existingItems);
|
||||
}
|
||||
|
||||
void recalculateOuterDimensions(long tenantId, InboundMeasurementEntity measurement,
|
||||
List<InboundMeasurementItemEntity> existingItems, long itemId,
|
||||
BigDecimal outerLengthCm, BigDecimal outerWidthCm,
|
||||
BigDecimal outerHeightCm) {
|
||||
InboundMeasurementItemEntity target = existingItems.stream()
|
||||
.filter(item -> item.getId() != null && item.getId() == itemId)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new BizException(ResultCode.NOT_FOUND, "测量明细不存在"));
|
||||
MeasurementBatchCommand command = new MeasurementBatchCommand(
|
||||
tenantId, measurement.getInboundId(), measurement.getId(), measurement.getVersion(),
|
||||
measurement.getSourceType(), measurement.getSourceCode(), measurement.getExternalEventNo(),
|
||||
measurement.getDeviceCode(), measurement.getWarehouseCode(), measurement.getMeasuredAt(),
|
||||
measurement.getRawPayloadJson(), measurement.getOriginalDocumentNo(),
|
||||
measurement.getOriginalGrossWeightKg(), measurement.getOriginalVolumeCbm(),
|
||||
measurement.getActualGoodsName(), measurement.getCreatedBy(),
|
||||
List.of(new MeasurementBatchCommand.Item(
|
||||
target.getInboundDetailId(), target.getItemNo(), target.getBoxSpecification(),
|
||||
target.getPackageCount(), target.getLengthCm(), target.getWidthCm(), target.getHeightCm(),
|
||||
null, target.getSourceImageFileId(), target.getOverLimitWarning(), outerLengthCm,
|
||||
outerWidthCm, outerHeightCm, target.getChargeRuleId(), target.getCoefficientMode(),
|
||||
target.getRemark())));
|
||||
InboundMeasurementItemEntity recalculated = calculateItems(
|
||||
command, dimensionWeighting.load(tenantId)).get(0);
|
||||
applyRecalculatedItem(target, recalculated, true);
|
||||
applyTotals(measurement, existingItems);
|
||||
}
|
||||
|
||||
private void applyRecalculatedItem(InboundMeasurementItemEntity target,
|
||||
InboundMeasurementItemEntity source,
|
||||
boolean updateManualDimensions) {
|
||||
target.setGirthCm(source.getGirthCm());
|
||||
target.setUnitVolumeCbm(source.getUnitVolumeCbm());
|
||||
target.setVolumeRawCbm(source.getVolumeRawCbm());
|
||||
target.setVolumeCbm(source.getVolumeCbm());
|
||||
target.setTotalVolumeCbm(source.getTotalVolumeCbm());
|
||||
target.setVolumeWeightKg(source.getVolumeWeightKg());
|
||||
target.setChargeRuleId(source.getChargeRuleId());
|
||||
target.setCoefficientMode(source.getCoefficientMode());
|
||||
target.setCalculationRuleVersion(source.getCalculationRuleVersion());
|
||||
target.setOuterLengthCm(source.getOuterLengthCm());
|
||||
target.setOuterWidthCm(source.getOuterWidthCm());
|
||||
target.setOuterHeightCm(source.getOuterHeightCm());
|
||||
if (updateManualDimensions) {
|
||||
target.setManualOuterLengthCm(source.getManualOuterLengthCm());
|
||||
target.setManualOuterWidthCm(source.getManualOuterWidthCm());
|
||||
target.setManualOuterHeightCm(source.getManualOuterHeightCm());
|
||||
}
|
||||
target.setUpdatedAt(LocalDateTime.now());
|
||||
if (!repository.updateItem(target)) {
|
||||
throw concurrentModification();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 人工补录会整批替换可编辑测量项,但不能抹掉同一项已冻结的扫码身份与设备取证。
|
||||
* 只按入库货物明细和明细号精确匹配,避免把一个箱码猜测性地复制给其他测量项。
|
||||
|
||||
Reference in New Issue
Block a user