Procházet zdrojové kódy

fix(payment): 修正充电金额计算公式并优化跨时段费用计算

- 更新充电金额计算公式,改为先计算可充电度数再换算基础费用金额
- 修正跨时段计算逻辑,按当前时段价格及费用比例计算可用余额
- 优化当前时段的运营费和增值费用查询与处理方式
- 增加日志记录,方便调试时段费用和计算结果
- 处理无当前时段价格及基础价格为0的异常情况,避免计算错误
- 移除多时段循环计算,简化为单时段比例计算逻辑
SheepHy před 3 týdny
rodič
revize
cab0d396cc

+ 5 - 6
src/main/java/com/zsElectric/boot/business/service/AppletHomeService.java

@@ -82,13 +82,12 @@ public interface AppletHomeService {
 
     /**
      * 计算当前用户可用充电金额
-     * 公式:可用余额 = 当前用户余额 - 安全价 - [((当前用户余额 - 安全价) / (电费 + 服务费)) × (运营费 + 增值费用)]
+     * 公式:
+     * 1. 可充电度数 = (用户余额 - 安全价) / (电费 + 服务费 + 运营费 + 增值费)
+     * 2. 可用充电金额 = 可充电度数 × (电费 + 服务费)
      * 说明:
-     * 1. 先计算可充电度数 = (余额 - 安全价) / (电费 + 服务费)
-     * 2. 根据度数计算运营费总额 = 度数 × 运营费
-     * 3. 根据度数计算增值费用总额 = 度数 × 增值费用
-     * 4. 可用余额 = 余额 - 安全价 - 运营费总额 - 增值费用总额
-     * 注意:需要处理跨时段情况,不同时段价格不同,需要分段计算
+     * - 先按总单价计算能充多少度电,再换算为基础费用金额
+     * - 这样确保度数计算时已包含所有费用,避免重复扣费
      *
      * @param connectorCode 充电设备接口
      * @return 可用充电金额(元)

+ 99 - 94
src/main/java/com/zsElectric/boot/business/service/impl/AppletHomeServiceImpl.java

@@ -881,110 +881,115 @@ public class AppletHomeServiceImpl implements AppletHomeService {
                         .last("LIMIT 1")
         );
 
-        // 7. 跨时段计算可用充电金额
-        // 公式:可用余额 = 当前用户余额 - 安全价 - [(度数 × 运营费) + (度数 × 增值费用)]
-        // 其中:度数 = (余额 - 安全价) / (电费 + 服务费)
-
-        BigDecimal remainingBalance = balanceAfterSafety; // 剩余可用余额(用于计算度数)
-        BigDecimal totalOpFeeCost = BigDecimal.ZERO; // 总运营费
-        BigDecimal totalValueAddedCost = BigDecimal.ZERO; // 总增值费用
-
-        // 遍历每个时段,计算在该时段能充多少度电及对应的费用
-        for (ThirdPartyPolicyInfo policyInfo : policyInfoList) {
-            // 获取电费、服务费
-            BigDecimal elecPrice = policyInfo.getElecPrice() != null ? policyInfo.getElecPrice() : BigDecimal.ZERO;
-            BigDecimal servicePrice = policyInfo.getServicePrice() != null ? policyInfo.getServicePrice() : BigDecimal.ZERO;
-            BigDecimal basePrice = elecPrice.add(servicePrice); // 基础价格 = 电费 + 服务费
-
-            if (basePrice.compareTo(BigDecimal.ZERO) == 0) {
-                continue; // 跳过基础价格为0的时段
-            }
-
-            // 查询该时段的运营费
-            PolicyFee policyFee;
-            if (userFirm != null) {
-                policyFee = policyFeeMapper.selectOne(
-                        new LambdaQueryWrapper<PolicyFee>()
-                                .eq(PolicyFee::getStationInfoId, stationInfo.getId())
-                                .eq(PolicyFee::getPeriodFlag, policyInfo.getPeriodFlag())
-                                .eq(PolicyFee::getSalesType, 1)
-                                .eq(PolicyFee::getFirmId, userFirm.getFirmId())
-                                .eq(PolicyFee::getIsDeleted, 0)
-                                .last("LIMIT 1")
-                );
-            } else {
-                policyFee = policyFeeMapper.selectOne(
-                        new LambdaQueryWrapper<PolicyFee>()
-                                .eq(PolicyFee::getStationInfoId, stationInfo.getId())
-                                .eq(PolicyFee::getPeriodFlag, policyInfo.getPeriodFlag())
-                                .eq(PolicyFee::getSalesType, 0)
-                                .eq(PolicyFee::getIsDeleted, 0)
-                                .last("LIMIT 1")
-                );
-            }
-
-            BigDecimal opFee = (policyFee != null && policyFee.getOpFee() != null)
-                    ? policyFee.getOpFee()
-                    : getDefaultOpFee();
-
-            // 查询增值费用
-            BigDecimal valueAddedFee = BigDecimal.ZERO;
-            if (policyInfo.getPeriodFlag() != null) {
-                String periodLabel = "";
-                switch (policyInfo.getPeriodFlag()) {
-                    case 1: periodLabel = "尖"; break;
-                    case 2: periodLabel = "峰"; break;
-                    case 3: periodLabel = "平"; break;
-                    case 4: periodLabel = "谷"; break;
-                }
-
-                if (!periodLabel.isEmpty()) {
-                    DictItem valueAddedItem = dictItemMapper.selectOne(
-                            new LambdaQueryWrapper<DictItem>()
-                                    .eq(DictItem::getDictCode, "time_period_flag")
-                                    .eq(DictItem::getLabel, periodLabel)
-                                    .eq(DictItem::getStatus, 1)
-                                    .last("LIMIT 1")
-                    );
-                    if (valueAddedItem != null && valueAddedItem.getValue() != null) {
-                        valueAddedFee = new BigDecimal(valueAddedItem.getValue());
-                    }
-                }
-            }
-
-            // 计算在当前时段能充多少度电(使用剩余余额)
-            // 度数 = 剩余余额 / (电费 + 服务费)
-            BigDecimal kwh = remainingBalance.divide(basePrice, 4, RoundingMode.HALF_UP);
-
-            // 如果剩余余额不足以充电,结束计算
-            if (kwh.compareTo(BigDecimal.ZERO) <= 0) {
+        // 7. 计算可用充电金额
+        // 正确公式:可用余额 = (用户余额 - 安全价) × 基础费率占比
+        // 其中:基础费率占比 = (电费 + 服务费) / (电费 + 服务费 + 运营费 + 增值费)
+        // 这样计算确保度数计算时已包含所有费用,避免重复扣费
+
+        // 获取当前时段的价格信息(用于计算费率占比)
+        String currentTimeStr = LocalTime.now().format(TIME_FORMATTER);
+        ThirdPartyPolicyInfo currentPolicyInfo = null;
+        for (int i = 0; i < policyInfoList.size(); i++) {
+            ThirdPartyPolicyInfo policyInfo = policyInfoList.get(i);
+            String startTime = policyInfo.getStartTime();
+            // 下一个时段的开始时间作为当前时段的结束时间
+            String endTime = (i + 1 < policyInfoList.size()) 
+                    ? policyInfoList.get(i + 1).getStartTime() 
+                    : "240000";
+            
+            // 判断当前时间是否在该时段内
+            if (currentTimeStr.compareTo(startTime) >= 0 && currentTimeStr.compareTo(endTime) < 0) {
+                currentPolicyInfo = policyInfo;
                 break;
             }
+        }
+        // 如果没找到当前时段,使用第一个时段
+        if (currentPolicyInfo == null && !policyInfoList.isEmpty()) {
+            currentPolicyInfo = policyInfoList.get(0);
+        }
+        if (currentPolicyInfo == null) {
+            log.warn("无法确定当前时段价格 - connectorId: {}", connectorCode);
+            return BigDecimal.ZERO;
+        }
 
-            // 计算该时段的运营费和增值费用
-            BigDecimal periodOpFeeCost = kwh.multiply(opFee);
-            BigDecimal periodValueAddedCost = kwh.multiply(valueAddedFee);
+        // 获取电费、服务费
+        BigDecimal elecPrice = currentPolicyInfo.getElecPrice() != null ? currentPolicyInfo.getElecPrice() : BigDecimal.ZERO;
+        BigDecimal servicePrice = currentPolicyInfo.getServicePrice() != null ? currentPolicyInfo.getServicePrice() : BigDecimal.ZERO;
+        BigDecimal basePrice = elecPrice.add(servicePrice); // 基础价格 = 电费 + 服务费
 
-            totalOpFeeCost = totalOpFeeCost.add(periodOpFeeCost);
-            totalValueAddedCost = totalValueAddedCost.add(periodValueAddedCost);
+        if (basePrice.compareTo(BigDecimal.ZERO) == 0) {
+            log.warn("基础价格为0 - connectorId: {}", connectorCode);
+            return BigDecimal.ZERO;
+        }
 
-            // 更新剩余余额(减去当前时段的所有费用)
-            // 当前时段总费用 = (电费 + 服务费) * 度数 + 运营费 + 增值费用
-            BigDecimal periodBaseCost = kwh.multiply(basePrice);
-            BigDecimal periodTotalCost = periodBaseCost.add(periodOpFeeCost).add(periodValueAddedCost);
-            remainingBalance = remainingBalance.subtract(periodTotalCost);
+        // 查询该时段的运营费
+        PolicyFee policyFee;
+        if (userFirm != null) {
+            policyFee = policyFeeMapper.selectOne(
+                    new LambdaQueryWrapper<PolicyFee>()
+                            .eq(PolicyFee::getStationInfoId, stationInfo.getId())
+                            .eq(PolicyFee::getPeriodFlag, currentPolicyInfo.getPeriodFlag())
+                            .eq(PolicyFee::getSalesType, 1)
+                            .eq(PolicyFee::getFirmId, userFirm.getFirmId())
+                            .eq(PolicyFee::getIsDeleted, 0)
+                            .last("LIMIT 1")
+            );
+        } else {
+            policyFee = policyFeeMapper.selectOne(
+                    new LambdaQueryWrapper<PolicyFee>()
+                            .eq(PolicyFee::getStationInfoId, stationInfo.getId())
+                            .eq(PolicyFee::getPeriodFlag, currentPolicyInfo.getPeriodFlag())
+                            .eq(PolicyFee::getSalesType, 0)
+                            .eq(PolicyFee::getIsDeleted, 0)
+                            .last("LIMIT 1")
+            );
+        }
 
-            log.debug("时段计算 - startTime: {}, 电费: {}, 服务费: {}, 运营费: {}, 增值费: {}, 度数: {}, 该时段费用: {}, 剩余余额: {}",
-                    policyInfo.getStartTime(), elecPrice, servicePrice, opFee, valueAddedFee, kwh, periodTotalCost, remainingBalance);
+        BigDecimal opFee = (policyFee != null && policyFee.getOpFee() != null)
+                ? policyFee.getOpFee()
+                : getDefaultOpFee();
+
+        // 查询增值费用
+        BigDecimal valueAddedFee = BigDecimal.ZERO;
+        if (currentPolicyInfo.getPeriodFlag() != null) {
+            String periodLabel = "";
+            switch (currentPolicyInfo.getPeriodFlag()) {
+                case 1: periodLabel = "尖"; break;
+                case 2: periodLabel = "峰"; break;
+                case 3: periodLabel = "平"; break;
+                case 4: periodLabel = "谷"; break;
+            }
 
-            // 如果剩余余额小于等于0,结束计算
-            if (remainingBalance.compareTo(BigDecimal.ZERO) <= 0) {
-                break;
+            if (!periodLabel.isEmpty()) {
+                DictItem valueAddedItem = dictItemMapper.selectOne(
+                        new LambdaQueryWrapper<DictItem>()
+                                .eq(DictItem::getDictCode, "time_period_flag")
+                                .eq(DictItem::getLabel, periodLabel)
+                                .eq(DictItem::getStatus, 1)
+                                .last("LIMIT 1")
+                );
+                if (valueAddedItem != null && valueAddedItem.getValue() != null) {
+                    valueAddedFee = new BigDecimal(valueAddedItem.getValue());
+                }
             }
         }
 
-        // 8. 计算最终可用余额
-        BigDecimal availableAmount = userBalance.subtract(safetyFee).subtract(totalOpFeeCost).subtract(totalValueAddedCost);
+        // 计算总单价(每度电的总费用)
+        BigDecimal totalUnitPrice = basePrice.add(opFee).add(valueAddedFee);
+        
+        // 计算可充电度数 = (余额 - 安全价) / 总单价
+        BigDecimal kwh = balanceAfterSafety.divide(totalUnitPrice, 4, RoundingMode.HALF_UP);
+        
+        // 计算可用于基础费用(电费+服务费)的金额 = 度数 × 基础价格
+        // 这就是实际可用于充电的金额(不含运营费和增值费)
+        BigDecimal availableAmount = kwh.multiply(basePrice);
+        
+        // 计算运营费和增值费用(用于日志记录)
+        BigDecimal totalOpFeeCost = kwh.multiply(opFee);
+        BigDecimal totalValueAddedCost = kwh.multiply(valueAddedFee);
+
+        log.debug("时段计算 - 电费: {}, 服务费: {}, 运营费: {}, 增值费: {}, 总单价: {}, 可充度数: {}, 基础费用: {}",
+                elecPrice, servicePrice, opFee, valueAddedFee, totalUnitPrice, kwh, availableAmount);
 
         // 确保不为负数
         if (availableAmount.compareTo(BigDecimal.ZERO) < 0) {