Kaynağa Gözat

Merge remote-tracking branch 'origin/dev_20260630' into dev_20260630

xucaiqin 10 saat önce
ebeveyn
işleme
e0641f56a8

+ 11 - 0
sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/dubbo/ParkingWalletFeeRemoteService.java

@@ -26,6 +26,17 @@ public interface ParkingWalletFeeRemoteService {
      */
     ParkingWalletFeeFreezeResult unfreezeChargeStrategy(ParkingWalletFeeFreezeParam param);
 
+    /**
+     * 贸易订单完结时结算服务费
+     * <p>
+     * 先解冻下单冻结的履约中金额,再按实际履约量扣减服务费。
+     * 入参需包含:orderNo、proEntId、supEntId、actualQuantity、chargeStrategyId。
+     *
+     * @param param 结算参数
+     * @return 结算结果,freezeAmount 为实际扣减金额,serviceFeeBalance 为扣减后余额
+     */
+    ParkingWalletFeeFreezeResult settleChargeStrategy(ParkingWalletFeeFreezeParam param);
+
     /**
      * 查询采购方服务费余额
      *

+ 16 - 2
sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/param/ParkingWalletFeeFreezeParam.java

@@ -7,7 +7,11 @@ import java.io.Serializable;
 import java.math.BigDecimal;
 
 /**
- * 贸易订单服务费冻结参数
+ * 贸易订单服务费操作参数
+ * <p>
+ * 下单冻结:orderNo、proEntId、supEntId、purchaseQuantity
+ * 审核拒绝解冻:orderNo、proEntId、supEntId
+ * 订单完结结算:orderNo、proEntId、supEntId、actualQuantity、chargeStrategyId
  */
 @Data
 public class ParkingWalletFeeFreezeParam implements Serializable {
@@ -31,10 +35,20 @@ public class ParkingWalletFeeFreezeParam implements Serializable {
     private Long supEntId;
 
     /**
-     * 采购数量
+     * 采购数量(下单冻结时使用,冻结金额 = purchaseQuantity × 策略单价)
      */
     private BigDecimal purchaseQuantity;
 
+    /**
+     * 实际履约数量(订单完结结算时使用,扣减金额 = actualQuantity × 策略单价)
+     */
+    private BigDecimal actualQuantity;
+
+    /**
+     * 收费策略id(订单完结结算时使用,用于查询策略单价)
+     */
+    private Long chargeStrategyId;
+
     /**
      * 操作人id(Dubbo调用时由订单模块传入)
      */

+ 70 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java

@@ -3450,7 +3450,10 @@ public class KwoTradeOrderService {
         if (CollUtil.isNotEmpty(logisticOrderList)) {
             if (logisticOrderList.stream().allMatch(d -> Objects.equals(d.getStatus(), LogisticsOrderV1Enum.NEARING_COMPLETION.getCode()))) {
                 kwoTradeOrder.setStatus(TradeOrderStatusEnum.SUCCESS.getCode());
+                // 预付钱包完结:解冻 + 按实际装/卸货量消费
                 calculatePrepaidBalance(kwoTradeOrderDB);
+                // 收费策略服务费完结:解冻履约中金额 + 按实际履约量扣减(内部含详细日志)
+                settleChargeStrategyIfApplied(kwoTradeOrderDB, logisticOrderList);
             } else {
                 kwoTradeOrder.setStatus(TradeOrderStatusEnum.DEAL.getCode());
             }
@@ -3473,6 +3476,73 @@ public class KwoTradeOrderService {
         return true;
     }
 
+    /**
+     * 贸易订单完结时,处理收费策略关联的服务费结算
+     * <p>
+     * 触发条件:订单 applyChargeStrategy=1(下单时已冻结服务费)。
+     * 实际履约量:按 chargeType 汇总关联物流单装货量或卸货量,与预付钱包消费逻辑保持一致。
+     * 通过 Dubbo 调用 transport 模块完成解冻 + 扣减 + 钱包明细落库。
+     *
+     * @param order             贸易订单(需含 chargeStrategyId、chargeType、tOrderNo 等)
+     * @param logisticOrderList 关联物流单列表,用于汇总实际履约量
+     */
+    private void settleChargeStrategyIfApplied(KwoTradeOrder order, List<KwtLogisticsOrderVO> logisticOrderList) {
+        if (!Objects.equals(order.getApplyChargeStrategy(), Global.YES)) {
+            log.info("贸易订单未应用收费策略,跳过服务费结算,orderId:{}, orderNo:{}, applyChargeStrategy:{}",
+                    order.getId(), order.getTOrderNo(), order.getApplyChargeStrategy());
+            return;
+        }
+        log.info("贸易订单已应用收费策略,开始准备服务费结算,orderId:{}, orderNo:{}, chargeStrategyId:{}, chargeType:{}",
+                order.getId(), order.getTOrderNo(), order.getChargeStrategyId(), order.getChargeType());
+
+        List<KwoTradeOrderUnit> orderUnits = kwoTradeOrderUnitService.getByOrderId(order.getId());
+        if (CollUtil.isEmpty(orderUnits)) {
+            log.error("贸易订单企业信息不存在,无法结算服务费,orderId:{}, orderNo:{}", order.getId(), order.getTOrderNo());
+            throw new BusinessException("贸易订单企业信息不存在");
+        }
+        Map<String, KwoTradeOrderUnit> unitMap = orderUnits.stream()
+                .collect(Collectors.toMap(KwoTradeOrderUnit::getUnitType, e -> e, (a, b) -> a));
+        if (unitMap.size() < 2) {
+            log.error("贸易订单企业信息缺失,无法结算服务费,orderId:{}, orderNo:{}, unitSize:{}",
+                    order.getId(), order.getTOrderNo(), unitMap.size());
+            throw new BusinessException("贸易订单企业信息缺失");
+        }
+
+        // chargeType=1 按装货量结算,否则按卸货量结算
+        BigDecimal actualQuantity;
+        if (Objects.equals(order.getChargeType(), 1)) {
+            actualQuantity = logisticOrderList.stream()
+                    .map(KwtLogisticsOrderVO::getLoadAmount)
+                    .filter(Objects::nonNull)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            log.info("按装货量汇总实际履约量,orderNo:{}, logisticOrderCount:{}, actualQuantity:{}",
+                    order.getTOrderNo(), logisticOrderList.size(), actualQuantity);
+        } else {
+            actualQuantity = logisticOrderList.stream()
+                    .map(KwtLogisticsOrderVO::getUnloadAmount)
+                    .filter(Objects::nonNull)
+                    .reduce(BigDecimal.ZERO, BigDecimal::add);
+            log.info("按卸货量汇总实际履约量,orderNo:{}, logisticOrderCount:{}, actualQuantity:{}",
+                    order.getTOrderNo(), logisticOrderList.size(), actualQuantity);
+        }
+
+        Long proEntId = unitMap.get(String.valueOf(1)).getEntId();
+        Long supEntId = unitMap.get(String.valueOf(2)).getEntId();
+        ParkingWalletFeeFreezeParam settleParam = new ParkingWalletFeeFreezeParam();
+        settleParam.setOrderNo(order.getTOrderNo());
+        settleParam.setProEntId(proEntId);
+        settleParam.setSupEntId(supEntId);
+        settleParam.setActualQuantity(actualQuantity);
+        settleParam.setChargeStrategyId(order.getChargeStrategyId());
+        settleParam.setOperatorId(LoginUserHolder.getUserId());
+        log.info("调用服务费完结结算,orderNo:{}, proEntId:{}, supEntId:{}, actualQuantity:{}, chargeStrategyId:{}, operatorId:{}",
+                order.getTOrderNo(), proEntId, supEntId, actualQuantity, order.getChargeStrategyId(), settleParam.getOperatorId());
+
+        ParkingWalletFeeFreezeResult settleResult = parkingWalletFeeRemoteService.settleChargeStrategy(settleParam);
+        log.info("服务费完结结算完成,orderNo:{}, consumeAmount:{}, serviceFeeBalanceAfter:{}",
+                order.getTOrderNo(), settleResult.getFreezeAmount(), settleResult.getServiceFeeBalance());
+    }
+
     private void calculatePrepaidBalance(KwoTradeOrder kwoTradeOrder) {
         //贸易订单已完结,计算预付余额
         if (Objects.equals(kwoTradeOrder.getSettlement(), 1)) {

+ 11 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dubbo/ParkingWalletFeeRemoteServiceImpl.java

@@ -5,6 +5,7 @@ import com.sckw.transport.api.model.param.ParkingWalletFeeFreezeParam;
 import com.sckw.transport.api.model.vo.ParkingWalletFeeFreezeResult;
 import com.sckw.transport.service.ParkingWalletFeeService;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboService;
 import org.springframework.stereotype.Service;
 
@@ -13,6 +14,7 @@ import java.math.BigDecimal;
 /**
  * 服务费远程服务实现
  */
+@Slf4j
 @Service
 @DubboService(group = "design", version = "1.0.0", timeout = 60000)
 @RequiredArgsConstructor
@@ -30,6 +32,15 @@ public class ParkingWalletFeeRemoteServiceImpl implements ParkingWalletFeeRemote
         return parkingWalletFeeService.unfreezeChargeStrategy(param);
     }
 
+    @Override
+    public ParkingWalletFeeFreezeResult settleChargeStrategy(ParkingWalletFeeFreezeParam param) {
+        log.info("Dubbo调用服务费完结结算,orderNo:{}, proEntId:{}, supEntId:{}",
+                param == null ? null : param.getOrderNo(),
+                param == null ? null : param.getProEntId(),
+                param == null ? null : param.getSupEntId());
+        return parkingWalletFeeService.settleChargeStrategy(param);
+    }
+
     @Override
     public BigDecimal queryServiceFeeBalance(Long proEntId, Long supEntId) {
         return parkingWalletFeeService.queryPurchaserServiceFeeBalance(proEntId, supEntId);

+ 217 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/ParkingWalletFeeService.java

@@ -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;