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.feignClient.BillingTemplateClient;
|
import com.ruoyi.chargingPile.api.model.TAccountingStrategyDetail;
|
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;
|
import com.ruoyi.other.api.dto.CompanyPreferentialDTO;
|
import com.ruoyi.other.api.feignClient.CompanyPreferentialClient;
|
import com.ruoyi.other.api.feignClient.GroupCompanyClient;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
import org.springframework.stereotype.Component;
|
|
import javax.annotation.Resource;
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.time.Duration;
|
import java.time.LocalDateTime;
|
import java.time.LocalTime;
|
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeParseException;
|
import java.util.Collections;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Set;
|
|
/**
|
* VIN码/刷卡充电公共账户支持(VIN 与刷卡共用,防止两套逻辑漂移):
|
* 1) 企业充电授权窗口解析(时间方式=全天/指定时段/指定电价时段)与授权截止时间计算;
|
* 2) 企业优惠设置装载(1=不优惠,2=服务费按比例优惠,3=按计费模板优惠单价收费);
|
* 3) 授权时间到期停充判定(提前量 = min(60秒, 授权总时长));
|
* 4) 企业额度分配记录-消费流水写入与已结算订单号查询(charge_type=2,按订单号+变更类型幂等,失败不影响主流程);
|
* 5) 充电账户上下文(账户模式/锁定额度/授权截止时间/失败码)承载。
|
*/
|
@Component
|
public class ChargeAccountSupport {
|
|
private static final Logger log = LoggerFactory.getLogger(ChargeAccountSupport.class);
|
|
/** 账户模式:共享(锁企业账户余额) */
|
public static final int ACCOUNT_MODE_SHARED = 1;
|
/** 账户模式:分配(锁VIN/卡账户余额) */
|
public static final int ACCOUNT_MODE_ALLOCATED = 2;
|
/** 未配置充电授权时的默认下发金额上限(元) */
|
public static final BigDecimal DEFAULT_DISPATCH_AMOUNT = new BigDecimal("200");
|
/** 授权时间到期停充提前量(秒):固定提前量,剩余授权时间不足该值即下发停充(授权总时长更短时按总时长提前) */
|
public static final int AUTHORIZE_STOP_AHEAD_SECONDS = 60;
|
/** 授权时间方式:全天 */
|
public static final int AUTHORIZE_TIME_MODE_ALL_DAY = 1;
|
/** 授权时间方式:指定时段 */
|
public static final int AUTHORIZE_TIME_MODE_RANGE = 2;
|
/** 授权时间方式:指定电价时段 */
|
public static final int AUTHORIZE_TIME_MODE_PRICE = 3;
|
/** 企业优惠方式:不优惠 */
|
public static final int PREFERENTIAL_TYPE_NONE = 1;
|
/** 企业优惠方式:按比例优惠(服务费折扣) */
|
public static final int PREFERENTIAL_TYPE_RATE = 2;
|
/** 企业优惠方式:按优惠单价收费(取计费模板单价) */
|
public static final int PREFERENTIAL_TYPE_TEMPLATE = 3;
|
/** 金额比较用零值常量 */
|
private static final BigDecimal ZERO_AMOUNT = BigDecimal.ZERO;
|
/** 百分比换算基数 */
|
private static final BigDecimal HUNDRED = new BigDecimal("100");
|
|
@Resource
|
private AccountingStrategyDetailClient accountingStrategyDetailClient;
|
@Resource
|
private GroupCompanyClient groupCompanyClient;
|
@Resource
|
private CompanyPreferentialClient companyPreferentialClient;
|
@Resource
|
private BillingTemplateClient billingTemplateClient;
|
@Resource
|
private CompanyAllocateRecordClient companyAllocateRecordClient;
|
|
/**
|
* 授权时间窗解析结果:maxEndTime 为空表示不做授权到期停充
|
*/
|
public static class AuthorizationWindow {
|
private final LocalDateTime maxEndTime;
|
|
public AuthorizationWindow(LocalDateTime maxEndTime) {
|
this.maxEndTime = maxEndTime;
|
}
|
|
public LocalDateTime getMaxEndTime() {
|
return maxEndTime;
|
}
|
}
|
|
/**
|
* 当前命中的电价阶段:type(1=尖 2=峰 3=平 4=谷)、该阶段结束时刻
|
*/
|
public static class PeriodTypeWindow {
|
private final Integer type;
|
private final LocalTime endTime;
|
|
public PeriodTypeWindow(Integer type, LocalTime endTime) {
|
this.type = type;
|
this.endTime = endTime;
|
}
|
|
public Integer getType() {
|
return type;
|
}
|
|
public LocalTime getEndTime() {
|
return endTime;
|
}
|
}
|
|
/**
|
* 充电账户上下文:账户模式/锁定额度(同时作为下发给充电桩的可用余额)/授权截止时间/失败码
|
*/
|
public static class ChargeContext {
|
private Integer accountMode;
|
private BigDecimal dispatchAmount;
|
private LocalDateTime maxEndTime;
|
private Integer failCode;
|
private String failReason;
|
|
public static ChargeContext fail(Integer failCode, String failReason) {
|
ChargeContext context = new ChargeContext();
|
context.failCode = failCode;
|
context.failReason = failReason;
|
return context;
|
}
|
|
public static ChargeContext ok(Integer accountMode, BigDecimal dispatchAmount, LocalDateTime maxEndTime) {
|
ChargeContext context = new ChargeContext();
|
context.accountMode = accountMode;
|
context.dispatchAmount = dispatchAmount;
|
context.maxEndTime = maxEndTime;
|
return context;
|
}
|
|
public boolean isOk() {
|
return failCode == null;
|
}
|
|
public Integer getAccountMode() {
|
return accountMode;
|
}
|
|
public BigDecimal getDispatchAmount() {
|
return dispatchAmount;
|
}
|
|
public LocalDateTime getMaxEndTime() {
|
return maxEndTime;
|
}
|
|
public Integer getFailCode() {
|
return failCode;
|
}
|
|
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;
|
}
|
}
|
|
/**
|
* 校验充电授权时间方式并计算授权截止时间:
|
* 1) 全天:不做时间校验,截止时间为空;
|
* 2) 指定时段:当前时间须落在某段可用时段内,截止时间=该段结束时间(未开启自动停充时为空);
|
* 3) 指定电价时段:当前电价阶段须在授权范围内,截止时间=该阶段结束时间(未开启自动停充时为空)。
|
* 返回 null 表示当前时间不在授权范围内。
|
*/
|
public AuthorizationWindow resolveAuthorizationWindow(ChargeAuthorizationDTO authorization, Integer strategyId,
|
Integer companyId) {
|
Integer timeMode = authorization.getTimeMode();
|
if (timeMode == null || timeMode == AUTHORIZE_TIME_MODE_ALL_DAY) {
|
return new AuthorizationWindow(null);
|
}
|
boolean autoStop = Boolean.TRUE.equals(authorization.getAutoCharge());
|
LocalDateTime now = LocalDateTime.now();
|
LocalTime current = now.toLocalTime();
|
if (timeMode == AUTHORIZE_TIME_MODE_RANGE) {
|
List<ChargeTimeRange> ranges = authorization.getTimeRanges();
|
if (ranges == null || ranges.isEmpty()) {
|
log.warn("[充电授权] 指定时段授权未配置时段,按放行处理 companyId={}", companyId);
|
return new AuthorizationWindow(null);
|
}
|
for (ChargeTimeRange range : ranges) {
|
if (range == null) {
|
continue;
|
}
|
LocalTime start = parseLocalTime(range.getStartTime());
|
LocalTime end = parseLocalTime(range.getEndTime());
|
if (start == null || end == null || !start.isBefore(end)) {
|
continue;
|
}
|
if (!current.isBefore(start) && current.isBefore(end)) {
|
return new AuthorizationWindow(autoStop ? LocalDateTime.of(now.toLocalDate(), end) : null);
|
}
|
}
|
return null;
|
}
|
if (timeMode == AUTHORIZE_TIME_MODE_PRICE) {
|
List<Integer> priceTypes = authorization.getPriceTypes();
|
if (priceTypes == null || priceTypes.isEmpty()) {
|
log.warn("[充电授权] 指定电价时段授权未配置电价类型,按放行处理 companyId={}", companyId);
|
return new AuthorizationWindow(null);
|
}
|
PeriodTypeWindow period = findCurrentPeriodType(strategyId, current);
|
if (period == null) {
|
log.warn("[充电授权] 未匹配到当前电价阶段,按放行处理 companyId={} strategyId={}", companyId, strategyId);
|
return new AuthorizationWindow(null);
|
}
|
if (!priceTypes.contains(period.getType())) {
|
return null;
|
}
|
return new AuthorizationWindow(autoStop ? LocalDateTime.of(now.toLocalDate(), period.getEndTime()) : null);
|
}
|
return new AuthorizationWindow(null);
|
}
|
|
/**
|
* 按计费策略明细匹配当前时间命中的电价阶段(HH:mm 口径,跨天/非法时段跳过)
|
*/
|
public PeriodTypeWindow findCurrentPeriodType(Integer strategyId, LocalTime current) {
|
if (strategyId == null) {
|
return null;
|
}
|
List<TAccountingStrategyDetail> details;
|
try {
|
R<List<TAccountingStrategyDetail>> detailResult =
|
accountingStrategyDetailClient.getListByAccountingStrategyId(strategyId);
|
details = detailResult == null ? null : detailResult.getData();
|
} catch (Exception ex) {
|
log.error("[充电时段] 查询计费策略明细失败 strategyId={}", strategyId, ex);
|
return null;
|
}
|
if (details == null || details.isEmpty()) {
|
return null;
|
}
|
for (TAccountingStrategyDetail detail : details) {
|
if (detail == null || detail.getType() == null) {
|
continue;
|
}
|
LocalTime start = parseLocalTime(detail.getStartTime());
|
LocalTime end = parseLocalTime(detail.getEndTime());
|
if (start == null || end == null || !start.isBefore(end)) {
|
continue;
|
}
|
if (!current.isBefore(start) && current.isBefore(end)) {
|
return new PeriodTypeWindow(detail.getType(), end);
|
}
|
}
|
return null;
|
}
|
|
/**
|
* 解析 HH:mm 时段(兼容 yyyy-MM-dd HH:mm 等带日期拼接格式;24:00 按当日末刻处理)
|
*/
|
public static LocalTime parseLocalTime(String value) {
|
if (StringUtils.isEmpty(value)) {
|
return null;
|
}
|
String trimmed = splitPeriodHourMinute(value);
|
if (trimmed == null || trimmed.isEmpty()) {
|
return null;
|
}
|
if ("24:00".equals(trimmed) || "24:00:00".equals(trimmed)) {
|
return LocalTime.of(23, 59, 59);
|
}
|
try {
|
return LocalTime.parse(trimmed, DateTimeFormatter.ofPattern("HH:mm"));
|
} catch (DateTimeParseException ex) {
|
try {
|
return LocalTime.parse(trimmed);
|
} catch (DateTimeParseException ex2) {
|
log.warn("[充电时段] 时段格式异常 value={}", value);
|
return null;
|
}
|
}
|
}
|
|
/**
|
* 截取快照时段位 HH:mm(对齐 CD 充电 TaskUtil.endOrder 中 detailOrder.getStartTime().split(" ")[1]):
|
* 1) snapshot 已为 HH:mm 直接返回;
|
* 2) 若被上游拼接为 yyyy-MM-dd HH:mm:ss / yyyy-MM-dd HH:mm 等带日期格式,按空格 split 取末段。
|
*/
|
public static String splitPeriodHourMinute(String value) {
|
if (value == null) {
|
return null;
|
}
|
String trimmed = value.trim();
|
if (trimmed.isEmpty()) {
|
return trimmed;
|
}
|
int spaceIdx = trimmed.lastIndexOf(' ');
|
return spaceIdx < 0 ? trimmed : trimmed.substring(spaceIdx + 1);
|
}
|
|
/**
|
* 查询企业优惠设置(失败或未配置返回 null,按不优惠结算)
|
*/
|
public CompanyPreferentialDTO loadCompanyPreferential(Integer companyId) {
|
if (companyId == null) {
|
return null;
|
}
|
try {
|
R<CompanyPreferentialDTO> result = companyPreferentialClient.getDetailByCompanyId(companyId);
|
return result == null ? null : result.getData();
|
} catch (Exception ex) {
|
log.error("[充电优惠] 查询企业优惠设置失败,按不优惠结算 companyId={}", companyId, ex);
|
return null;
|
}
|
}
|
|
/**
|
* 按优惠方式加载计费模板(仅方式3需要,取不到时该段按原价结算)
|
*/
|
public TBillingTemplateDTO loadBillingTemplate(CompanyPreferentialDTO preferential) {
|
if (preferential == null
|
|| !Integer.valueOf(PREFERENTIAL_TYPE_TEMPLATE).equals(preferential.getPreferentialType())
|
|| preferential.getBillingTemplateId() == null) {
|
return null;
|
}
|
try {
|
R<TBillingTemplateDTO> result = billingTemplateClient.getTemplateDetail(preferential.getBillingTemplateId());
|
return result == null ? null : result.getData();
|
} catch (Exception ex) {
|
log.error("[充电优惠] 查询计费模板失败,按原价结算 templateId={}", preferential.getBillingTemplateId(), ex);
|
return 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) {
|
if (servicePrice == null || discountRate == null || discountRate.compareTo(ZERO_AMOUNT) <= 0) {
|
return servicePrice;
|
}
|
if (discountRate.compareTo(HUNDRED) >= 0) {
|
return ZERO_AMOUNT;
|
}
|
BigDecimal rate = BigDecimal.ONE.subtract(discountRate.divide(HUNDRED, 4, RoundingMode.HALF_UP));
|
return servicePrice.multiply(rate).setScale(2, RoundingMode.HALF_UP);
|
}
|
|
/**
|
* 按电价阶段类型匹配计费模板明细(type:1=尖 2=峰 3=平 4=谷)
|
*/
|
public static TBillingTemplateDetail findTemplateDetail(TBillingTemplateDTO template, Integer type) {
|
if (template == null || template.getDetails() == null || type == null) {
|
return null;
|
}
|
for (TBillingTemplateDetail detail : template.getDetails()) {
|
if (detail != null && type.equals(detail.getType())) {
|
return detail;
|
}
|
}
|
return null;
|
}
|
|
/**
|
* 授权时间到期停充判定:剩余授权时间不足固定提前量(60 秒)时需主动停充,规避数据传输延迟;
|
* 授权总时长不足提前量时按整个授权时长提前(表现为启动后即触发停充),确保授权截止时间前结束充电;
|
* maxEndTime 为空(全天授权/未开启自动停充)时不介入。
|
*/
|
public boolean needAuthorizeStop(LocalDateTime maxEndTime, LocalDateTime startTime) {
|
if (maxEndTime == null) {
|
return false;
|
}
|
LocalDateTime now = LocalDateTime.now();
|
if (startTime == null || !now.isAfter(startTime)) {
|
return false;
|
}
|
long remainSeconds = Duration.between(now, maxEndTime).getSeconds();
|
if (remainSeconds <= 0) {
|
return true;
|
}
|
long authorizeSeconds = Duration.between(startTime, maxEndTime).getSeconds();
|
long aheadSeconds = Math.min(AUTHORIZE_STOP_AHEAD_SECONDS, Math.max(authorizeSeconds, 1));
|
return remainSeconds <= aheadSeconds;
|
}
|
|
/**
|
* 查询公司(失败返回 null,仅用于流水展示)
|
*/
|
public TGroupCompany queryCompanyQuietly(Integer companyId) {
|
try {
|
R<TGroupCompany> result = groupCompanyClient.getCompanyById(companyId);
|
return result == null ? null : result.getData();
|
} catch (Exception ex) {
|
log.error("[充电账户] 查询公司失败 companyId={}", companyId, ex);
|
return null;
|
}
|
}
|
|
/**
|
* 批量查询已落消费流水的订单号(订单侧兜底扫描双判据使用)
|
*
|
* @return 已结算订单号集合;查询失败返回 null 表示无法确认,调用方须按「不得回退订单状态」保守处理
|
*/
|
public Set<String> listConsumedOrderCodesQuietly(List<String> orderCodes) {
|
if (orderCodes == null || orderCodes.isEmpty()) {
|
return Collections.emptySet();
|
}
|
try {
|
R<List<String>> result = companyAllocateRecordClient.listConsumedOrderCodes(orderCodes);
|
if (result == null || result.getCode() != R.SUCCESS || result.getData() == null) {
|
log.error("[充电账户] 查询已结算订单号失败 msg={}", result == null ? "null" : result.getMsg());
|
return null;
|
}
|
return new HashSet<>(result.getData());
|
} catch (Exception ex) {
|
log.error("[充电账户] 查询已结算订单号异常", ex);
|
return null;
|
}
|
}
|
|
/**
|
* 写企业额度分配记录-消费流水(订单号+change_type 幂等):失败不影响结算主流程
|
*
|
* @param amount 本次变更金额(消费传负数)
|
*/
|
public void recordConsume(Integer companyId, Integer userType, String userInfo, String userName,
|
BigDecimal beforeBalance, BigDecimal amount, String orderCode, String remark) {
|
if (companyId == null) {
|
return;
|
}
|
try {
|
R<Boolean> result = companyAllocateRecordClient.recordConsume(companyId, userType, userInfo, userName,
|
beforeBalance, amount, orderCode, remark);
|
if (result == null || result.getCode() != R.SUCCESS) {
|
log.error("[充电账户] 消费流水写入失败 code={} companyId={} amount={} msg={}", orderCode, companyId,
|
amount, result == null ? "null" : result.getMsg());
|
}
|
} catch (Exception ex) {
|
log.error("[充电账户] 消费流水写入异常 code={} companyId={} amount={}", orderCode, companyId, amount, ex);
|
}
|
}
|
}
|