puzhibing
2026-09-30 64464568546b827fc5778a20efb347bf56ba34ac
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/support/ChargeAccountSupport.java
@@ -1,5 +1,6 @@
package com.ruoyi.order.support;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.account.api.feignClient.CompanyAllocateRecordClient;
import com.ruoyi.chargingPile.api.dto.TBillingTemplateDTO;
import com.ruoyi.chargingPile.api.feignClient.AccountingStrategyDetailClient;
@@ -8,6 +9,7 @@
import com.ruoyi.chargingPile.api.model.TBillingTemplateDetail;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.order.api.model.TChargingOrder;
import com.ruoyi.other.api.domain.TGroupCompany;
import com.ruoyi.other.api.dto.ChargeAuthorizationDTO;
import com.ruoyi.other.api.dto.ChargeTimeRange;
@@ -163,6 +165,61 @@
        public String getFailReason() {
            return failReason;
        }
    }
    /**
     * 企业优惠快照载体(建单时固化 / 结算与补偿时解析的结果):
     * 与既有 CompanyPreferentialDTO + TBillingTemplateDTO 调用点同构(getPreferentialType/getServiceDiscountRate/
     * getBillingTemplateId 方法名保持一致),三链路优惠计算代码仅需把数据来源从 Feign 实时查询改为本解析结果,
     * 计算逻辑不变。
     */
    public static class PreferentialSnapshot {
        /** 优惠方式(1=不优惠,2=按比例优惠,3=按优惠单价收费;null=无优惠配置) */
        private final Integer preferentialType;
        /** 服务费优惠比例(%,方式2) */
        private final BigDecimal serviceDiscountRate;
        /** 计费模板ID(方式3) */
        private final Integer billingTemplateId;
        /** 方式3计费模板(订单快照反序列化或实时查询结果) */
        private final TBillingTemplateDTO template;
        /** 订单快照为空、本次为实时查询所得(结算链路需回填订单快照列,保证补偿重算口径一致) */
        private final boolean fromRealtimeQuery;
        public PreferentialSnapshot(Integer preferentialType, BigDecimal serviceDiscountRate, Integer billingTemplateId,
                                    TBillingTemplateDTO template, boolean fromRealtimeQuery) {
            this.preferentialType = preferentialType;
            this.serviceDiscountRate = serviceDiscountRate;
            this.billingTemplateId = billingTemplateId;
            this.template = template;
            this.fromRealtimeQuery = fromRealtimeQuery;
        }
        public Integer getPreferentialType() {
            return preferentialType;
        }
        public BigDecimal getServiceDiscountRate() {
            return serviceDiscountRate;
        }
        public Integer getBillingTemplateId() {
            return billingTemplateId;
        }
        public TBillingTemplateDTO getTemplate() {
            return template;
        }
        public boolean isFromRealtimeQuery() {
            return fromRealtimeQuery;
        }
        /**
         * 是否需要把本次解析结果回填到订单快照列(订单快照为空且实时解析到了配置)
         */
        public boolean needBackfill() {
            return fromRealtimeQuery && preferentialType != null;
        }
    }
@@ -335,6 +392,108 @@
    }
    /**
     * 建单时固化企业优惠快照:读取当前生效的公司优惠设置(方式3连带加载计费模板明细),
     * 供调用方写入订单 preferential_* 快照列;未配置或查询失败返回 null(结算时按实时查询兼容)。
     */
    public PreferentialSnapshot snapshotPreferential(Integer companyId) {
        CompanyPreferentialDTO preferential = loadCompanyPreferential(companyId);
        if (preferential == null) {
            return null;
        }
        return new PreferentialSnapshot(preferential.getPreferentialType(), preferential.getServiceDiscountRate(),
                preferential.getBillingTemplateId(), loadBillingTemplate(preferential), false);
    }
    /**
     * 建单时把当前生效的企业优惠设置写入订单快照字段(含方式3模板明细JSON,未配置/查询失败时不落快照)
     */
    public void fillPreferentialSnapshot(TChargingOrder order, Integer companyId) {
        if (order == null) {
            return;
        }
        PreferentialSnapshot snapshot = snapshotPreferential(companyId);
        if (snapshot == null || snapshot.getPreferentialType() == null) {
            return;
        }
        order.setPreferentialType(snapshot.getPreferentialType());
        order.setServiceDiscountRate(snapshot.getServiceDiscountRate());
        order.setBillingTemplateId(snapshot.getBillingTemplateId());
        order.setBillingTemplateSnapshot(serializeTemplateSnapshot(snapshot.getTemplate()));
        order.setPreferentialSnapshotTime(LocalDateTime.now());
    }
    /**
     * 结算/补偿时解析企业优惠:订单快照优先(建单后策略变更不影响本单口径),
     * 快照为空(历史订单)才实时查询并标记回填;返回 null 表示无优惠配置(按不优惠结算)。
     */
    public PreferentialSnapshot resolvePreferential(TChargingOrder order) {
        if (order != null && order.getPreferentialType() != null) {
            TBillingTemplateDTO template = null;
            if (Integer.valueOf(PREFERENTIAL_TYPE_TEMPLATE).equals(order.getPreferentialType())) {
                template = parseTemplateSnapshot(order.getBillingTemplateSnapshot());
                if (template == null && order.getBillingTemplateId() != null) {
                    // 快照JSON缺失(历史订单/序列化失败)按模板ID实时查询兜底,尽量不倒退回原价
                    template = loadTemplateById(order.getBillingTemplateId());
                }
            }
            return new PreferentialSnapshot(order.getPreferentialType(), order.getServiceDiscountRate(),
                    order.getBillingTemplateId(), template, false);
        }
        CompanyPreferentialDTO preferential = loadCompanyPreferential(order == null ? null : order.getCompanyId());
        if (preferential == null) {
            return null;
        }
        return new PreferentialSnapshot(preferential.getPreferentialType(), preferential.getServiceDiscountRate(),
                preferential.getBillingTemplateId(), loadBillingTemplate(preferential), true);
    }
    /**
     * 计费模板明细序列化为订单快照JSON(方式3建单固化:模板后续被改动/删除不影响本单结算与补偿口径)
     */
    public static String serializeTemplateSnapshot(TBillingTemplateDTO template) {
        if (template == null) {
            return null;
        }
        try {
            return JSON.toJSONString(template);
        } catch (Exception ex) {
            log.error("[充电优惠] 计费模板快照序列化失败 templateId={}", template.getId(), ex);
            return null;
        }
    }
    /**
     * 订单快照JSON反序列化为计费模板(失败返回 null,由调用方按模板ID实时查询或按原价兜底)
     */
    public static TBillingTemplateDTO parseTemplateSnapshot(String snapshotJson) {
        if (StringUtils.isEmpty(snapshotJson)) {
            return null;
        }
        try {
            return JSON.parseObject(snapshotJson, TBillingTemplateDTO.class);
        } catch (Exception ex) {
            log.error("[充电优惠] 计费模板快照反序列化失败,按模板ID实时查询兜底", ex);
            return null;
        }
    }
    /**
     * 按模板ID查询计费模板(仅方式3快照兜底使用,取不到时该段按原价结算)
     */
    public TBillingTemplateDTO loadTemplateById(Integer templateId) {
        if (templateId == null) {
            return null;
        }
        try {
            R<TBillingTemplateDTO> result = billingTemplateClient.getTemplateDetail(templateId);
            return result == null ? null : result.getData();
        } catch (Exception ex) {
            log.error("[充电优惠] 查询计费模板失败,按原价结算 templateId={}", templateId, ex);
            return null;
        }
    }
    /**
     * 服务费按比例优惠:最终单价 = 原价 × (1 - 折扣率/100),折扣率缺失或越界时按原价
     */
    public static BigDecimal applyServiceDiscountRate(BigDecimal servicePrice, BigDecimal discountRate) {