fix(transfer): settle linked relabel fees after final receipt

This commit is contained in:
2026-10-05 02:47:22 +08:00
parent 9e4a838ba1
commit b371fab53d
4 changed files with 151 additions and 18 deletions
@@ -614,22 +614,7 @@ public class RelabelApplicationService implements WalletResultHandler {
&& !preDispatched) {
releaseInventory(customerId, operatorId, application);
}
if (!"NOT_REQUIRED".equals(application.getWalletStatus())) {
BigDecimal quantity = BigDecimal.valueOf(Objects.requireNonNullElse(application.getTotalPackageCount(), 0));
feeSources.publishApproved(operatorId, "RELABEL:" + relabelNo,
new OverseasFeeSourceDtos.SaveRequest(customerId, linkedOrders.get(0).getCustomerName(),
"OVERSEAS_RELABEL_FEE", "RELABEL_ORDER", application.getId(), relabelNo,
application.getLastlegOrderId(), null, null, null, null, null,
parseLong(application.getOverseasWarehouseId()), "RELABEL_SERVICE", "换标费", "RECEIVABLE",
null, null, null, null, null, null, null, List.of(), LocalDate.now(),
application.getChargeCurrency(), application.getChargeAmount(),
quantity.signum() > 0 ? quantity : null, quantity.signum() > 0 ? "BOX" : null,
quantity.signum() > 0 ? application.getChargeAmount().divide(quantity, 6, java.math.RoundingMode.HALF_UP) : null,
null, null));
walletCommands.publish("SETTLE", Long.parseLong(customerId), "RELABEL_ORDER", relabelNo, null,
"换标完成,结算费用", 2L);
application.setWalletStatus("SETTLE_PENDING"); applications.updateById(application);
}
publishCompletionFeesAndSettlement(application, operatorId, linkedOrders);
} else if (Set.of("REJECTED", "CANCELLED").contains(next)) {
releaseInventory(customerId, operatorId, application);
if ("FROZEN".equals(application.getWalletStatus())) {
@@ -795,6 +780,58 @@ public class RelabelApplicationService implements WalletResultHandler {
insertHistory(customerId, application.getId(), before, "PROCESSING", operatorId, remark, LocalDateTime.now());
}
@Transactional
public void completeWarehouseTransferFulfillment(String customerId, String relabelNo, Long transferId,
String operatorId) {
RelabelApplication application = applications.lockNo(customerId, relabelNo);
WarehouseTransferOrder transfer = warehouseTransfers.lockByRelabelNo(customerId, relabelNo);
if (application == null || transfer == null || !customerId.equals(application.getCustomerId())
|| !customerId.equals(transfer.getCustomerId()) || !Objects.equals(transfer.getId(), transferId)
|| !Boolean.TRUE.equals(transfer.getNeedRelabel()) || !"RECEIVED".equals(transfer.getStatus())
|| !Objects.equals(application.getId(), transfer.getRelabelApplicationId())
|| !Objects.equals(application.getLastlegOrderId(), transfer.getLastlegOrderId())
|| !Objects.equals(application.getCustomerOrderNo(), transfer.getTransferNo())
|| application.getCustomerConfirmedAt() == null || transfer.getRelabelCompletedAt() == null) {
throw conflict("本票换标完成归属或客户确认不一致");
}
// Completion replay has no side effects, including for an old TEST row missing settlement.
if ("COMPLETED".equals(application.getStatus())) return;
if (!READY_FOR_OUTBOUND.equals(application.getStatus())
|| !"OUTBOUNDED".equals(application.getFulfillmentStatus())
|| !Set.of("FROZEN", "NOT_REQUIRED").contains(application.getWalletStatus())) {
throw conflict("本票换标尚未完成出库或资金冻结");
}
List<LastlegOrder> linkedOrders = tailOrders(application);
if (linkedOrders.isEmpty()) throw conflict("Linked tail-leg order is missing");
LocalDateTime now = LocalDateTime.now();
application.setStatus("COMPLETED"); application.setFulfillmentStatus("COMPLETED");
application.setUpdatedAt(now); applications.updateById(application);
// The original transfer already released/transformed/shipped its own stock; never release it again.
publishCompletionFeesAndSettlement(application, operatorId, linkedOrders);
insertHistory(customerId, application.getId(), READY_FOR_OUTBOUND, "COMPLETED", operatorId,
"Warehouse transfer received; relabel fulfillment completed", now);
}
private void publishCompletionFeesAndSettlement(RelabelApplication application, String operatorId,
List<LastlegOrder> linkedOrders) {
if ("NOT_REQUIRED".equals(application.getWalletStatus())) return;
BigDecimal quantity = BigDecimal.valueOf(Objects.requireNonNullElse(application.getTotalPackageCount(), 0));
String customerId = application.getCustomerId(), relabelNo = application.getRelabelNo();
feeSources.publishApproved(operatorId, "RELABEL:" + relabelNo,
new OverseasFeeSourceDtos.SaveRequest(customerId, linkedOrders.get(0).getCustomerName(),
"OVERSEAS_RELABEL_FEE", "RELABEL_ORDER", application.getId(), relabelNo,
application.getLastlegOrderId(), null, null, null, null, null,
parseLong(application.getOverseasWarehouseId()), "RELABEL_SERVICE", "换标费", "RECEIVABLE",
null, null, null, null, null, null, null, List.of(), LocalDate.now(),
application.getChargeCurrency(), application.getChargeAmount(),
quantity.signum() > 0 ? quantity : null, quantity.signum() > 0 ? "BOX" : null,
quantity.signum() > 0 ? application.getChargeAmount().divide(quantity, 6, java.math.RoundingMode.HALF_UP) : null,
null, null));
walletCommands.publish("SETTLE", Long.parseLong(customerId), "RELABEL_ORDER", relabelNo, null,
"换标完成,结算费用", 2L);
application.setWalletStatus("SETTLE_PENDING"); applications.updateById(application);
}
@Transactional
public RelabelResponses.Detail confirmCompletion(String customerId, String relabelNo, String operatorId,
RelabelRequests.CustomerConfirm request) {
@@ -1283,8 +1283,8 @@ public class WarehouseTransferService {
RelabelApplication linked = relabelApplications.lockNo(customerId, transfer.getRelabelNo());
if (linked == null || !Objects.equals(linked.getId(), transfer.getRelabelApplicationId()))
throw conflict("本票换标完成归属不一致");
linked.setStatus("COMPLETED"); linked.setFulfillmentStatus("COMPLETED");
linked.setUpdatedAt(now); relabelApplications.updateById(linked);
relabelService.completeWarehouseTransferFulfillment(customerId, transfer.getRelabelNo(),
transfer.getId(), operatorId);
}
return detail(customerId, transferNo);
}