This commit is contained in:
2026-08-17 18:32:38 +08:00
parent 5c22018edd
commit 5199146e97
13 changed files with 194 additions and 17 deletions
@@ -155,6 +155,40 @@ public class FeeItemRepository {
.set(InboundFeeDetailEntity::getUpdatedAt, now));
}
public int supersedeFreight(long tenantId, long inboundId, long retainedSourceFeeId,
long operatorId, LocalDateTime now) {
return feeDetails.update(null, new LambdaUpdateWrapper<InboundFeeDetailEntity>()
.eq(InboundFeeDetailEntity::getTenantId, tenantId)
.eq(InboundFeeDetailEntity::getInboundId, inboundId)
.eq(InboundFeeDetailEntity::getSourceType, "FBA_PRICING")
.in(InboundFeeDetailEntity::getPublicationStatus, List.of("DRAFT", "PUBLISHED"))
.ne(InboundFeeDetailEntity::getSourceFeeId, retainedSourceFeeId)
.set(InboundFeeDetailEntity::getPublicationStatus, "SUPERSEDED")
.set(InboundFeeDetailEntity::getSettlementIncluded, false)
.set(InboundFeeDetailEntity::getUpdatedBy, operatorId)
.set(InboundFeeDetailEntity::getUpdatedAt, now));
}
public int publishCalculatedFreight(long tenantId, long sourceFeeId, long operatorId,
String operatorName, LocalDateTime now) {
return feeDetails.update(null, new LambdaUpdateWrapper<InboundFeeDetailEntity>()
.eq(InboundFeeDetailEntity::getTenantId, tenantId)
.eq(InboundFeeDetailEntity::getSourceType, "FBA_PRICING")
.eq(InboundFeeDetailEntity::getSourceFeeId, sourceFeeId)
.eq(InboundFeeDetailEntity::getPublicationStatus, "DRAFT")
.set(InboundFeeDetailEntity::getReviewStatus, "APPROVED")
.set(InboundFeeDetailEntity::getReviewerId, operatorId)
.set(InboundFeeDetailEntity::getReviewerName, operatorName)
.set(InboundFeeDetailEntity::getReviewedAt, now)
.set(InboundFeeDetailEntity::getPublicationStatus, "PUBLISHED")
.set(InboundFeeDetailEntity::getPublishedBy, operatorId)
.set(InboundFeeDetailEntity::getPublishedByName, operatorName)
.set(InboundFeeDetailEntity::getPublishedAt, now)
.set(InboundFeeDetailEntity::getSettlementIncluded, true)
.set(InboundFeeDetailEntity::getUpdatedBy, operatorId)
.set(InboundFeeDetailEntity::getUpdatedAt, now));
}
public void insertDetail(InboundFeeDetailEntity detail) {
feeDetails.insert(detail);
}
@@ -81,6 +81,7 @@ public class FbaPricingService {
estimate.chargeUnit(), chargeable) != 1) {
throw conflict("入库单状态已变化,费用试算未保存");
}
feeDetails.materializeCalculatedFreight(tenantId, inboundId, fee.getId(), operatorId, now);
complete(token, fee.getId(), now);
String nextStatus = isCompletedInbound(inbound.getProcessStatus())
? inbound.getProcessStatus() : "FEE_CONFIRMING";
@@ -215,6 +216,9 @@ public class FbaPricingService {
feeDetails.publishFreight(tenantId, inboundId, adjusted.getId(), operatorId, null, now);
financeOutbox.publishInbound(FinanceFeeSourceOutboxService.ADJUSTED,
tenantId, inboundId, adjusted.getId(), nextVersion, now);
} else {
feeDetails.materializeCalculatedFreight(
tenantId, inboundId, adjusted.getId(), operatorId, now);
}
complete(token, adjusted.getId(), now);
return response(tenantId, adjusted.getId(), inbound.getProcessStatus());
@@ -48,7 +48,7 @@ public class InboundFeeDetailService {
long operatorId, String operatorName,
LocalDateTime publishedAt) {
InboundFeeDetailEntity existing = repository.findBySourceFee(tenantId, "FBA_PRICING", feeId);
if (existing != null) return existing;
if (existing != null && "PUBLISHED".equals(existing.getPublicationStatus())) return existing;
InboundOrderEntity inbound = requiredInbound(tenantId, inboundId);
InboundFeeEntity fee = pricingRepository.findFee(tenantId, feeId);
if (fee == null || !Long.valueOf(inboundId).equals(fee.getInboundId())) {
@@ -58,6 +58,14 @@ public class InboundFeeDetailService {
throw conflict("只有已发送状态的计费快照才能生成客户运费明细");
}
LocalDateTime now = publishedAt == null ? LocalDateTime.now() : publishedAt;
if (existing != null) {
repository.supersedeFreight(tenantId, inboundId, feeId, operatorId, now);
if (repository.publishCalculatedFreight(
tenantId, feeId, operatorId, operatorName, now) != 1) {
throw conflict("初算费用明细状态已变化,请刷新后重试");
}
return repository.findBySourceFee(tenantId, "FBA_PRICING", feeId);
}
FeeItemEntity feeItem = requiredFreightItem();
InboundFeeItemEntity baseItem = pricingRepository.findBaseItem(tenantId, feeId);
repository.supersedePublishedFreight(tenantId, inboundId, feeId, operatorId, now);
@@ -121,6 +129,62 @@ public class InboundFeeDetailService {
}
}
/** 初算成功后生成后台可见的草稿运费明细,不发布 Finance。 */
@Transactional
public InboundFeeDetailEntity materializeCalculatedFreight(long tenantId, long inboundId, long feeId,
long operatorId, LocalDateTime calculatedAt) {
InboundFeeDetailEntity existing = repository.findBySourceFee(tenantId, "FBA_PRICING", feeId);
if (existing != null) return existing;
InboundOrderEntity inbound = requiredInbound(tenantId, inboundId);
InboundFeeEntity fee = pricingRepository.findFee(tenantId, feeId);
if (fee == null || !Long.valueOf(inboundId).equals(fee.getInboundId())) {
throw new BizException(ResultCode.NOT_FOUND, "待展示的入库计费快照不存在");
}
if (!"CALCULATED".equals(fee.getFeeStatus())) {
throw conflict("只有初算状态的计费快照可生成草稿运费明细");
}
LocalDateTime now = calculatedAt == null ? LocalDateTime.now() : calculatedAt;
repository.supersedeFreight(tenantId, inboundId, feeId, operatorId, now);
InboundFeeDetailEntity detail = freightDetail(
tenantId, inboundId, operatorId, null, now, inbound, fee, requiredFreightItem(), false);
repository.insertDetail(detail);
return detail;
}
private InboundFeeDetailEntity freightDetail(long tenantId, long inboundId, long operatorId,
String operatorName, LocalDateTime now,
InboundOrderEntity inbound, InboundFeeEntity fee,
FeeItemEntity feeItem, boolean published) {
InboundFeeItemEntity baseItem = pricingRepository.findBaseItem(tenantId, fee.getId());
InboundFeeDetailEntity detail = new InboundFeeDetailEntity();
detail.setTenantId(tenantId); detail.setInboundId(inboundId); detail.setFeeItemId(feeItem.getId());
detail.setSourceType("FBA_PRICING"); detail.setSourceFeeId(fee.getId());
detail.setSourceFeeVersion(fee.getFeeVersion()); detail.setSourceFeeNo(fee.getFeeNo());
detail.setInboundCode(inbound.getInboundCode()); detail.setDirection("RECEIVABLE");
detail.setBusinessDate(now.toLocalDate()); detail.setCustomerId(inbound.getCustomerId());
detail.setCustomerName(inbound.getCustomerName()); detail.setInvoiceTitle(inbound.getCompanyName());
detail.setFeeCategorySnapshot(feeItem.getCategory()); detail.setTradeScopeSnapshot(feeItem.getTradeScope());
detail.setFeeTypeNameSnapshot(feeItem.getFeeTypeName()); detail.setChannelCode(inbound.getChannelCode());
detail.setChannelName(inbound.getChannelName()); detail.setDestinationCode(inbound.getDestinationCode());
detail.setDestinationName(inbound.getDestinationName()); detail.setPackageCount(inbound.getTotalPackageCount());
detail.setActualGrossWeightKg(fee.getActualWeightKg()); detail.setActualVolumeCbm(fee.getActualVolumeCbm());
detail.setVolumeWeightKg(inbound.getVolumeWeightKg());
detail.setChargeableWeightKg("KG".equals(fee.getChargeUnit()) ? fee.getChargeableQuantity() : inbound.getChargeableWeightKg());
detail.setChargeableVolumeCbm("CBM".equals(fee.getChargeUnit()) ? fee.getChargeableQuantity() : inbound.getChargeableVolumeCbm());
detail.setChargeableQuantity(fee.getChargeableQuantity()); detail.setChargeUnit(fee.getChargeUnit());
detail.setUnitPrice(baseItem == null ? effectiveUnitPrice(fee) : baseItem.getUnitPrice());
detail.setCurrency(fee.getCurrency()); detail.setAmount(fee.getTotalAmount().setScale(2, RoundingMode.HALF_UP));
detail.setRemark("来源费用单:" + fee.getFeeNo());
detail.setReviewStatus(published ? "APPROVED" : "PENDING");
detail.setPublicationStatus(published ? "PUBLISHED" : "DRAFT");
detail.setSettlementIncluded(published); detail.setCreatedBy(operatorId); detail.setUpdatedBy(operatorId);
if (published) {
detail.setReviewerId(operatorId); detail.setReviewerName(operatorName); detail.setReviewedAt(now);
detail.setPublishedBy(operatorId); detail.setPublishedByName(operatorName); detail.setPublishedAt(now);
}
return detail;
}
@Transactional
public InboundFeeDetailEntity create(long tenantId, long inboundId, long operatorId,
SaveRequest request) {