Sfoglia il codice sorgente

提交收费系统

chenxiaofei 9 ore fa
parent
commit
3f488b5ad0

+ 7 - 2
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(兜底)、chargeStrategyAmount(下单快照单价,优先)
  */
 @Data
 public class ParkingWalletFeeFreezeParam implements Serializable {
@@ -45,10 +45,15 @@ public class ParkingWalletFeeFreezeParam implements Serializable {
     private BigDecimal actualQuantity;
 
     /**
-     * 收费策略id(订单完结结算兜底使用,优先取采购方当前最优策略
+     * 收费策略id(订单完结结算兜底使用,优先取订单下单快照
      */
     private Long chargeStrategyId;
 
+    /**
+     * 下单时收费策略单价快照(订单完结结算优先使用,防止策略变更后金额变化)
+     */
+    private BigDecimal chargeStrategyAmount;
+
     /**
      * 操作人id(Dubbo调用时由订单模块传入)
      */

+ 5 - 0
sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/vo/ParkingWalletFeeFreezeResult.java

@@ -30,6 +30,11 @@ public class ParkingWalletFeeFreezeResult implements Serializable {
      */
     private String chargeStrategyDesc;
 
+    /**
+     * 下单时收费策略单价快照
+     */
+    private BigDecimal chargeStrategyAmount;
+
     /**
      * 冻结服务费金额
      */

+ 6 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/KwoTradeOrder.java

@@ -178,5 +178,11 @@ public class KwoTradeOrder extends BaseModel implements Serializable {
     @TableField("charge_strategy_desc")
     private String chargeStrategyDesc;
 
+    /**
+     * 下单时收费策略单价快照,防止策略表变更后单价变化
+     */
+    @TableField("charge_strategy_amount")
+    private BigDecimal chargeStrategyAmount;
+
 
 }

+ 6 - 3
sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java

@@ -2824,6 +2824,7 @@ public class KwoTradeOrderService {
         order.setApplyChargeStrategy(feeFreezeResult.getApplyChargeStrategy() == null ? Global.NO : feeFreezeResult.getApplyChargeStrategy());
         order.setChargeStrategyId(feeFreezeResult.getChargeStrategyId());
         order.setChargeStrategyDesc(feeFreezeResult.getChargeStrategyDesc());
+        order.setChargeStrategyAmount(feeFreezeResult.getChargeStrategyAmount());
     }
 
     /**
@@ -3481,7 +3482,7 @@ public class KwoTradeOrderService {
      * <p>
      * 触发条件:所有运单完成且订单 applyChargeStrategy=1(下单时已冻结服务费)。
      * 运输净重量:汇总已完成运单子任务的装/卸货净重(与看板运单完成量统计口径一致)。
-     * 扣减逻辑:运输净重量 × 采购方当前最优收费策略单价,由 transport 模块计算并写入钱包明细备注。
+     * 扣减逻辑:运输净重量 × 订单下单时快照的收费策略单价(无快照时回退当前策略)
      *
      * @param order             贸易订单
      * @param logisticOrderList 关联物流单列表(用于日志,净重量以运单统计为准)
@@ -3521,9 +3522,11 @@ public class KwoTradeOrderService {
         settleParam.setSupEntId(supEntId);
         settleParam.setActualQuantity(transportNetWeight);
         settleParam.setChargeStrategyId(order.getChargeStrategyId());
+        settleParam.setChargeStrategyAmount(order.getChargeStrategyAmount());
         settleParam.setOperatorId(LoginUserHolder.getUserId());
-        log.info("调用服务费完结结算,订单号:{}, 采购方企业ID:{}, 供应商企业ID:{}, 运输净重量:{}, 兜底收费策略ID:{}, 操作人ID:{}",
-                order.getTOrderNo(), proEntId, supEntId, transportNetWeight, order.getChargeStrategyId(), settleParam.getOperatorId());
+        log.info("调用服务费完结结算,订单号:{}, 采购方企业ID:{}, 供应商企业ID:{}, 运输净重量:{}, 收费策略ID:{}, 快照单价:{}, 操作人ID:{}",
+                order.getTOrderNo(), proEntId, supEntId, transportNetWeight, order.getChargeStrategyId(),
+                order.getChargeStrategyAmount(), settleParam.getOperatorId());
 
         ParkingWalletFeeFreezeResult settleResult = parkingWalletFeeRemoteService.settleChargeStrategy(settleParam);
         log.info("服务费完结结算完成,订单号:{}, 扣减金额:{}, 收费策略ID:{}, 收费策略描述:{}, 扣减后服务费余额:{}",

+ 2 - 2
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/ParkingWalletFeeController.java

@@ -62,12 +62,12 @@ public class ParkingWalletFeeController {
     }
 
     /**
-     * 保存服务费
+     * 人工录入服务费
      *
      * @param param
      * @return
      */
-    @Operation(summary = "保存服务费", description = "保存服务费")
+    @Operation(summary = "人工录入服务费", description = "人工录入服务费")
     @PostMapping("/saveServerFee")
     public BaseResult saveServerFee(@RequestBody @Valid ParkingWalletFeeSaveParam param){
         parkingWalletFeeService.saveServerFee(param);

+ 8 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtParkingWalletFeeBalance.java

@@ -9,7 +9,9 @@ import java.math.BigDecimal;
 import java.util.Date;
 
 /**
- * 预存服务费明细(钱包日志)
+ * @author lfdc
+ * @description 服务费明细
+ * @date 2023-06-26 16:06:12
  */
 @Data
 @TableName("kwt_parking_wallet_fee_balance")
@@ -41,6 +43,11 @@ public class KwtParkingWalletFeeBalance implements Serializable {
      */
     private Integer tradeType;
 
+    /**
+     * 资金类型(1-预付、 2-扣款)
+     */
+    private Integer paymentType;
+
     /**
      * 交易金额
      */

+ 15 - 1
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/ParkingWalletFeeSaveParam.java

@@ -29,12 +29,19 @@ public class ParkingWalletFeeSaveParam extends PageReq implements Serializable {
     @Schema(description = "主键id")
     private Long id;
 
+    /**
+     * 平台企业id
+     */
+    @NotNull(message = "平台企业id不能为空")
+    @Schema(description = "平台企业id")
+    private Long supEntId;
+
     /**
      * 采购商企业id
      */
     @NotNull(message = "采购商企业id不能为空")
     @Schema(description = "采购商企业id")
-    private Long entId;
+    private Long proEntId;
 
     /**
      * 服务费余额
@@ -58,4 +65,11 @@ public class ParkingWalletFeeSaveParam extends PageReq implements Serializable {
     private String remark;
 
 
+    /**
+     * 资金类型(1-预付、 2-扣款)
+     */
+    @NotNull(message = "资金类型为空")
+    @Schema(description = "资金类型")
+    private Integer paymentType;
+
 }

+ 7 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/ParkingWalletFeeBalanceResp.java

@@ -1,5 +1,6 @@
 package com.sckw.transport.model.param;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serial;
@@ -59,6 +60,11 @@ public class ParkingWalletFeeBalanceResp implements Serializable {
      */
     private String tradeTypeDesc;
 
+    /**
+     * 资金类型(1-预付、 2-扣款)
+     */
+    private Integer paymentType;
+
     /**
      * 交易金额
      */
@@ -87,5 +93,6 @@ public class ParkingWalletFeeBalanceResp implements Serializable {
     /**
      * 创建时间
      */
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date createTime;
 }

+ 4 - 2
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/ParkingWalletFeeResp.java

@@ -1,5 +1,6 @@
 package com.sckw.transport.model.param;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import lombok.Data;
 
 import java.io.Serial;
@@ -32,7 +33,7 @@ public class ParkingWalletFeeResp implements Serializable {
     /**
      * 采购商企业名称
      */
-    private Long proEntName;
+    private String proEntName;
 
     /**
      * 平台企业id
@@ -42,7 +43,7 @@ public class ParkingWalletFeeResp implements Serializable {
     /**
      * 平台企业名称
      */
-    private Long supEntName;
+    private String supEntName;
 
     /**
      * 服务费余额
@@ -57,6 +58,7 @@ public class ParkingWalletFeeResp implements Serializable {
     /**
      * 更新时间
      */
+    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
     private Date updateTime;
 
 }

+ 330 - 264
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/ParkingWalletFeeService.java

@@ -47,6 +47,15 @@ import java.util.stream.Collectors;
 @RequiredArgsConstructor
 public class ParkingWalletFeeService {
 
+    private final KwtParkingWalletFeeRepository parkingWalletFeeRepository;
+    private final KwtParkingWalletFeeBalanceRepository parkingWalletFeeBalanceRepository;
+
+    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;
@@ -57,14 +66,279 @@ public class ParkingWalletFeeService {
     /** 钱包明细交易类型:消费(订单完结按实际履约量正式扣减服务费) */
     private static final int TRADE_TYPE_CONSUME = 6;
 
-    private final KwtParkingWalletFeeRepository parkingWalletFeeRepository;
-    private final KwtParkingWalletFeeBalanceRepository parkingWalletFeeBalanceRepository;
-    private final KwtParkingChangeStrategyRepository parkingChangeStrategyRepository;
-    private final KwtParkingChangeStrategyUnitRepository parkingChangeStrategyUnitRepository;
-    private final KwtParkingStrategySwitchRepository parkingStrategySwitchRepository;
+    /**
+     * 查询汇总服务费
+     * @param param
+     */
+    public ParkingWalletFeeResp queryTotalServerFee(ParkingWalletFeeTotalQueryParam param) {
+        log.info("查询汇总服务费:{}", JSON.toJSONString(param));
+        if (param.getSupEntId() != null) {
+            param.setSupEntId(551429255406292993L);
+        }
 
-    @DubboReference(version = "1.0.0", group = "design", check = false, timeout = 6000)
-    RemoteSystemService remoteSystemService;
+        //构建查询条件
+        LambdaQueryWrapper<KwtParkingWalletFee> wrapper = buildAndExecuteQuery(param);
+
+        // 初始化
+        ParkingWalletFeeResp resp = new ParkingWalletFeeResp();
+        resp.setServiceFeeBalance(BigDecimal.ZERO);
+        resp.setTradingAmount(BigDecimal.ZERO);
+
+        List<KwtParkingWalletFee> records = parkingWalletFeeRepository.list(wrapper);
+        if (CollectionUtils.isEmpty(records)) {
+            log.info("汇总服务费信息为空");
+            return resp;
+        }
+        // 查询采购商汇总服务费
+        if (param.getProEntId() != null) {
+            return getProWalletFee(records.get(0));
+        }
+        // 查询平台汇总服务费
+        if (param.getSupEntId() != null) {
+            return getSupWalletFee(records);
+        }
+        return resp;
+    }
+
+    /**
+     * 构建查询条件并执行分页查询
+     */
+    private LambdaQueryWrapper<KwtParkingWalletFee> buildAndExecuteQuery(ParkingWalletFeeTotalQueryParam param) {
+        return  Wrappers.<KwtParkingWalletFee>lambdaQuery()
+                .eq(param.getProEntId() != null, KwtParkingWalletFee::getProEntId, param.getProEntId())
+                .eq(param.getSupEntId() != null, KwtParkingWalletFee::getSupEntId, param.getSupEntId())
+                .orderByDesc(KwtParkingWalletFee::getUpdateTime);
+    }
+
+    /**
+     * 获取采购商汇总服务费
+     * @param fee
+     */
+    private ParkingWalletFeeResp getProWalletFee(KwtParkingWalletFee fee) {
+        log.info("开始汇总采购商服务费,param:{}", JSON.toJSONString(fee));
+
+        ParkingWalletFeeResp resp = new ParkingWalletFeeResp();
+        resp.setServiceFeeBalance(fee.getServiceFeeBalance() != null ? fee.getServiceFeeBalance() : BigDecimal.ZERO);
+        resp.setTradingAmount(fee.getTradingAmount() != null ? fee.getTradingAmount() : BigDecimal.ZERO);
+        resp.setProEntName(getEntName(fee.getProEntId()));
+        log.info("汇总采购商服务费结束,总余额:{}, 总履约中金额:{}", fee.getServiceFeeBalance(), fee.getTradingAmount());
+        return resp;
+
+    }
+
+    /**
+     * 获取平台汇总服务费
+     * @param feeList
+     */
+    private ParkingWalletFeeResp getSupWalletFee(List<KwtParkingWalletFee> feeList) {
+        log.info("开始汇总平台服务费, size:{}", CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());
+        // 汇总余额
+        BigDecimal totalServiceFee  = feeList.stream()
+                .map(fee -> fee.getServiceFeeBalance() != null ? fee.getServiceFeeBalance() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        // 汇总冻结金额
+        BigDecimal totalTradingAmount   = feeList.stream()
+                .map(fee -> fee.getTradingAmount() != null ? fee.getTradingAmount() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        ParkingWalletFeeResp resp = new ParkingWalletFeeResp();
+        resp.setServiceFeeBalance(totalServiceFee);
+        resp.setTradingAmount(totalTradingAmount);
+        resp.setSupEntName(getEntName(feeList.get(0).getSupEntId()));
+        log.info("汇总平台服务费结束,总余额:{}, 总履约中金额:{}", totalServiceFee, totalTradingAmount);
+        return resp;
+    }
+
+
+    /**
+     * 分页查询服务费
+     * @param param
+     */
+    public PageDataResult<ParkingWalletFeeResp> pageQueryServerFee(ParkingWalletFeeQueryParam param) {
+        log.info("分页查询服务费:{}", JSON.toJSONString(param));
+        if (param.getSupEntId() != null) {
+            param.setSupEntId(551429255406292993L);
+        }
+
+        //构建查询条件
+        LambdaQueryWrapper<KwtParkingWalletFee> wrapper = buildAndExecuteQuery(param);
+
+        Page<KwtParkingWalletFee> pageByStatus = parkingWalletFeeRepository.page(new Page<>(param.getPageNum(), param.getPageSize()), wrapper);
+        List<KwtParkingWalletFee> records = pageByStatus.getRecords();
+        if (CollectionUtils.isEmpty(records)) {
+            log.info("服务费信息为空");
+            return PageDataResult.empty(param.getPageNum(), param.getPageSize());
+        }
+        List<ParkingWalletFeeResp> changeStrategyRespList = getWalletFeePageResult(records);
+        return PageDataResult.success(param.getPageNum(), param.getPageSize(), pageByStatus.getTotal(), changeStrategyRespList);
+    }
+
+
+    /**
+     * 构建查询条件并执行分页查询
+     */
+    private LambdaQueryWrapper<KwtParkingWalletFee> buildAndExecuteQuery(ParkingWalletFeeQueryParam param) {
+        return  Wrappers.<KwtParkingWalletFee>lambdaQuery()
+                .eq(param.getSupEntId() != null, KwtParkingWalletFee::getSupEntId, param.getSupEntId())
+                .eq(param.getProEntId() != null, KwtParkingWalletFee::getProEntId, param.getProEntId())
+                .orderByDesc(KwtParkingWalletFee::getUpdateTime);
+    }
+
+    /**
+     * 获取收费策略
+     * @param records
+     */
+    private List<ParkingWalletFeeResp> getWalletFeePageResult(List<KwtParkingWalletFee> records) {
+        log.info("组装服务费,param:{}", JSON.toJSONString(records));
+        if (CollectionUtils.isEmpty(records)) {
+            return Collections.emptyList();
+        }
+        List<ParkingWalletFeeResp> walletFeeList = records.stream()
+                .map(fee -> {
+                    ParkingWalletFeeResp resp = new ParkingWalletFeeResp();
+                    resp.setId(fee.getId());
+                    resp.setProEntId(fee.getProEntId());
+                    resp.setProEntName(getEntName(fee.getProEntId()));
+                    resp.setSupEntId(fee.getSupEntId());
+                    resp.setSupEntName(getEntName(fee.getSupEntId()));
+                    resp.setServiceFeeBalance(fee.getServiceFeeBalance());
+                    resp.setTradingAmount(fee.getTradingAmount());
+                    resp.setUpdateTime(fee.getUpdateTime());
+                    return resp;
+                }).collect(Collectors.toList());
+
+        log.info("组装服务费结束,size:{}", JSON.toJSONString(walletFeeList.size()));
+        return walletFeeList;
+    }
+
+
+    /**
+     * 保存服务费
+     * @param param
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void saveServerFee(ParkingWalletFeeSaveParam param) {
+        log.info("保存服务费,param:{}", JSON.toJSONString(param));
+        if (param.getSupEntId() != null) {
+            param.setSupEntId(551429255406292993L);
+        }
+
+        KwtParkingWalletFee walletFee = parkingWalletFeeRepository.getById(param.getId());
+        if (walletFee == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费信息不存在");
+        }
+
+        // 预付
+        if (Objects.equals(param.getPaymentType(), 1)) {
+            walletFee.setServiceFeeBalance(walletFee.getServiceFeeBalance().add(param.getServiceFee()));
+        }else {
+            if (walletFee.getServiceFeeBalance().compareTo(param.getServiceFee()) < 0) {
+                throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "扣款金额不能大于服务余额");
+            }
+            walletFee.setServiceFeeBalance(walletFee.getServiceFeeBalance().subtract(param.getServiceFee()));
+        }
+
+        // 1. 先修改服务余额
+        parkingWalletFeeRepository.updateById(walletFee);
+
+        // 2.新增明细
+        KwtParkingWalletFeeBalance feeBalance = new KwtParkingWalletFeeBalance();
+        feeBalance.setSupEntId(param.getSupEntId());
+        feeBalance.setProEntId(param.getProEntId());
+        feeBalance.setTradeType(9);
+        feeBalance.setPaymentType(param.getPaymentType());
+        feeBalance.setTradeAmount(param.getServiceFee());
+        feeBalance.setServiceFeeBalance(walletFee.getServiceFeeBalance());
+        feeBalance.setTradingAmount(walletFee.getTradingAmount());
+        feeBalance.setVoucherUrl(param.getVoucherUrl());
+        feeBalance.setRemark(param.getRemark());
+        parkingWalletFeeBalanceRepository.save(feeBalance);
+    }
+
+    /**
+     * 分页查询服务费明细
+     * @param param
+     */
+    public PageDataResult<ParkingWalletFeeBalanceResp> pageQueryServerFeeDetails(ParkingWalletFeeBalanceQueryParam param) {
+        log.info("分页查询服务费明细:{}", JSON.toJSONString(param));
+        if (param.getSupEntId() != null) {
+            param.setSupEntId(551429255406292993L);
+        }
+        //构建查询条件
+        LambdaQueryWrapper<KwtParkingWalletFeeBalance> wrapper = buildAndExecuteQuery(param);
+
+        Page<KwtParkingWalletFeeBalance> pageByStatus = parkingWalletFeeBalanceRepository.page(new Page<>(param.getPageNum(), param.getPageSize()), wrapper);
+        List<KwtParkingWalletFeeBalance> records = pageByStatus.getRecords();
+        if (CollectionUtils.isEmpty(records)) {
+            log.info("分页查询服务费明细为空");
+            return PageDataResult.empty(param.getPageNum(), param.getPageSize());
+        }
+        List<ParkingWalletFeeBalanceResp> changeStrategyRespList = getWalletFeeBalancePageResult(records);
+        return PageDataResult.success(param.getPageNum(), param.getPageSize(), pageByStatus.getTotal(), changeStrategyRespList);
+    }
+
+
+    /**
+     * 构建查询条件并执行分页查询
+     */
+    private LambdaQueryWrapper<KwtParkingWalletFeeBalance> buildAndExecuteQuery(ParkingWalletFeeBalanceQueryParam param) {
+        return  Wrappers.<KwtParkingWalletFeeBalance>lambdaQuery()
+                .eq(param.getSupEntId() != null, KwtParkingWalletFeeBalance::getSupEntId, param.getSupEntId())
+                .eq(param.getProEntId() != null, KwtParkingWalletFeeBalance::getProEntId, param.getProEntId())
+                .eq(KwtParkingWalletFeeBalance::getOrderNo, param.getOrderNo())
+                .eq(KwtParkingWalletFeeBalance::getTradeType, param.getTradeType())
+                .ge(KwtParkingWalletFeeBalance::getTradeAmount, param.getMinFee())
+                .le(KwtParkingWalletFeeBalance::getTradeAmount, param.getMaxFee())
+                .ge(KwtParkingWalletFeeBalance::getCreateTime, param.getStartCreateTime())
+                .le(KwtParkingWalletFeeBalance::getCreateTime, param.getEndCreateTime())
+                .orderByDesc(KwtParkingWalletFeeBalance::getServiceFeeBalance);
+    }
+
+    /**
+     * 获取服务费明细
+     * @param records
+     */
+    private List<ParkingWalletFeeBalanceResp> getWalletFeeBalancePageResult(List<KwtParkingWalletFeeBalance> records) {
+        log.info("组装服务费明细,param:{}", JSON.toJSONString(records));
+        if (CollectionUtils.isEmpty(records)) {
+            return Collections.emptyList();
+        }
+        List<ParkingWalletFeeBalanceResp> walletFeeList = records.stream()
+                .map(feeBalance -> {
+                    ParkingWalletFeeBalanceResp resp = new ParkingWalletFeeBalanceResp();
+                    resp.setId(feeBalance.getId());
+                    resp.setOrderNo(feeBalance.getOrderNo());
+                    resp.setProEntId(feeBalance.getProEntId());
+                    resp.setProEntName(getEntName(feeBalance.getProEntId()));
+                    resp.setSupEntId(feeBalance.getSupEntId());
+                    resp.setSupEntName(getEntName(feeBalance.getSupEntId()));
+                    resp.setTradeType(feeBalance.getTradeType());
+                    resp.setTradeTypeDesc(WalletTypEnum.getDescByCode(feeBalance.getTradeType()));
+                    resp.setPaymentType(feeBalance.getPaymentType());
+                    resp.setTradeAmount(Objects.equals(feeBalance.getPaymentType(), 1) ? feeBalance.getTradeAmount() : feeBalance.getTradeAmount().negate());
+                    resp.setServiceFeeBalance(feeBalance.getServiceFeeBalance());
+                    resp.setTradingAmount(feeBalance.getTradingAmount());
+                    resp.setVoucherUrl(feeBalance.getVoucherUrl());
+                    resp.setRemark(feeBalance.getRemark());
+                    resp.setCreateTime(feeBalance.getCreateTime());
+                    return resp;
+                }).collect(Collectors.toList());
+
+        log.info("组装服务费明细结束,size:{}", JSON.toJSONString(walletFeeList.size()));
+        return walletFeeList;
+    }
+
+    /**
+     * 通过企业id查询企业名称
+     * @param entId
+     */
+    private String getEntName(Long entId) {
+        // 查询企业名称
+        EntCacheResDto entCacheResDto = remoteSystemService.queryEntDetails(entId);
+        if (entCacheResDto == null) {
+            return null;
+        }
+        return entCacheResDto.getFirmName();
+    }
 
     /**
      * 查询服务费余额、本次预计服务费与最大可购买数量。
@@ -72,7 +346,7 @@ public class ParkingWalletFeeService {
      * 业务规则:
      * 1. 服务费余额取当前登录采购企业的可用服务费余额;
      * 2. 收费策略总开关关闭时,不计算预计服务费,最大可购买数量返回 null,表示不做余额约束;
-     * 3. 收费策略总开关开启时,按企业绑定的当前收费策略计算预计服务费;
+     * 3. 收费策略总开关开启时,按企业绑定的当前收费策略计算预计服务费;无企业策略时使用开关表 default_fee;
      * 4. 策略 method 为 0.00 时,实际单价使用 kwt_parking_strategy_switch.default_fee;
      * 5. 最大可购买数量 = 服务费余额 / 实际策略单价,向下取整,避免超出余额。
      *
@@ -99,7 +373,7 @@ public class ParkingWalletFeeService {
         // 收费策略总开关统一以 kwt_parking_strategy_switch.status 为准,策略表 status 不再作为计费开关。
         boolean chargeStrategySwitchOpen = isChargeStrategySwitchOpen();
 
-        // 查询当前企业绑定的最新收费策略;若 method 为 0.00,resolveStrategyUnitFee 会使用开关表 default_fee。
+        // 查询当前企业绑定的最新收费策略;无企业策略或 method 为 0.00 时,resolveStrategyUnitFee 会使用开关表 default_fee。
         KwtParkingChargeStrategy currentStrategy = queryCurrentEnableStrategy(entId);
         BigDecimal currentStrategyUnitFee = resolveStrategyUnitFee(currentStrategy);
         log.info("服务费预估策略匹配完成,企业id:{}, 开关状态:{}, 策略id:{}, 策略原始单价:{}, 实际计费单价:{}",
@@ -116,8 +390,8 @@ public class ParkingWalletFeeService {
         log.info("服务费预估金额计算完成,企业id:{}, 采购数量:{}, 实际计费单价:{}, 预计服务费:{}",
                 entId, param.getPurchaseQuantity(), currentStrategyUnitFee, estimatedServiceFee);
 
-        // 开关开启且存在有效策略单价时,按服务费余额计算最大可购买数量;否则返回 null。
-        BigDecimal maxPurchaseQuantity = calculateMaxPurchaseQuantity(currentStrategy, currentStrategyUnitFee, serviceFeeBalance);
+        // 开关开启且存在有效单价时,按服务费余额计算最大可购买数量;否则返回 null。
+        BigDecimal maxPurchaseQuantity = calculateMaxPurchaseQuantity(currentStrategyUnitFee, serviceFeeBalance);
         log.info("服务费预估计算完成,企业id:{}, 采购数量:{}, 服务费余额:{}, 预计服务费:{}, 最大可购买数量:{}",
                 entId, param.getPurchaseQuantity(), serviceFeeBalance, estimatedServiceFee, maxPurchaseQuantity);
 
@@ -129,6 +403,7 @@ public class ParkingWalletFeeService {
         return resp;
     }
 
+
     /**
      * 贸易订单下单应用收费策略并冻结服务费
      * 规则:
@@ -200,6 +475,7 @@ public class ParkingWalletFeeService {
         result.setApplyChargeStrategy(Global.YES);
         result.setChargeStrategyId(currentStrategy.getId());
         result.setChargeStrategyDesc(buildStrategyMethodDesc(currentStrategy));
+        result.setChargeStrategyAmount(unitFee);
         result.setFreezeAmount(freezeAmount);
         result.setServiceFeeBalance(walletFee.getServiceFeeBalance());
         log.info("贸易订单应用收费策略完成,orderNo:{}, result:{}", param.getOrderNo(), result);
@@ -263,6 +539,7 @@ public class ParkingWalletFeeService {
         return result;
     }
 
+
     /**
      * 贸易订单完结结算服务费
      * <p>
@@ -390,6 +667,7 @@ public class ParkingWalletFeeService {
                                      String strategyDesc) {
     }
 
+
     /**
      * 查询采购方服务费余额
      */
@@ -430,8 +708,8 @@ public class ParkingWalletFeeService {
     /**
      * 解析订单完结服务费扣减信息
      * <p>
-     * 扣减金额 = 运输净重量 × 采购方当前最优收费策略单价;
-     * 最优策略:企业已绑定且开启的策略中,单价最低的一条(对采购方最优惠)。
+     * 扣减金额 = 运输净重量 × 收费策略单价;
+     * 单价优先取订单下单快照(chargeStrategyAmount),无快照时再取采购方当前最优策略;
      * 若无法匹配策略,则回退为下单冻结金额。
      *
      * @param param        结算参数(actualQuantity 为运输净重量)
@@ -440,6 +718,17 @@ public class ParkingWalletFeeService {
      */
     private SettleConsumeInfo resolveSettleConsumeInfo(ParkingWalletFeeFreezeParam param, BigDecimal freezeAmount) {
         BigDecimal transportNetWeight = param.getActualQuantity() == null ? ZERO_AMOUNT : param.getActualQuantity();
+
+        // 优先使用订单下单时快照的收费策略单价,避免策略表变更后扣减金额变化
+        BigDecimal snapshotUnitFee = param.getChargeStrategyAmount();
+        if (snapshotUnitFee != null && snapshotUnitFee.compareTo(ZERO_AMOUNT) > 0) {
+            BigDecimal consumeAmount = transportNetWeight.multiply(snapshotUnitFee).setScale(2, RoundingMode.HALF_UP);
+            log.info("按订单快照单价计算扣减,orderNo:{}, transportNetWeight:{}, snapshotUnitFee:{}, consumeAmount:{}",
+                    param.getOrderNo(), transportNetWeight, snapshotUnitFee, consumeAmount);
+            return new SettleConsumeInfo(consumeAmount, transportNetWeight, param.getChargeStrategyId(),
+                    null, buildSnapshotStrategyDesc(snapshotUnitFee));
+        }
+
         KwtParkingChargeStrategy strategy = queryOptimalEnableStrategy(param.getProEntId());
         if (strategy == null && param.getChargeStrategyId() != null) {
             strategy = parkingChangeStrategyRepository.getById(param.getChargeStrategyId());
@@ -531,7 +820,6 @@ public class ParkingWalletFeeService {
                 param.getOrderNo(), param.getProEntId(), param.getSupEntId(), TRADE_TYPE_UNFREEZE,
                 unfreezeAmount, walletFee.getServiceFeeBalance());
     }
-
     /**
      * 写入订单完结解冻明细(trade_type=5)
      * 仅释放履约中金额至可用余额,不代表最终扣减完成
@@ -562,6 +850,7 @@ public class ParkingWalletFeeService {
                 walletFee.getServiceFeeBalance(), walletFee.getTradingAmount());
     }
 
+
     /**
      * 写入订单完结扣减明细(trade_type=6)
      * 标志服务费正式消费完成,同时作为结算幂等依据
@@ -608,6 +897,13 @@ public class ParkingWalletFeeService {
         return unitFee.stripTrailingZeros().toPlainString() + "元/月";
     }
 
+    private String buildSnapshotStrategyDesc(BigDecimal snapshotUnitFee) {
+        if (snapshotUnitFee == null || snapshotUnitFee.compareTo(ZERO_AMOUNT) <= 0) {
+            return null;
+        }
+        return snapshotUnitFee.stripTrailingZeros().toPlainString() + "元";
+    }
+
     /**
      * 写入服务费钱包日志(kwt_parking_wallet_fee_balance)
      */
@@ -657,23 +953,19 @@ public class ParkingWalletFeeService {
     }
 
     /**
-     * 计算服务费余额支持的最大可购买数量
-     * 仅在收费策略开关开启且单价大于0时计算:服务费余额 / 策略单价(向下取整,保留4位小数
+     * 计算服务费余额支持的最大可购买数量
+     * 仅在收费策略开关开启且实际单价大于 0 时计算:服务费余额 / 单价(向下取整)
      *
-     * @param currentStrategy       当前收费策略
-     * @param currentStrategyUnitFee 策略单价
-     * @param serviceFeeBalance     服务费余额
-     * @return 最大可购买数量;策略未开启时不做余额约束,返回null
+     * @param currentStrategyUnitFee 实际计费单价(含 default_fee 兜底)
+     * @param serviceFeeBalance      服务费余额
+     * @return 最大可购买数量;开关未开启或单价无效时返回 null
      */
-    private BigDecimal calculateMaxPurchaseQuantity(KwtParkingChargeStrategy currentStrategy,
-                                                    BigDecimal currentStrategyUnitFee,
+    private BigDecimal calculateMaxPurchaseQuantity(BigDecimal currentStrategyUnitFee,
                                                     BigDecimal serviceFeeBalance) {
         boolean strategySwitchOpen = isChargeStrategySwitchOpen();
-        if (!strategySwitchOpen || currentStrategy == null
-                || currentStrategyUnitFee.compareTo(ZERO_AMOUNT) <= 0) {
+        if (!strategySwitchOpen || currentStrategyUnitFee.compareTo(ZERO_AMOUNT) <= 0) {
             log.info("最大可购买数量未计算,原因:{}",
-                    !strategySwitchOpen ? "收费策略开关未开启"
-                            : currentStrategy == null ? "未匹配到收费策略" : "策略单价<=0");
+                    !strategySwitchOpen ? "收费策略开关未开启" : "实际计费单价<=0");
             return null;
         }
         //向下取整
@@ -683,6 +975,7 @@ public class ParkingWalletFeeService {
         return maxPurchaseQuantity.max(ZERO_AMOUNT);
     }
 
+
     /**
      * 判断收费策略总开关是否开启(以 kwt_parking_strategy_switch 表为准)
      */
@@ -703,10 +996,20 @@ public class ParkingWalletFeeService {
     }
 
     /**
-     * 解析策略实际计费单价:method 为 0.00 时使用开关表 default_fee
+     * 解析策略实际计费单价。
+     * <ul>
+     *     <li>无企业策略且收费策略总开关开启:使用 {@code kwt_parking_strategy_switch.default_fee}</li>
+     *     <li>有企业策略且 method 为 0.00:使用开关表 default_fee</li>
+     *     <li>有企业策略且 method &gt; 0:使用策略 method</li>
+     * </ul>
      */
     private BigDecimal resolveStrategyUnitFee(KwtParkingChargeStrategy strategy) {
         if (strategy == null) {
+            if (isChargeStrategySwitchOpen()) {
+                BigDecimal defaultFee = resolveDefaultFee();
+                log.info("未匹配到企业收费策略,收费策略开关已开启,使用默认单价:{}", defaultFee);
+                return defaultFee;
+            }
             return ZERO_AMOUNT;
         }
         BigDecimal method = strategy.getMethod() == null ? ZERO_AMOUNT : strategy.getMethod();
@@ -811,242 +1114,5 @@ public class ParkingWalletFeeService {
         return walletFee.getServiceFeeBalance();
     }
 
-    /**
-     * 查询汇总服务费
-     * @param param
-     */
-    public ParkingWalletFeeResp queryTotalServerFee(ParkingWalletFeeTotalQueryParam param) {
-        log.info("查询汇总服务费:{}", JSON.toJSONString(param));
-        //构建查询条件
-        LambdaQueryWrapper<KwtParkingWalletFee> wrapper = buildAndExecuteQuery(param);
-
-        // 初始化
-        ParkingWalletFeeResp resp = new ParkingWalletFeeResp();
-        resp.setServiceFeeBalance(BigDecimal.ZERO);
-        resp.setTradingAmount(BigDecimal.ZERO);
-
-        List<KwtParkingWalletFee> records = parkingWalletFeeRepository.list(wrapper);
-        if (CollectionUtils.isEmpty(records)) {
-            log.info("汇总服务费信息为空");
-            return resp;
-        }
-        // 查询采购商汇总服务费
-        if (param.getProEntId() != null) {
-            return getProWalletFee(records.get(0));
-        }
-        // 查询平台汇总服务费
-        if (param.getSupEntId() != null) {
-            return getSupWalletFee(records);
-        }
-        return resp;
-    }
-
-    /**
-     * 获取采购商汇总服务费
-     * @param fee
-     */
-    private ParkingWalletFeeResp getProWalletFee(KwtParkingWalletFee fee) {
-        log.info("开始汇总采购商服务费,param:{}", JSON.toJSONString(fee));
-
-        ParkingWalletFeeResp resp = new ParkingWalletFeeResp();
-        resp.setServiceFeeBalance(fee.getServiceFeeBalance() != null ? fee.getServiceFeeBalance() : BigDecimal.ZERO);
-        resp.setTradingAmount(fee.getTradingAmount() != null ? fee.getTradingAmount() : BigDecimal.ZERO);
-        log.info("汇总采购商服务费结束,总余额:{}, 总履约中金额:{}", fee.getServiceFeeBalance(), fee.getTradingAmount());
-        return resp;
-
-    }
-
-
-    /**
-     * 获取平台汇总服务费
-     * @param feeList
-     */
-    private ParkingWalletFeeResp getSupWalletFee(List<KwtParkingWalletFee> feeList) {
-        log.info("开始汇总平台服务费, size:{}", CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());
-        // 汇总余额
-        BigDecimal totalServiceFee  = feeList.stream()
-                .map(fee -> fee.getServiceFeeBalance() != null ? fee.getServiceFeeBalance() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-        // 汇总冻结金额
-        BigDecimal totalTradingAmount   = feeList.stream()
-                .map(fee -> fee.getTradingAmount() != null ? fee.getTradingAmount() : BigDecimal.ZERO)
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-        ParkingWalletFeeResp resp = new ParkingWalletFeeResp();
-        resp.setServiceFeeBalance(totalServiceFee);
-        resp.setTradingAmount(totalTradingAmount);
-        log.info("汇总平台服务费结束,总余额:{}, 总履约中金额:{}", totalServiceFee, totalTradingAmount);
-        return resp;
-    }
-    /**
-     * 构建查询条件并执行分页查询
-     */
-    private LambdaQueryWrapper<KwtParkingWalletFee> buildAndExecuteQuery(ParkingWalletFeeTotalQueryParam param) {
-        return  Wrappers.<KwtParkingWalletFee>lambdaQuery()
-                .eq(param.getProEntId() != null, KwtParkingWalletFee::getProEntId, param.getProEntId())
-                .eq(param.getSupEntId() != null, KwtParkingWalletFee::getSupEntId, param.getSupEntId())
-                .orderByDesc(KwtParkingWalletFee::getUpdateTime);
-    }
-
-    /**
-     * 分页查询服务费
-     * @param param
-     */
-    public PageDataResult<ParkingWalletFeeResp> pageQueryServerFee(ParkingWalletFeeQueryParam param) {
-        log.info("分页查询服务费:{}", JSON.toJSONString(param));
-
-        //构建查询条件
-        LambdaQueryWrapper<KwtParkingWalletFee> wrapper = buildAndExecuteQuery(param);
-
-        Page<KwtParkingWalletFee> pageByStatus = parkingWalletFeeRepository.page(new Page<>(param.getPageNum(), param.getPageSize()), wrapper);
-        List<KwtParkingWalletFee> records = pageByStatus.getRecords();
-        if (CollectionUtils.isEmpty(records)) {
-            log.info("服务费信息为空");
-            return PageDataResult.empty(param.getPageNum(), param.getPageSize());
-        }
-        List<ParkingWalletFeeResp> changeStrategyRespList = getWalletFeePageResult(records);
-        return PageDataResult.success(param.getPageNum(), param.getPageSize(), pageByStatus.getTotal(), changeStrategyRespList);
-    }
-
-    /**
-     * 获取收费策略
-     * @param records
-     */
-    private List<ParkingWalletFeeResp> getWalletFeePageResult(List<KwtParkingWalletFee> records) {
-        log.info("组装服务费,param:{}", JSON.toJSONString(records));
-        if (CollectionUtils.isEmpty(records)) {
-            return Collections.emptyList();
-        }
-        List<ParkingWalletFeeResp> walletFeeList = records.stream()
-                .map(fee -> {
-                    ParkingWalletFeeResp resp = new ParkingWalletFeeResp();
-                    resp.setId(fee.getId());
-                    resp.setProEntId(fee.getProEntId());
-                    resp.setProEntName(null);
-                    resp.setSupEntId(fee.getSupEntId());
-                    resp.setSupEntName(null);
-                    resp.setServiceFeeBalance(fee.getServiceFeeBalance());
-                    resp.setTradingAmount(fee.getTradingAmount());
-                    resp.setUpdateTime(fee.getUpdateTime());
-                    return resp;
-                }).collect(Collectors.toList());
-
-        log.info("组装服务费结束,size:{}", JSON.toJSONString(walletFeeList.size()));
-        return walletFeeList;
-    }
-    /**
-     * 构建查询条件并执行分页查询
-     */
-    private LambdaQueryWrapper<KwtParkingWalletFee> buildAndExecuteQuery(ParkingWalletFeeQueryParam param) {
-        return  Wrappers.<KwtParkingWalletFee>lambdaQuery()
-                .eq(param.getSupEntId() != null, KwtParkingWalletFee::getSupEntId, param.getSupEntId())
-                .eq(param.getProEntId() != null, KwtParkingWalletFee::getProEntId, param.getProEntId())
-                .orderByDesc(KwtParkingWalletFee::getUpdateTime);
-    }
-
-    /**
-     * 保存服务费
-     * @param param
-     */
-    @Transactional(rollbackFor = Exception.class)
-    public void saveServerFee(ParkingWalletFeeSaveParam param) {
-        log.info("保存服务费,param:{}", JSON.toJSONString(param));
-        KwtParkingWalletFee walletFee = parkingWalletFeeRepository.getById(param.getId());
-        if (walletFee == null) {
-            throw new BusinessPlatfromException(ErrorCodeEnum.DRIVER_NOT_FOUND, "服务费信息不存在");
-        }
-        // 1. 先修改服务余额
-        walletFee.setServiceFeeBalance(walletFee.getServiceFeeBalance().add(param.getServiceFee()));
-        parkingWalletFeeRepository.updateById(walletFee);
-
-        // 2.新增明细
-        KwtParkingWalletFeeBalance feeBalance = new KwtParkingWalletFeeBalance();
-        feeBalance.setProEntId(param.getEntId());
-        feeBalance.setTradeType(9);
-        feeBalance.setTradeAmount(param.getServiceFee());
-        feeBalance.setServiceFeeBalance(walletFee.getServiceFeeBalance());
-        feeBalance.setTradingAmount(walletFee.getTradingAmount());
-        feeBalance.setVoucherUrl(param.getVoucherUrl());
-        feeBalance.setRemark(param.getRemark());
-        parkingWalletFeeBalanceRepository.save(feeBalance);
-    }
-    /**
-     * 分页查询服务费明细
-     * @param param
-     */
-    public PageDataResult<ParkingWalletFeeBalanceResp> pageQueryServerFeeDetails(ParkingWalletFeeBalanceQueryParam param) {
-        log.info("分页查询服务费明细:{}", JSON.toJSONString(param));
-
-        //构建查询条件
-        LambdaQueryWrapper<KwtParkingWalletFeeBalance> wrapper = buildAndExecuteQuery(param);
 
-        Page<KwtParkingWalletFeeBalance> pageByStatus = parkingWalletFeeBalanceRepository.page(new Page<>(param.getPageNum(), param.getPageSize()), wrapper);
-        List<KwtParkingWalletFeeBalance> records = pageByStatus.getRecords();
-        if (CollectionUtils.isEmpty(records)) {
-            log.info("分页查询服务费明细为空");
-            return PageDataResult.empty(param.getPageNum(), param.getPageSize());
-        }
-        List<ParkingWalletFeeBalanceResp> changeStrategyRespList = getWalletFeeBalancePageResult(records);
-        return PageDataResult.success(param.getPageNum(), param.getPageSize(), pageByStatus.getTotal(), changeStrategyRespList);
-    }
-    /**
-     * 构建查询条件并执行分页查询
-     */
-    private LambdaQueryWrapper<KwtParkingWalletFeeBalance> buildAndExecuteQuery(ParkingWalletFeeBalanceQueryParam param) {
-        return  Wrappers.<KwtParkingWalletFeeBalance>lambdaQuery()
-                .eq(param.getSupEntId() != null, KwtParkingWalletFeeBalance::getSupEntId, param.getSupEntId())
-                .eq(param.getProEntId() != null, KwtParkingWalletFeeBalance::getProEntId, param.getProEntId())
-                .eq(KwtParkingWalletFeeBalance::getOrderNo, param.getOrderNo())
-                .eq(KwtParkingWalletFeeBalance::getTradeType, param.getTradeType())
-                .ge(KwtParkingWalletFeeBalance::getTradeAmount, param.getMinFee())
-                .le(KwtParkingWalletFeeBalance::getTradeAmount, param.getMaxFee())
-                .ge(KwtParkingWalletFeeBalance::getCreateTime, param.getStartCreateTime())
-                .le(KwtParkingWalletFeeBalance::getCreateTime, param.getEndCreateTime())
-                .orderByDesc(KwtParkingWalletFeeBalance::getServiceFeeBalance);
-    }
-    /**
-     * 获取服务费明细
-     * @param records
-     */
-    private List<ParkingWalletFeeBalanceResp> getWalletFeeBalancePageResult(List<KwtParkingWalletFeeBalance> records) {
-        log.info("组装服务费明细,param:{}", JSON.toJSONString(records));
-        if (CollectionUtils.isEmpty(records)) {
-            return Collections.emptyList();
-        }
-        List<ParkingWalletFeeBalanceResp> walletFeeList = records.stream()
-                .map(feeBalance -> {
-                    ParkingWalletFeeBalanceResp resp = new ParkingWalletFeeBalanceResp();
-                    resp.setId(feeBalance.getId());
-                    resp.setOrderNo(feeBalance.getOrderNo());
-                    resp.setProEntId(feeBalance.getProEntId());
-                    resp.setProEntName(getEntName(feeBalance.getProEntId()));
-                    resp.setSupEntId(feeBalance.getSupEntId());
-                    resp.setSupEntName(getEntName(feeBalance.getSupEntId()));
-                    resp.setTradeType(feeBalance.getTradeType());
-                    resp.setTradeTypeDesc(WalletTypEnum.getDescByCode(feeBalance.getTradeType()));
-                    resp.setTradeAmount(feeBalance.getTradeAmount());
-                    resp.setServiceFeeBalance(feeBalance.getServiceFeeBalance());
-                    resp.setTradingAmount(feeBalance.getTradingAmount());
-                    resp.setVoucherUrl(feeBalance.getVoucherUrl());
-                    resp.setRemark(feeBalance.getRemark());
-                    resp.setCreateTime(feeBalance.getCreateTime());
-                    return resp;
-                }).collect(Collectors.toList());
-
-        log.info("组装服务费明细结束,size:{}", JSON.toJSONString(walletFeeList.size()));
-        return walletFeeList;
-    }
-
-
-    /**
-     * 通过企业id查询企业名称
-     * @param entId
-     */
-    private String getEntName(Long entId) {
-        // 查询企业名称
-        EntCacheResDto entCacheResDto = remoteSystemService.queryEntDetails(entId);
-        if (entCacheResDto == null) {
-            return null;
-        }
-        return entCacheResDto.getFirmName();
-    }
 }

+ 2 - 0
sql/2026/06/2026_06_27_add_trade_order_charge_strategy_amount.sql

@@ -0,0 +1,2 @@
+ALTER TABLE kwo_trade_order
+    ADD COLUMN charge_strategy_amount decimal(16, 2) DEFAULT NULL COMMENT '下单时收费策略单价快照(元/吨等),防止策略变更后金额变化' AFTER charge_strategy_desc;