This commit is contained in:
2026-08-06 18:34:52 +08:00
parent d2e3fef8f8
commit 74db8e165d
10 changed files with 717 additions and 43 deletions
@@ -201,15 +201,17 @@ public class FbaPricingRepository {
.set(InboundFeeEntity::getUpdatedAt, LocalDateTime.now()));
}
public int markInboundCalculated(long tenantId, long inboundId, String chargeMode,
public int markInboundCalculated(long tenantId, long inboundId, String processStatus, String chargeMode,
java.math.BigDecimal chargeable) {
LambdaUpdateWrapper<InboundPricingEntity> update = new LambdaUpdateWrapper<InboundPricingEntity>()
.eq(InboundPricingEntity::getTenantId, tenantId)
.eq(InboundPricingEntity::getId, inboundId)
.in(InboundPricingEntity::getProcessStatus, List.of("WAIT_PRICING", "FEE_CONFIRMING"))
.eq(InboundPricingEntity::getProcessStatus, processStatus)
.in(InboundPricingEntity::getFeeStatus, List.of("UNPRICED", "CALCULATED"))
.set(InboundPricingEntity::getProcessStatus, "FEE_CONFIRMING")
.set(InboundPricingEntity::getFeeStatus, "CALCULATED");
if (!"WAIT_SPLIT".equals(processStatus)) {
update.set(InboundPricingEntity::getProcessStatus, "FEE_CONFIRMING");
}
if ("KG".equals(chargeMode)) update.set(InboundPricingEntity::getChargeableWeightKg, chargeable);
else update.set(InboundPricingEntity::getChargeableVolumeCbm, chargeable);
return inbounds.update(null, update);
@@ -227,7 +229,7 @@ public class FbaPricingRepository {
public int markInboundConfirmed(long tenantId, long inboundId) {
return inbounds.update(null, new LambdaUpdateWrapper<InboundPricingEntity>()
.eq(InboundPricingEntity::getTenantId, tenantId).eq(InboundPricingEntity::getId, inboundId)
.eq(InboundPricingEntity::getProcessStatus, "FEE_CONFIRMING")
.in(InboundPricingEntity::getProcessStatus, List.of("FEE_CONFIRMING", "WAIT_SPLIT"))
.eq(InboundPricingEntity::getFeeStatus, "CALCULATED")
.set(InboundPricingEntity::getFeeStatus, "CONFIRMED"));
}
@@ -47,7 +47,7 @@ public class FbaPricingService {
String hash = hash("CALCULATE", inboundId, requestedAt == null ? "CURRENT" : requestedAt.toString());
InboundPricingIdempotencyEntity token = beginIdempotency(
tenantId, operatorId, inboundId, "FEE_CALCULATE", idempotencyKey, hash);
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), "FEE_CONFIRMING");
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), null);
InboundPricingEntity inbound = requireEligibleInbound(tenantId, inboundId);
List<CustomerChargeRuleEntity> matchedRules = repository.matchRules(inbound, pricingAt);
@@ -88,11 +88,14 @@ public class FbaPricingService {
repository.insertFee(fee);
repository.insertItem(baseItem(tenantId, fee, rate, chargeable, subtotal, now));
if (additional.signum() > 0) repository.insertItem(additionalItem(tenantId, fee, additional, now));
if (repository.markInboundCalculated(tenantId, inboundId, rule.getChargeMode(), chargeable) != 1) {
if (repository.markInboundCalculated(tenantId, inboundId, inbound.getProcessStatus(),
rule.getChargeMode(), chargeable) != 1) {
throw conflict("入库单状态已变化,费用试算未保存");
}
complete(token, fee.getId(), now);
return response(inbound, fee, rate.getUnitPrice(), "FEE_CONFIRMING");
String nextStatus = "WAIT_SPLIT".equals(inbound.getProcessStatus())
? "WAIT_SPLIT" : "FEE_CONFIRMING";
return response(inbound, fee, rate.getUnitPrice(), nextStatus);
}
@Transactional
@@ -103,14 +106,14 @@ public class FbaPricingService {
String hash = hash("CONFIRM", inboundId, current.getId().toString());
InboundPricingIdempotencyEntity token = beginIdempotency(
tenantId, operatorId, inboundId, "FEE_CONFIRM", idempotencyKey, hash);
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), "FEE_CONFIRMING");
if ("COMPLETED".equals(token.getStatus())) return response(tenantId, token.getResultResourceId(), null);
LocalDateTime now = LocalDateTime.now(clock);
if (repository.confirmFee(tenantId, current.getId(), operatorId, now) != 1
|| repository.markInboundConfirmed(tenantId, inboundId) != 1) {
throw conflict("费用或入库单状态已变化,不能重复确认");
}
complete(token, current.getId(), now);
return response(tenantId, current.getId(), "FEE_CONFIRMING");
return response(tenantId, current.getId(), null);
}
@Transactional
@@ -232,7 +235,7 @@ public class FbaPricingService {
private InboundPricingEntity requireEligibleInbound(long tenantId, long inboundId) {
InboundPricingEntity inbound = requireApprovedInbound(tenantId, inboundId);
if (!List.of("WAIT_PRICING", "FEE_CONFIRMING").contains(inbound.getProcessStatus())
if (!List.of("WAIT_PRICING", "FEE_CONFIRMING", "WAIT_SPLIT").contains(inbound.getProcessStatus())
|| !List.of("UNPRICED", "CALCULATED").contains(inbound.getFeeStatus())) {
throw conflict("当前入库单状态不允许费用试算");
}
@@ -54,19 +54,20 @@ paths:
/api/platform/v1/inbounds/{inboundId}/fees/calculate:
post:
summary: 按审核通过测量快照试算 FBA 费用
description: 精确匹配唯一客户规则和唯一船司价格;需要 firstleg:fba-pricing:calculate 权限。
description: 精确匹配唯一客户规则和唯一船司价格;测量审核后即使主流程已到 WAIT_SPLIT,也可独立试算且不回退主流程状态;需要 firstleg:fba-pricing:calculate 权限。
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
requestBody: {content: {application/json: {schema: {$ref: '#/components/schemas/CalculateRequest'}}}}
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
/api/platform/v1/inbounds/{inboundId}/fees/confirm:
post:
summary: 确认最新费用快照
description: 确认费用只更新费用状态;主流程已到 WAIT_SPLIT 时保持不变。
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
/api/platform/v1/inbounds/{inboundId}/fees/send:
post:
summary: 手工恢复发送费用快照
description: 仅作为异常恢复接口;正常业务由客服测量审核通过后自动计费、发布并完成入库。
summary: 发送已确认费用快照
description: 该动作涉及后续仓储流程,本轮二期验收不调用。
parameters: [{$ref: '#/components/parameters/TenantId'}, {$ref: '#/components/parameters/InboundId'}, {$ref: '#/components/parameters/IdempotencyKey'}]
responses: {'200': {description: 成功或业务失败}, '403': {description: 无权限}}
/api/platform/v1/inbounds/{inboundId}/fees/latest: