longz
2026-07-30 d83061ad5d5088eb32c71c32d21dd5db278d503a
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java
@@ -13,6 +13,7 @@
import com.ruoyi.account.api.model.TAppUserIntegralChange;
import com.ruoyi.chargingPile.api.feignClient.ChargingGunClient;
import com.ruoyi.chargingPile.api.feignClient.SiteClient;
import com.ruoyi.chargingPile.api.model.Partner;
import com.ruoyi.chargingPile.api.model.Site;
import com.ruoyi.chargingPile.api.model.TChargingGun;
import com.ruoyi.common.core.domain.R;
@@ -39,10 +40,13 @@
import com.ruoyi.other.api.vo.ExchangeCodeConsumeResultVO;
import com.ruoyi.payment.api.feignClient.AliPaymentClient;
import com.ruoyi.payment.api.feignClient.H5AliPaymentClient;
import com.ruoyi.payment.api.feignClient.IcbcPaymentClient;
import com.ruoyi.payment.api.feignClient.WxPaymentClient;
import com.ruoyi.payment.api.model.RefundReq;
import com.ruoyi.payment.api.model.RefundResp;
import com.ruoyi.payment.api.model.WxPaymentRefundModel;
import com.ruoyi.payment.api.vo.IcbcRefundReq;
import com.ruoyi.payment.api.vo.IcbcRefundResp;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
@@ -77,6 +81,9 @@
@Slf4j
@Component
public class TaskUtil implements ApplicationListener<WebServerInitializedEvent> {
    private static final int WHITELIST_BENEFIT_ALL_FREE = 1;
    private static final int WHITELIST_BENEFIT_ELECTRIC_FREE = 2;
    private static final int WHITELIST_BENEFIT_SERVICE_FREE = 3;
    @Resource
    private TChargingBillService chargingBillService;
@@ -137,6 +144,9 @@
    private WxPaymentClient wxPaymentClient;
    @Resource
    private IcbcPaymentClient icbcPaymentClient;
    @Resource
    private AliPaymentClient aliPaymentClient;
    @Resource
    private H5AliPaymentClient h5AliPaymentClient;
@@ -152,14 +162,105 @@
    /**
     * 判断订单是否使用余额或余额组合支付
     *
     * @param payMethod 支付方式
     * @return true=余额相关支付方式
     */
    private boolean isBalancePayMode(Integer payMethod) {
        return payMethod != null && (payMethod == 1 || payMethod == 2);
    }
    /**
     * 将金额空值统一转换为两位小数,避免补偿结算金额计算出现空指针
     *
     * @param amount 原始金额
     * @return 非空两位小数金额
     */
    private BigDecimal money(BigDecimal amount) {
        return amount == null ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP) : amount.setScale(2, RoundingMode.HALF_UP);
    }
    /**
     * 获取第三方实付金额,兼容早期未写入third_pay_amount的纯第三方支付订单。
     *
     * @param order 充电订单
     * @return 第三方可退款本金
     */
    private BigDecimal getRefundableThirdPayAmount(TChargingOrder order) {
        if (order == null || isWhitelistAllFreeOrder(order)) {
            return BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
        }
        BigDecimal thirdPayAmount = money(order.getThirdPayAmount());
        if (thirdPayAmount.compareTo(BigDecimal.ZERO) > 0) {
            return thirdPayAmount;
        }
        if ("ICBC".equalsIgnoreCase(order.getSourceName())) {
            return money(order.getRechargeAmount())
                    .subtract(money(order.getBalancePayAmount()))
                    .max(BigDecimal.ZERO)
                    .setScale(2, RoundingMode.HALF_UP);
        }
        if (Integer.valueOf(3).equals(order.getPayMethod())) {
            return money(order.getRechargeAmount());
        }
        return thirdPayAmount;
    }
    /**
     * 判断订单是否为白名单订单
     *
     * @param order 充电订单
     * @return true=白名单订单
     */
    private boolean isWhitelistOrder(TChargingOrder order) {
        return order != null && Integer.valueOf(1).equals(order.getIsWhitelist());
    }
    /**
     * 判断订单是否为白名单全部免费权益
     *
     * @param order 充电订单
     * @return true=全部免费白名单订单
     */
    private boolean isWhitelistAllFreeOrder(TChargingOrder order) {
        return isWhitelistOrder(order)
                && Integer.valueOf(WHITELIST_BENEFIT_ALL_FREE).equals(order.getWhitelistBenefitType());
    }
    /**
     * 按白名单权益拆分补偿结算的电费和服务费减免金额,确保任务补偿与正常结算口径一致
     *
     * @param order 充电订单
     * @param electrovalence 原始电费
     * @param serviceCharge 原始服务费
     * @return 数组下标0=电费减免,下标1=服务费减免
     */
    private BigDecimal[] calculateWhitelistRefundSplit(TChargingOrder order, BigDecimal electrovalence, BigDecimal serviceCharge) {
        BigDecimal electricDiscount = BigDecimal.ZERO;
        BigDecimal serviceDiscount = BigDecimal.ZERO;
        if (!isWhitelistOrder(order)) {
            return new BigDecimal[]{electricDiscount, serviceDiscount};
        }
        if (Integer.valueOf(WHITELIST_BENEFIT_ALL_FREE).equals(order.getWhitelistBenefitType())) {
            electricDiscount = electrovalence == null ? BigDecimal.ZERO : electrovalence;
            serviceDiscount = serviceCharge == null ? BigDecimal.ZERO : serviceCharge;
        } else if (Integer.valueOf(WHITELIST_BENEFIT_ELECTRIC_FREE).equals(order.getWhitelistBenefitType())) {
            electricDiscount = electrovalence == null ? BigDecimal.ZERO : electrovalence;
        } else if (Integer.valueOf(WHITELIST_BENEFIT_SERVICE_FREE).equals(order.getWhitelistBenefitType())) {
            serviceDiscount = serviceCharge == null ? BigDecimal.ZERO : serviceCharge;
        }
        return new BigDecimal[]{electricDiscount, serviceDiscount};
    }
    /**
     * 记录兑换码余额退款流水
     *
     * @param order 充电订单
     * @param refundAmount 退款金额
     * @param refundNo 退款单号
     */
    private void recordBalanceRefund(TChargingOrder order, BigDecimal refundAmount, String refundNo) {
        if (order == null || order.getAppUserId() == null || order.getId() == null
                || refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
@@ -178,7 +279,7 @@
    @Scheduled(fixedRate = 1000 * 60)
    public void taskOneMinutes() {
        if(null != port && port == 5400){
        if (isScheduledTaskPort(port)) {
            List<TChargingOrder> upStatusChargingOrder = chargingOrderService.findUpStatusChargingOrder();
            log.info("定时任务执行,查询需要手动修改状态的订单数量:{}", upStatusChargingOrder.size());
            for (TChargingOrder order : upStatusChargingOrder) {
@@ -196,7 +297,7 @@
    //十分钟循环执行的定时任务
    @Scheduled(fixedRate = 1000 * 60 * 10)
    public void taskTenMinutes() {
        if(null != port && port == 5400){
        if (isScheduledTaskPort(port)) {
            List<TChargingOrder> failedStartupOrder = chargingOrderService.findFailedStartupOrder();
            log.info("定时任务执行,查询到启动失败的订单数量:{}", failedStartupOrder.size());
            for (TChargingOrder order : failedStartupOrder) {
@@ -291,12 +392,12 @@
                    R r = chargingOrderService.endChargeBillingCharge(vo);
                    log.info("定时任务执行,充电中的订单处理结果:{}", JSON.toJSONString(r));
                }else{
                    //判断充电流水超过15分钟没有更新,则判定为充电结束
                    //判断充电流水超过10分钟没有更新,则判定为充电结束
                    List<UploadRealTimeMonitoringData> dataList = uploadRealTimeMonitoringDataService.getDataByOrderCode(order.getCode());
                    if(!dataList.isEmpty()){
                        UploadRealTimeMonitoringData uploadRealTimeMonitoringData = dataList.get(0);
                        Date createTime = uploadRealTimeMonitoringData.getCreate_time();
                        if(System.currentTimeMillis() - createTime.getTime() - (8 * 60 * 60 * 1000) > 1000 * 60 * 30){
                        if(System.currentTimeMillis() - createTime.getTime() - (8 * 60 * 60 * 1000) > 1000 * 60 * 10){
                            endOrder(order);
                        }
                    }
@@ -323,7 +424,9 @@
    //定时任务添加结算账单汇总数据
    @Scheduled(cron = "0 30 0 * * ?")
    public void taskMonth() {
        if(null != port && port == 5400){
        if (isScheduledTaskPort(port)) {
            //推送三方引流平台数据
            pushOrderDrainagePlatform();
            //添加充电算账单汇总数据
            addChargingSettlementBill();
            //添加账户结算账单数据
@@ -334,7 +437,7 @@
    // 每个月最后一天23点执行的定时任务
    @Scheduled(cron = "0 0 23 * * ?")
    public void taskLastDay() {
        if(null != port && port == 5400){
        if (isScheduledTaskPort(port)) {
            try {
                int dayOfMonth = LocalDate.now().getDayOfMonth();
                int lastDayOfMonth = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
@@ -357,6 +460,10 @@
    public void onApplicationEvent(WebServerInitializedEvent event) {
        port = event.getWebServer().getPort();
        System.out.println("端口号:" + port);
    }
    static boolean isScheduledTaskPort(Integer serverPort) {
        return Integer.valueOf(5400).equals(serverPort) || Integer.valueOf(9400).equals(serverPort);
    }
@@ -456,7 +563,7 @@
                                BigDecimal serviceCharge = originalServicePrice;
                                BigDecimal vipDiscountAmount = BigDecimal.ZERO;
                                //计算优惠金额
                                if (null != order.getVipDiscount()) {
                                if (!isWhitelistOrder(order) && null != order.getVipDiscount()) {
                                    //0.58折
                                    vipDiscountAmount = serviceCharge.multiply(new BigDecimal(1).subtract(order.getVipDiscount()));
                                    serviceCharge = serviceCharge.multiply(order.getVipDiscount());
@@ -519,7 +626,7 @@
                    //计算优惠券
                    BigDecimal couponDiscount = BigDecimal.ZERO;
                    if (null != order.getAppCouponId()) {
                    if (!isWhitelistOrder(order) && null != order.getAppCouponId()) {
                        //判断实际充电金额是否满足优惠券使用条件,如果不满足则不适用优惠券。
                        TAppCoupon appCoupon = appCouponClient.getAppCouponById(order.getAppCouponId()).getData();
                        String couponJson = appCoupon.getCouponJson();
@@ -567,6 +674,17 @@
                            }
                        }
                    }
                    BigDecimal[] whitelistRefundSplit = calculateWhitelistRefundSplit(order, periodElectricPrice_total, serviceCharge_total);
                    BigDecimal electricDiscount = whitelistRefundSplit[0];
                    BigDecimal serviceDiscount = whitelistRefundSplit[1];
                    BigDecimal whitelistDiscount = electricDiscount.add(serviceDiscount).setScale(4, RoundingMode.HALF_EVEN);
                    if (whitelistDiscount.compareTo(BigDecimal.ZERO) > 0) {
                        // 补偿任务需与实时结算使用同一拆分口径,避免财务导出金额不一致。
                        periodElectricPrice_total = periodElectricPrice_total.subtract(electricDiscount).max(BigDecimal.ZERO);
                        serviceCharge_total = serviceCharge_total.subtract(serviceDiscount).max(BigDecimal.ZERO);
                        refundAmount = refundAmount.add(whitelistDiscount);
                        payAmount = payAmount.subtract(whitelistDiscount).max(BigDecimal.ZERO);
                    }
                    //优惠券优惠金额
                    couponDiscount = couponDiscount.setScale(4, RoundingMode.HALF_EVEN);
                    //退款金额+优惠券
@@ -577,6 +695,10 @@
                        serviceCharge_total = serviceCharge_total.subtract(couponDiscount);
                    }
                    order.setCouponDiscountAmount(couponDiscount.setScale(2, RoundingMode.HALF_EVEN));
                    order.setWhitelistDiscountAmount(whitelistDiscount.setScale(2, RoundingMode.HALF_EVEN));
                    order.setWhitelistElectricRefundAmount(electricDiscount.setScale(2, RoundingMode.HALF_EVEN));
                    order.setWhitelistServiceRefundAmount(serviceDiscount.setScale(2, RoundingMode.HALF_EVEN));
                    order.setElectrovalence(periodElectricPrice_total.setScale(2, RoundingMode.HALF_EVEN));
                    order.setServiceCharge(serviceCharge_total.setScale(2, RoundingMode.HALF_EVEN));
                    order.setPaymentAmount(payAmount.setScale(2, RoundingMode.HALF_EVEN));
                    order.setRefundAmount(refundAmount.setScale(2, RoundingMode.HALF_EVEN));
@@ -681,25 +803,28 @@
                        //用户推荐奖励
                        chargingOrderService.referralReward(order);
                         //开始构建退款费用
                        //开始构建退款费用
                        refundAmount = refundAmount.setScale(2, RoundingMode.HALF_EVEN);
                        if (refundAmount.compareTo(BigDecimal.ZERO) > 0) {
                            Integer rechargePaymentType = order.getRechargePaymentType();
                            Integer payMethod = order.getPayMethod();
                            BigDecimal thirdRefundAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
                            BigDecimal balanceRefundAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
                            if (Integer.valueOf(1).equals(payMethod)) {
                                balanceRefundAmount = refundAmount.min(money(order.getBalancePayAmount())).setScale(2, RoundingMode.HALF_UP);
                            } else if (Integer.valueOf(2).equals(payMethod)) {
                                thirdRefundAmount = refundAmount.min(money(order.getThirdPayAmount())).setScale(2, RoundingMode.HALF_UP);
                                balanceRefundAmount = refundAmount.subtract(thirdRefundAmount).min(money(order.getBalancePayAmount())).setScale(2, RoundingMode.HALF_UP);
                            } else {
                                thirdRefundAmount = refundAmount;
                            }
                        BigDecimal refundableThirdPayAmount = getRefundableThirdPayAmount(order);
                        BigDecimal remainingThirdAmount = refundableThirdPayAmount
                                .subtract(money(order.getThirdRefundAmount())).max(BigDecimal.ZERO);
                        BigDecimal remainingBalanceAmount = money(order.getBalancePayAmount())
                                .subtract(money(order.getBalanceRefundAmount())).max(BigDecimal.ZERO);
                        BigDecimal refundableAmount = remainingThirdAmount.add(remainingBalanceAmount).setScale(2, RoundingMode.HALF_UP);
                        // 白名单全部免费只记录预估预充金额,实际渠道退款金额保持0,防止补偿任务重复发起退款。
                        BigDecimal recordedRefundAmount = isWhitelistAllFreeOrder(order) ? money(order.getRechargeAmount()) : refundAmount;
                        BigDecimal channelRefundAmount = isWhitelistAllFreeOrder(order)
                                ? BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP)
                                : refundAmount.min(refundableAmount).setScale(2, RoundingMode.HALF_UP);
                        Integer rechargePaymentType = order.getRechargePaymentType();
                        BigDecimal thirdRefundAmount = channelRefundAmount.min(remainingThirdAmount).setScale(2, RoundingMode.HALF_UP);
                        BigDecimal balanceRefundAmount = channelRefundAmount.subtract(thirdRefundAmount)
                                .min(remainingBalanceAmount).setScale(2, RoundingMode.HALF_UP);
                        if (channelRefundAmount.compareTo(BigDecimal.ZERO) > 0) {
                            refundAmount = thirdRefundAmount.add(balanceRefundAmount).setScale(2, RoundingMode.HALF_UP);
                            order.setThirdRefundAmount(thirdRefundAmount);
                            order.setBalanceRefundAmount(balanceRefundAmount);
                            order.setRefundAmount(refundAmount);
                            order.setThirdRefundAmount(money(order.getThirdRefundAmount()).add(thirdRefundAmount));
                            order.setBalanceRefundAmount(money(order.getBalanceRefundAmount()).add(balanceRefundAmount));
                            order.setRefundAmount(recordedRefundAmount);
                            order.setRefundStatus(refundAmount.compareTo(BigDecimal.ZERO) > 0 ? 1 : 2);
                            chargingOrderService.updateById(order);
                            //构建退款明细
@@ -728,6 +853,25 @@
                                order.setRefundStatus(2);
                                order.setRefundTime(LocalDateTime.now());
                                chargingOrderService.updateById(order);
                            } else if ("ICBC".equalsIgnoreCase(order.getSourceName())) {
                                IcbcRefundReq req = new IcbcRefundReq();
                                req.setOutTradeNo(order.getCode());
                                req.setOrderId(order.getRechargeSerialNumber());
                                req.setOutRefundNo(chargingOrderRefund.getRefundCode());
                                req.setRefundAmount(thirdRefundAmount);
                                req.setTotalAmount(getRefundableThirdPayAmount(order));
                                req.setReason("充电完成退款");
                                R<IcbcRefundResp> result = icbcPaymentClient.refund(req);
                                IcbcRefundResp resp = result == null ? null : result.getData();
                                if (result != null && result.getCode() == 200 && resp != null && "SUCCESS".equals(resp.getStatus())) {
                                    chargingOrderRefundService.save(chargingOrderRefund);
                                    String refundId = StringUtils.isNotEmpty(resp.getRefundId()) ? resp.getRefundId() : resp.getOutRefundNo();
                                    chargingOrderService.chargingOrderStartupFailureWxRefund(
                                            chargingOrderRefund.getRefundCode(), refundId, "SUCCESS", resp.getRefundTime());
                                } else {
                                    log.error("工行充电完成退款失败,订单号:{},退款单号:{},返回:{}",
                                            order.getCode(), chargingOrderRefund.getRefundCode(), result == null ? null : result.getMsg());
                                }
                            } else if (1 == rechargePaymentType) {
                                WxPaymentRefundModel model = new WxPaymentRefundModel();
                                model.setOut_trade_no(order.getCode());
@@ -736,7 +880,7 @@
                                model.setNotify_url("/payment/wx/refund/notify");
                                WxPaymentRefundModel.RefundAmount amount = new WxPaymentRefundModel.RefundAmount();
                                amount.setRefund(thirdRefundAmount.multiply(new BigDecimal(100)).intValue());
                                amount.setTotal((isBalancePayMode(order.getPayMethod()) ? money(order.getThirdPayAmount()) : rechargeAmount).multiply(new BigDecimal(100)).intValue());
                                amount.setTotal(getRefundableThirdPayAmount(order).multiply(new BigDecimal(100)).intValue());
                                amount.setCurrency("CNY");
                                model.setAmount(amount);
                                R<Map<String, Object>> result = wxPaymentClient.refundOrderR(model);
@@ -774,6 +918,32 @@
                                    chargingOrderService.chargingOrderStartupFailureWxRefund(chargingOrderRefund.getRefundCode(), resp.getTradeNo(), "SUCCESS", null);
                                }
                            }
                        } else if (recordedRefundAmount.compareTo(BigDecimal.ZERO) > 0) {
                            order.setThirdRefundAmount(thirdRefundAmount);
                            order.setBalanceRefundAmount(balanceRefundAmount);
                            order.setRefundAmount(recordedRefundAmount);
                            order.setRefundStatus(2);
                            chargingOrderService.updateById(order);
                            TChargingOrderRefund chargingOrderRefund = new TChargingOrderRefund();
                            chargingOrderRefund.setChargingOrderId(order.getId());
                            SimpleDateFormat sdfaa = new SimpleDateFormat("yyyyMMddHHmmssSSS");
                            chargingOrderRefund.setRefundCode("CDF" + sdfaa.format(new Date()) + (Double.valueOf(Math.random() * 1000).intValue()));
                            chargingOrderRefund.setRefundAmount(recordedRefundAmount);
                            chargingOrderRefund.setBalanceRefundAmount(balanceRefundAmount);
                            chargingOrderRefund.setThirdRefundAmount(thirdRefundAmount);
                            chargingOrderRefund.setRefundStatus(2);
                            chargingOrderRefund.setRefundSerialNumber("WHITELIST");
                            chargingOrderRefund.setRefundTime(LocalDateTime.now());
                            chargingOrderRefund.setPayType(rechargePaymentType);
                            chargingOrderRefund.setCode(order.getCode());
                            chargingOrderRefund.setRefundTitle("充电完成退款");
                            chargingOrderRefund.setRefundContent("充电完成退款");
                            chargingOrderRefund.setRefundReason("充电完成退款");
                            chargingOrderRefund.setRefundRemark("白名单全部免费,记录预估退款金额");
                            chargingOrderRefund.setRefundTotalAmount(recordedRefundAmount);
                            chargingOrderRefund.setPayAmount(rechargeAmount);
                            chargingOrderRefundService.save(chargingOrderRefund);
                        }
                    }
@@ -823,29 +993,36 @@
     * 添加充电算账单汇总数据
     */
    public void addChargingSettlementBill(){
        int day = LocalDate.now().getDayOfMonth();
        int lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
        if (2 <= day && day <= lastDay) {
            refreshChargingSettlementBill(LocalDate.now());
        }
    }
    /**
     * 主动刷新指定月份的充电结算账单。只重建该月未出账数据,已出账数据不受影响。
     */
    public synchronized void refreshChargingSettlementBill(LocalDate billingMonth){
        try {
            int day = LocalDate.now().getDayOfMonth();
            int lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
            //时间在当月的2号到当月的最后一天才执行汇总数据和生成新的汇总数据(中间有修改数据的情况)
            if(2 <= day && day <= lastDay){
                // 获取上个月的开始和结束日期
                LocalDate firstDayOfLastMonth = LocalDate.now().minusMonths(1).
                        withDayOfMonth(2);
                String string = firstDayOfLastMonth.toString();
            if (billingMonth == null) {
                throw new IllegalArgumentException("账单月份不能为空");
            }
                LocalDate firstDayOfBillingMonth = billingMonth.withDayOfMonth(2);
                String string = firstDayOfBillingMonth.toString();
                // 将-替换为空字符串
                string = string.replace("-", "");
                //上个月
                String lastMonth = LocalDate.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyy-MM"));
                String targetMonth = billingMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                //统计数据的开始时间
                String startTime = lastMonth + "-01 00:00:00";
                String startTime = targetMonth + "-01 00:00:00";
                //统计数据的结束时间
                int lasDay = LocalDate.now().minusMonths(1).with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
                String endTime = lastMonth + "-" + String.format("%02d", lasDay) + " 23:59:59";
                LocalDateTime createTime = LocalDateTime.parse(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")) + "-02 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                int lastDay = billingMonth.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
                String endTime = targetMonth + "-" + String.format("%02d", lastDay) + " 23:59:59";
                LocalDateTime createTime = LocalDateTime.now();
                //删除之前生成的汇总数据,在添加最新的汇总数据
                String format = firstDayOfLastMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                String format = firstDayOfBillingMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                chargingSettlementBillService.remove(new QueryWrapper<TChargingSettlementBill>().eq("status", 1).last(" and DATE_FORMAT(billing_cycle, '%Y-%m') = '" + format + "'"));
                //循环生成按照开始时间和结束时间分别统计的汇总数据
@@ -858,7 +1035,7 @@
                            .collect(Collectors.joining()); // 将其连接成一个字符串
                    chargingSettlementBill.setSiteId(0);
                    chargingSettlementBill.setStatisticalDateType(i);
                    chargingSettlementBill.setBillingCycle(firstDayOfLastMonth);
                    chargingSettlementBill.setBillingCycle(firstDayOfBillingMonth);
                    chargingSettlementBill.setStatus(1);
                    chargingSettlementBill.setCode("JSD"+string+randomDigits);
@@ -893,7 +1070,7 @@
                                .collect(Collectors.joining()); // 将其连接成一个字符串
                        chargingSettlementBill1.setSiteId(Integer.valueOf(map.get("siteId").toString()));
                        chargingSettlementBill1.setStatisticalDateType(i);
                        chargingSettlementBill1.setBillingCycle(firstDayOfLastMonth);
                        chargingSettlementBill1.setBillingCycle(firstDayOfBillingMonth);
                        chargingSettlementBill1.setStatus(1);
                        chargingSettlementBill1.setCode("JSD"+string+randomDigits1);
                        chargingSettlementBill1.setChargingDuration(charging_duration);
@@ -935,9 +1112,9 @@
                    chargingSettlementBillService.save(chargingSettlementBill);
                    chargingSettlementBillService.saveBatch(list);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("生成{}充电结算账单失败", billingMonth, e);
            throw new RuntimeException("生成充电结算账单失败", e);
        }
    }
@@ -947,33 +1124,42 @@
     * 添加账户结算账单数据
     */
    public void addAccountSettlementStatement(){
        int day = LocalDate.now().getDayOfMonth();
        int lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
        //时间在当月的2号到当月的最后一天才执行汇总数据和生成新的汇总数据(中间有修改数据的情况)
        if (2 <= day && day <= lastDay) {
            refreshAccountSettlementStatement(LocalDate.now().minusMonths(1));
        }
    }
    /**
     * 主动刷新指定月份的账户结算账单。只重建该月未出账数据,已出账数据不受影响。
     */
    public synchronized void refreshAccountSettlementStatement(LocalDate billingMonth) {
        try {
            int day = LocalDate.now().getDayOfMonth();
            int lastDay = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
            //时间在当月的2号到当月的最后一天才执行汇总数据和生成新的汇总数据(中间有修改数据的情况)
            if(2 <= day && day <= lastDay){
                // 获取上个月的开始和结束日期
                LocalDate firstDayOfLastMonth = LocalDate.now().minusMonths(1).
                        withDayOfMonth(2);
                String string = firstDayOfLastMonth.toString();
                if (billingMonth == null) {
                    throw new IllegalArgumentException("账单月份不能为空");
                }
                LocalDate firstDayOfBillingMonth = billingMonth.withDayOfMonth(2);
                String string = firstDayOfBillingMonth.toString();
                // 将-替换为空字符串
                string = string.replace("-", "");
                //上个月
                String lastMonth = LocalDate.now().minusMonths(1).format(DateTimeFormatter.ofPattern("yyyy-MM"));
                String targetMonth = billingMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                //统计数据的开始时间
                String startTime = lastMonth + "-01 00:00:00";
                String startTime = targetMonth + "-01 00:00:00";
                //统计数据的结束时间
                int lasDay = LocalDate.now().minusMonths(1).with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
                String endTime = lastMonth + "-" + String.format("%02d", lasDay) + " 23:59:59";
                LocalDateTime createTime = LocalDateTime.parse(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")) + "-02 00:00:00", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                int targetLastDay = billingMonth.with(TemporalAdjusters.lastDayOfMonth()).getDayOfMonth();
                String endTime = targetMonth + "-" + String.format("%02d", targetLastDay) + " 23:59:59";
                LocalDateTime createTime = LocalDateTime.now();
                //删除之前生成的汇总数据,在添加最新的汇总数据
                String format = firstDayOfLastMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                String format = firstDayOfBillingMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
                accountSettlementStatementService.remove(new QueryWrapper<TAccountSettlementStatement>().eq("status", 1).last(" and DATE_FORMAT(billing_cycle, '%Y-%m') = '" + format + "'"));
                //循环生成账户类型分别统计的汇总数据
                for (int i = 1; i < 3; i++) {
                Map<Integer, BigDecimal> siteServiceShareRatioMap = loadSiteServiceShareRatioMap();
                // V2.0 账户类型统一为工商银行商户,只生成明星充电小程序订单账单。
                for (int i = 3; i < 4; i++) {
                    TAccountSettlementStatement accountSettlementStatement = new TAccountSettlementStatement();
                    // 订单生成规则JSD+20231201(账单所属月份)+1131304205(随机10位数)+001(当月账单序号,每月重置)
                    Random random = new Random();
@@ -982,47 +1168,54 @@
                            .collect(Collectors.joining()); // 将其连接成一个字符串
                    accountSettlementStatement.setSiteId(0);
                    accountSettlementStatement.setAccountType(i);
                    accountSettlementStatement.setBillingCycle(firstDayOfLastMonth);
                    accountSettlementStatement.setBillingCycle(firstDayOfBillingMonth);
                    accountSettlementStatement.setStatus(1);
                    accountSettlementStatement.setCode("JSD"+string+randomDigits);
                    //根据业务订单查询汇总数据
                    List<Map<String, Object>> mapList = chargingOrderService.getAccountSettlementStatement(startTime, endTime, i);
                    List<Map<String, Object>> mapList = chargingOrderService.getAccountSettlementStatement(startTime, endTime);
                    BigDecimal rechargeAmount = BigDecimal.ZERO;
                    BigDecimal refundAmount = BigDecimal.ZERO;
                    BigDecimal platformFee = BigDecimal.ZERO;
                    BigDecimal tripartiteCommission = BigDecimal.ZERO;
                    BigDecimal recordedAmount = BigDecimal.ZERO;
                    BigDecimal platformIncome = BigDecimal.ZERO;
                    BigDecimal partnerIncome = BigDecimal.ZERO;
                    List<TAccountSettlementStatement> list = new ArrayList<>();
                    for (Map<String, Object> map : mapList) {
                        Integer siteId = Integer.valueOf(map.get("siteId").toString());
                        BigDecimal recharge_amount = null == map.get("rechargeAmount") ? BigDecimal.ZERO : new BigDecimal(map.get("rechargeAmount").toString());
                        BigDecimal refund_amount = null == map.get("refundAmount") ? BigDecimal.ZERO : new BigDecimal(map.get("refundAmount").toString());
                        BigDecimal platform_fee = null == map.get("platformFee") ? BigDecimal.ZERO : new BigDecimal(map.get("platformFee").toString());
                        BigDecimal tripartite_commission = null == map.get("tripartiteCommission") ? BigDecimal.ZERO : new BigDecimal(map.get("tripartiteCommission").toString());
                        BigDecimal recorded_amount = recharge_amount.subtract(refund_amount).subtract(platform_fee).subtract(tripartite_commission);
                        BigDecimal service_amount = null == map.get("serviceAmount") ? BigDecimal.ZERO : new BigDecimal(map.get("serviceAmount").toString());
                        BigDecimal partner_income = calculateAccountPartnerIncome(
                                service_amount, siteServiceShareRatioMap.get(siteId), false);
                        BigDecimal platform_income = calculateAccountPlatformIncome(recharge_amount, partner_income);
                        BigDecimal recorded_amount = recharge_amount.subtract(refund_amount);
                        //构建每个站点的汇总数据
                        TAccountSettlementStatement accountSettlementStatement1 = new TAccountSettlementStatement();
                        String randomDigits1 = random.ints(10, 0, 10) // 生成10个随机数字,范围在0-9
                                .mapToObj(String::valueOf)
                                .collect(Collectors.joining()); // 将其连接成一个字符串
                        accountSettlementStatement1.setSiteId(Integer.valueOf(map.get("siteId").toString()));
                        accountSettlementStatement1.setSiteId(siteId);
                        accountSettlementStatement1.setAccountType(i);
                        accountSettlementStatement1.setBillingCycle(firstDayOfLastMonth);
                        accountSettlementStatement1.setBillingCycle(firstDayOfBillingMonth);
                        accountSettlementStatement1.setStatus(1);
                        accountSettlementStatement1.setCode("JSD"+string+randomDigits1);
                        accountSettlementStatement1.setRechargeAmount(recharge_amount.setScale(2, RoundingMode.HALF_EVEN));
                        accountSettlementStatement1.setRefundAmount(refund_amount.setScale(2, RoundingMode.HALF_EVEN));
                        accountSettlementStatement1.setPlatformFee(platform_fee.setScale(2, RoundingMode.HALF_EVEN));
                        accountSettlementStatement1.setTripartiteCommission(tripartite_commission.setScale(2, RoundingMode.HALF_EVEN));
                        accountSettlementStatement1.setPlatformFee(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_EVEN));
                        accountSettlementStatement1.setTripartiteCommission(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_EVEN));
                        accountSettlementStatement1.setRecordedAmount(recorded_amount.setScale(2, RoundingMode.HALF_EVEN));
                        accountSettlementStatement1.setPlatformIncome(platform_income.setScale(2, RoundingMode.HALF_UP));
                        accountSettlementStatement1.setPartnerIncome(partner_income.setScale(2, RoundingMode.HALF_UP));
                        accountSettlementStatement1.setCreateTime(createTime);
                        list.add(accountSettlementStatement1);
                        //计算全站汇总数据
                        rechargeAmount = rechargeAmount.add(recharge_amount);
                        refundAmount = refundAmount.add(refund_amount);
                        platformFee = platformFee.add(platform_fee);
                        tripartiteCommission = tripartiteCommission.add(tripartite_commission);
                        recordedAmount = recordedAmount.add(recorded_amount);
                        platformIncome = platformIncome.add(platform_income);
                        partnerIncome = partnerIncome.add(partner_income);
                    }
                    accountSettlementStatement.setRechargeAmount(rechargeAmount.setScale(2, RoundingMode.HALF_EVEN));
@@ -1030,13 +1223,69 @@
                    accountSettlementStatement.setPlatformFee(platformFee.setScale(2, RoundingMode.HALF_EVEN));
                    accountSettlementStatement.setTripartiteCommission(tripartiteCommission.setScale(2, RoundingMode.HALF_EVEN));
                    accountSettlementStatement.setRecordedAmount(recordedAmount.setScale(2, RoundingMode.HALF_EVEN));
                    accountSettlementStatement.setPlatformIncome(platformIncome.setScale(2, RoundingMode.HALF_UP));
                    accountSettlementStatement.setPartnerIncome(partnerIncome.setScale(2, RoundingMode.HALF_UP));
                    accountSettlementStatement.setCreateTime(createTime);
                    accountSettlementStatementService.save(accountSettlementStatement);
                    accountSettlementStatementService.saveBatch(list);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("生成{}账户结算账单失败", billingMonth, e);
            throw new RuntimeException("生成账户结算账单失败", e);
        }
    }
    static BigDecimal calculateAccountPartnerIncome(BigDecimal paidServiceAmount, BigDecimal serviceShareRatio,
                                                     boolean thirdPartyOrder) {
        if (thirdPartyOrder || paidServiceAmount == null || serviceShareRatio == null
                || serviceShareRatio.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        return paidServiceAmount.multiply(serviceShareRatio)
                .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
    }
    static BigDecimal calculateAccountPlatformIncome(BigDecimal paymentAmount, BigDecimal partnerIncome) {
        BigDecimal paid = paymentAmount == null ? BigDecimal.ZERO : paymentAmount;
        BigDecimal partner = partnerIncome == null ? BigDecimal.ZERO : partnerIncome;
        return paid.subtract(partner).setScale(2, RoundingMode.HALF_UP);
    }
    private Map<Integer, BigDecimal> loadSiteServiceShareRatioMap() {
        List<Site> sites = Optional.ofNullable(siteClient.getSiteAll())
                .map(R::getData).orElse(Collections.emptyList());
        List<Partner> partners = Optional.ofNullable(siteClient.getAllPartner())
                .map(R::getData).orElse(Collections.emptyList());
        Map<Integer, Partner> partnerMap = partners.stream()
                .filter(partner -> partner != null && partner.getId() != null)
                .collect(Collectors.toMap(Partner::getId, partner -> partner, (left, right) -> left));
        Map<Integer, BigDecimal> ratioMap = new HashMap<>();
        for (Site site : sites) {
            if (site == null || site.getId() == null || site.getPartnerId() == null) {
                continue;
            }
            Partner partner = partnerMap.get(site.getPartnerId());
            if (partner != null && partner.getServiceShareRatio() != null) {
                ratioMap.put(site.getId(), partner.getServiceShareRatio());
            }
        }
        return ratioMap;
    }
    /**
     * 推送三方引流平台充电数据
     */
    public void pushOrderDrainagePlatform(){
        String format = LocalDateTime.now().minusDays(1).format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        List<TChargingOrder> list = chargingOrderService.list(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getStatus, 5)
                .eq(TChargingOrder::getOrderSource, 2).eq(TChargingOrder::getDelFlag, 0)
                .isNotNull(TChargingOrder::getStartChargeSeq).isNotNull(TChargingOrder::getOperatorId)
                .last(" and DATE_FORMAT(end_time, '%Y-%m-%d') = '" + format + "'"));
        for (TChargingOrder tChargingOrder : list) {
            String code = tChargingOrder.getCode();
            chargingOrderService.pushOrderInfo(code);
        }
    }
}