瀏覽代碼

合并晓飞代码

donglang 6 小時之前
父節點
當前提交
abcd459c2f
共有 13 個文件被更改,包括 838 次插入17 次删除
  1. 37 0
      sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/dubbo/ParkingWalletFeeRemoteService.java
  2. 42 0
      sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/param/ParkingWalletFeeFreezeParam.java
  3. 42 0
      sckw-modules-api/sckw-transport-api/src/main/java/com/sckw/transport/api/model/vo/ParkingWalletFeeFreezeResult.java
  4. 19 0
      sckw-modules/sckw-order/src/main/java/com/sckw/order/model/KwoTradeOrder.java
  5. 22 0
      sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/res/TradeOrderAuditResp.java
  6. 123 3
      sckw-modules/sckw-order/src/main/java/com/sckw/order/serivce/KwoTradeOrderService.java
  7. 35 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/ParkingWalletFeeEstimateController.java
  8. 37 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/dubbo/ParkingWalletFeeRemoteServiceImpl.java
  9. 2 2
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtParkingWalletFee.java
  10. 2 2
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtParkingWalletFeeBalance.java
  11. 25 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/ParkingWalletFeeEstimateQueryParam.java
  12. 36 0
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/ParkingWalletFeeEstimateResp.java
  13. 416 10
      sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/ParkingWalletFeeService.java

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

@@ -0,0 +1,37 @@
+package com.sckw.transport.api.dubbo;
+
+import com.sckw.transport.api.model.param.ParkingWalletFeeFreezeParam;
+import com.sckw.transport.api.model.vo.ParkingWalletFeeFreezeResult;
+
+import java.math.BigDecimal;
+
+/**
+ * 服务费远程服务
+ */
+public interface ParkingWalletFeeRemoteService {
+
+    /**
+     * 贸易订单下单时应用收费策略并冻结服务费余额
+     *
+     * @param param 冻结参数
+     * @return 收费策略应用结果
+     */
+    ParkingWalletFeeFreezeResult applyChargeStrategyFreeze(ParkingWalletFeeFreezeParam param);
+
+    /**
+     * 贸易订单审核拒绝时解冻服务费并返还余额
+     *
+     * @param param 解冻参数
+     * @return 解冻结果(含采购方服务费余额)
+     */
+    ParkingWalletFeeFreezeResult unfreezeChargeStrategy(ParkingWalletFeeFreezeParam param);
+
+    /**
+     * 查询采购方服务费余额
+     *
+     * @param proEntId 采购方企业id
+     * @param supEntId 供应商企业id
+     * @return 服务费余额
+     */
+    BigDecimal queryServiceFeeBalance(Long proEntId, Long supEntId);
+}

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

@@ -0,0 +1,42 @@
+package com.sckw.transport.api.model.param;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 贸易订单服务费冻结参数
+ */
+@Data
+public class ParkingWalletFeeFreezeParam implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 贸易订单号
+     */
+    private String orderNo;
+
+    /**
+     * 采购方企业id
+     */
+    private Long proEntId;
+
+    /**
+     * 供应商企业id
+     */
+    private Long supEntId;
+
+    /**
+     * 采购数量
+     */
+    private BigDecimal purchaseQuantity;
+
+    /**
+     * 操作人id(Dubbo调用时由订单模块传入)
+     */
+    private Long operatorId;
+}

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

@@ -0,0 +1,42 @@
+package com.sckw.transport.api.model.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 贸易订单服务费冻结结果
+ */
+@Data
+public class ParkingWalletFeeFreezeResult implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 是否应用收费策略(0-否,1-是)
+     */
+    private Integer applyChargeStrategy;
+
+    /**
+     * 收费策略id
+     */
+    private Long chargeStrategyId;
+
+    /**
+     * 收费策略描述,如10元/吨
+     */
+    private String chargeStrategyDesc;
+
+    /**
+     * 冻结服务费金额
+     */
+    private BigDecimal freezeAmount;
+
+    /**
+     * 采购方服务费余额
+     */
+    private BigDecimal serviceFeeBalance;
+}

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

@@ -153,5 +153,24 @@ public class KwoTradeOrder extends BaseModel implements Serializable {
      */
     private Integer archiveFlag;
 
+    /**
+     * 是否应用收费策略(0-否,1-是)
+     */
+    @TableField("apply_charge_strategy")
+    private Integer applyChargeStrategy;
+
+    /**
+     * 应用的收费策略id
+     */
+    @TableField("charge_strategy_id")
+    private Long chargeStrategyId;
+
+    /**
+     * 应用的收费策略描述,如10元/吨
+     */
+    @TableField("charge_strategy_desc")
+    private String chargeStrategyDesc;
+
+
 
 }

+ 22 - 0
sckw-modules/sckw-order/src/main/java/com/sckw/order/model/vo/res/TradeOrderAuditResp.java

@@ -0,0 +1,22 @@
+package com.sckw.order.model.vo.res;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 贸易订单审核结果
+ */
+@Data
+public class TradeOrderAuditResp implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 采购方服务费余额
+     */
+    private BigDecimal serviceFeeBalance;
+}

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

@@ -84,6 +84,7 @@ import com.sckw.system.api.feign.DataPermissionFeignService;
 import com.sckw.system.api.model.dto.req.ActualDisPatchDto;
 import com.sckw.system.api.model.dto.req.DataPermissionFilterReqDto;
 import com.sckw.system.api.model.dto.res.*;
+import com.sckw.transport.api.dubbo.ParkingWalletFeeRemoteService;
 import com.sckw.transport.api.dubbo.TransportRemoteStatisticsService;
 import com.sckw.transport.api.dubbo.TransportRemoteService;
 import com.sckw.transport.api.model.dto.RawOreOrderExecutionDto;
@@ -91,8 +92,10 @@ import com.sckw.transport.api.model.dto.TradeOrderWaybillAggDto;
 import com.sckw.transport.api.model.param.AddLogisticOrderParam;
 import com.sckw.transport.api.model.param.LogisticInfo;
 import com.sckw.transport.api.model.param.OrderFinishParam;
+import com.sckw.transport.api.model.param.ParkingWalletFeeFreezeParam;
 import com.sckw.transport.api.model.vo.KwtLogisticsOrderVO;
 import com.sckw.transport.api.model.vo.LogisticContractVo;
+import com.sckw.transport.api.model.vo.ParkingWalletFeeFreezeResult;
 import jakarta.annotation.Resource;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
@@ -136,6 +139,11 @@ public class KwoTradeOrderService {
 
     private static final int ORDER_EXECUTION_DISPLAY_LIMIT = 500;
 
+    /**
+     * 贸易合同/贸易订单代理单位类型。
+     */
+    static final String AGENT_UNIT_TYPE = "3";
+
     @DubboReference(version = "1.0.0", group = "design", check = false)
     private RemoteSystemService remoteSystemService;
     @DubboReference(version = "1.0.0", group = "design", check = false)
@@ -165,6 +173,9 @@ public class KwoTradeOrderService {
     @DubboReference(version = "1.0.0", group = "design", check = false, timeout = 6000)
     protected RemoteFleetService remoteFleetService;
 
+    @DubboReference(version = "1.0.0", group = "design", check = false, timeout = 6000)
+    private ParkingWalletFeeRemoteService parkingWalletFeeRemoteService;
+
     @Autowired
     private PaymentFeignService paymentFeignService;
 
@@ -2231,6 +2242,20 @@ public class KwoTradeOrderService {
         }
         // ====== 下单前校验 end ======
 
+        // 收费策略开启时冻结采购方服务费,并记录订单收费策略信息
+        Long purchaseEntId = resolvePurchaseEntId(tradeContractResDto.getUnitList());
+        Long supplyEntId = resolveSupplyEntId(tradeContractResDto.getUnitList());
+        ParkingWalletFeeFreezeParam feeFreezeParam = new ParkingWalletFeeFreezeParam();
+        feeFreezeParam.setOrderNo(order.getTOrderNo());
+        feeFreezeParam.setProEntId(purchaseEntId);
+        feeFreezeParam.setSupEntId(supplyEntId);
+        feeFreezeParam.setPurchaseQuantity(tradeOrderParam.getAmount());
+        feeFreezeParam.setOperatorId(LoginUserHolder.getUserId());
+        log.info("贸易订单下单应用收费策略,orderNo:{}, feeFreezeParam:{}", order.getTOrderNo(), JSON.toJSONString(feeFreezeParam));
+        ParkingWalletFeeFreezeResult feeFreezeResult = parkingWalletFeeRemoteService.applyChargeStrategyFreeze(feeFreezeParam);
+        fillChargeStrategyInfo(order, feeFreezeResult);
+        log.info("贸易订单收费策略处理完成,orderNo:{}, feeFreezeResult:{}", order.getTOrderNo(), JSON.toJSONString(feeFreezeResult));
+
         //商品地址信息
         AddressInfoDetail goodsAddress = goodsInfoService.getGoodsAddress(tradeOrderParam.getGoodsId());
         if (Objects.isNull(goodsAddress)) {
@@ -2387,6 +2412,66 @@ public class KwoTradeOrderService {
         }
     }
 
+    /**
+     * 将贸易合同单位类型转换为贸易订单单位类型。
+     * <p>
+     * 贸易合同中的供应/采购单位与贸易订单中的采购/销售单位方向相反;
+     * 代理单位不参与方向反转,固定落库为类型3。
+     * </p>
+     *
+     * @param contractUnitType 贸易合同单位类型
+     * @return 贸易订单单位类型
+     */
+    static String convertContractUnitTypeToOrderUnitType(String contractUnitType) {
+        if (StrUtil.equals(contractUnitType, AGENT_UNIT_TYPE)) {
+            return AGENT_UNIT_TYPE;
+        }
+        return StrUtil.equals(contractUnitType, "1") ? "2" : "1";
+    }
+
+    /**
+     * 解析贸易合同中的采购方企业id
+     */
+    private Long resolvePurchaseEntId(List<TradeContractUnitDto> unitList) {
+        if (CollUtil.isEmpty(unitList)) {
+            throw new BusinessException("贸易合同企业信息不存在");
+        }
+        return unitList.stream()
+                .filter(unit -> StrUtil.equals(convertContractUnitTypeToOrderUnitType(unit.getUnitType()), "1"))
+                .map(TradeContractUnitDto::getEntId)
+                .filter(Objects::nonNull)
+                .findFirst()
+                .orElseThrow(() -> new BusinessException("采购方企业信息不存在"));
+    }
+
+    /**
+     * 解析贸易合同中的供应方企业id
+     */
+    private Long resolveSupplyEntId(List<TradeContractUnitDto> unitList) {
+        if (CollUtil.isEmpty(unitList)) {
+            throw new BusinessException("贸易合同企业信息不存在");
+        }
+        return unitList.stream()
+                .filter(unit -> StrUtil.equals(convertContractUnitTypeToOrderUnitType(unit.getUnitType()), "2"))
+                .map(TradeContractUnitDto::getEntId)
+                .filter(Objects::nonNull)
+                .findFirst()
+                .orElseThrow(() -> new BusinessException("供应方企业信息不存在"));
+    }
+
+    /**
+     * 回填贸易订单收费策略信息
+     */
+    private void fillChargeStrategyInfo(KwoTradeOrder order, ParkingWalletFeeFreezeResult feeFreezeResult) {
+        if (feeFreezeResult == null) {
+            order.setApplyChargeStrategy(Global.NO);
+            return;
+        }
+        order.setApplyChargeStrategy(feeFreezeResult.getApplyChargeStrategy() == null ? Global.NO : feeFreezeResult.getApplyChargeStrategy());
+        order.setChargeStrategyId(feeFreezeResult.getChargeStrategyId());
+        order.setChargeStrategyDesc(feeFreezeResult.getChargeStrategyDesc());
+    }
+
     /**
      * 买家撤销贸易订单
      */
@@ -2525,7 +2610,7 @@ public class KwoTradeOrderService {
      * @return
      */
     @Transactional(rollbackFor = Exception.class)
-    public Object auditOrder(TradeOrderAuditParam tradeOrderAuditParam) {
+    public TradeOrderAuditResp auditOrder(TradeOrderAuditParam tradeOrderAuditParam) {
         KwoTradeOrder kwoTradeOrder = kwoTradeOrderMapper.selectOne(new LambdaQueryWrapper<KwoTradeOrder>().eq(KwoTradeOrder::getId, tradeOrderAuditParam.getId()).eq(KwoTradeOrder::getDelFlag, 0));
         if (Objects.isNull(kwoTradeOrder)) {
             throw new BusinessException("贸易订单不存在");
@@ -2555,6 +2640,21 @@ public class KwoTradeOrderService {
         }
 
         if (Objects.equals(tradeOrderAuditParam.getStatus(), 2)) {
+            BigDecimal serviceFeeBalance = null;
+            // 审核拒绝:若应用了收费策略,解冻并返还采购方服务费余额
+            if (Objects.equals(kwoTradeOrder.getApplyChargeStrategy(), Global.YES)) {
+                ParkingWalletFeeFreezeParam serviceFeeUnfreezeParam = new ParkingWalletFeeFreezeParam();
+                serviceFeeUnfreezeParam.setOrderNo(kwoTradeOrder.getTOrderNo());
+                serviceFeeUnfreezeParam.setProEntId(unitMap.get(String.valueOf(1)).getEntId());
+                serviceFeeUnfreezeParam.setSupEntId(unitMap.get(String.valueOf(2)).getEntId());
+                serviceFeeUnfreezeParam.setOperatorId(LoginUserHolder.getUserId());
+                log.info("贸易订单审核拒绝解冻服务费,orderNo:{}, param:{}",
+                        kwoTradeOrder.getTOrderNo(), JSON.toJSONString(serviceFeeUnfreezeParam));
+                ParkingWalletFeeFreezeResult unfreezeResult =
+                        parkingWalletFeeRemoteService.unfreezeChargeStrategy(serviceFeeUnfreezeParam);
+                serviceFeeBalance = unfreezeResult.getServiceFeeBalance();
+            }
+
             //钱包退回金额
             WalletPrepaidDto walletFreeze = new WalletPrepaidDto();
 //            walletFreeze.setTTradeOrderId(kwoTradeOrder.getId());
@@ -2591,7 +2691,7 @@ public class KwoTradeOrderService {
             kwoTradeOrderTrackService.insert(kwoTradeOrderTrack);
             kwoTradeOrder.setStatus(Objects.equals(tradeOrderAuditParam.getStatus(), 2) ? TradeOrderStatusEnum.BACK.getCode() : TradeOrderStatusEnum.ING.getCode());
             kwoTradeOrderMapper.updateById(kwoTradeOrder);
-            return true;
+            return buildAuditServiceFeeBalanceResp(kwoTradeOrder, unitMap, serviceFeeBalance);
         }
         TradeContractResDto tradeContractResDto = remoteContractService.queryTradeContract(tradeOrderAuditParam.getTradeContractId(), byOrderId.getGoodsId());
         if (Objects.isNull(tradeContractResDto)) {
@@ -2841,7 +2941,27 @@ public class KwoTradeOrderService {
         //更新贸易订单状态
         kwoTradeOrder.setStatus(Objects.equals(tradeOrderAuditParam.getStatus(), 2) ? TradeOrderStatusEnum.BACK.getCode() : TradeOrderStatusEnum.ING.getCode());
         kwoTradeOrderMapper.updateById(kwoTradeOrder);
-        return true;
+        return buildAuditServiceFeeBalanceResp(kwoTradeOrder, unitMap, null);
+    }
+
+    /**
+     * 构建审核结果,返回采购方服务费余额
+     */
+    private TradeOrderAuditResp buildAuditServiceFeeBalanceResp(KwoTradeOrder order,
+                                                                Map<String, KwoTradeOrderUnit> unitMap,
+                                                                BigDecimal serviceFeeBalance) {
+        TradeOrderAuditResp resp = new TradeOrderAuditResp();
+        if (!Objects.equals(order.getApplyChargeStrategy(), Global.YES)) {
+            return resp;
+        }
+        if (serviceFeeBalance != null) {
+            resp.setServiceFeeBalance(serviceFeeBalance);
+            return resp;
+        }
+        Long proEntId = unitMap.get(String.valueOf(1)).getEntId();
+        Long supEntId = unitMap.get(String.valueOf(2)).getEntId();
+        resp.setServiceFeeBalance(parkingWalletFeeRemoteService.queryServiceFeeBalance(proEntId, supEntId));
+        return resp;
     }
 
     @NotNull

+ 35 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/controller/ParkingWalletFeeEstimateController.java

@@ -0,0 +1,35 @@
+package com.sckw.transport.controller;
+
+import com.sckw.core.web.response.BaseResult;
+import com.sckw.transport.model.param.ParkingWalletFeeEstimateQueryParam;
+import com.sckw.transport.model.param.ParkingWalletFeeEstimateResp;
+import com.sckw.transport.service.ParkingWalletFeeService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * Author: donglang
+ * Time: 2026-06-25
+ * Des: 服务费预估
+ * Version: 1.0
+ */
+@RestController
+@RequestMapping("/parking/wallet/fee")
+@RequiredArgsConstructor
+@Tag(name = "服务费预估接口", description = "服务费预估接口")
+public class ParkingWalletFeeEstimateController {
+
+    private final ParkingWalletFeeService parkingWalletFeeService;
+
+    @Operation(summary = "查询服务费余额、本次预计服务费与最大可购买数量", description = "查询服务费余额、本次预计服务费与最大可购买数量")
+    @PostMapping("/queryEstimate")
+    public BaseResult<ParkingWalletFeeEstimateResp> queryEstimate(@RequestBody @Valid ParkingWalletFeeEstimateQueryParam param) {
+        return BaseResult.success(parkingWalletFeeService.queryEstimateServiceFee(param));
+    }
+}

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

@@ -0,0 +1,37 @@
+package com.sckw.transport.dubbo;
+
+import com.sckw.transport.api.dubbo.ParkingWalletFeeRemoteService;
+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 org.apache.dubbo.config.annotation.DubboService;
+import org.springframework.stereotype.Service;
+
+import java.math.BigDecimal;
+
+/**
+ * 服务费远程服务实现
+ */
+@Service
+@DubboService(group = "design", version = "1.0.0", timeout = 60000)
+@RequiredArgsConstructor
+public class ParkingWalletFeeRemoteServiceImpl implements ParkingWalletFeeRemoteService {
+
+    private final ParkingWalletFeeService parkingWalletFeeService;
+
+    @Override
+    public ParkingWalletFeeFreezeResult applyChargeStrategyFreeze(ParkingWalletFeeFreezeParam param) {
+        return parkingWalletFeeService.applyChargeStrategyFreeze(param);
+    }
+
+    @Override
+    public ParkingWalletFeeFreezeResult unfreezeChargeStrategy(ParkingWalletFeeFreezeParam param) {
+        return parkingWalletFeeService.unfreezeChargeStrategy(param);
+    }
+
+    @Override
+    public BigDecimal queryServiceFeeBalance(Long proEntId, Long supEntId) {
+        return parkingWalletFeeService.queryPurchaserServiceFeeBalance(proEntId, supEntId);
+    }
+}

+ 2 - 2
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/KwtParkingWalletFee.java

@@ -49,7 +49,7 @@ public class KwtParkingWalletFee implements Serializable {
     /**
      * 创建人
      */
-    private Long createBy;
+    private Long createUser;
 
     /**
      * 创建时间
@@ -59,7 +59,7 @@ public class KwtParkingWalletFee implements Serializable {
     /**
      * 创建人更新人
      */
-    private Long updateBy;
+    private Long updateUser;
 
     /**
      * 更新时间

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

@@ -69,7 +69,7 @@ public class KwtParkingWalletFeeBalance implements Serializable {
     /**
      * 创建人
      */
-    private Long createBy;
+    private Long createUser;
 
     /**
      * 创建时间
@@ -79,7 +79,7 @@ public class KwtParkingWalletFeeBalance implements Serializable {
     /**
      * 创建人更新人
      */
-    private Long updateBy;
+    private Long updateUser;
 
     /**
      * 更新时间

+ 25 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/ParkingWalletFeeEstimateQueryParam.java

@@ -0,0 +1,25 @@
+package com.sckw.transport.model.param;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 服务费预估查询参数
+ */
+@Data
+public class ParkingWalletFeeEstimateQueryParam implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = -5630735374527006854L;
+
+    @NotNull(message = "采购数量不能为空")
+    @DecimalMin(value = "0", message = "采购数量不能小于0")
+    @Schema(description = "本次采购数量")
+    private BigDecimal purchaseQuantity;
+}

+ 36 - 0
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/model/param/ParkingWalletFeeEstimateResp.java

@@ -0,0 +1,36 @@
+package com.sckw.transport.model.param;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 服务费预估结果
+ */
+@Data
+public class ParkingWalletFeeEstimateResp implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = -5146116069901904418L;
+
+    /**
+     * 服务费余额
+     */
+    @Schema(description = "服务费余额")
+    private BigDecimal serviceFeeBalance;
+
+    /**
+     * 本次预计服务费
+     */
+    @Schema(description = "本次预计服务费")
+    private BigDecimal estimatedServiceFee;
+
+    /**
+     * 最大可购买数量(收费策略开启时,按服务费余额/策略单价计算)
+     */
+    @Schema(description = "最大可购买数量")
+    private BigDecimal maxPurchaseQuantity;
+}

+ 416 - 10
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/ParkingWalletFeeService.java

@@ -7,19 +7,20 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.sckw.core.common.enums.enums.ErrorCodeEnum;
 import com.sckw.core.exception.BusinessPlatfromException;
+import com.sckw.core.model.constant.Global;
+import com.sckw.core.model.enums.ParkingChangeStrategyEnum;
 import com.sckw.core.utils.CollectionUtils;
+import com.sckw.core.web.context.LoginUserHolder;
 import com.sckw.core.web.response.result.PageDataResult;
 import com.sckw.system.api.RemoteSystemService;
 import com.sckw.system.api.model.dto.res.EntCacheResDto;
-import com.sckw.transport.model.KwtParkingWalletFee;
-import com.sckw.transport.model.KwtParkingWalletFeeBalance;
-import com.sckw.transport.model.ParkingWalletFeeSaveParam;
-import com.sckw.transport.model.ParkingWalletFeeTotalQueryParam;
+import com.sckw.transport.api.model.param.ParkingWalletFeeFreezeParam;
+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.ParkingWalletFeeBalanceQueryParam;
-import com.sckw.transport.model.param.ParkingWalletFeeBalanceResp;
-import com.sckw.transport.model.param.ParkingWalletFeeQueryParam;
-import com.sckw.transport.model.param.ParkingWalletFeeResp;
+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 lombok.RequiredArgsConstructor;
@@ -29,8 +30,8 @@ import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
 import java.math.BigDecimal;
-import java.util.Collections;
-import java.util.List;
+import java.math.RoundingMode;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -48,9 +49,16 @@ public class ParkingWalletFeeService {
     private final KwtParkingWalletFeeRepository parkingWalletFeeRepository;
     private final KwtParkingWalletFeeBalanceRepository parkingWalletFeeBalanceRepository;
 
+    private final KwtParkingChangeStrategyRepository parkingChangeStrategyRepository;
+    private final KwtParkingChangeStrategyUnitRepository parkingChangeStrategyUnitRepository;
+
     @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;
 
     /**
      * 查询汇总服务费
@@ -298,4 +306,402 @@ public class ParkingWalletFeeService {
         return entCacheResDto.getFirmName();
     }
 
+    /**
+     * 查询服务费余额、本次预计服务费与最大可购买数量
+     * 规则:
+     * 1. 服务费余额 = 采购方当前服务费余额
+     * 2. 本次预计服务费 = 采购数量 * 采购方当前配置的收费策略单价
+     * 3. 最大可购买数量 = 收费策略开启时,服务费余额 / 策略单价;未开启时不返回余额约束
+     *
+     * @param param 查询参数
+     * @return 服务费余额、本次预计服务费与最大可购买数量
+     */
+    public ParkingWalletFeeEstimateResp queryEstimateServiceFee(ParkingWalletFeeEstimateQueryParam param) {
+        Long entId = LoginUserHolder.getEntId();
+        log.info("服务费预估开始,entId:{}, param:{}", entId, param);
+        if (param.getPurchaseQuantity() == null || param.getPurchaseQuantity().compareTo(ZERO_AMOUNT) < 0) {
+            log.warn("服务费预估参数异常,采购数量非法,entId:{}, purchaseQuantity:{}",
+                    entId, param.getPurchaseQuantity());
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "采购数量不能为空且不能小于0");
+        }
+
+        // 查询采购方当前服务费余额
+        BigDecimal serviceFeeBalance = queryServiceFeeBalance(entId, null);
+
+        // 查询当前企业生效中的收费策略(仅取状态开启的最新一条)
+        KwtParkingChargeStrategy currentStrategy = queryCurrentEnableStrategy(entId);
+        BigDecimal currentStrategyUnitFee = currentStrategy == null || currentStrategy.getMethod() == null
+                ? ZERO_AMOUNT : currentStrategy.getMethod();
+        log.info("服务费预估策略信息,entId:{}, strategyId:{}, strategyStatus:{}, unitFee:{}",
+                entId,
+                currentStrategy == null ? null : currentStrategy.getId(),
+                currentStrategy == null ? null : currentStrategy.getStatus(),
+                currentStrategyUnitFee);
+
+        // 本次预计服务费 = 采购数量 * 策略单价,金额统一保留2位
+        BigDecimal estimatedServiceFee = param.getPurchaseQuantity().multiply(currentStrategyUnitFee)
+                .setScale(2, RoundingMode.HALF_UP);
+
+        // 收费策略开关开启时,按服务费余额计算最大可购买数量
+        BigDecimal maxPurchaseQuantity = calculateMaxPurchaseQuantity(currentStrategy, currentStrategyUnitFee, serviceFeeBalance);
+        log.info("服务费预估计算完成,entId:{}, purchaseQuantity:{}, serviceFeeBalance:{}, estimatedServiceFee:{}, maxPurchaseQuantity:{}",
+                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);
+        return resp;
+    }
+
+
+    /**
+     * 贸易订单下单应用收费策略并冻结服务费
+     * 规则:
+     * 1. 收费策略开关未开启时,不冻结服务费,订单记录为未应用收费策略
+     * 2. 收费策略开关开启时,冻结金额=采购数量*策略单价,并记录策略信息
+     *
+     * @param param 冻结参数
+     * @return 收费策略应用结果
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public ParkingWalletFeeFreezeResult applyChargeStrategyFreeze(ParkingWalletFeeFreezeParam param) {
+        log.info("贸易订单应用收费策略开始,param:{}", param);
+        if (param == null || param.getProEntId() == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "采购方企业id不能为空");
+        }
+        if (param.getPurchaseQuantity() == null || param.getPurchaseQuantity().compareTo(ZERO_AMOUNT) < 0) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "采购数量不能为空且不能小于0");
+        }
+        if (param.getOrderNo() == null || param.getOrderNo().isBlank()) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "贸易订单号不能为空");
+        }
+
+        ParkingWalletFeeFreezeResult result = new ParkingWalletFeeFreezeResult();
+        result.setApplyChargeStrategy(Global.NO);
+        result.setFreezeAmount(ZERO_AMOUNT);
+
+        KwtParkingChargeStrategy currentStrategy = queryCurrentEnableStrategy(param.getProEntId());
+        if (currentStrategy == null || !Objects.equals(currentStrategy.getStatus(), STRATEGY_OPEN)) {
+            log.info("收费策略未开启或未匹配,跳过服务费冻结,proEntId:{}, orderNo:{}",
+                    param.getProEntId(), param.getOrderNo());
+            return result;
+        }
+
+        BigDecimal unitFee = currentStrategy.getMethod() == null ? ZERO_AMOUNT : currentStrategy.getMethod();
+        if (unitFee.compareTo(ZERO_AMOUNT) <= 0) {
+            log.info("收费策略单价无效,跳过服务费冻结,strategyId:{}, unitFee:{}",
+                    currentStrategy.getId(), unitFee);
+            return result;
+        }
+
+        BigDecimal freezeAmount = param.getPurchaseQuantity().multiply(unitFee).setScale(2, RoundingMode.HALF_UP);
+        KwtParkingWalletFee walletFee = queryWalletFee(param.getProEntId(), param.getSupEntId());
+        if (walletFee == null) {
+            log.warn("服务费账户不存在,proEntId:{}, supEntId:{}, orderNo:{}",
+                    param.getProEntId(), param.getSupEntId(), param.getOrderNo());
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "采购方服务费账户不存在");
+        }
+
+        BigDecimal currentBalance = walletFee.getServiceFeeBalance() == null ? ZERO_AMOUNT : walletFee.getServiceFeeBalance();
+        if (currentBalance.compareTo(freezeAmount) < 0) {
+            log.warn("服务费余额不足,proEntId:{}, orderNo:{}, balance:{}, freezeAmount:{}",
+                    param.getProEntId(), param.getOrderNo(), currentBalance, freezeAmount);
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费余额不足,无法下单");
+        }
+
+        BigDecimal currentTradingAmount = walletFee.getTradingAmount() == null ? ZERO_AMOUNT : walletFee.getTradingAmount();
+        walletFee.setServiceFeeBalance(currentBalance.subtract(freezeAmount));
+        walletFee.setTradingAmount(currentTradingAmount.add(freezeAmount));
+        parkingWalletFeeRepository.updateById(walletFee);
+
+        saveFreezeWalletBalanceLog(param, walletFee, freezeAmount);
+
+        result.setApplyChargeStrategy(Global.YES);
+        result.setChargeStrategyId(currentStrategy.getId());
+        result.setChargeStrategyDesc(buildStrategyMethodDesc(currentStrategy));
+        result.setFreezeAmount(freezeAmount);
+        result.setServiceFeeBalance(walletFee.getServiceFeeBalance());
+        log.info("贸易订单应用收费策略完成,orderNo:{}, result:{}", param.getOrderNo(), result);
+        return result;
+    }
+
+    /**
+     * 贸易订单审核拒绝解冻服务费
+     * 规则:根据订单冻结记录解冻,返还服务费余额并写入钱包日志
+     *
+     * @param param 解冻参数
+     * @return 解冻结果(含采购方服务费余额)
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public ParkingWalletFeeFreezeResult unfreezeChargeStrategy(ParkingWalletFeeFreezeParam param) {
+        log.info("贸易订单解冻服务费开始,param:{}", param);
+        validateUnfreezeParam(param);
+
+        ParkingWalletFeeFreezeResult result = new ParkingWalletFeeFreezeResult();
+        result.setApplyChargeStrategy(Global.YES);
+
+        if (existsUnfreezeRecord(param.getOrderNo(), param.getProEntId(), param.getSupEntId())) {
+            log.info("服务费已解冻,跳过重复处理,orderNo:{}, proEntId:{}", param.getOrderNo(), param.getProEntId());
+            result.setServiceFeeBalance(queryServiceFeeBalance(param.getProEntId(), param.getSupEntId()));
+            return result;
+        }
+
+        KwtParkingWalletFeeBalance freezeRecord = queryFreezeBalanceRecord(param);
+        if (freezeRecord == null) {
+            log.warn("未找到服务费冻结记录,orderNo:{}, proEntId:{}", param.getOrderNo(), param.getProEntId());
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费冻结记录不存在,无法解冻");
+        }
+
+        BigDecimal unfreezeAmount = freezeRecord.getTradeAmount() == null ? ZERO_AMOUNT : freezeRecord.getTradeAmount();
+        if (unfreezeAmount.compareTo(ZERO_AMOUNT) <= 0) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费冻结金额异常,无法解冻");
+        }
+
+        KwtParkingWalletFee walletFee = queryWalletFee(param.getProEntId(), param.getSupEntId());
+        if (walletFee == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "采购方服务费账户不存在");
+        }
+
+        BigDecimal currentTradingAmount = walletFee.getTradingAmount() == null ? ZERO_AMOUNT : walletFee.getTradingAmount();
+        if (currentTradingAmount.compareTo(unfreezeAmount) < 0) {
+            log.warn("服务费履约中金额不足,无法解冻,orderNo:{}, tradingAmount:{}, unfreezeAmount:{}",
+                    param.getOrderNo(), currentTradingAmount, unfreezeAmount);
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "服务费履约中金额不足,无法解冻");
+        }
+
+        BigDecimal currentBalance = walletFee.getServiceFeeBalance() == null ? ZERO_AMOUNT : walletFee.getServiceFeeBalance();
+        walletFee.setServiceFeeBalance(currentBalance.add(unfreezeAmount));
+        walletFee.setTradingAmount(currentTradingAmount.subtract(unfreezeAmount));
+        parkingWalletFeeRepository.updateById(walletFee);
+
+        saveUnfreezeWalletBalanceLog(param, walletFee, unfreezeAmount);
+        result.setFreezeAmount(unfreezeAmount);
+        result.setServiceFeeBalance(walletFee.getServiceFeeBalance());
+        log.info("贸易订单解冻服务费完成,orderNo:{}, unfreezeAmount:{}, balanceAfter:{}",
+                param.getOrderNo(), unfreezeAmount, walletFee.getServiceFeeBalance());
+        return result;
+    }
+
+    /**
+     * 查询采购方服务费余额
+     */
+    public BigDecimal queryPurchaserServiceFeeBalance(Long proEntId, Long supEntId) {
+        return queryServiceFeeBalance(proEntId, supEntId);
+    }
+
+    private void validateUnfreezeParam(ParkingWalletFeeFreezeParam param) {
+        if (param == null || param.getProEntId() == null) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "采购方企业id不能为空");
+        }
+        if (param.getOrderNo() == null || param.getOrderNo().isBlank()) {
+            throw new BusinessPlatfromException(ErrorCodeEnum.PARAM_ERROR, "贸易订单号不能为空");
+        }
+    }
+
+    private boolean existsUnfreezeRecord(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_UNFREEZE)
+                        .eq(KwtParkingWalletFeeBalance::getDelFlag, Global.NO)
+        ) > 0;
+    }
+
+    private KwtParkingWalletFeeBalance queryFreezeBalanceRecord(ParkingWalletFeeFreezeParam param) {
+        return parkingWalletFeeBalanceRepository.getOne(
+                Wrappers.<KwtParkingWalletFeeBalance>lambdaQuery()
+                        .eq(KwtParkingWalletFeeBalance::getOrderNo, param.getOrderNo())
+                        .eq(KwtParkingWalletFeeBalance::getProEntId, param.getProEntId())
+                        .eq(param.getSupEntId() != null, KwtParkingWalletFeeBalance::getSupEntId, param.getSupEntId())
+                        .eq(KwtParkingWalletFeeBalance::getTradeType, TRADE_TYPE_FREEZE)
+                        .eq(KwtParkingWalletFeeBalance::getDelFlag, Global.NO)
+                        .orderByDesc(KwtParkingWalletFeeBalance::getId)
+                        .last("limit 1"),
+                false
+        );
+    }
+
+    private void saveUnfreezeWalletBalanceLog(ParkingWalletFeeFreezeParam param,
+                                              KwtParkingWalletFee walletFee,
+                                              BigDecimal unfreezeAmount) {
+        Long operatorId = resolveOperatorId(param.getOperatorId());
+        Date now = new Date();
+        KwtParkingWalletFeeBalance balanceRecord = new KwtParkingWalletFeeBalance();
+        balanceRecord.setOrderNo(param.getOrderNo());
+        balanceRecord.setProEntId(param.getProEntId());
+        balanceRecord.setSupEntId(param.getSupEntId());
+        balanceRecord.setTradeType(TRADE_TYPE_UNFREEZE);
+        balanceRecord.setTradeAmount(unfreezeAmount);
+        balanceRecord.setServiceFeeBalance(walletFee.getServiceFeeBalance());
+        balanceRecord.setTradingAmount(walletFee.getTradingAmount());
+        balanceRecord.setRemark("贸易订单号:" + param.getOrderNo() + ",审核拒绝解冻服务费");
+        balanceRecord.setDelFlag(Global.NO);
+        balanceRecord.setCreateUser(operatorId);
+        balanceRecord.setUpdateUser(operatorId);
+        balanceRecord.setCreateTime(now);
+        balanceRecord.setUpdateTime(now);
+        parkingWalletFeeBalanceRepository.save(balanceRecord);
+        log.info("服务费解冻钱包日志已写入kwt_parking_wallet_fee_balance,orderNo:{}, proEntId:{}, supEntId:{}, tradeType:{}, tradeAmount:{}, balanceAfter:{}",
+                param.getOrderNo(), param.getProEntId(), param.getSupEntId(), TRADE_TYPE_UNFREEZE,
+                unfreezeAmount, walletFee.getServiceFeeBalance());
+    }
+
+    private String buildStrategyMethodDesc(KwtParkingChargeStrategy strategy) {
+        if (strategy == null || strategy.getMethod() == null) {
+            return null;
+        }
+        if (Objects.equals(ParkingChangeStrategyEnum.BY_TON.getCode(), strategy.getType())) {
+            return strategy.getMethod().stripTrailingZeros().toPlainString() + "元/吨";
+        }
+        if (Objects.equals(ParkingChangeStrategyEnum.BY_TIME.getCode(), strategy.getType())) {
+            return strategy.getMethod().stripTrailingZeros().toPlainString() + "元/次";
+        }
+        return strategy.getMethod().stripTrailingZeros().toPlainString() + "元/月";
+    }
+
+    /**
+     * 写入服务费钱包日志(kwt_parking_wallet_fee_balance)
+     */
+    private void saveFreezeWalletBalanceLog(ParkingWalletFeeFreezeParam param,
+                                            KwtParkingWalletFee walletFee,
+                                            BigDecimal freezeAmount) {
+        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_FREEZE);
+        balanceRecord.setTradeAmount(freezeAmount);
+        balanceRecord.setServiceFeeBalance(walletFee.getServiceFeeBalance());
+        balanceRecord.setTradingAmount(walletFee.getTradingAmount());
+        balanceRecord.setRemark("贸易订单号:" + param.getOrderNo() + ",下单冻结服务费");
+        balanceRecord.setDelFlag(Global.NO);
+        balanceRecord.setCreateUser(operatorId);
+        balanceRecord.setUpdateUser(operatorId);
+        balanceRecord.setCreateTime(now);
+        balanceRecord.setUpdateTime(now);
+        parkingWalletFeeBalanceRepository.save(balanceRecord);
+        log.info("服务费钱包日志已写入kwt_parking_wallet_fee_balance,orderNo:{}, proEntId:{}, supEntId:{}, tradeType:{}, tradeAmount:{}, balanceAfter:{}",
+                param.getOrderNo(), param.getProEntId(), param.getSupEntId(), TRADE_TYPE_FREEZE,
+                freezeAmount, walletFee.getServiceFeeBalance());
+    }
+
+    private KwtParkingWalletFee queryWalletFee(Long proEntId, Long supEntId) {
+        return parkingWalletFeeRepository.getOne(
+                Wrappers.<KwtParkingWalletFee>lambdaQuery()
+                        .eq(KwtParkingWalletFee::getProEntId, proEntId)
+                        .eq(supEntId != null, KwtParkingWalletFee::getSupEntId, supEntId)
+                        .eq(KwtParkingWalletFee::getDelFlag, Global.NO)
+                        .orderByDesc(KwtParkingWalletFee::getId)
+                        .last("limit 1"),
+                false
+        );
+    }
+
+    private Long resolveOperatorId(Long operatorId) {
+        Long loginUserId = LoginUserHolder.getUserId();
+        if (loginUserId != null) {
+            return loginUserId;
+        }
+        return operatorId == null ? -1L : operatorId;
+    }
+
+    /**
+     * 计算服务费余额支持的最大可购买数量
+     * 仅在收费策略开关开启且单价大于0时计算:服务费余额 / 策略单价(向下取整,保留4位小数)
+     *
+     * @param currentStrategy       当前收费策略
+     * @param currentStrategyUnitFee 策略单价
+     * @param serviceFeeBalance     服务费余额
+     * @return 最大可购买数量;策略未开启时不做余额约束,返回null
+     */
+    private BigDecimal calculateMaxPurchaseQuantity(KwtParkingChargeStrategy currentStrategy,
+                                                    BigDecimal currentStrategyUnitFee,
+                                                    BigDecimal serviceFeeBalance) {
+        if (currentStrategy == null || !Objects.equals(currentStrategy.getStatus(), STRATEGY_OPEN)
+                || currentStrategyUnitFee.compareTo(ZERO_AMOUNT) <= 0) {
+            log.info("最大可购买数量未计算,原因:{}",
+                    currentStrategy == null ? "未匹配到收费策略" : "收费策略未开启或单价<=0");
+            return null;
+        }
+        //向下取整
+        BigDecimal maxPurchaseQuantity = serviceFeeBalance.divide(currentStrategyUnitFee, 4, RoundingMode.DOWN);
+        log.info("最大可购买数量计算完成,serviceFeeBalance:{}, unitFee:{}, maxPurchaseQuantity:{}",
+                serviceFeeBalance, currentStrategyUnitFee, maxPurchaseQuantity);
+        return maxPurchaseQuantity.max(ZERO_AMOUNT);
+    }
+
+    /**
+     * 查询企业当前生效的收费策略
+     * 逻辑:先查企业绑定的策略,再查策略详情,最后取“开启状态”且id最大(最新)的一条
+     *
+     * @param proEntId 采购企业id
+     * @return 生效策略;未匹配到则返回null
+     */
+    private KwtParkingChargeStrategy queryCurrentEnableStrategy(Long proEntId) {
+        List<KwtParkingChargeStrategyUnit> strategyUnitList = parkingChangeStrategyUnitRepository.list(
+                Wrappers.<KwtParkingChargeStrategyUnit>lambdaQuery()
+                        .eq(KwtParkingChargeStrategyUnit::getEntId, proEntId)
+                        .eq(KwtParkingChargeStrategyUnit::getDelFlag, Global.NO)
+        );
+        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;
+        }
+        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;
+        }
+        List<KwtParkingChargeStrategy> strategyList = parkingChangeStrategyRepository.list(
+                Wrappers.<KwtParkingChargeStrategy>lambdaQuery()
+                        .in(KwtParkingChargeStrategy::getId, strategyIds)
+                        .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;
+        }
+        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;
+    }
+
+    /**
+     * 查询企业当前可用服务费余额
+     *
+     * @param proEntId 采购企业id
+     * @param supEntId 供应商企业id,可为空
+     * @return 服务费余额,无记录时返回0
+     */
+    private BigDecimal queryServiceFeeBalance(Long proEntId, Long supEntId) {
+        KwtParkingWalletFee walletFee = queryWalletFee(proEntId, supEntId);
+        if (walletFee == null || walletFee.getServiceFeeBalance() == null) {
+            log.info("查询服务费余额为空,按0处理,proEntId:{}, supEntId:{}", proEntId, supEntId);
+            return ZERO_AMOUNT;
+        }
+        log.info("查询服务费余额成功,proEntId:{}, supEntId:{}, serviceFeeBalance:{}",
+                proEntId, supEntId, walletFee.getServiceFeeBalance());
+        return walletFee.getServiceFeeBalance();
+    }
+
+
 }