yanderong
2026-10-02 bca4733e4b0fcd9b6496e608304549a09f982463
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java
@@ -465,6 +465,16 @@
                && ChronoUnit.MILLIS.between(createTime, LocalDateTime.now()) > NO_DEVICE_DATA_TIMEOUT_MILLIS;
    }
    /**
     * 企业账户订单是否已完成闭环(已结算 settle_status=1 或锁定额度已释放 lock_released=1):
     * 兜底扫描不回退这类订单重跑,避免结算完成后被回退重跑造成重复结算与重复扣款。
     */
    static boolean isEnterpriseOrderSettledOrReleased(TChargingOrder order) {
        return null != order
                && (Integer.valueOf(1).equals(order.getSettleStatus())
                || Integer.valueOf(1).equals(order.getLockReleased()));
    }
    //十分钟循环执行的定时任务
    @Scheduled(fixedRate = 1000 * 60 * 10)
@@ -482,22 +492,28 @@
                    log.info("定时任务执行,查询到启动失败的订单,执行退款:{}", order.getCode());
                    if(1 == order.getOrderSource()){
                        chargingOrderService.refund(order.getCode());
                        int num = 0;
                        while (true){
                            TChargingOrderRefund one = chargingOrderRefundService.getOne(new LambdaQueryWrapper<TChargingOrderRefund>().eq(TChargingOrderRefund::getChargingOrderId, order.getId()));
                            if(null != one && 2 == one.getRefundStatus()){
                                order.setStatus(-1);
                                chargingOrderService.updateById(order);
                                break;
                            }
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                            num++;
                            if(num > 30){
                                break;
                        //企业账户订单启动失败不落退款明细(资金来自企业额度,仅释放额度并终结),无需等待退款记录,直接终结
                        if (chargingOrderService.isWhitelistEnterpriseOrder(order)) {
                            order.setStatus(-1);
                            chargingOrderService.updateById(order);
                        } else {
                            int num = 0;
                            while (true){
                                TChargingOrderRefund one = chargingOrderRefundService.getOne(new LambdaQueryWrapper<TChargingOrderRefund>().eq(TChargingOrderRefund::getChargingOrderId, order.getId()));
                                if(null != one && 2 == one.getRefundStatus()){
                                    order.setStatus(-1);
                                    chargingOrderService.updateById(order);
                                    break;
                                }
                                try {
                                    Thread.sleep(1000);
                                } catch (InterruptedException e) {
                                    throw new RuntimeException(e);
                                }
                                num++;
                                if(num > 30){
                                    break;
                                }
                            }
                        }
                    }else{
@@ -533,7 +549,13 @@
                    R r = chargingOrderService.endChargeBillingCharge(vo);
                    log.info("定时任务执行,停止中的订单处理结果:{}", JSON.toJSONString(r));
                }else{
                    endOrder(order);
                    if (chargingOrderService.isWhitelistEnterpriseOrder(order)
                            && shouldFallbackSettle(null, order.getCreateTime(), false)) {
                        // 企业账户订单停止中且长时间无任何设备数据与账单:释放锁定额度并兜底关单(金额0、状态5),避免悬挂
                        chargingOrderService.closeEnterpriseOrderWithoutData(order);
                    } else {
                        endOrder(order);
                    }
                }
            }
@@ -572,6 +594,11 @@
                        if(System.currentTimeMillis() - createTime.getTime() - (8 * 60 * 60 * 1000) > 1000 * 60 * 10){
                            endOrder(order);
                        }
                    } else if (chargingOrderService.isWhitelistEnterpriseOrder(order)
                            && shouldFallbackSettle(null, order.getCreateTime(), false)) {
                        // 企业账户订单长时间无任何实时监控数据(设备未上报/报文丢失)且无账单:
                        // 释放企业账户锁定额度并兜底关单(金额0、状态5、结算完成标记),避免订单长期悬挂被反复扫描
                        chargingOrderService.closeEnterpriseOrderWithoutData(order);
                    }
                }
            }
@@ -580,6 +607,13 @@
            List<TChargingOrder> endChargingOrder = chargingOrderService.findEndChargingOrder();
            log.info("定时任务执行,查询到已结束未退款的订单数量:{}", endChargingOrder.size());
            for (TChargingOrder order : endChargingOrder) {
                // 企业账户订单:已结算(settle_status=1)或锁定额度已释放(lock_released=1)说明已完成闭环,
                // 不再回退状态重跑,避免重复结算与重复扣款(企业账户订单结算不落退款记录,无法靠退款记录过滤)
                if (chargingOrderService.isWhitelistEnterpriseOrder(order)
                        && isEnterpriseOrderSettledOrReleased(order)) {
                    log.info("定时任务执行,企业账户订单已结算/已终结,跳过回退:{}", order.getCode());
                    continue;
                }
                if(-1 == order.getStatus()){
                    order.setStatus(2);
                }
@@ -691,12 +725,12 @@
                    }
                    //企业账户流程:白名单企业账户订单按企业优惠结算(方式2=服务费比例优惠,方式3=按计费模板同阶段单价)
                    //优惠口径优先取订单快照(建单时固化,策略后续变更不影响补偿重算),快照为空的历史订单实时查询并回填
                    boolean enterpriseSettlement = chargingOrderService.isWhitelistEnterpriseOrder(order);
                    CompanyPreferentialDTO enterprisePreferential = enterpriseSettlement
                            ? chargeAccountSupport.loadCompanyPreferential(order.getCompanyId()) : null;
                    ChargeAccountSupport.PreferentialSnapshot enterprisePreferential = enterpriseSettlement
                            ? chargeAccountSupport.resolvePreferential(order) : null;
                    Integer enterprisePreferentialType = null == enterprisePreferential ? null : enterprisePreferential.getPreferentialType();
                    TBillingTemplateDTO enterpriseTemplate = enterpriseSettlement
                            ? chargeAccountSupport.loadBillingTemplate(enterprisePreferential) : null;
                    TBillingTemplateDTO enterpriseTemplate = null == enterprisePreferential ? null : enterprisePreferential.getTemplate();
                    BigDecimal enterpriseElectricDiscount = BigDecimal.ZERO;
                    BigDecimal enterpriseServiceDiscount = BigDecimal.ZERO;
@@ -947,12 +981,27 @@
                    if(null != desc){
                        order.setEndSoc(new BigDecimal(desc.getSoc()));
                    }
                    //企业账户流程:历史订单(快照为空)实时解析的优惠结果回填订单快照列,保证后续补偿重算口径一致;
                    //同时置结算完成标记,兜底扫描(findEndChargingOrder)不再回退已结算订单,防止重复结算与重复扣款
                    if (enterpriseSettlement) {
                        if (null != enterprisePreferential && enterprisePreferential.needBackfill()) {
                            order.setPreferentialType(enterprisePreferential.getPreferentialType());
                            order.setServiceDiscountRate(enterprisePreferential.getServiceDiscountRate());
                            order.setBillingTemplateId(enterprisePreferential.getBillingTemplateId());
                            order.setBillingTemplateSnapshot(
                                    ChargeAccountSupport.serializeTemplateSnapshot(enterprisePreferential.getTemplate()));
                            order.setPreferentialSnapshotTime(LocalDateTime.now());
                        }
                        order.setSettleStatus(1);
                    }
                    chargingOrderService.updateById(order);
                    //企业账户流程:结算企业额度(按实际消费扣减锁定额度、差额退回企业账户)并写消费流水
                    if (enterpriseSettlement && !chargingOrderService.settleWhitelistEnterpriseAccount(order, payAmount)) {
                        // 结算失败回退为停止中,交由补偿任务下一轮重新结算,避免企业额度长期冻结
                        // 结算失败回退为停止中,交由补偿任务下一轮重新结算,避免企业额度长期冻结;
                        // 同时清除结算完成标记,保证下一轮补偿仍会处理该订单
                        order.setStatus(4);
                        order.setSettleStatus(0);
                        chargingOrderService.updateById(order);
                        log.error("[CD补偿结算] 企业账户额度结算失败,订单回退停止中等待重试 code={}", order.getCode());
                        return;