merge: sync test and stabilize full acceptance
This commit is contained in:
+2
-2
@@ -79,7 +79,7 @@ public class PlatformInboundMeasurementController {
|
||||
|
||||
@BusinessAudit(event = "INBOUND_MEASUREMENT_DRAFT_SAVE", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "保存入库测量草稿")
|
||||
@Operation(summary = "保存测量草稿", description = "仅在待测量、测量中或审核驳回状态保存人工测量批次和服务端计算明细;"
|
||||
@Operation(summary = "保存测量草稿", description = "订单待审核但货物已到仓时可先保存;也可在待测量、测量中或审核驳回状态保存人工测量批次和服务端计算明细;"
|
||||
+ "进入待审核后批次冻结,确认箱数修正须使用 measurement/items/{itemId}/confirmed-package-count 专用接口。")
|
||||
@PutMapping("/measurement/draft")
|
||||
@PreAuthorize("hasAnyAuthority('platform:firstleg:inbound:warehouse-suggestion:edit',"
|
||||
@@ -96,7 +96,7 @@ public class PlatformInboundMeasurementController {
|
||||
@BusinessAudit(event = "INBOUND_MEASUREMENT_SUBMIT", resourceType = "INBOUND",
|
||||
resourceId = "#inboundId", description = "确认测量并提交审核")
|
||||
@Operation(summary = "确认测量并提交审核",
|
||||
description = "校验测量图片和全部 OCR 重量后,最多推进到 MEASURE_PENDING。")
|
||||
description = "校验测量图片和全部 OCR 重量后,最多推进到 MEASURE_PENDING;订单未审核时只保留待审核测量,不能审核通过或进入后续履约。")
|
||||
@PostMapping("/measurement/submit")
|
||||
@PreAuthorize("hasAuthority('platform:firstleg:inbound:measure:submit')")
|
||||
public ApiResponse<InboundMeasurementResponses.MeasurementSubmitted> submit(
|
||||
|
||||
@@ -34,9 +34,9 @@ import java.util.Set;
|
||||
public class CartonMeasurementService {
|
||||
private static final Logger log = LoggerFactory.getLogger(CartonMeasurementService.class);
|
||||
private static final Set<String> DEVICE_WRITABLE_STATUSES = Set.of(
|
||||
"WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
"PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
private static final Set<String> MANUAL_COUNT_EDITABLE_STATUSES = Set.of(
|
||||
"WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED");
|
||||
"PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_PENDING", "MEASURE_REJECTED");
|
||||
private static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
||||
private static final long MAX_IMAGE_BYTES = 5L * 1024 * 1024;
|
||||
|
||||
|
||||
@@ -219,6 +219,7 @@ public class FbaPricingService {
|
||||
/**
|
||||
* 柜内明细变更后,按最新有效柜内成员重新命中区域重量阶梯。
|
||||
* 仅使用当前费用快照冻结的选定重量、体积和测量来源,绝不回退到入库单实测值。
|
||||
* 已发送或客户已确认的费用继续参与柜级计费量聚合,但自身快照保持不变。
|
||||
*/
|
||||
@Transactional
|
||||
public ContainerFeeRecalculationResult recalculateUnlockedContainerCustomerFees(
|
||||
@@ -257,19 +258,22 @@ public class FbaPricingService {
|
||||
notReady = true;
|
||||
continue;
|
||||
}
|
||||
if ("SENT".equals(current.getFeeStatus()) || current.getCustomerConfirmedAt() != null) {
|
||||
throw conflict("柜内存在已发送或客户已确认费用,不能重算阶梯费用");
|
||||
}
|
||||
if (!List.of("CALCULATED", "CONFIRMED").contains(current.getFeeStatus())) {
|
||||
boolean locked = List.of("CONFIRMED", "SENT").contains(current.getFeeStatus())
|
||||
|| current.getCustomerConfirmedAt() != null;
|
||||
if (!locked && !"CALCULATED".equals(current.getFeeStatus())) {
|
||||
throw conflict("柜内费用状态已变化,不能重算阶梯费用");
|
||||
}
|
||||
BigDecimal weight = requiredFrozenQuantity(current.getActualWeightKg(), "重量");
|
||||
BigDecimal volume = requiredFrozenQuantity(current.getActualVolumeCbm(), "体积");
|
||||
members.add(new ContainerFeeMember(inbound, current,
|
||||
weight.setScale(3, RoundingMode.HALF_UP), volume.setScale(4, RoundingMode.HALF_UP),
|
||||
chargeableWeight(weight, volume)));
|
||||
chargeableWeight(weight, volume), locked));
|
||||
}
|
||||
if (notReady) return new ContainerFeeRecalculationResult(ContainerFeeRecalculationStatus.NOT_READY, 0);
|
||||
List<ContainerFeeMember> unlockedMembers = members.stream().filter(member -> !member.locked()).toList();
|
||||
if (unlockedMembers.isEmpty()) {
|
||||
return new ContainerFeeRecalculationResult(ContainerFeeRecalculationStatus.NOT_APPLICABLE, 0);
|
||||
}
|
||||
InboundPricingIdempotencyEntity token = beginContainerRecalculation(
|
||||
tenantId, containerBookingId, key, normalizedTrigger);
|
||||
if ("COMPLETED".equals(token.getStatus())) {
|
||||
@@ -281,7 +285,7 @@ public class FbaPricingService {
|
||||
.map(member -> new ContainerTierAmountAllocator.Member(member.inbound().getId(), member.chargeableQuantity()))
|
||||
.toList();
|
||||
List<ContainerFeeQuote> quotes = new ArrayList<>();
|
||||
for (ContainerFeeMember member : members) {
|
||||
for (ContainerFeeMember member : unlockedMembers) {
|
||||
PricingPolicy policy = pricingPolicy(member.inbound(), now);
|
||||
ActivePriceBookEstimator.Estimate estimate = priceBookEstimator.estimate(tenantId, new EstimateRequest(
|
||||
member.inbound().getWarehouseId(), member.inbound().getChannelCode(), member.inbound().getDestinationCode(),
|
||||
@@ -371,7 +375,8 @@ public class FbaPricingService {
|
||||
}
|
||||
|
||||
private record ContainerFeeMember(InboundPricingEntity inbound, InboundFeeEntity current,
|
||||
BigDecimal weight, BigDecimal volume, BigDecimal chargeableQuantity) { }
|
||||
BigDecimal weight, BigDecimal volume, BigDecimal chargeableQuantity,
|
||||
boolean locked) { }
|
||||
|
||||
private record ContainerFeeQuote(ContainerFeeMember member, PricingPolicy policy,
|
||||
ActivePriceBookEstimator.Estimate estimate) { }
|
||||
|
||||
+2
-1
@@ -47,7 +47,8 @@ import java.util.Set;
|
||||
public class InboundMeasurementOpenApiService {
|
||||
private static final Logger log = LoggerFactory.getLogger(InboundMeasurementOpenApiService.class);
|
||||
static final String CALLBACK_PATH = "/openapi/v1/inbound-measurements";
|
||||
private static final Set<String> ACCEPTABLE_INBOUND_STATUSES = Set.of("WAIT_MEASURE", "MEASURE_REJECTED");
|
||||
private static final Set<String> ACCEPTABLE_INBOUND_STATUSES = Set.of(
|
||||
"PREPARING", "WAIT_MEASURE", "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";
|
||||
|
||||
@@ -49,9 +49,9 @@ public class InboundMeasurementService {
|
||||
/** OCR 结果无效或不完整,必须人工录入或修正。 */
|
||||
private static final String WEIGHT_NEEDS_MANUAL = "NEEDS_MANUAL";
|
||||
private static final Set<String> MEASUREMENT_DRAFT_EDITABLE_STATUSES = Set.of(
|
||||
"WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
"PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
private static final Set<String> WEIGHT_EDITABLE_STATUSES = Set.of(
|
||||
"WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
"PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
/** 测量完成后的默认普通托盘类型;实际拆托和库位占用由提交编排服务执行。 */
|
||||
private static final String RECOMMENDED_PALLET_TYPE = "STANDARD";
|
||||
private static final BigDecimal LB_TO_KG = new BigDecimal("0.45359237");
|
||||
@@ -167,7 +167,7 @@ public class InboundMeasurementService {
|
||||
long tenantId, long inboundId, long operatorId,
|
||||
InboundMeasurementRequests.SubmitRequest request) {
|
||||
InboundOrderEntity inbound = requireInbound(tenantId, inboundId);
|
||||
if (!Set.of("WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED")
|
||||
if (!Set.of("PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED")
|
||||
.contains(inbound.getProcessStatus())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "测量已提交或审核完成,不能重复提交或修改冻结结果");
|
||||
}
|
||||
@@ -384,7 +384,7 @@ public class InboundMeasurementService {
|
||||
inbound.setActualGrossWeightKg(totals.grossWeightKg());
|
||||
inbound.setActualNetWeightKg(totals.netWeightKg());
|
||||
inbound.setPalletCount(totals.palletCount());
|
||||
if ("WAIT_MEASURE".equals(inbound.getProcessStatus())) {
|
||||
if (Set.of("PREPARING", "WAIT_MEASURE").contains(inbound.getProcessStatus())) {
|
||||
inbound.setProcessStatus("MEASURING");
|
||||
}
|
||||
inbound.setUpdatedBy(operatorId);
|
||||
|
||||
+12
-8
@@ -40,7 +40,7 @@ public class InboundOrderCreationService {
|
||||
long inboundId = requireMatchingInboundCode(existing, command.inboundCode());
|
||||
FulfillmentTaskEntity task = fulfillmentTasks.provision(command, LocalDateTime.now());
|
||||
linkTask(existing, task.getId());
|
||||
upgradePreparing(existing, command);
|
||||
approvePreparedInbound(existing, command);
|
||||
savePickupSnapshot(existing.getId(), command, LocalDateTime.now());
|
||||
return inboundId;
|
||||
}
|
||||
@@ -132,7 +132,7 @@ public class InboundOrderCreationService {
|
||||
}
|
||||
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
InboundOrderEntity inbound = newInbound(command, now, null, "PREPARING", "DRAFT");
|
||||
InboundOrderEntity inbound = newInbound(command, now, null, "PREPARING", "SUBMITTED");
|
||||
try {
|
||||
orders.insert(inbound);
|
||||
savePickupSnapshot(inbound.getId(), command, now);
|
||||
@@ -153,6 +153,7 @@ public class InboundOrderCreationService {
|
||||
.eq(InboundOrderEntity::getTenantId, existing.getTenantId())
|
||||
.eq(InboundOrderEntity::getId, existing.getId())
|
||||
.eq(InboundOrderEntity::getProcessStatus, "PREPARING")
|
||||
.set(InboundOrderEntity::getOrderStatusSnapshot, "SUBMITTED")
|
||||
.set(InboundOrderEntity::getOrderNo, command.orderNo())
|
||||
.set(InboundOrderEntity::getCustomerId, command.customerId())
|
||||
.set(InboundOrderEntity::getCustomerName, command.customerName())
|
||||
@@ -175,13 +176,16 @@ public class InboundOrderCreationService {
|
||||
.setSql("version = version + 1"));
|
||||
}
|
||||
|
||||
private void upgradePreparing(InboundOrderEntity existing, OrderApprovedInboundCommand command) {
|
||||
if (!"PREPARING".equals(existing.getProcessStatus())) return;
|
||||
private void approvePreparedInbound(InboundOrderEntity existing, OrderApprovedInboundCommand command) {
|
||||
String currentStatus = existing.getProcessStatus();
|
||||
if (currentStatus == null || !java.util.Set.of("PREPARING", "MEASURING", "MEASURE_PENDING")
|
||||
.contains(currentStatus)) return;
|
||||
String approvedStatus = "PREPARING".equals(currentStatus) ? "WAIT_MEASURE" : currentStatus;
|
||||
var update = new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<InboundOrderEntity>()
|
||||
.eq(InboundOrderEntity::getTenantId, existing.getTenantId())
|
||||
.eq(InboundOrderEntity::getId, existing.getId())
|
||||
.eq(InboundOrderEntity::getProcessStatus, "PREPARING")
|
||||
.set(InboundOrderEntity::getProcessStatus, "WAIT_MEASURE")
|
||||
.eq(InboundOrderEntity::getProcessStatus, currentStatus)
|
||||
.set(InboundOrderEntity::getProcessStatus, approvedStatus)
|
||||
.set(InboundOrderEntity::getOrderStatusSnapshot, "APPROVED")
|
||||
.set(InboundOrderEntity::getTotalPackageCount, command.totalPackageCount())
|
||||
.set(InboundOrderEntity::getFulfillmentType, fulfillmentType(command.fulfillmentType()))
|
||||
@@ -189,12 +193,12 @@ public class InboundOrderCreationService {
|
||||
.setSql("version = version + 1");
|
||||
if (orders.update(null, update) != 1) {
|
||||
InboundOrderEntity current = findByOrder(existing.getTenantId(), existing.getOrderId(), true);
|
||||
if (current == null || !"WAIT_MEASURE".equals(current.getProcessStatus())) {
|
||||
if (current == null || !"APPROVED".equals(current.getOrderStatusSnapshot())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "预入库单审核升级发生并发冲突");
|
||||
}
|
||||
return;
|
||||
}
|
||||
addHistory(existing.getId(), existing.getTenantId(), "PREPARING", "WAIT_MEASURE",
|
||||
addHistory(existing.getId(), existing.getTenantId(), currentStatus, approvedStatus,
|
||||
"ORDER_APPROVED", LocalDateTime.now());
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public class MeasurementBatchService {
|
||||
/** 已接收第三方测量数据,待平台提交审核。 */
|
||||
static final String STATUS_RECEIVED = "RECEIVED";
|
||||
private static final Set<String> EDITABLE_INBOUND_STATUSES = Set.of(
|
||||
"WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
"PREPARING", "WAIT_MEASURE", "MEASURING", "MEASURE_REJECTED");
|
||||
static final BigDecimal VOLUME_WEIGHT_FACTOR = new BigDecimal("167");
|
||||
private static final BigDecimal MAX_RAW_VOLUME_CBM = new BigDecimal("999999999999.999999");
|
||||
private static final BigDecimal MAX_TOTAL_VOLUME_CBM = new BigDecimal("99999999999999.9999");
|
||||
|
||||
@@ -64,6 +64,9 @@ public class MeasurementReviewService {
|
||||
|
||||
InboundOrderEntity order = requireOrder(tenantId, inboundId);
|
||||
requirePendingReview(order);
|
||||
if (!"APPROVED".equals(order.getOrderStatusSnapshot())) {
|
||||
throw new BizException(ResultCode.CONFLICT, "订单尚未审核通过,已到仓测量数据可保留但暂不能审核");
|
||||
}
|
||||
InboundMeasurementEntity measurement = repository.findMeasurement(
|
||||
tenantId, inboundId, request.measurementId());
|
||||
if (measurement == null) {
|
||||
|
||||
Reference in New Issue
Block a user