puzhibing
2026-09-29 a70433b2ee5bb37823ab73422a01251343cb5f58
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java
@@ -11,10 +11,12 @@
import com.ruoyi.account.api.model.TAppCoupon;
import com.ruoyi.account.api.model.TAppUser;
import com.ruoyi.account.api.model.TAppUserIntegralChange;
import com.ruoyi.chargingPile.api.dto.TBillingTemplateDTO;
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.TBillingTemplateDetail;
import com.ruoyi.chargingPile.api.model.TChargingGun;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
@@ -29,11 +31,13 @@
import com.ruoyi.order.api.vo.TransactionRecordMessageVO;
import com.ruoyi.order.service.*;
import com.ruoyi.order.service.impl.ChargingCouponSupport;
import com.ruoyi.order.support.ChargeAccountSupport;
import com.ruoyi.order.util.mongodb.service.TransactionRecordService;
import com.ruoyi.order.util.mongodb.service.UploadRealTimeMonitoringDataService;
import com.ruoyi.other.api.domain.TCoupon;
import com.ruoyi.other.api.domain.TIntegralRule;
import com.ruoyi.other.api.domain.TVip;
import com.ruoyi.other.api.dto.CompanyPreferentialDTO;
import com.ruoyi.other.api.dto.ExchangeCodeConsumeDTO;
import com.ruoyi.other.api.feignClient.IntegralRuleClient;
import com.ruoyi.other.api.feignClient.OtherClient;
@@ -96,6 +100,12 @@
    private SiteClient siteClient;
    @Resource
    private TChargingOrderService chargingOrderService;
    @Resource
    private TCardChargingOrderService cardChargingOrderService;
    @Resource
    private TVinChargingOrderService vinChargingOrderService;
    
    @Resource
    private TChargingOrderRefundService chargingOrderRefundService;
@@ -158,6 +168,9 @@
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ChargeAccountSupport chargeAccountSupport;
@@ -281,6 +294,175 @@
                chargingOrderService.updateById(order);
            }
        }
    }
    /** 实时监控静默阈值:超过该时间仍无交易记录时按异常断电补偿 */
    private static final long MONITORING_SILENT_MILLIS = TimeUnit.MINUTES.toMillis(10);
    /** 无任何设备数据时的最长保护时间,避免 Mongo 短暂延迟造成误关单 */
    private static final long NO_DEVICE_DATA_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(24);
    /**
     * 刷卡/VIN 订单补偿(10分钟一轮):真实交易记录优先统一结算;启动中有实时数据则恢复充电中;
     * 仅在设备数据缺失或静默达到保护阈值后执行关单/累计电量兜底结算。
     */
    @Scheduled(fixedRate = 1000 * 60 * 10, initialDelay = 1000 * 60)
    public void cardVinOrderCompensationTask() {
        if (isScheduledTaskPort(port)) {
            compensateCardOrders();
            compensateVinOrders();
        }
    }
    /**
     * VIN 结算失败重试(1分钟一轮,结算失败主触发):处理账户结算失败回退的状态4订单,
     * 与10分钟补偿任务(DB兜底扫描)互补;重试幂等由账户侧消费流水保证,不会重复扣款。
     */
    @Scheduled(fixedRate = 1000 * 60, initialDelay = 1000 * 30)
    public void vinSettleRetryTask() {
        if (isScheduledTaskPort(port)) {
            try {
                vinChargingOrderService.retryPendingVinSettle();
            } catch (Exception e) {
                log.error("[VD结算重试] 定时任务异常", e);
            }
        }
    }
    private void compensateCardOrders() {
        try {
            int recovered = cardChargingOrderService.recoverFinishedOrdersWithoutDeduct();
            if (recovered > 0) {
                log.warn("[SD扣款补偿] 本轮回退未扣款已结束订单数量:{}", recovered);
            }
        } catch (Exception e) {
            log.error("[SD扣款补偿] 已结束未扣款扫描异常", e);
        }
        try {
            for (TCardChargingOrder order : cardChargingOrderService.listTimeoutStartingOrders()) {
                compensateCardStartingOrder(order);
            }
        } catch (Exception e) {
            log.error("[SD补偿] 启动超时扫描异常", e);
        }
        try {
            for (TCardChargingOrder order : cardChargingOrderService.listTimeoutChargingOrders()) {
                compensateCardActiveOrder(order);
            }
        } catch (Exception e) {
            log.error("[SD补偿] 活动订单扫描异常", e);
        }
    }
    private void compensateCardStartingOrder(TCardChargingOrder order) {
        try {
            TransactionRecord record = findCompleteTransactionRecord(order.getCode());
            if (record != null) {
                cardChargingOrderService.cardEndChargeBillingCharge(toTransactionRecordVO(record));
                return;
            }
            UploadRealTimeMonitoringData latest = uploadRealTimeMonitoringDataService.getLastDataById(order.getCode());
            if (latest != null && (latest.getCreate_time() == null
                    || System.currentTimeMillis() - latest.getCreate_time().getTime() <= MONITORING_SILENT_MILLIS)) {
                cardChargingOrderService.handleHandshakeVin(order.getCode(), null);
                log.info("[SD补偿] 启动应答缺失但存在活跃实时数据,恢复充电中 code={}", order.getCode());
                return;
            }
            cardChargingOrderService.closeTimeoutStartingOrder(order);
        } catch (Exception ex) {
            log.error("[SD补偿] 启动订单处理异常,保留原状态待下轮重试 code={}", order.getCode(), ex);
        }
    }
    private void compensateCardActiveOrder(TCardChargingOrder order) {
        try {
            TransactionRecord record = findCompleteTransactionRecord(order.getCode());
            if (record != null) {
                cardChargingOrderService.cardEndChargeBillingCharge(toTransactionRecordVO(record));
                return;
            }
            UploadRealTimeMonitoringData latest = uploadRealTimeMonitoringDataService.getLastDataById(order.getCode());
            if (shouldFallbackSettle(latest, order.getCreateTime(), false)) {
                cardChargingOrderService.compensationSettle(order);
            }
        } catch (Exception ex) {
            log.error("[SD补偿] 活动订单处理异常,保留原状态待下轮重试 code={}", order.getCode(), ex);
        }
    }
    private void compensateVinOrders() {
        try {
            for (TVinChargingOrder order : vinChargingOrderService.listTimeoutStartingOrders()) {
                compensateVinStartingOrder(order);
            }
        } catch (Exception e) {
            log.error("[VD补偿] 启动超时扫描异常", e);
        }
        try {
            for (TVinChargingOrder order : vinChargingOrderService.listTimeoutChargingOrders()) {
                compensateVinActiveOrder(order);
            }
        } catch (Exception e) {
            log.error("[VD补偿] 活动订单扫描异常", e);
        }
    }
    private void compensateVinStartingOrder(TVinChargingOrder order) {
        try {
            TransactionRecord record = findCompleteTransactionRecord(order.getCode());
            if (record != null) {
                vinChargingOrderService.vinEndChargeBillingCharge(toTransactionRecordVO(record));
                return;
            }
            UploadRealTimeMonitoringData latest = uploadRealTimeMonitoringDataService.getLastDataById(order.getCode());
            if (latest != null && (latest.getCreate_time() == null
                    || System.currentTimeMillis() - latest.getCreate_time().getTime() <= MONITORING_SILENT_MILLIS)) {
                vinChargingOrderService.handleHandshakeVin(order.getCode(), null);
                log.info("[VD补偿] 启动应答缺失但存在活跃实时数据,恢复充电中 code={}", order.getCode());
                return;
            }
            vinChargingOrderService.closeTimeoutStartingOrder(order);
        } catch (Exception ex) {
            log.error("[VD补偿] 启动订单处理异常,保留原状态待下轮重试 code={}", order.getCode(), ex);
        }
    }
    private void compensateVinActiveOrder(TVinChargingOrder order) {
        try {
            TransactionRecord record = findCompleteTransactionRecord(order.getCode());
            if (record != null) {
                vinChargingOrderService.vinEndChargeBillingCharge(toTransactionRecordVO(record));
                return;
            }
            UploadRealTimeMonitoringData latest = uploadRealTimeMonitoringDataService.getLastDataById(order.getCode());
            boolean stopping = Integer.valueOf(4).equals(order.getStatus());
            if (shouldFallbackSettle(latest, order.getCreateTime(), stopping)) {
                vinChargingOrderService.compensationSettle(order);
            }
        } catch (Exception ex) {
            log.error("[VD补偿] 活动订单处理异常,保留原状态待下轮重试 code={}", order.getCode(), ex);
        }
    }
    private TransactionRecord findCompleteTransactionRecord(String code) {
        TransactionRecord record = transactionRecordService.findOne(code);
        return record != null && StringUtils.isNotEmpty(record.getResult()) ? record : null;
    }
    private TransactionRecordMessageVO toTransactionRecordVO(TransactionRecord record) {
        TransactionRecordMessageVO vo = new TransactionRecordMessageVO();
        BeanUtils.copyProperties(record, vo);
        return vo;
    }
    static boolean shouldFallbackSettle(UploadRealTimeMonitoringData latest, LocalDateTime createTime, boolean stopping) {
        if (latest != null && latest.getCreate_time() != null) {
            return stopping || System.currentTimeMillis() - latest.getCreate_time().getTime() > MONITORING_SILENT_MILLIS;
        }
        if (stopping) {
            return true;
        }
        return createTime != null
                && ChronoUnit.MILLIS.between(createTime, LocalDateTime.now()) > NO_DEVICE_DATA_TIMEOUT_MILLIS;
    }
@@ -508,6 +690,16 @@
                        }
                    }
                    //企业账户流程:白名单企业账户订单按企业优惠结算(方式2=服务费比例优惠,方式3=按计费模板同阶段单价)
                    boolean enterpriseSettlement = chargingOrderService.isWhitelistEnterpriseOrder(order);
                    CompanyPreferentialDTO enterprisePreferential = enterpriseSettlement
                            ? chargeAccountSupport.loadCompanyPreferential(order.getCompanyId()) : null;
                    Integer enterprisePreferentialType = null == enterprisePreferential ? null : enterprisePreferential.getPreferentialType();
                    TBillingTemplateDTO enterpriseTemplate = enterpriseSettlement
                            ? chargeAccountSupport.loadBillingTemplate(enterprisePreferential) : null;
                    BigDecimal enterpriseElectricDiscount = BigDecimal.ZERO;
                    BigDecimal enterpriseServiceDiscount = BigDecimal.ZERO;
                    SimpleDateFormat sdf1 = new SimpleDateFormat("yyyyMMddHHmm");
                    List<TChargingOrderAccountingStrategy> list = new ArrayList<>();
                    for (AccountingStrategyDetailOrder detailOrder : detailOrders) {
@@ -548,15 +740,46 @@
                                chargingOrderAccountingStrategy.setEndTime(detailOrder.getEndTime().split(" ")[1]);
                                //已充电总度数
                                BigDecimal electrovalenc = detailOrder.getElectrovalence().multiply(subtract);
                                BigDecimal finalElectricPrice = detailOrder.getElectrovalence();
                                BigDecimal finalServicePrice = detailOrder.getServiceCharge();
                                //企业账户流程:按企业优惠计算最终用电单价
                                if (enterpriseSettlement) {
                                    if (Integer.valueOf(ChargeAccountSupport.PREFERENTIAL_TYPE_RATE).equals(enterprisePreferentialType)) {
                                        //方式2:服务费按比例优惠
                                        finalServicePrice = ChargeAccountSupport.applyServiceDiscountRate(finalServicePrice, enterprisePreferential.getServiceDiscountRate());
                                    } else if (Integer.valueOf(ChargeAccountSupport.PREFERENTIAL_TYPE_TEMPLATE).equals(enterprisePreferentialType)) {
                                        //方式3:按计费模板同电价阶段单价
                                        TBillingTemplateDetail templateDetail = ChargeAccountSupport.findTemplateDetail(enterpriseTemplate, detailOrder.getType());
                                        if (null == templateDetail) {
                                            log.error("[CD补偿结算] 计费模板缺少对应电价阶段,该阶段按原价结算 code={} type={}", order.getCode(), detailOrder.getType());
                                        } else {
                                            if (null != templateDetail.getElectrovalence()) {
                                                finalElectricPrice = templateDetail.getElectrovalence();
                                            }
                                            if (null != templateDetail.getServiceCharge()) {
                                                finalServicePrice = templateDetail.getServiceCharge();
                                            }
                                        }
                                    }
                                }
                                BigDecimal electrovalenc = finalElectricPrice.multiply(subtract);
                                BigDecimal originalServicePrice = detailOrder.getServiceCharge().multiply(subtract);
                                BigDecimal serviceCharge = originalServicePrice;
                                BigDecimal serviceCharge = finalServicePrice.multiply(subtract);
                                BigDecimal vipDiscountAmount = BigDecimal.ZERO;
                                //计算优惠金额;余额支付(含组合支付)不参与会员折扣
                                if (!isWhitelistOrder(order) && !isBalancePayMode(order.getPayMethod()) && null != order.getVipDiscount()) {
                                //计算优惠金额;企业账户流程按企业优惠结算,其余订单中余额支付(含组合支付)不参与会员折扣
                                if (enterpriseSettlement) {
                                    //企业优惠金额(原价-优惠价),用于订单优惠统计与 whitelist_* 快照
                                    enterpriseElectricDiscount = enterpriseElectricDiscount.add(detailOrder.getElectrovalence().multiply(subtract).subtract(electrovalenc));
                                    enterpriseServiceDiscount = enterpriseServiceDiscount.add(originalServicePrice.subtract(serviceCharge));
                                } else if (!isWhitelistOrder(order) && !isBalancePayMode(order.getPayMethod()) && null != order.getVipDiscount()) {
                                    //0.58折
                                    vipDiscountAmount = serviceCharge.multiply(new BigDecimal(1).subtract(order.getVipDiscount()));
                                    serviceCharge = serviceCharge.multiply(order.getVipDiscount());
                                }
                                //企业账户流程:明细单价记录优惠后单价,金额与单价口径一致
                                if (enterpriseSettlement) {
                                    chargingOrderAccountingStrategy.setElectrovalence(finalElectricPrice);
                                    chargingOrderAccountingStrategy.setServiceCharge(finalServicePrice);
                                }
                                chargingOrderAccountingStrategy.setPeriodElectricPrice(electrovalenc.setScale(4, RoundingMode.HALF_EVEN));
                                chargingOrderAccountingStrategy.setPeriodServicePrice(serviceCharge.setScale(4, RoundingMode.HALF_EVEN));
@@ -668,7 +891,12 @@
                    BigDecimal electricDiscount = whitelistRefundSplit[0];
                    BigDecimal serviceDiscount = whitelistRefundSplit[1];
                    BigDecimal whitelistDiscount = electricDiscount.add(serviceDiscount).setScale(4, RoundingMode.HALF_EVEN);
                    if (whitelistDiscount.compareTo(BigDecimal.ZERO) > 0) {
                    if (enterpriseSettlement) {
                        // 企业账户流程:企业优惠已在明细阶段计入金额,这里只做统计快照(金额口径与实时结算一致)
                        electricDiscount = enterpriseElectricDiscount.max(BigDecimal.ZERO).setScale(4, RoundingMode.HALF_EVEN);
                        serviceDiscount = enterpriseServiceDiscount.max(BigDecimal.ZERO).setScale(4, RoundingMode.HALF_EVEN);
                        whitelistDiscount = electricDiscount.add(serviceDiscount);
                    } else if (whitelistDiscount.compareTo(BigDecimal.ZERO) > 0) {
                        // 补偿任务需与实时结算使用同一拆分口径,避免财务导出金额不一致。
                        periodElectricPrice_total = periodElectricPrice_total.subtract(electricDiscount).max(BigDecimal.ZERO);
                        serviceCharge_total = serviceCharge_total.subtract(serviceDiscount).max(BigDecimal.ZERO);
@@ -685,14 +913,32 @@
                        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));
                    //企业账户流程:企业优惠金额与 whitelist_* 三列快照双写(订单优惠统计沿用 whitelist_* 口径)
                    if (enterpriseSettlement) {
                        order.setDiscountElectrovalence(electricDiscount.setScale(2, RoundingMode.HALF_EVEN));
                        order.setDiscountServiceCharge(serviceDiscount.setScale(2, RoundingMode.HALF_EVEN));
                        order.setWhitelistElectricRefundAmount(electricDiscount.setScale(2, RoundingMode.HALF_EVEN));
                        order.setWhitelistServiceRefundAmount(serviceDiscount.setScale(2, RoundingMode.HALF_EVEN));
                        order.setWhitelistDiscountAmount(whitelistDiscount.setScale(2, RoundingMode.HALF_EVEN));
                    } else {
                        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));
                    order.setRefundStatus(1);
                    if (enterpriseSettlement) {
                        // 企业账户流程:资金来自企业账户额度(差额结算时退回企业账户),订单无用户侧退款,实付金额=实际消费金额
                        order.setBalancePayAmount(payAmount.setScale(2, RoundingMode.HALF_EVEN));
                        order.setPaymentAmount(payAmount.setScale(2, RoundingMode.HALF_EVEN));
                        order.setRefundAmount(BigDecimal.ZERO.setScale(2, RoundingMode.HALF_EVEN));
                        order.setRefundStatus(2);
                        refundAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_EVEN);
                    } else {
                        order.setPaymentAmount(payAmount.setScale(2, RoundingMode.HALF_EVEN));
                        order.setRefundAmount(refundAmount.setScale(2, RoundingMode.HALF_EVEN));
                        order.setRefundStatus(1);
                    }
                    UploadRealTimeMonitoringData asc = uploadRealTimeMonitoringDataService.getDataByOrderCode(order.getCode(), "asc");
                    UploadRealTimeMonitoringData desc = uploadRealTimeMonitoringDataService.getDataByOrderCode(order.getCode(), "desc");
                    if(null != asc){
@@ -703,6 +949,15 @@
                    }
                    chargingOrderService.updateById(order);
                    //企业账户流程:结算企业额度(按实际消费扣减锁定额度、差额退回企业账户)并写消费流水
                    if (enterpriseSettlement && !chargingOrderService.settleWhitelistEnterpriseAccount(order, payAmount)) {
                        // 结算失败回退为停止中,交由补偿任务下一轮重新结算,避免企业额度长期冻结
                        order.setStatus(4);
                        chargingOrderService.updateById(order);
                        log.error("[CD补偿结算] 企业账户额度结算失败,订单回退停止中等待重试 code={}", order.getCode());
                        return;
                    }
                    order = chargingOrderService.getById(order.getId());
                    Site site = siteClient.getSiteByIds(Arrays.asList(order.getSiteId())).getData().get(0);
                    //推送三方平台