chenxiaofei 7 tuntia sitten
vanhempi
commit
10d9310e8e

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

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

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

@@ -11,7 +11,7 @@ import java.math.BigDecimal;
  * <p>
  * 下单冻结:orderNo、proEntId、supEntId、purchaseQuantity
  * 审核拒绝解冻:orderNo、proEntId、supEntId
- * 订单完结结算:orderNo、proEntId、supEntId、actualQuantity、chargeStrategyId
+ * 订单完结结算:orderNo、proEntId、supEntId、actualQuantity(运输净重量)、chargeStrategyId(兜底)
  */
 @Data
 public class ParkingWalletFeeFreezeParam implements Serializable {
@@ -40,12 +40,12 @@ public class ParkingWalletFeeFreezeParam implements Serializable {
     private BigDecimal purchaseQuantity;
 
     /**
-     * 实际履约数量(订单完结结算时使用,扣减金额 = actualQuantity × 策略单价)
+     * 运输净重量(订单完结结算时使用,扣减金额 = 运输净重量 × 当前最优策略单价)
      */
     private BigDecimal actualQuantity;
 
     /**
-     * 收费策略id(订单完结结算时使用,用于查询策略单价
+     * 收费策略id(订单完结结算兜底使用,优先取采购方当前最优策略
      */
     private Long chargeStrategyId;
 

+ 34 - 31
sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java

@@ -3479,52 +3479,39 @@ public class KwoTradeOrderService {
     /**
      * 贸易订单完结时,处理收费策略关联的服务费结算
      * <p>
-     * 触发条件:订单 applyChargeStrategy=1(下单时已冻结服务费)。
-     * 实际履约量:按 chargeType 汇总关联物流单装货量或卸货量,与预付钱包消费逻辑保持一致
-     * 通过 Dubbo 调用 transport 模块完成解冻 + 扣减 + 钱包明细落库
+     * 触发条件:所有运单完成且订单 applyChargeStrategy=1(下单时已冻结服务费)。
+     * 运输净重量:汇总已完成运单子任务的装/卸货净重(与看板运单完成量统计口径一致)
+     * 扣减逻辑:运输净重量 × 采购方当前最优收费策略单价,由 transport 模块计算并写入钱包明细备注
      *
-     * @param order             贸易订单(需含 chargeStrategyId、chargeType、tOrderNo 等)
-     * @param logisticOrderList 关联物流单列表,用于汇总实际履约量
+     * @param order             贸易订单
+     * @param logisticOrderList 关联物流单列表(用于日志,净重量以运单统计为准)
      */
     private void settleChargeStrategyIfApplied(KwoTradeOrder order, List<KwtLogisticsOrderVO> logisticOrderList) {
         if (!Objects.equals(order.getApplyChargeStrategy(), Global.YES)) {
-            log.info("贸易订单未应用收费策略,跳过服务费结算,orderId:{}, orderNo:{}, applyChargeStrategy:{}",
+            log.info("贸易订单未应用收费策略,跳过服务费结算,订单ID:{}, 订单号:{}, 是否应用收费策略:{}",
                     order.getId(), order.getTOrderNo(), order.getApplyChargeStrategy());
             return;
         }
-        log.info("贸易订单已应用收费策略,开始准备服务费结算,orderId:{}, orderNo:{}, chargeStrategyId:{}, chargeType:{}",
+        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("贸易订单企业信息不存在,无法结算服务费,orderId:{}, orderNo:{}", order.getId(), order.getTOrderNo());
+            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("贸易订单企业信息缺失,无法结算服务费,orderId:{}, orderNo:{}, unitSize:{}",
+            log.error("贸易订单企业信息缺失,无法结算服务费,订单ID:{}, 订单号:{}, 企业数量:{}",
                     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);
-        }
+        // 运输净重量:汇总已完成运单的装/卸货净重(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();
@@ -3532,15 +3519,31 @@ public class KwoTradeOrderService {
         settleParam.setOrderNo(order.getTOrderNo());
         settleParam.setProEntId(proEntId);
         settleParam.setSupEntId(supEntId);
-        settleParam.setActualQuantity(actualQuantity);
+        settleParam.setActualQuantity(transportNetWeight);
         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());
+        log.info("调用服务费完结结算,订单号:{}, 采购方企业ID:{}, 供应商企业ID:{}, 运输净重量:{}, 兜底收费策略ID:{}, 操作人ID:{}",
+                order.getTOrderNo(), proEntId, supEntId, transportNetWeight, order.getChargeStrategyId(), settleParam.getOperatorId());
 
         ParkingWalletFeeFreezeResult settleResult = parkingWalletFeeRemoteService.settleChargeStrategy(settleParam);
-        log.info("服务费完结结算完成,orderNo:{}, consumeAmount:{}, serviceFeeBalanceAfter:{}",
-                order.getTOrderNo(), settleResult.getFreezeAmount(), settleResult.getServiceFeeBalance());
+        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) {

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

@@ -34,7 +34,7 @@ public class ParkingWalletFeeRemoteServiceImpl implements ParkingWalletFeeRemote
 
     @Override
     public ParkingWalletFeeFreezeResult settleChargeStrategy(ParkingWalletFeeFreezeParam param) {
-        log.info("Dubbo调用服务费完结结算,orderNo:{}, proEntId:{}, supEntId:{}",
+        log.info("远程调用服务费完结结算,订单号:{}, 采购方企业ID:{}, 供应商企业ID:{}",
                 param == null ? null : param.getOrderNo(),
                 param == null ? null : param.getProEntId(),
                 param == null ? null : param.getSupEntId());

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

@@ -167,31 +167,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)
@@ -200,6 +220,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))
@@ -210,9 +233,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);
@@ -224,12 +251,17 @@ 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。

+ 127 - 59
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/ParkingWalletFeeService.java

@@ -249,15 +249,15 @@ public class ParkingWalletFeeService {
      * <ol>
      *   <li>解冻:将下单时冻结的履约中金额(trading_amount)释放回可用余额(service_fee_balance),
      *       并写入 trade_type=5 的解冻明细(备注:订单完结解冻服务费)</li>
-     *   <li>扣减:按「实际履约量 × 收费策略单价」从可用余额中正式扣减服务费,
-     *       并写入 trade_type=6 的消费明细(备注:订单完结扣减服务费)</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
+     * @param param 结算参数,需包含 orderNo、proEntId、actualQuantity(运输净重量)
      * @return 结算结果,含实际扣减金额(freezeAmount字段)及扣减后服务费余额
      */
     @Transactional(rollbackFor = Exception.class)
@@ -318,16 +318,19 @@ public class ParkingWalletFeeService {
         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);
+        // 第二步:运输净重量 × 采购方当前最优收费策略单价 = 本次实际扣减服务费
+        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:{}, actualQuantity:{}, chargeStrategyId:{}, freezeAmount:{}, consumeAmount:{}",
-                param.getOrderNo(), param.getActualQuantity(), param.getChargeStrategyId(), freezeAmount, consumeAmount);
+        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) {
@@ -338,15 +341,33 @@ public class ParkingWalletFeeService {
         walletFee.setServiceFeeBalance(balanceAfterUnfreeze.subtract(consumeAmount));
         parkingWalletFeeRepository.updateById(walletFee);
 
-        saveConsumeWalletBalanceLog(param, walletFee, consumeAmount);
+        saveConsumeWalletBalanceLog(param, walletFee, consumeInfo);
+        result.setChargeStrategyId(consumeInfo.strategyId());
+        result.setChargeStrategyDesc(consumeInfo.strategyDesc());
         result.setFreezeAmount(consumeAmount);
         result.setServiceFeeBalance(walletFee.getServiceFeeBalance());
-        log.info("贸易订单完结结算服务费完成,orderNo:{}, freezeAmount:{}, consumeAmount:{}, balanceBeforeSettle:{}, balanceAfterSettle:{}, tradingBeforeSettle:{}, tradingAfterSettle:{}",
-                param.getOrderNo(), freezeAmount, consumeAmount,
+        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) {
+    }
+
     /**
      * 查询采购方服务费余额
      */
@@ -385,31 +406,54 @@ public class ParkingWalletFeeService {
     }
 
     /**
-     * 计算订单完结时应扣减的服务费金额
+     * 解析订单完结服务费扣减信息
      * <p>
-     * 优先按「实际履约量 × 收费策略单价」计算;若实际量或策略id缺失,则回退为下单冻结金额。
+     * 扣减金额 = 运输净重量 × 采购方当前最优收费策略单价;
+     * 最优策略:企业已绑定且开启的策略中,单价最低的一条(对采购方最优惠)。
+     * 若无法匹配策略,则回退为下单冻结金额。
      *
-     * @param param        结算参数
-     * @param freezeAmount 下单时冻结金额(兜底扣减值)
-     * @return 应扣减服务费,保留2位小数
+     * @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 || strategy.getMethod() == null
+                || strategy.getMethod().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(strategy.getMethod()).setScale(2, RoundingMode.HALF_UP);
+        log.info("按运输净重量×最优策略单价计算扣减,orderNo:{}, transportNetWeight:{}, strategyId:{}, strategyName:{}, unitFee:{}, consumeAmount:{}",
+                param.getOrderNo(), transportNetWeight, strategy.getId(), strategy.getStrategyName(),
+                strategy.getMethod(), consumeAmount);
+        return new SettleConsumeInfo(consumeAmount, transportNetWeight, strategy.getId(),
+                strategy.getStrategyName(), strategyDesc);
+    }
+
+    /**
+     * 构建完结结算钱包明细备注
      */
-    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;
+    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());
         }
-        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;
+        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) {
@@ -468,7 +512,8 @@ public class ParkingWalletFeeService {
      */
     private void saveSettleUnfreezeWalletBalanceLog(ParkingWalletFeeFreezeParam param,
                                                     KwtParkingWalletFee walletFee,
-                                                    BigDecimal unfreezeAmount) {
+                                                    BigDecimal unfreezeAmount,
+                                                    SettleConsumeInfo consumeInfo) {
         Long operatorId = resolveOperatorId(param.getOperatorId());
         Date now = new Date();
         KwtParkingWalletFeeBalance balanceRecord = new KwtParkingWalletFeeBalance();
@@ -479,16 +524,16 @@ public class ParkingWalletFeeService {
         balanceRecord.setTradeAmount(unfreezeAmount);
         balanceRecord.setServiceFeeBalance(walletFee.getServiceFeeBalance());
         balanceRecord.setTradingAmount(walletFee.getTradingAmount());
-        balanceRecord.setRemark("贸易订单号:" + param.getOrderNo() + ",订单完结解冻服务费");
+        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:{}, proEntId:{}, supEntId:{}, tradeType:{}, tradeAmount:{}, balanceAfter:{}, tradingAfter:{}",
-                param.getOrderNo(), param.getProEntId(), param.getSupEntId(), TRADE_TYPE_UNFREEZE,
-                unfreezeAmount, walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
+        log.info("服务费完结解冻明细已写入kwt_parking_wallet_fee_balance,orderNo:{}, remark:{}, tradeAmount:{}, balanceAfter:{}, tradingAfter:{}",
+                param.getOrderNo(), balanceRecord.getRemark(), unfreezeAmount,
+                walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
     }
 
     /**
@@ -497,7 +542,7 @@ public class ParkingWalletFeeService {
      */
     private void saveConsumeWalletBalanceLog(ParkingWalletFeeFreezeParam param,
                                              KwtParkingWalletFee walletFee,
-                                             BigDecimal consumeAmount) {
+                                             SettleConsumeInfo consumeInfo) {
         Long operatorId = resolveOperatorId(param.getOperatorId());
         Date now = new Date();
         KwtParkingWalletFeeBalance balanceRecord = new KwtParkingWalletFeeBalance();
@@ -505,19 +550,19 @@ public class ParkingWalletFeeService {
         balanceRecord.setProEntId(param.getProEntId());
         balanceRecord.setSupEntId(param.getSupEntId());
         balanceRecord.setTradeType(TRADE_TYPE_CONSUME);
-        balanceRecord.setTradeAmount(consumeAmount);
+        balanceRecord.setTradeAmount(consumeInfo.consumeAmount());
         balanceRecord.setServiceFeeBalance(walletFee.getServiceFeeBalance());
         balanceRecord.setTradingAmount(walletFee.getTradingAmount());
-        balanceRecord.setRemark("贸易订单号:" + param.getOrderNo() + ",订单完结扣减服务费");
+        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:{}, proEntId:{}, supEntId:{}, tradeType:{}, tradeAmount:{}, balanceAfter:{}, tradingAfter:{}",
-                param.getOrderNo(), param.getProEntId(), param.getSupEntId(), TRADE_TYPE_CONSUME,
-                consumeAmount, walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
+        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) {
@@ -607,13 +652,43 @@ public class ParkingWalletFeeService {
     }
 
     /**
-     * 查询企业当前生效的收费策略
-     * 逻辑:先查企业绑定的策略,再查策略详情,最后取“开启状态”且id最大(最新)的一条
+     * 查询企业当前生效的收费策略(开启状态中 id 最大的一条,用于下单冻结/预估)
      *
      * @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;
+    }
+
+    /**
+     * 查询采购方当前最优收费策略(开启状态中单价最低的一条,用于订单完结扣费)
+     *
+     * @param proEntId 采购企业id
+     * @return 最优策略;未匹配到则返回null
+     */
+    private KwtParkingChargeStrategy queryOptimalEnableStrategy(Long proEntId) {
+        KwtParkingChargeStrategy optimalStrategy = listEnableStrategiesByProEntId(proEntId).stream()
+                .filter(item -> item.getMethod() != null && item.getMethod().compareTo(ZERO_AMOUNT) > 0)
+                .min(Comparator.comparing(KwtParkingChargeStrategy::getMethod)
+                        .thenComparing(Comparator.comparing(KwtParkingChargeStrategy::getId).reversed()))
+                .orElse(null);
+        log.info("匹配最优收费策略结果,proEntId:{}, optimalStrategyId:{}, unitFee:{}",
+                proEntId,
+                optimalStrategy == null ? null : optimalStrategy.getId(),
+                optimalStrategy == null ? null : optimalStrategy.getMethod());
+        return optimalStrategy;
+    }
+
+    /**
+     * 查询企业已绑定且处于开启状态的收费策略列表
+     */
+    private List<KwtParkingChargeStrategy> listEnableStrategiesByProEntId(Long proEntId) {
         List<KwtParkingChargeStrategyUnit> strategyUnitList = parkingChangeStrategyUnitRepository.list(
                 Wrappers.<KwtParkingChargeStrategyUnit>lambdaQuery()
                         .eq(KwtParkingChargeStrategyUnit::getEntId, proEntId)
@@ -623,17 +698,16 @@ public class ParkingWalletFeeService {
                 proEntId, strategyUnitList == null ? 0 : strategyUnitList.size());
 
         if (CollectionUtils.isEmpty(strategyUnitList)) {
-            log.info("企业未绑定任何策略,proEntId:{}, strategyUnitList:[]", proEntId);
-            return null;
+            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 (CollectionUtils.isEmpty(strategyIds)) {
-            log.info("企业绑定策略ID集合为空");
-            return null;
+            log.info("企业绑定策略ID集合为空,proEntId:{}", proEntId);
+            return List.of();
         }
         List<KwtParkingChargeStrategy> strategyList = parkingChangeStrategyRepository.list(
                 Wrappers.<KwtParkingChargeStrategy>lambdaQuery()
@@ -641,18 +715,12 @@ public class ParkingWalletFeeService {
                         .eq(KwtParkingChargeStrategy::getDelFlag, Global.NO)
                         .orderByDesc(KwtParkingChargeStrategy::getId)
         );
-        log.info("查询策略详情完成,proEntId:{}, strategyCount:{}",
-                proEntId, strategyList == null ? 0 : strategyList.size());
         if (CollectionUtils.isEmpty(strategyList)) {
-            return null;
+            return List.of();
         }
-        KwtParkingChargeStrategy hitStrategy = strategyList.stream()
+        return 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;
+                .toList();
     }
 
     /**