|
|
@@ -49,8 +49,12 @@ public class ParkingWalletFeeService {
|
|
|
|
|
|
private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO;
|
|
|
private static final int STRATEGY_OPEN = 1;
|
|
|
+ /** 钱包明细交易类型:冻结(下单时服务费从可用余额转入履约中) */
|
|
|
private static final int TRADE_TYPE_FREEZE = 4;
|
|
|
+ /** 钱包明细交易类型:解冻(审核拒绝全额返还,或订单完结释放履约中金额) */
|
|
|
private static final int TRADE_TYPE_UNFREEZE = 5;
|
|
|
+ /** 钱包明细交易类型:消费(订单完结按实际履约量正式扣减服务费) */
|
|
|
+ private static final int TRADE_TYPE_CONSUME = 6;
|
|
|
|
|
|
private final KwtParkingWalletFeeRepository parkingWalletFeeRepository;
|
|
|
private final KwtParkingWalletFeeBalanceRepository parkingWalletFeeBalanceRepository;
|
|
|
@@ -237,6 +241,112 @@ public class ParkingWalletFeeService {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 贸易订单完结结算服务费
|
|
|
+ * <p>
|
|
|
+ * 适用场景:所有运单完成后,订单已应用收费策略(applyChargeStrategy=1)。
|
|
|
+ * 处理流程与预付钱包完结一致,分两步完成资金流转:
|
|
|
+ * <ol>
|
|
|
+ * <li>解冻:将下单时冻结的履约中金额(trading_amount)释放回可用余额(service_fee_balance),
|
|
|
+ * 并写入 trade_type=5 的解冻明细(备注:订单完结解冻服务费)</li>
|
|
|
+ * <li>扣减:按「实际履约量 × 收费策略单价」从可用余额中正式扣减服务费,
|
|
|
+ * 并写入 trade_type=6 的消费明细(备注:订单完结扣减服务费)</li>
|
|
|
+ * </ol>
|
|
|
+ * 幂等:以 trade_type=6 的消费记录作为结算完成标识,重复调用直接返回当前余额。
|
|
|
+ * <p>
|
|
|
+ * 钱包状态变化示例(冻结50元,实际扣减40元):
|
|
|
+ * 解冻前 balance=50, trading=50 → 解冻后 balance=100, trading=0 → 扣减后 balance=60, trading=0
|
|
|
+ *
|
|
|
+ * @param param 结算参数,需包含 orderNo、proEntId、actualQuantity、chargeStrategyId
|
|
|
+ * @return 结算结果,含实际扣减金额(freezeAmount字段)及扣减后服务费余额
|
|
|
+ */
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public ParkingWalletFeeFreezeResult settleChargeStrategy(ParkingWalletFeeFreezeParam param) {
|
|
|
+ log.info("贸易订单完结结算服务费开始,orderNo:{}, proEntId:{}, supEntId:{}, actualQuantity:{}, chargeStrategyId:{}",
|
|
|
+ param.getOrderNo(), param.getProEntId(), param.getSupEntId(),
|
|
|
+ param.getActualQuantity(), param.getChargeStrategyId());
|
|
|
+ validateSettleParam(param);
|
|
|
+
|
|
|
+ ParkingWalletFeeFreezeResult result = new ParkingWalletFeeFreezeResult();
|
|
|
+ result.setApplyChargeStrategy(Global.YES);
|
|
|
+
|
|
|
+ // 幂等校验:已存在消费明细则视为结算完成,避免重复扣减
|
|
|
+ if (existsConsumeRecord(param.getOrderNo(), param.getProEntId(), param.getSupEntId())) {
|
|
|
+ BigDecimal currentBalance = queryServiceFeeBalance(param.getProEntId(), param.getSupEntId());
|
|
|
+ log.info("服务费已结算,跳过重复处理,orderNo:{}, proEntId:{}, currentBalance:{}",
|
|
|
+ param.getOrderNo(), param.getProEntId(), currentBalance);
|
|
|
+ result.setServiceFeeBalance(currentBalance);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询下单时的冻结明细,作为解冻金额依据
|
|
|
+ KwtParkingWalletFeeBalance freezeRecord = queryFreezeBalanceRecord(param);
|
|
|
+ if (freezeRecord == null) {
|
|
|
+ log.warn("未找到服务费冻结记录,无法结算,orderNo:{}, proEntId:{}, supEntId:{}",
|
|
|
+ param.getOrderNo(), param.getProEntId(), param.getSupEntId());
|
|
|
+ throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费冻结记录不存在,无法结算");
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal freezeAmount = freezeRecord.getTradeAmount() == null ? ZERO_AMOUNT : freezeRecord.getTradeAmount();
|
|
|
+ if (freezeAmount.compareTo(ZERO_AMOUNT) <= 0) {
|
|
|
+ log.warn("服务费冻结金额异常,无法结算,orderNo:{}, freezeRecordId:{}, freezeAmount:{}",
|
|
|
+ param.getOrderNo(), freezeRecord.getId(), freezeAmount);
|
|
|
+ throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费冻结金额异常,无法结算");
|
|
|
+ }
|
|
|
+ log.info("查询到服务费冻结记录,orderNo:{}, freezeRecordId:{}, freezeAmount:{}",
|
|
|
+ param.getOrderNo(), freezeRecord.getId(), freezeAmount);
|
|
|
+
|
|
|
+ KwtParkingWalletFee walletFee = queryWalletFee(param.getProEntId(), param.getSupEntId());
|
|
|
+ if (walletFee == null) {
|
|
|
+ log.warn("采购方服务费账户不存在,无法结算,orderNo:{}, proEntId:{}, supEntId:{}",
|
|
|
+ param.getOrderNo(), param.getProEntId(), param.getSupEntId());
|
|
|
+ throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "采购方服务费账户不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal balanceBeforeSettle = walletFee.getServiceFeeBalance() == null ? ZERO_AMOUNT : walletFee.getServiceFeeBalance();
|
|
|
+ BigDecimal tradingBeforeSettle = walletFee.getTradingAmount() == null ? ZERO_AMOUNT : walletFee.getTradingAmount();
|
|
|
+ log.info("结算前钱包状态,orderNo:{}, walletFeeId:{}, balance:{}, tradingAmount:{}",
|
|
|
+ param.getOrderNo(), walletFee.getId(), balanceBeforeSettle, tradingBeforeSettle);
|
|
|
+
|
|
|
+ // 第一步:解冻履约中金额,释放回可用余额(与预付钱包完结解冻逻辑一致)
|
|
|
+ if (tradingBeforeSettle.compareTo(freezeAmount) < 0) {
|
|
|
+ log.warn("服务费履约中金额不足,无法结算,orderNo:{}, tradingAmount:{}, freezeAmount:{}",
|
|
|
+ param.getOrderNo(), tradingBeforeSettle, freezeAmount);
|
|
|
+ throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费履约中金额不足,无法结算");
|
|
|
+ }
|
|
|
+ walletFee.setServiceFeeBalance(balanceBeforeSettle.add(freezeAmount));
|
|
|
+ walletFee.setTradingAmount(tradingBeforeSettle.subtract(freezeAmount));
|
|
|
+ log.info("完结解冻完成(内存态),orderNo:{}, unfreezeAmount:{}, balanceAfterUnfreeze:{}, tradingAfterUnfreeze:{}",
|
|
|
+ param.getOrderNo(), freezeAmount, walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
|
|
|
+ saveSettleUnfreezeWalletBalanceLog(param, walletFee, freezeAmount);
|
|
|
+
|
|
|
+ // 第二步:按实际履约量计算应扣减服务费,并从可用余额中扣除
|
|
|
+ BigDecimal consumeAmount = calculateSettleConsumeAmount(param, freezeAmount);
|
|
|
+ if (consumeAmount.compareTo(ZERO_AMOUNT) < 0) {
|
|
|
+ log.warn("服务费扣减金额计算异常,orderNo:{}, consumeAmount:{}", param.getOrderNo(), consumeAmount);
|
|
|
+ throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费扣减金额异常,无法结算");
|
|
|
+ }
|
|
|
+ log.info("服务费扣减金额计算完成,orderNo:{}, actualQuantity:{}, chargeStrategyId:{}, freezeAmount:{}, consumeAmount:{}",
|
|
|
+ param.getOrderNo(), param.getActualQuantity(), param.getChargeStrategyId(), freezeAmount, consumeAmount);
|
|
|
+
|
|
|
+ BigDecimal balanceAfterUnfreeze = walletFee.getServiceFeeBalance();
|
|
|
+ if (balanceAfterUnfreeze.compareTo(consumeAmount) < 0) {
|
|
|
+ log.warn("服务费余额不足,无法扣减,orderNo:{}, balanceAfterUnfreeze:{}, consumeAmount:{}",
|
|
|
+ param.getOrderNo(), balanceAfterUnfreeze, consumeAmount);
|
|
|
+ throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费余额不足,无法结算扣减");
|
|
|
+ }
|
|
|
+ walletFee.setServiceFeeBalance(balanceAfterUnfreeze.subtract(consumeAmount));
|
|
|
+ parkingWalletFeeRepository.updateById(walletFee);
|
|
|
+
|
|
|
+ saveConsumeWalletBalanceLog(param, walletFee, consumeAmount);
|
|
|
+ result.setFreezeAmount(consumeAmount);
|
|
|
+ result.setServiceFeeBalance(walletFee.getServiceFeeBalance());
|
|
|
+ log.info("贸易订单完结结算服务费完成,orderNo:{}, freezeAmount:{}, consumeAmount:{}, balanceBeforeSettle:{}, balanceAfterSettle:{}, tradingBeforeSettle:{}, tradingAfterSettle:{}",
|
|
|
+ param.getOrderNo(), freezeAmount, consumeAmount,
|
|
|
+ balanceBeforeSettle, walletFee.getServiceFeeBalance(), tradingBeforeSettle, walletFee.getTradingAmount());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 查询采购方服务费余额
|
|
|
*/
|
|
|
@@ -253,6 +363,55 @@ public class ParkingWalletFeeService {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 校验订单完结结算入参(订单号、采购方企业id必填)
|
|
|
+ */
|
|
|
+ private void validateSettleParam(ParkingWalletFeeFreezeParam param) {
|
|
|
+ validateUnfreezeParam(param);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 判断订单是否已完成服务费结算(存在 trade_type=6 的消费明细即为已结算)
|
|
|
+ */
|
|
|
+ private boolean existsConsumeRecord(String orderNo, Long proEntId, Long supEntId) {
|
|
|
+ return parkingWalletFeeBalanceRepository.count(
|
|
|
+ Wrappers.<KwtParkingWalletFeeBalance>lambdaQuery()
|
|
|
+ .eq(KwtParkingWalletFeeBalance::getOrderNo, orderNo)
|
|
|
+ .eq(KwtParkingWalletFeeBalance::getProEntId, proEntId)
|
|
|
+ .eq(supEntId != null, KwtParkingWalletFeeBalance::getSupEntId, supEntId)
|
|
|
+ .eq(KwtParkingWalletFeeBalance::getTradeType, TRADE_TYPE_CONSUME)
|
|
|
+ .eq(KwtParkingWalletFeeBalance::getDelFlag, Global.NO)
|
|
|
+ ) > 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算订单完结时应扣减的服务费金额
|
|
|
+ * <p>
|
|
|
+ * 优先按「实际履约量 × 收费策略单价」计算;若实际量或策略id缺失,则回退为下单冻结金额。
|
|
|
+ *
|
|
|
+ * @param param 结算参数
|
|
|
+ * @param freezeAmount 下单时冻结金额(兜底扣减值)
|
|
|
+ * @return 应扣减服务费,保留2位小数
|
|
|
+ */
|
|
|
+ private BigDecimal calculateSettleConsumeAmount(ParkingWalletFeeFreezeParam param, BigDecimal freezeAmount) {
|
|
|
+ if (param.getActualQuantity() == null || param.getChargeStrategyId() == null) {
|
|
|
+ log.info("实际履约量或收费策略id为空,扣减金额按冻结金额处理,orderNo:{}, actualQuantity:{}, chargeStrategyId:{}, freezeAmount:{}",
|
|
|
+ param.getOrderNo(), param.getActualQuantity(), param.getChargeStrategyId(), freezeAmount);
|
|
|
+ return freezeAmount;
|
|
|
+ }
|
|
|
+ KwtParkingChargeStrategy strategy = parkingChangeStrategyRepository.getById(param.getChargeStrategyId());
|
|
|
+ if (strategy == null || strategy.getMethod() == null) {
|
|
|
+ log.warn("收费策略不存在或单价为空,扣减金额按冻结金额处理,orderNo:{}, chargeStrategyId:{}, freezeAmount:{}",
|
|
|
+ param.getOrderNo(), param.getChargeStrategyId(), freezeAmount);
|
|
|
+ return freezeAmount;
|
|
|
+ }
|
|
|
+ BigDecimal unitFee = strategy.getMethod();
|
|
|
+ BigDecimal consumeAmount = param.getActualQuantity().multiply(unitFee).setScale(2, RoundingMode.HALF_UP);
|
|
|
+ log.info("按实际履约量计算扣减金额,orderNo:{}, actualQuantity:{}, unitFee:{}, consumeAmount:{}",
|
|
|
+ param.getOrderNo(), param.getActualQuantity(), unitFee, consumeAmount);
|
|
|
+ return consumeAmount;
|
|
|
+ }
|
|
|
+
|
|
|
private boolean existsUnfreezeRecord(String orderNo, Long proEntId, Long supEntId) {
|
|
|
return parkingWalletFeeBalanceRepository.count(
|
|
|
Wrappers.<KwtParkingWalletFeeBalance>lambdaQuery()
|
|
|
@@ -303,6 +462,64 @@ public class ParkingWalletFeeService {
|
|
|
unfreezeAmount, walletFee.getServiceFeeBalance());
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 写入订单完结解冻明细(trade_type=5)
|
|
|
+ * 仅释放履约中金额至可用余额,不代表最终扣减完成
|
|
|
+ */
|
|
|
+ private void saveSettleUnfreezeWalletBalanceLog(ParkingWalletFeeFreezeParam param,
|
|
|
+ KwtParkingWalletFee walletFee,
|
|
|
+ BigDecimal unfreezeAmount) {
|
|
|
+ Long operatorId = resolveOperatorId(param.getOperatorId());
|
|
|
+ Date now = new Date();
|
|
|
+ KwtParkingWalletFeeBalance balanceRecord = new KwtParkingWalletFeeBalance();
|
|
|
+ balanceRecord.setOrderNo(param.getOrderNo());
|
|
|
+ balanceRecord.setProEntId(param.getProEntId());
|
|
|
+ balanceRecord.setSupEntId(param.getSupEntId());
|
|
|
+ balanceRecord.setTradeType(TRADE_TYPE_UNFREEZE);
|
|
|
+ balanceRecord.setTradeAmount(unfreezeAmount);
|
|
|
+ balanceRecord.setServiceFeeBalance(walletFee.getServiceFeeBalance());
|
|
|
+ balanceRecord.setTradingAmount(walletFee.getTradingAmount());
|
|
|
+ balanceRecord.setRemark("贸易订单号:" + param.getOrderNo() + ",订单完结解冻服务费");
|
|
|
+ balanceRecord.setDelFlag(Global.NO);
|
|
|
+ balanceRecord.setCreateUser(operatorId);
|
|
|
+ balanceRecord.setUpdateUser(operatorId);
|
|
|
+ balanceRecord.setCreateTime(now);
|
|
|
+ balanceRecord.setUpdateTime(now);
|
|
|
+ parkingWalletFeeBalanceRepository.save(balanceRecord);
|
|
|
+ log.info("服务费完结解冻明细已写入kwt_parking_wallet_fee_balance,orderNo:{}, proEntId:{}, supEntId:{}, tradeType:{}, tradeAmount:{}, balanceAfter:{}, tradingAfter:{}",
|
|
|
+ param.getOrderNo(), param.getProEntId(), param.getSupEntId(), TRADE_TYPE_UNFREEZE,
|
|
|
+ unfreezeAmount, walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 写入订单完结扣减明细(trade_type=6)
|
|
|
+ * 标志服务费正式消费完成,同时作为结算幂等依据
|
|
|
+ */
|
|
|
+ private void saveConsumeWalletBalanceLog(ParkingWalletFeeFreezeParam param,
|
|
|
+ KwtParkingWalletFee walletFee,
|
|
|
+ BigDecimal consumeAmount) {
|
|
|
+ Long operatorId = resolveOperatorId(param.getOperatorId());
|
|
|
+ Date now = new Date();
|
|
|
+ KwtParkingWalletFeeBalance balanceRecord = new KwtParkingWalletFeeBalance();
|
|
|
+ balanceRecord.setOrderNo(param.getOrderNo());
|
|
|
+ balanceRecord.setProEntId(param.getProEntId());
|
|
|
+ balanceRecord.setSupEntId(param.getSupEntId());
|
|
|
+ balanceRecord.setTradeType(TRADE_TYPE_CONSUME);
|
|
|
+ balanceRecord.setTradeAmount(consumeAmount);
|
|
|
+ balanceRecord.setServiceFeeBalance(walletFee.getServiceFeeBalance());
|
|
|
+ balanceRecord.setTradingAmount(walletFee.getTradingAmount());
|
|
|
+ balanceRecord.setRemark("贸易订单号:" + param.getOrderNo() + ",订单完结扣减服务费");
|
|
|
+ balanceRecord.setDelFlag(Global.NO);
|
|
|
+ balanceRecord.setCreateUser(operatorId);
|
|
|
+ balanceRecord.setUpdateUser(operatorId);
|
|
|
+ balanceRecord.setCreateTime(now);
|
|
|
+ balanceRecord.setUpdateTime(now);
|
|
|
+ parkingWalletFeeBalanceRepository.save(balanceRecord);
|
|
|
+ log.info("服务费扣减明细已写入kwt_parking_wallet_fee_balance,orderNo:{}, proEntId:{}, supEntId:{}, tradeType:{}, tradeAmount:{}, balanceAfter:{}, tradingAfter:{}",
|
|
|
+ param.getOrderNo(), param.getProEntId(), param.getSupEntId(), TRADE_TYPE_CONSUME,
|
|
|
+ consumeAmount, walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
|
|
|
+ }
|
|
|
+
|
|
|
private String buildStrategyMethodDesc(KwtParkingChargeStrategy strategy) {
|
|
|
if (strategy == null || strategy.getMethod() == null) {
|
|
|
return null;
|