yanderong
2026-10-02 bca4733e4b0fcd9b6496e608304549a09f982463
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -1003,6 +1003,8 @@
    /**
     * 释放企业账户锁定额度(订单未结算/关单时调用;失败仅记录日志,不影响主流程)
     * 释放幂等:先用条件更新抢占 lock_released 标记(0->1),保证同一订单的锁定额在多路径并发或
     * 重复补偿下只释放一次;释放失败把标记回滚为 0,留待后续补偿或人工重试。
     */
    @Override
    public void unlockWhitelistEnterpriseAmountQuietly(TChargingOrder chargingOrder, String scene) {
@@ -1013,6 +1015,22 @@
        if (lockedAmount.compareTo(BigDecimal.ZERO) <= 0) {
            return;
        }
        boolean claimed;
        try {
            claimed = lambdaUpdate()
                    .eq(TChargingOrder::getId, chargingOrder.getId())
                    .eq(TChargingOrder::getLockReleased, 0)
                    .set(TChargingOrder::getLockReleased, 1)
                    .update();
        } catch (Exception ex) {
            log.error("[CD关单] 抢占锁定额释放标记异常 scene={} code={}", scene, chargingOrder.getCode(), ex);
            return;
        }
        if (!claimed) {
            log.info("[CD关单] 锁定额已释放,跳过重复释放 scene={} code={}", scene, chargingOrder.getCode());
            return;
        }
        boolean unlocked = false;
        try {
            R<BigDecimal> result;
            if (Integer.valueOf(ChargeAccountSupport.ACCOUNT_MODE_ALLOCATED).equals(chargingOrder.getAccountMode())
@@ -1025,12 +1043,52 @@
                log.error("[CD关单] 释放企业账户锁定额度失败 scene={} code={} companyId={} amount={} msg={}",
                        scene, chargingOrder.getCode(), chargingOrder.getCompanyId(), lockedAmount,
                        null == result ? "null" : result.getMsg());
                return;
            } else {
                unlocked = true;
                log.info("[CD关单] 释放企业账户锁定额度完成 scene={} code={} amount={}", scene, chargingOrder.getCode(), lockedAmount);
            }
            log.info("[CD关单] 释放企业账户锁定额度完成 scene={} code={} amount={}", scene, chargingOrder.getCode(), lockedAmount);
        } catch (Exception ex) {
            log.error("[CD关单] 释放企业账户锁定额度异常 scene={} code={} companyId={} amount={}",
                    scene, chargingOrder.getCode(), chargingOrder.getCompanyId(), lockedAmount, ex);
        }
        if (!unlocked) {
            // 释放失败回滚标记(1->0),保证锁定额不被“标记已释放”而漏释放,可被后续补偿重试
            lambdaUpdate()
                    .eq(TChargingOrder::getId, chargingOrder.getId())
                    .eq(TChargingOrder::getLockReleased, 1)
                    .set(TChargingOrder::getLockReleased, 0)
                    .update();
        }
    }
    /**
     * 企业账户订单无任何设备数据/账单时的兜底关单:释放锁定额度并终结订单(金额0、状态5、结算完成标记),
     * 保证订单不被任何补偿任务重复扫描重跑。释放幂等由 lock_released 抢占保证;状态条件更新保证只终结一次。
     */
    @Override
    public void closeEnterpriseOrderWithoutData(TChargingOrder chargingOrder) {
        if (!isWhitelistEnterpriseOrder(chargingOrder) || null == chargingOrder.getStatus()) {
            return;
        }
        if (2 != chargingOrder.getStatus() && 3 != chargingOrder.getStatus() && 4 != chargingOrder.getStatus()) {
            return;
        }
        // 释放企业账户锁定额度(lock_released 抢占保证只释放一次,失败回滚标记留待后续补偿重试)
        unlockWhitelistEnterpriseAmountQuietly(chargingOrder, "无设备数据关单");
        boolean closed = lambdaUpdate()
                .eq(TChargingOrder::getId, chargingOrder.getId())
                .in(TChargingOrder::getStatus, 2, 3, 4)
                .set(TChargingOrder::getStatus, 5)
                .set(TChargingOrder::getSettleStatus, 1)
                .set(TChargingOrder::getRefundStatus, 2)
                .set(TChargingOrder::getEndMode, 1)
                .set(TChargingOrder::getEndTime, LocalDateTime.now())
                .update();
        if (closed) {
            log.info("[CD关单] 企业账户订单无设备数据兜底关单完成 code={} companyId={} lockedAmount={}",
                    chargingOrder.getCode(), chargingOrder.getCompanyId(), chargingOrder.getLockedAmount());
        } else {
            log.info("[CD关单] 企业账户订单状态已变更,跳过无设备数据兜底关单 code={}", chargingOrder.getCode());
        }
    }
@@ -1312,6 +1370,8 @@
            chargingOrder.setAccountMode(chargeContext.getAccountMode());
            chargingOrder.setLockedAmount(chargeContext.getDispatchAmount());
            chargingOrder.setMaxEndTime(chargeContext.getMaxEndTime());
            // 企业优惠快照落库(建单时固化,结算/补偿优先读快照,抵抗公司优惠设置后续变更)
            chargeAccountSupport.fillPreferentialSnapshot(chargingOrder, whitelist.getCompanyId());
        }
        //会员折扣支持直营网点和加盟商站点;余额支付(含组合支付)与企业账户流程不参与会员折扣
@@ -2411,10 +2471,14 @@
                        }
                        //企业账户流程:启动失败全额释放已锁定的企业额度(订单未产生消费)
                        unlockWhitelistEnterpriseAmountQuietly(chargingOrder, "启动失败退款");
                        chargingOrderRefund.setRefundStatus(2);
                        chargingOrderRefund.setRefundSerialNumber(isWhitelistAllFreeOrder(chargingOrder) ? "WHITELIST" : "BALANCE");
                        chargingOrderRefund.setRefundTime(LocalDateTime.now());
                        chargingOrderRefundService.save(chargingOrderRefund);
                        //企业账户订单资金全部来自企业账户额度,无用户侧渠道/余额退款,不落退款明细(保持 0 行),
                        //仅更新订单退款快照字段与状态,避免 0 元退款记录进入退款对账
                        if (!isWhitelistEnterpriseOrder(chargingOrder)) {
                            chargingOrderRefund.setRefundStatus(2);
                            chargingOrderRefund.setRefundSerialNumber(isWhitelistAllFreeOrder(chargingOrder) ? "WHITELIST" : "BALANCE");
                            chargingOrderRefund.setRefundTime(LocalDateTime.now());
                            chargingOrderRefundService.save(chargingOrderRefund);
                        }
                        this.updateById(chargingOrder);
                        return;
                    }
@@ -5486,12 +5550,12 @@
                }
                //企业账户流程:白名单企业账户订单按企业优惠结算(方式2=服务费比例优惠,方式3=按计费模板同阶段单价)
                //优惠口径优先取订单快照(建单时固化,策略后续变更不影响本单结算),快照为空的历史订单实时查询并回填
                boolean enterpriseSettlement = isWhitelistEnterpriseOrder(chargingOrder);
                CompanyPreferentialDTO enterprisePreferential = enterpriseSettlement
                        ? chargeAccountSupport.loadCompanyPreferential(chargingOrder.getCompanyId()) : null;
                ChargeAccountSupport.PreferentialSnapshot enterprisePreferential = enterpriseSettlement
                        ? chargeAccountSupport.resolvePreferential(chargingOrder) : 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;
                //开始处理明细
@@ -5723,6 +5787,19 @@
                if (null != desc) {
                    chargingOrder.setEndSoc(new BigDecimal(desc.getSoc()));
                }
                //企业账户流程:历史订单(快照为空)实时解析的优惠结果回填订单快照列,保证后续补偿重算口径一致;
                //同时置结算完成标记,兜底扫描(findEndChargingOrder)不再回退已结算订单,防止重复结算与重复扣款
                if (enterpriseSettlement) {
                    if (null != enterprisePreferential && enterprisePreferential.needBackfill()) {
                        chargingOrder.setPreferentialType(enterprisePreferential.getPreferentialType());
                        chargingOrder.setServiceDiscountRate(enterprisePreferential.getServiceDiscountRate());
                        chargingOrder.setBillingTemplateId(enterprisePreferential.getBillingTemplateId());
                        chargingOrder.setBillingTemplateSnapshot(
                                ChargeAccountSupport.serializeTemplateSnapshot(enterprisePreferential.getTemplate()));
                        chargingOrder.setPreferentialSnapshotTime(LocalDateTime.now());
                    }
                    chargingOrder.setSettleStatus(1);
                }
                this.updateById(chargingOrder);
                //企业账户流程:结算企业额度(按实际消费扣减锁定额度、差额退回企业账户)并写消费流水