Ver código fonte

合并下单逻辑

donglang 8 horas atrás
pai
commit
f839d40fa5

+ 6 - 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,14 @@ 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

@@ -171,6 +171,12 @@ 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

@@ -2470,6 +2470,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());
     }
 
     /**
@@ -3074,7 +3075,7 @@ public class KwoTradeOrderService {
      * <p>
      * 触发条件:所有运单完成且订单 applyChargeStrategy=1(下单时已冻结服务费)。
      * 运输净重量:汇总已完成运单子任务的装/卸货净重(与看板运单完成量统计口径一致)。
-     * 扣减逻辑:运输净重量 × 采购方当前最优收费策略单价,由 transport 模块计算并写入钱包明细备注。
+     * 扣减逻辑:运输净重量 × 订单下单时快照的收费策略单价(无快照时回退当前策略)
      *
      * @param order             贸易订单
      * @param logisticOrderList 关联物流单列表(用于日志,净重量以运单统计为准)
@@ -3114,9 +3115,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:{}, 收费策略描述:{}, 扣减后服务费余额:{}",

+ 44 - 19
sckw-modules/sckw-transport/src/main/java/com/sckw/transport/service/ParkingWalletFeeService.java

@@ -345,7 +345,7 @@ public class ParkingWalletFeeService {
      * 业务规则:
      * 1. 服务费余额取当前登录采购企业的可用服务费余额;
      * 2. 收费策略总开关关闭时,不计算预计服务费,最大可购买数量返回 null,表示不做余额约束;
-     * 3. 收费策略总开关开启时,按企业绑定的当前收费策略计算预计服务费;
+     * 3. 收费策略总开关开启时,按企业绑定的当前收费策略计算预计服务费;无企业策略时使用开关表 default_fee;
      * 4. 策略 method 为 0.00 时,实际单价使用 kwt_parking_strategy_switch.default_fee;
      * 5. 最大可购买数量 = 服务费余额 / 实际策略单价,向下取整,避免超出余额。
      *
@@ -372,7 +372,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:{}, 策略原始单价:{}, 实际计费单价:{}",
@@ -389,8 +389,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);
 
@@ -474,6 +474,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);
@@ -706,8 +707,8 @@ public class ParkingWalletFeeService {
     /**
      * 解析订单完结服务费扣减信息
      * <p>
-     * 扣减金额 = 运输净重量 × 采购方当前最优收费策略单价;
-     * 最优策略:企业已绑定且开启的策略中,单价最低的一条(对采购方最优惠)。
+     * 扣减金额 = 运输净重量 × 收费策略单价;
+     * 单价优先取订单下单快照(chargeStrategyAmount),无快照时再取采购方当前最优策略;
      * 若无法匹配策略,则回退为下单冻结金额。
      *
      * @param param        结算参数(actualQuantity 为运输净重量)
@@ -716,6 +717,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());
@@ -884,6 +896,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)
      */
@@ -933,23 +952,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;
         }
         //向下取整
@@ -980,10 +995,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();