Преглед изворни кода

合并服务费下单逻辑

donglang пре 7 часа
родитељ
комит
93a3e6c99d

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

@@ -26,6 +26,18 @@ public interface ParkingWalletFeeRemoteService {
      */
     ParkingWalletFeeFreezeResult unfreezeChargeStrategy(ParkingWalletFeeFreezeParam param);
 
+    /**
+     * 贸易订单完结时结算服务费
+     * <p>
+     * 先解冻下单冻结的履约中金额,再按「运输净重量 × 采购方当前最优收费策略单价」扣减服务费。
+     * 钱包明细备注会记录采用的策略名称及单价描述(如10元/吨)。
+     *
+     * @param param 结算参数,actualQuantity 为运输净重量
+     * @return 结算结果,freezeAmount 为实际扣减金额,chargeStrategyDesc 为采用的策略单价描述
+     */
+    ParkingWalletFeeFreezeResult settleChargeStrategy(ParkingWalletFeeFreezeParam param);
+
+
     /**
      * 查询采购方服务费余额
      *

+ 17 - 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,21 @@ public class ParkingWalletFeeFreezeParam implements Serializable {
     private Long supEntId;
 
     /**
-     * 采购数量
+     * 采购数量(下单冻结时使用,冻结金额 = purchaseQuantity × 策略单价)
      */
     private BigDecimal purchaseQuantity;
 
+    /**
+     * 运输净重量(订单完结结算时使用,扣减金额 = 运输净重量 × 当前最优策略单价)
+     */
+    private BigDecimal actualQuantity;
+
+    /**
+     * 收费策略id(订单完结结算兜底使用,优先取采购方当前最优策略)
+     */
+    private Long chargeStrategyId;
+
+
     /**
      * 操作人id(Dubbo调用时由订单模块传入)
      */

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

@@ -3043,7 +3043,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());
             }
@@ -3066,6 +3069,76 @@ public class KwoTradeOrderService {
         return true;
     }
 
+    /**
+     * 贸易订单完结时,处理收费策略关联的服务费结算
+     * <p>
+     * 触发条件:所有运单完成且订单 applyChargeStrategy=1(下单时已冻结服务费)。
+     * 运输净重量:汇总已完成运单子任务的装/卸货净重(与看板运单完成量统计口径一致)。
+     * 扣减逻辑:运输净重量 × 采购方当前最优收费策略单价,由 transport 模块计算并写入钱包明细备注。
+     *
+     * @param order             贸易订单
+     * @param logisticOrderList 关联物流单列表(用于日志,净重量以运单统计为准)
+     */
+    private void settleChargeStrategyIfApplied(KwoTradeOrder order, List<KwtLogisticsOrderVO> logisticOrderList) {
+        if (!Objects.equals(order.getApplyChargeStrategy(), Global.YES)) {
+            log.info("贸易订单未应用收费策略,跳过服务费结算,订单ID:{}, 订单号:{}, 是否应用收费策略:{}",
+                    order.getId(), order.getTOrderNo(), order.getApplyChargeStrategy());
+            return;
+        }
+        log.info("贸易订单已应用收费策略,开始准备服务费结算,订单ID:{}, 订单号:{}, 收费策略ID:{}, 计费方式:{}",
+                order.getId(), order.getTOrderNo(), order.getChargeStrategyId(), order.getChargeType());
+
+        List<KwoTradeOrderUnit> orderUnits = kwoTradeOrderUnitService.getByOrderId(order.getId());
+        if (CollUtil.isEmpty(orderUnits)) {
+            log.error("贸易订单企业信息不存在,无法结算服务费,订单ID:{}, 订单号:{}", 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("贸易订单企业信息缺失,无法结算服务费,订单ID:{}, 订单号:{}, 企业数量:{}",
+                    order.getId(), order.getTOrderNo(), unitMap.size());
+            throw new BusinessException("贸易订单企业信息缺失");
+        }
+
+        // 运输净重量:汇总已完成运单的装/卸货净重(chargeType=1装货净重,=2卸货净重)
+        BigDecimal transportNetWeight = queryTransportNetWeight(order);
+        log.info("运输净重量汇总完成,订单号:{}, 计费方式:{}, 运输净重量:{}, 物流订单数量:{}",
+                order.getTOrderNo(), order.getChargeType(), transportNetWeight, logisticOrderList.size());
+
+        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(transportNetWeight);
+        settleParam.setChargeStrategyId(order.getChargeStrategyId());
+        settleParam.setOperatorId(LoginUserHolder.getUserId());
+        log.info("调用服务费完结结算,订单号:{}, 采购方企业ID:{}, 供应商企业ID:{}, 运输净重量:{}, 兜底收费策略ID:{}, 操作人ID:{}",
+                order.getTOrderNo(), proEntId, supEntId, transportNetWeight, order.getChargeStrategyId(), settleParam.getOperatorId());
+
+        ParkingWalletFeeFreezeResult settleResult = parkingWalletFeeRemoteService.settleChargeStrategy(settleParam);
+        log.info("服务费完结结算完成,订单号:{}, 扣减金额:{}, 收费策略ID:{}, 收费策略描述:{}, 扣减后服务费余额:{}",
+                order.getTOrderNo(), settleResult.getFreezeAmount(), settleResult.getChargeStrategyId(),
+                settleResult.getChargeStrategyDesc(), settleResult.getServiceFeeBalance());
+    }
+
+    /**
+     * 查询贸易订单运输净重量(已完成运单装/卸货净重之和)
+     */
+    private BigDecimal queryTransportNetWeight(KwoTradeOrder order) {
+        try {
+            return Optional.ofNullable(transportRemoteStatisticsService.sumCompletedWaybillVolumeByTimeRange(
+                            order.getId(), order.getChargeType(), null, null))
+                    .orElse(BigDecimal.ZERO);
+        } catch (Exception e) {
+            log.error("查询贸易订单运输净重量失败,订单ID:{}, 订单号:{}, 计费方式:{}",
+                    order.getId(), order.getTOrderNo(), order.getChargeType(), e);
+            throw new BusinessException("查询贸易订单运输净重量失败");
+        }
+    }
+
     private void calculatePrepaidBalance(KwoTradeOrder kwoTradeOrder) {
         //贸易订单已完结,计算预付余额
         if (Objects.equals(kwoTradeOrder.getSettlement(), 1)) {

+ 1 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/ParkingChangeStrategyController.java

@@ -114,7 +114,7 @@ public class ParkingChangeStrategyController {
      * @param
      * @return
      */
-    @Operation(summary = "查询收费策略开关", description = "查询收费策略开关")
+    @Operation(summary = "开启关闭收费策略开关", description = "开启关闭收费策略开关")
     @PostMapping("/updateSwitch")
     public BaseResult updateSwitch(@RequestBody @Valid ParkingStrategySwitchSaveParam param){
         parkingChangeStrategyService.updateSwitch(param);

+ 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("远程调用服务费完结结算,订单号:{}, 采购方企业ID:{}, 供应商企业ID:{}",
+                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);

+ 39 - 6
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dubbo/TransportStatisticsServiceImpl.java

@@ -170,31 +170,51 @@ public class TransportStatisticsServiceImpl implements TransportRemoteStatistics
     }
 
     /**
-     * 按时间范围汇总贸易订单已完成运单的完成量。
+     * 按时间范围汇总贸易订单已完成运单的运输净重量(完成量)。
+     * <p>
+     * 统计口径(三步):
+     * <ol>
+     *   <li>查询贸易订单下所有未删除的物流订单</li>
+     *   <li>筛选状态为「已完成」(CarWaybillV1Enum.COMPLETED) 的运单;
+     *       startTime/endTime 均非空时按运单 update_time 过滤,否则统计全部已完成运单</li>
+     *   <li>汇总运单子任务净重:chargeType=1 取 load_amount(装货净重),chargeType=2 取 unload_amount(卸货净重)</li>
+     * </ol>
+     * 典型场景:贸易订单完结服务费结算、看板今日/本月运单完成量统计。
      *
      * @param tradeOrderId 贸易订单ID
-     * @param chargeType 计费方式:1-装货量,2-卸货量
-     * @param startTime 开始时间
-     * @param endTime 结束时间
-     * @return 完成量
+     * @param chargeType   计费方式:1-装货净重,2-卸货净重
+     * @param startTime    开始时间(可为 null,须与 endTime 同时传入才生效)
+     * @param endTime      结束时间(可为 null,须与 startTime 同时传入才生效)
+     * @return 运输净重量合计,无数据时返回 0
      */
     @Override
     public BigDecimal sumCompletedWaybillVolumeByTimeRange(Long tradeOrderId, Integer chargeType,
                                                            LocalDateTime startTime, LocalDateTime endTime) {
+        log.info("汇总贸易订单已完成运单运输净重量开始,贸易订单ID:{}, 计费方式:{}, 开始时间:{}, 结束时间:{}",
+                tradeOrderId, chargeType, startTime, endTime);
+
         if (Objects.isNull(tradeOrderId)) {
+            log.warn("汇总运输净重量失败,贸易订单ID为空");
             throw new BusinessException("贸易订单ID不能为空");
         }
         if (Objects.isNull(chargeType)) {
+            log.warn("汇总运输净重量失败,计费方式为空,贸易订单ID:{}", tradeOrderId);
             throw new BusinessException("计费方式不能为空");
         }
         if (!Objects.equals(chargeType, NumberConstant.ONE) && !Objects.equals(chargeType, NumberConstant.TWO)) {
+            log.warn("汇总运输净重量跳过,计费方式不支持,贸易订单ID:{}, 计费方式:{}", tradeOrderId, chargeType);
             return BigDecimal.ZERO;
         }
+
+        // 第一步:查询贸易订单关联的有效物流订单
         List<Long> logisticsOrderIds = listLogisticsOrderIdsByTradeOrderId(tradeOrderId);
         if (CollectionUtils.isEmpty(logisticsOrderIds)) {
+            log.info("汇总运输净重量结束,未找到关联物流订单,贸易订单ID:{}, 合计净重量:0", tradeOrderId);
             return BigDecimal.ZERO;
         }
+        log.info("查询到关联物流订单,贸易订单ID:{}, 物流订单数量:{}", tradeOrderId, logisticsOrderIds.size());
 
+        // 第二步:筛选已完成运单,可选按完成时间过滤
         QueryWrapper<KwtWaybillOrder> waybillWrapper = new QueryWrapper<KwtWaybillOrder>()
                 .select("id")
                 .eq("del_flag", Global.NO)
@@ -203,6 +223,9 @@ public class TransportStatisticsServiceImpl implements TransportRemoteStatistics
         if (Objects.nonNull(startTime) && Objects.nonNull(endTime)) {
             waybillWrapper.ge("update_time", toDate(startTime))
                     .lt("update_time", toDate(endTime));
+            log.info("运单按时间范围过滤,贸易订单ID:{}, 开始时间:{}, 结束时间:{}", tradeOrderId, startTime, endTime);
+        } else {
+            log.info("运单未限制时间范围,统计全部已完成运单,贸易订单ID:{}", tradeOrderId);
         }
 
         List<Long> waybillOrderIds = Optional.ofNullable(waybillOrderMapper.selectList(waybillWrapper))
@@ -213,9 +236,13 @@ public class TransportStatisticsServiceImpl implements TransportRemoteStatistics
                 .distinct()
                 .collect(Collectors.toList());
         if (CollectionUtils.isEmpty(waybillOrderIds)) {
+            log.info("汇总运输净重量结束,未找到已完成运单,贸易订单ID:{}, 计费方式:{}, 合计净重量:0",
+                    tradeOrderId, chargeType);
             return BigDecimal.ZERO;
         }
+        log.info("查询到已完成运单,贸易订单ID:{}, 已完成运单数量:{}", tradeOrderId, waybillOrderIds.size());
 
+        // 第三步:汇总运单子任务装/卸货净重
         QueryWrapper<KwtWaybillOrderSubtask> subtaskWrapper = new QueryWrapper<KwtWaybillOrderSubtask>()
                 .eq("del_flag", Global.NO)
                 .in("w_order_id", waybillOrderIds);
@@ -227,13 +254,19 @@ public class TransportStatisticsServiceImpl implements TransportRemoteStatistics
                     .isNotNull("unload_amount");
         }
 
-        return Optional.ofNullable(waybillOrderSubtaskMapper.selectList(subtaskWrapper))
+        BigDecimal totalVolume = Optional.ofNullable(waybillOrderSubtaskMapper.selectList(subtaskWrapper))
                 .orElse(Collections.emptyList())
                 .stream()
                 .map(item -> Objects.equals(chargeType, NumberConstant.ONE) ? item.getLoadAmount() : item.getUnloadAmount())
                 .filter(Objects::nonNull)
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
+
+        String volumeType = Objects.equals(chargeType, NumberConstant.ONE) ? "装货净重" : "卸货净重";
+        log.info("汇总贸易订单已完成运单运输净重量完成,贸易订单ID:{}, 计费方式:{}, 净重类型:{}, 合计净重量:{}",
+                tradeOrderId, chargeType, volumeType, totalVolume);
+        return totalVolume;
     }
+
     /**
      * 根据贸易订单ID查询有效物流订单ID列表,统一复用 MyBatis-Plus 查询条件,避免手写SQL。
      *

+ 27 - 7
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/ParkingChangeStrategyService.java

@@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.springframework.stereotype.Service;
 
+import java.math.BigDecimal;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -44,6 +45,7 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 public class ParkingChangeStrategyService {
 
+    private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO;
 
     private final KwtParkingChangeStrategyRepository parkingChangeStrategyRepository;
     private final KwtParkingChangeStrategyUnitRepository parkingChangeStrategyUnitRepository;
@@ -104,14 +106,15 @@ public class ParkingChangeStrategyService {
                     resp.setStrategyName(change.getStrategyName());
                     resp.setType(change.getType());
                     resp.setTypeDesc(ParkingChangeStrategyEnum.geDesc(change.getType()));
-                    resp.setMethod(change.getMethod());
+                    BigDecimal displayMethod = resolveStrategyDisplayMethod(change.getMethod());
+                    resp.setMethod(displayMethod);
 
-                    if (Objects.equals(ParkingChangeStrategyEnum.BY_TON, change.getType())) {
-                        resp.setMethodDesc(change.getMethod() + "元/吨");
-                    } else if (Objects.equals(ParkingChangeStrategyEnum.BY_TIME, change.getType())) {
-                        resp.setMethodDesc(change.getMethod() + "元/次");
+                    if (Objects.equals(ParkingChangeStrategyEnum.BY_TON.getCode(), change.getType())) {
+                        resp.setMethodDesc(displayMethod + "元/吨");
+                    } else if (Objects.equals(ParkingChangeStrategyEnum.BY_TIME.getCode(), change.getType())) {
+                        resp.setMethodDesc(displayMethod + "元/次");
                     } else {
-                        resp.setMethodDesc(change.getMethod() + "元/月");
+                        resp.setMethodDesc(displayMethod + "元/月");
                     }
                     // 查询分配单位数量
                     List<KwtParkingChargeStrategyUnit> strategyUnitList = parkingChangeStrategyUnitRepository.queryByStrategyId(change.getId());
@@ -126,7 +129,7 @@ public class ParkingChangeStrategyService {
                     return resp;
                 }).collect(Collectors.toList());
 
-        log.info("收费策略查询结束,size:{}", changeStrategyList.size());
+        log.info("收费策略查询结束,size:{}", JSON.toJSONString(changeStrategyList.size()));
         return changeStrategyList;
 
     }
@@ -284,4 +287,21 @@ public class ParkingChangeStrategyService {
         parkingStrategySwitchRepository.updateById(strategySwitch);
     }
 
+    /**
+     * 策略 method 为 0.00 时,展示开关表配置的默认服务费
+     */
+    private BigDecimal resolveStrategyDisplayMethod(BigDecimal method) {
+        BigDecimal actualMethod = method == null ? ZERO_AMOUNT : method;
+        if (actualMethod.compareTo(ZERO_AMOUNT) != 0) {
+            log.info("收费策略配置的计费:{}", actualMethod);
+            return actualMethod;
+        }
+        KwtParkingStrategySwitch strategySwitch = parkingStrategySwitchRepository.queryParkingStrategySwitch();
+        if (strategySwitch == null || strategySwitch.getDefaultFee() == null) {
+            log.info("收费策略未配置计费,使用默认计费:{}", ZERO_AMOUNT);
+            return ZERO_AMOUNT;
+        }
+        return strategySwitch.getDefaultFee();
+    }
+
 }

+ 408 - 54
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/ParkingWalletFeeService.java

@@ -19,10 +19,7 @@ import com.sckw.transport.api.model.vo.ParkingWalletFeeFreezeResult;
 import com.sckw.transport.model.*;
 import com.sckw.transport.model.enuma.WalletTypEnum;
 import com.sckw.transport.model.param.*;
-import com.sckw.transport.repository.KwtParkingChangeStrategyRepository;
-import com.sckw.transport.repository.KwtParkingChangeStrategyUnitRepository;
-import com.sckw.transport.repository.KwtParkingWalletFeeBalanceRepository;
-import com.sckw.transport.repository.KwtParkingWalletFeeRepository;
+import com.sckw.transport.repository.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.dubbo.config.annotation.DubboReference;
@@ -51,14 +48,19 @@ public class ParkingWalletFeeService {
 
     private final KwtParkingChangeStrategyRepository parkingChangeStrategyRepository;
     private final KwtParkingChangeStrategyUnitRepository parkingChangeStrategyUnitRepository;
+    private final KwtParkingStrategySwitchRepository parkingStrategySwitchRepository;
 
     @DubboReference(version = "1.0.0", group = "design", check = false, timeout = 6000)
    RemoteSystemService remoteSystemService;
 
     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;
 
     /**
      * 查询汇总服务费
@@ -307,51 +309,65 @@ public class ParkingWalletFeeService {
     }
 
     /**
-     * 查询服务费余额、本次预计服务费与最大可购买数量
-     * 规则:
-     * 1. 服务费余额 = 采购方当前服务费余额
-     * 2. 本次预计服务费 = 采购数量 * 采购方当前配置的收费策略单价
-     * 3. 最大可购买数量 = 收费策略开启时,服务费余额 / 策略单价;未开启时不返回余额约束
+     * 查询服务费余额、本次预计服务费与最大可购买数量。
+     * <p>
+     * 业务规则:
+     * 1. 服务费余额取当前登录采购企业的可用服务费余额;
+     * 2. 收费策略总开关关闭时,不计算预计服务费,最大可购买数量返回 null,表示不做余额约束;
+     * 3. 收费策略总开关开启时,按企业绑定的当前收费策略计算预计服务费;
+     * 4. 策略 method 为 0.00 时,实际单价使用 kwt_parking_strategy_switch.default_fee;
+     * 5. 最大可购买数量 = 服务费余额 / 实际策略单价,向下取整,避免超出余额。
      *
-     * @param param 查询参数
+     * @param param 服务费预估查询参数,purchaseQuantity 为本次采购数量
      * @return 服务费余额、本次预计服务费与最大可购买数量
      */
     public ParkingWalletFeeEstimateResp queryEstimateServiceFee(ParkingWalletFeeEstimateQueryParam param) {
         Long entId = LoginUserHolder.getEntId();
-        log.info("服务费预估开始,entId:{}, param:{}", entId, param);
+        log.info("服务费预估开始,企业id:{}, 查询参数:{}", entId, param);
+        if (param == null) {
+            log.warn("服务费预估参数为空,企业id:{}", entId);
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费预估参数不能为空");
+        }
         if (param.getPurchaseQuantity() == null || param.getPurchaseQuantity().compareTo(ZERO_AMOUNT) < 0) {
-            log.warn("服务费预估参数异常,采购数量非法,entId:{}, purchaseQuantity:{}",
+            log.warn("服务费预估参数异常,采购数量不能为空且不能小于0,企业id:{}, 采购数量:{}",
                     entId, param.getPurchaseQuantity());
             throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "采购数量不能为空且不能小于0");
         }
 
-        // 查询采购方当前服务费余额
+        // 查询采购企业当前可用服务费余额;supEntId 为空时按采购企业汇总口径取最新钱包记录。
         BigDecimal serviceFeeBalance = queryServiceFeeBalance(entId, null);
+        log.info("服务费预估余额查询完成,企业id:{}, 服务费余额:{}", entId, serviceFeeBalance);
+
+        // 收费策略总开关统一以 kwt_parking_strategy_switch.status 为准,策略表 status 不再作为计费开关。
+        boolean chargeStrategySwitchOpen = isChargeStrategySwitchOpen();
 
-        // 查询当前企业生效中的收费策略(仅取状态开启的最新一条)
+        // 查询当前企业绑定的最新收费策略;若 method 为 0.00,resolveStrategyUnitFee 会使用开关表 default_fee。
         KwtParkingChargeStrategy currentStrategy = queryCurrentEnableStrategy(entId);
-        BigDecimal currentStrategyUnitFee = currentStrategy == null || currentStrategy.getMethod() == null
-                ? ZERO_AMOUNT : currentStrategy.getMethod();
-        log.info("服务费预估策略信息,entId:{}, strategyId:{}, strategyStatus:{}, unitFee:{}",
+        BigDecimal currentStrategyUnitFee = resolveStrategyUnitFee(currentStrategy);
+        log.info("服务费预估策略匹配完成,企业id:{}, 开关状态:{}, 策略id:{}, 策略原始单价:{}, 实际计费单价:{}",
                 entId,
+                chargeStrategySwitchOpen ? "开启" : "关闭",
                 currentStrategy == null ? null : currentStrategy.getId(),
-                currentStrategy == null ? null : currentStrategy.getStatus(),
+                currentStrategy == null ? null : currentStrategy.getMethod(),
                 currentStrategyUnitFee);
 
-        // 本次预计服务费 = 采购数量 * 策略单价,金额统一保留2位
-        BigDecimal estimatedServiceFee = param.getPurchaseQuantity().multiply(currentStrategyUnitFee)
-                .setScale(2, RoundingMode.HALF_UP);
+        // 本次预计服务费 = 采购数量 * 实际计费单价,金额保留2位小数;开关关闭时固定返回0。
+        BigDecimal estimatedServiceFee = chargeStrategySwitchOpen
+                ? param.getPurchaseQuantity().multiply(currentStrategyUnitFee).setScale(2, RoundingMode.HALF_UP)
+                : ZERO_AMOUNT;
+        log.info("服务费预估金额计算完成,企业id:{}, 采购数量:{}, 实际计费单价:{}, 预计服务费:{}",
+                entId, param.getPurchaseQuantity(), currentStrategyUnitFee, estimatedServiceFee);
 
-        // 收费策略开关开启时,按服务费余额计算最大可购买数量
+        // 开关开启且存在有效策略单价时,按服务费余额计算最大可购买数量;否则返回 null。
         BigDecimal maxPurchaseQuantity = calculateMaxPurchaseQuantity(currentStrategy, currentStrategyUnitFee, serviceFeeBalance);
-        log.info("服务费预估计算完成,entId:{}, purchaseQuantity:{}, serviceFeeBalance:{}, estimatedServiceFee:{}, maxPurchaseQuantity:{}",
+        log.info("服务费预估计算完成,企业id:{}, 采购数量:{}, 服务费余额:{}, 预计服务费:{}, 最大可购买数量:{}",
                 entId, param.getPurchaseQuantity(), serviceFeeBalance, estimatedServiceFee, maxPurchaseQuantity);
 
         ParkingWalletFeeEstimateResp resp = new ParkingWalletFeeEstimateResp();
         resp.setServiceFeeBalance(serviceFeeBalance);
         resp.setEstimatedServiceFee(estimatedServiceFee);
         resp.setMaxPurchaseQuantity(maxPurchaseQuantity);
-        log.info("服务费预估结束,entId:{}, resp:{}", entId, resp);
+        log.info("服务费预估结束,企业id:{}, 响应结果:{}", entId, resp);
         return resp;
     }
 
@@ -382,14 +398,20 @@ public class ParkingWalletFeeService {
         result.setApplyChargeStrategy(Global.NO);
         result.setFreezeAmount(ZERO_AMOUNT);
 
+        if (!isChargeStrategySwitchOpen()) {
+            log.info("收费策略开关未开启,跳过服务费冻结,proEntId:{}, orderNo:{}",
+                    param.getProEntId(), param.getOrderNo());
+            return result;
+        }
+
         KwtParkingChargeStrategy currentStrategy = queryCurrentEnableStrategy(param.getProEntId());
-        if (currentStrategy == null || !Objects.equals(currentStrategy.getStatus(), STRATEGY_OPEN)) {
-            log.info("收费策略未开启或未匹配,跳过服务费冻结,proEntId:{}, orderNo:{}",
+        if (currentStrategy == null) {
+            log.info("未匹配到收费策略,跳过服务费冻结,proEntId:{}, orderNo:{}",
                     param.getProEntId(), param.getOrderNo());
             return result;
         }
 
-        BigDecimal unitFee = currentStrategy.getMethod() == null ? ZERO_AMOUNT : currentStrategy.getMethod();
+        BigDecimal unitFee = resolveStrategyUnitFee(currentStrategy);
         if (unitFee.compareTo(ZERO_AMOUNT) <= 0) {
             log.info("收费策略单价无效,跳过服务费冻结,strategyId:{}, unitFee:{}",
                     currentStrategy.getId(), unitFee);
@@ -484,6 +506,135 @@ 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(运输净重量)
+     * @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());
+
+        // 第二步:运输净重量 × 采购方当前最优收费策略单价 = 本次实际扣减服务费
+        SettleConsumeInfo consumeInfo = resolveSettleConsumeInfo(param, freezeAmount);
+        BigDecimal consumeAmount = consumeInfo.consumeAmount();
+        if (consumeAmount.compareTo(ZERO_AMOUNT) < 0) {
+            log.warn("服务费扣减金额计算异常,orderNo:{}, consumeAmount:{}", param.getOrderNo(), consumeAmount);
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费扣减金额异常,无法结算");
+        }
+        log.info("服务费扣减金额计算完成,orderNo:{}, transportNetWeight:{}, strategyId:{}, strategyName:{}, strategyDesc:{}, freezeAmount:{}, consumeAmount:{}",
+                param.getOrderNo(), consumeInfo.transportNetWeight(), consumeInfo.strategyId(),
+                consumeInfo.strategyName(), consumeInfo.strategyDesc(), freezeAmount, consumeAmount);
+
+        saveSettleUnfreezeWalletBalanceLog(param, walletFee, freezeAmount, consumeInfo);
+
+        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, consumeInfo);
+        result.setChargeStrategyId(consumeInfo.strategyId());
+        result.setChargeStrategyDesc(consumeInfo.strategyDesc());
+        result.setFreezeAmount(consumeAmount);
+        result.setServiceFeeBalance(walletFee.getServiceFeeBalance());
+        log.info("贸易订单完结结算服务费完成,orderNo:{}, freezeAmount:{}, consumeAmount:{}, strategyDesc:{}, balanceBeforeSettle:{}, balanceAfterSettle:{}, tradingBeforeSettle:{}, tradingAfterSettle:{}",
+                param.getOrderNo(), freezeAmount, consumeAmount, consumeInfo.strategyDesc(),
+                balanceBeforeSettle, walletFee.getServiceFeeBalance(), tradingBeforeSettle, walletFee.getTradingAmount());
+        return result;
+    }
+
+    /**
+     * 订单完结服务费扣减计算结果
+     *
+     * @param consumeAmount     本次实际扣减服务费
+     * @param transportNetWeight 运输净重量
+     * @param strategyId        采用的收费策略id
+     * @param strategyName      采用的收费策略名称
+     * @param strategyDesc      收费策略单价描述,如10元/吨
+     */
+    private record SettleConsumeInfo(BigDecimal consumeAmount,
+                                     BigDecimal transportNetWeight,
+                                     Long strategyId,
+                                     String strategyName,
+                                     String strategyDesc) {
+    }
+
+
     /**
      * 查询采购方服务费余额
      */
@@ -500,6 +651,82 @@ 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>
+     * 扣减金额 = 运输净重量 × 采购方当前最优收费策略单价;
+     * 最优策略:企业已绑定且开启的策略中,单价最低的一条(对采购方最优惠)。
+     * 若无法匹配策略,则回退为下单冻结金额。
+     *
+     * @param param        结算参数(actualQuantity 为运输净重量)
+     * @param freezeAmount 下单冻结金额(兜底扣减值)
+     * @return 扣减计算结果
+     */
+    private SettleConsumeInfo resolveSettleConsumeInfo(ParkingWalletFeeFreezeParam param, BigDecimal freezeAmount) {
+        BigDecimal transportNetWeight = param.getActualQuantity() == null ? ZERO_AMOUNT : param.getActualQuantity();
+        KwtParkingChargeStrategy strategy = queryOptimalEnableStrategy(param.getProEntId());
+        if (strategy == null && param.getChargeStrategyId() != null) {
+            strategy = parkingChangeStrategyRepository.getById(param.getChargeStrategyId());
+            log.info("未匹配到当前最优策略,回退使用订单关联策略,orderNo:{}, chargeStrategyId:{}",
+                    param.getOrderNo(), param.getChargeStrategyId());
+        }
+        if (strategy == null) {
+            log.warn("收费策略无效,扣减金额按冻结金额处理,orderNo:{}, freezeAmount:{}", param.getOrderNo(), freezeAmount);
+            return new SettleConsumeInfo(freezeAmount, transportNetWeight, null, null, null);
+        }
+        BigDecimal unitFee = resolveStrategyUnitFee(strategy);
+        if (unitFee.compareTo(ZERO_AMOUNT) <= 0) {
+            log.warn("收费策略单价无效,扣减金额按冻结金额处理,orderNo:{}, freezeAmount:{}", param.getOrderNo(), freezeAmount);
+            return new SettleConsumeInfo(freezeAmount, transportNetWeight, null, null, null);
+        }
+        String strategyDesc = buildStrategyMethodDesc(strategy);
+        BigDecimal consumeAmount = transportNetWeight.multiply(unitFee).setScale(2, RoundingMode.HALF_UP);
+        log.info("按运输净重量×最优策略单价计算扣减,orderNo:{}, transportNetWeight:{}, strategyId:{}, strategyName:{}, unitFee:{}, consumeAmount:{}",
+                param.getOrderNo(), transportNetWeight, strategy.getId(), strategy.getStrategyName(),
+                unitFee, consumeAmount);
+        return new SettleConsumeInfo(consumeAmount, transportNetWeight, strategy.getId(),
+                strategy.getStrategyName(), strategyDesc);
+    }
+
+    /**
+     * 构建完结结算钱包明细备注
+     */
+    private String buildSettleWalletRemark(String actionDesc, ParkingWalletFeeFreezeParam param, SettleConsumeInfo consumeInfo) {
+        StringBuilder remark = new StringBuilder();
+        remark.append("订单").append(param.getOrderNo()).append(",").append(actionDesc);
+        if (consumeInfo.strategyName() != null) {
+            remark.append(",策略:").append(consumeInfo.strategyName());
+        }
+        if (consumeInfo.strategyDesc() != null) {
+            remark.append(",").append(consumeInfo.strategyDesc());
+        }
+        if (consumeInfo.transportNetWeight() != null) {
+            remark.append(",净重").append(consumeInfo.transportNetWeight().stripTrailingZeros().toPlainString());
+        }
+        return remark.toString();
+    }
+
     private boolean existsUnfreezeRecord(String orderNo, Long proEntId, Long supEntId) {
         return parkingWalletFeeBalanceRepository.count(
                 Wrappers.<KwtParkingWalletFeeBalance>lambdaQuery()
@@ -549,18 +776,81 @@ public class ParkingWalletFeeService {
                 param.getOrderNo(), param.getProEntId(), param.getSupEntId(), TRADE_TYPE_UNFREEZE,
                 unfreezeAmount, walletFee.getServiceFeeBalance());
     }
+    /**
+     * 写入订单完结解冻明细(trade_type=5)
+     * 仅释放履约中金额至可用余额,不代表最终扣减完成
+     */
+    private void saveSettleUnfreezeWalletBalanceLog(ParkingWalletFeeFreezeParam param,
+                                                    KwtParkingWalletFee walletFee,
+                                                    BigDecimal unfreezeAmount,
+                                                    SettleConsumeInfo consumeInfo) {
+        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(buildSettleWalletRemark("完结解冻服务费", param, consumeInfo));
+        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:{}, remark:{}, tradeAmount:{}, balanceAfter:{}, tradingAfter:{}",
+                param.getOrderNo(), balanceRecord.getRemark(), unfreezeAmount,
+                walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
+    }
+
+
+    /**
+     * 写入订单完结扣减明细(trade_type=6)
+     * 标志服务费正式消费完成,同时作为结算幂等依据
+     */
+    private void saveConsumeWalletBalanceLog(ParkingWalletFeeFreezeParam param,
+                                             KwtParkingWalletFee walletFee,
+                                             SettleConsumeInfo consumeInfo) {
+        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(consumeInfo.consumeAmount());
+        balanceRecord.setServiceFeeBalance(walletFee.getServiceFeeBalance());
+        balanceRecord.setTradingAmount(walletFee.getTradingAmount());
+        balanceRecord.setRemark(buildSettleWalletRemark("完结扣减服务费", param, consumeInfo));
+        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:{}, remark:{}, tradeAmount:{}, balanceAfter:{}, tradingAfter:{}",
+                param.getOrderNo(), balanceRecord.getRemark(), consumeInfo.consumeAmount(),
+                walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
+    }
 
     private String buildStrategyMethodDesc(KwtParkingChargeStrategy strategy) {
-        if (strategy == null || strategy.getMethod() == null) {
+        if (strategy == null) {
+            return null;
+        }
+        BigDecimal unitFee = resolveStrategyUnitFee(strategy);
+        if (unitFee.compareTo(ZERO_AMOUNT) <= 0) {
             return null;
         }
         if (Objects.equals(ParkingChangeStrategyEnum.BY_TON.getCode(), strategy.getType())) {
-            return strategy.getMethod().stripTrailingZeros().toPlainString() + "元/吨";
+            return unitFee.stripTrailingZeros().toPlainString() + "元/吨";
         }
         if (Objects.equals(ParkingChangeStrategyEnum.BY_TIME.getCode(), strategy.getType())) {
-            return strategy.getMethod().stripTrailingZeros().toPlainString() + "元/次";
+            return unitFee.stripTrailingZeros().toPlainString() + "元/次";
         }
-        return strategy.getMethod().stripTrailingZeros().toPlainString() + "元/月";
+        return unitFee.stripTrailingZeros().toPlainString() + "元/月";
     }
 
     /**
@@ -623,27 +913,100 @@ public class ParkingWalletFeeService {
     private BigDecimal calculateMaxPurchaseQuantity(KwtParkingChargeStrategy currentStrategy,
                                                     BigDecimal currentStrategyUnitFee,
                                                     BigDecimal serviceFeeBalance) {
-        if (currentStrategy == null || !Objects.equals(currentStrategy.getStatus(), STRATEGY_OPEN)
+        boolean strategySwitchOpen = isChargeStrategySwitchOpen();
+        if (!strategySwitchOpen || currentStrategy == null
                 || currentStrategyUnitFee.compareTo(ZERO_AMOUNT) <= 0) {
             log.info("最大可购买数量未计算,原因:{}",
-                    currentStrategy == null ? "未匹配到收费策略" : "收费策略未开启或单价<=0");
+                    !strategySwitchOpen ? "收费策略开关未开启"
+                            : currentStrategy == null ? "未匹配到收费策略" : "策略单价<=0");
             return null;
         }
         //向下取整
-        BigDecimal maxPurchaseQuantity = serviceFeeBalance.divide(currentStrategyUnitFee, 4, RoundingMode.DOWN);
+        BigDecimal maxPurchaseQuantity = serviceFeeBalance.divide(currentStrategyUnitFee, 0, RoundingMode.DOWN);
         log.info("最大可购买数量计算完成,serviceFeeBalance:{}, unitFee:{}, maxPurchaseQuantity:{}",
                 serviceFeeBalance, currentStrategyUnitFee, maxPurchaseQuantity);
         return maxPurchaseQuantity.max(ZERO_AMOUNT);
     }
 
+
+    /**
+     * 判断收费策略总开关是否开启(以 kwt_parking_strategy_switch 表为准)
+     */
+    private boolean isChargeStrategySwitchOpen() {
+        KwtParkingStrategySwitch strategySwitch = parkingStrategySwitchRepository.queryParkingStrategySwitch();
+        return strategySwitch != null && Objects.equals(strategySwitch.getStatus(), STRATEGY_OPEN);
+    }
+
     /**
-     * 查询企业当前生效的收费策略
-     * 逻辑:先查企业绑定的策略,再查策略详情,最后取“开启状态”且id最大(最新)的一条
+     * 查询收费策略开关配置的默认服务费
+     */
+    private BigDecimal resolveDefaultFee() {
+        KwtParkingStrategySwitch strategySwitch = parkingStrategySwitchRepository.queryParkingStrategySwitch();
+        if (strategySwitch == null || strategySwitch.getDefaultFee() == null) {
+            return ZERO_AMOUNT;
+        }
+        return strategySwitch.getDefaultFee();
+    }
+
+    /**
+     * 解析策略实际计费单价:method 为 0.00 时使用开关表 default_fee
+     */
+    private BigDecimal resolveStrategyUnitFee(KwtParkingChargeStrategy strategy) {
+        if (strategy == null) {
+            return ZERO_AMOUNT;
+        }
+        BigDecimal method = strategy.getMethod() == null ? ZERO_AMOUNT : strategy.getMethod();
+        if (method.compareTo(ZERO_AMOUNT) == 0) {
+            return resolveDefaultFee();
+        }
+        return method;
+    }
+
+    /**
+     * 查询企业当前绑定的收费策略(id 最大的一条,用于下单冻结/预估)。
+     * 收费策略是否启用统一由 kwt_parking_strategy_switch.status 控制,不再使用策略表 status 判断。
      *
      * @param proEntId 采购企业id
      * @return 生效策略;未匹配到则返回null
      */
     private KwtParkingChargeStrategy queryCurrentEnableStrategy(Long proEntId) {
+        KwtParkingChargeStrategy hitStrategy = listEnableStrategiesByProEntId(proEntId).stream()
+                .max(Comparator.comparing(KwtParkingChargeStrategy::getId))
+                .orElse(null);
+        log.info("匹配生效策略结果,proEntId:{}, hitStrategyId:{}",
+                proEntId, hitStrategy == null ? null : hitStrategy.getId());
+        return hitStrategy;
+    }
+
+    /**
+     * 查询采购方当前最优收费策略(实际单价最低的一条,用于订单完结扣费)。
+     * 收费策略是否启用统一由 kwt_parking_strategy_switch.status 控制,不再使用策略表 status 判断。
+     *
+     * @param proEntId 采购企业id
+     * @return 最优策略;未匹配到则返回null
+     */
+    private KwtParkingChargeStrategy queryOptimalEnableStrategy(Long proEntId) {
+        KwtParkingChargeStrategy optimalStrategy = listEnableStrategiesByProEntId(proEntId).stream()
+                .filter(item -> resolveStrategyUnitFee(item).compareTo(ZERO_AMOUNT) > 0)
+                .min(Comparator.comparing(this::resolveStrategyUnitFee)
+                        .thenComparing(Comparator.comparing(KwtParkingChargeStrategy::getId).reversed()))
+                .orElse(null);
+        log.info("匹配最优收费策略结果,proEntId:{}, optimalStrategyId:{}, unitFee:{}",
+                proEntId,
+                optimalStrategy == null ? null : optimalStrategy.getId(),
+                optimalStrategy == null ? null : resolveStrategyUnitFee(optimalStrategy));
+        return optimalStrategy;
+    }
+
+    /**
+     * 查询企业已绑定的收费策略列表。
+     * 收费策略总开关统一以 kwt_parking_strategy_switch.status 为准,不再使用 kwt_parking_charge_strategy.status 过滤。
+     */
+    private List<KwtParkingChargeStrategy> listEnableStrategiesByProEntId(Long proEntId) {
+        if (!isChargeStrategySwitchOpen()) {
+            log.info("收费策略总开关未开启,不查询企业收费策略绑定,proEntId:{}", proEntId);
+            return List.of();
+        }
         List<KwtParkingChargeStrategyUnit> strategyUnitList = parkingChangeStrategyUnitRepository.list(
                 Wrappers.<KwtParkingChargeStrategyUnit>lambdaQuery()
                         .eq(KwtParkingChargeStrategyUnit::getEntId, proEntId)
@@ -652,18 +1015,17 @@ public class ParkingWalletFeeService {
         log.info("查询企业策略绑定关系,proEntId:{}, bindCount:{}",
                 proEntId, strategyUnitList == null ? 0 : strategyUnitList.size());
 
-        if (org.apache.commons.collections4.CollectionUtils.isEmpty(strategyUnitList)) {
-            log.info("企业未绑定任何策略,proEntId:{}, strategyUnitList:[]", proEntId);
-            return null;
+        if (CollectionUtils.isEmpty(strategyUnitList)) {
+            log.info("企业未绑定任何策略,proEntId:{}", proEntId);
+            return List.of();
         }
         List<Long> strategyIds = strategyUnitList.stream()
                 .map(KwtParkingChargeStrategyUnit::getStrategyId)
                 .filter(Objects::nonNull)
                 .toList();
-        log.info("查询企业策略ID集合,proEntId:{}, strategyIds:{}", proEntId, strategyIds);
-        if (org.apache.commons.collections4.CollectionUtils.isEmpty(strategyIds)) {
-            log.info("企业绑定策略ID集合为空");
-            return null;
+        if (CollectionUtils.isEmpty(strategyIds)) {
+            log.info("企业绑定策略ID集合为空,proEntId:{}", proEntId);
+            return List.of();
         }
         List<KwtParkingChargeStrategy> strategyList = parkingChangeStrategyRepository.list(
                 Wrappers.<KwtParkingChargeStrategy>lambdaQuery()
@@ -671,18 +1033,10 @@ public class ParkingWalletFeeService {
                         .eq(KwtParkingChargeStrategy::getDelFlag, Global.NO)
                         .orderByDesc(KwtParkingChargeStrategy::getId)
         );
-        log.info("查询策略详情完成,proEntId:{}, strategyCount:{}",
-                proEntId, strategyList == null ? 0 : strategyList.size());
-        if (org.apache.commons.collections4.CollectionUtils.isEmpty(strategyList)) {
-            return null;
+        if (CollectionUtils.isEmpty(strategyList)) {
+            return List.of();
         }
-        KwtParkingChargeStrategy hitStrategy = strategyList.stream()
-                .filter(item -> Objects.equals(item.getStatus(), STRATEGY_OPEN))
-                .max(Comparator.comparing(KwtParkingChargeStrategy::getId))
-                .orElse(null);
-        log.info("匹配生效策略结果,proEntId:{}, hitStrategyId:{}",
-                proEntId, hitStrategy == null ? null : hitStrategy.getId());
-        return hitStrategy;
+        return strategyList;
     }
 
     /**