From bca4733e4b0fcd9b6496e608304549a09f982463 Mon Sep 17 00:00:00 2001
From: yanderong <307678080@qq.com>
Date: 星期五, 02 十月 2026 19:56:27 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/2.3' into 2.3

---
 ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java |   93 +++++++++++++++++++++++++++++++++++-----------
 1 files changed, 71 insertions(+), 22 deletions(-)

diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java
index 22b88b7..e15a804 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/util/task/TaskUtil.java
+++ b/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;

--
Gitblit v1.7.1