| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | |
| | | 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; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 建单时固化企业优惠快照:读取当前生效的公司优惠设置(方式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) { |