From c825a6f7e52ce1d61b21c352861c1da082a9fcb6 Mon Sep 17 00:00:00 2001
From: puzhibing <393733352@qq.com>
Date: 星期二, 13 十月 2026 17:50:24 +0800
Subject: [PATCH] 修改小程序bug
---
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java | 845 +++++++++++++++++++++++++++++++++++++++++++++++++------
1 files changed, 746 insertions(+), 99 deletions(-)
diff --git a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
index 38a1afb..8d08113 100644
--- a/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
+++ b/ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -15,6 +15,7 @@
import com.ruoyi.account.api.vo.GetAppUserVipDetail;
import com.ruoyi.account.api.vo.GetInviteUser;
import com.ruoyi.chargingPile.api.dto.GetSiteListDTO;
+import com.ruoyi.chargingPile.api.dto.TBillingTemplateDTO;
import com.ruoyi.chargingPile.api.feignClient.*;
import com.ruoyi.chargingPile.api.model.*;
import com.ruoyi.chargingPile.api.support.ReservationTimeRangeSupport;
@@ -55,11 +56,13 @@
import com.ruoyi.order.mapper.TChargingOrderMapper;
import com.ruoyi.order.mapper.TChargingOrderShareRecordMapper;
import com.ruoyi.order.mapper.TSettlementConfirmMapper;
+import com.ruoyi.order.mapper.TVinChargingOrderMapper;
import com.ruoyi.order.service.TChargingOrderAccountingStrategyService;
import com.ruoyi.order.service.TChargingOrderRefundService;
import com.ruoyi.order.service.TChargingOrderService;
import com.ruoyi.order.service.TOrderEvaluateService;
import com.ruoyi.order.service.*;
+import com.ruoyi.order.support.ChargeAccountSupport;
import com.ruoyi.order.support.ReservationOrderSupport;
import com.ruoyi.order.support.ReservationRecommendationCalculator;
import com.ruoyi.order.util.mongodb.service.PlatformStartChargingReplyService;
@@ -74,6 +77,8 @@
import com.ruoyi.other.api.domain.*;
import com.ruoyi.order.vo.ChargingOrderListInfoVO;
import com.ruoyi.other.api.domain.TVip;
+import com.ruoyi.other.api.dto.ChargeAuthorizationDTO;
+import com.ruoyi.other.api.dto.CompanyPreferentialDTO;
import com.ruoyi.other.api.dto.ExchangeCodeConsumeDTO;
import com.ruoyi.other.api.feignClient.*;
import com.ruoyi.other.api.vo.ExchangeCodeConsumeResultVO;
@@ -136,8 +141,6 @@
private Logger log = LoggerFactory.getLogger(TChargingOrderServiceImpl.class);
private static final int WHITELIST_BENEFIT_ALL_FREE = 1;
- private static final int WHITELIST_BENEFIT_ELECTRIC_FREE = 2;
- private static final int WHITELIST_BENEFIT_SERVICE_FREE = 3;
private static final String SHARE_AFTER_COMMIT_RESOURCE_PREFIX = "chargingOrderShareAfterCommit:";
private static final String SPECIAL_REFUND_TITLE = "特殊退款处理";
private static final int RESERVATION_RECOMMENDATION_HISTORY_DAYS = 30;
@@ -238,6 +241,8 @@
private AppCouponClient appCouponClient;
@Resource
private AppUserWhitelistClient appUserWhitelistClient;
+ @Resource
+ private GroupCompanyClient groupCompanyClient;
@Resource
private AppUserVipDetailClient appUserVipDetailClient;
@@ -247,6 +252,9 @@
@Resource
private UploadRealTimeMonitoringDataService uploadRealTimeMonitoringDataService;
+
+ @Resource
+ private TVinChargingOrderMapper vinChargingOrderMapper;
@Resource
private TCECClient tcecClient;
@@ -267,6 +275,12 @@
@Resource
private AccountingStrategyClient accountingStrategyClient;
+
+ @Resource
+ private ChargeAccountSupport chargeAccountSupport;
+
+ @Resource
+ private ChargeAuthorizationClient chargeAuthorizationClient;
@Resource
private PlatformStartChargingReplyService platformStartChargingReplyService;
@@ -766,6 +780,140 @@
}
/**
+ * 判断订单是否为白名单专属订单(企业账户流程订单或历史白名单权益订单):
+ * 此类订单按企业优惠/历史权益结算,不参与会员折扣、优惠券等普通用户特权;
+ * 白名单用户选择第三方支付的订单(无企业账户模式、无权益类型)不在此列,按普通用户流程执行。
+ *
+ * @param chargingOrder 充电订单
+ * @return true=白名单专属订单
+ */
+ private boolean isWhitelistSpecialOrder(TChargingOrder chargingOrder) {
+ return isWhitelistOrder(chargingOrder)
+ && (chargingOrder.getAccountMode() != null || chargingOrder.getWhitelistBenefitType() != null);
+ }
+
+ /**
+ * 判断订单是否为白名单企业账户订单(企业账户流程的唯一判定口径):
+ * 白名单订单且已写入账户模式(共享/分配)时按企业优惠与额度结算,第三方支付白名单订单/历史权益订单返回false。
+ *
+ * @param chargingOrder 充电订单
+ * @return true=白名单企业账户订单
+ */
+ @Override
+ public boolean isWhitelistEnterpriseOrder(TChargingOrder chargingOrder) {
+ return null != chargingOrder && isWhitelistSpecialOrder(chargingOrder) && null != chargingOrder.getAccountMode();
+ }
+
+ /**
+ * 解析白名单企业账户充电上下文(白名单用户选择余额支付时调用):
+ * 1) 校验归属公司存在且启用,未归属/停用直接拒绝;
+ * 2) 校验企业充电授权(站点范围、可用时段/电价阶段)并计算授权截止时间,未配置授权按放行处理(对齐 VIN 口径);
+ * 3) 下发额度 = min(用户输入充值金额, 账户可用额度, 企业设定的充值金额):
+ * - 账户可用额度:共享模式=公司可用余额,分配模式=白名单账户余额;
+ * - 企业设定充值金额=授权配置下发金额,未配置默认 200 元;
+ * - 用户输入金额为空或非正数时按 min(企业设定充值金额, 账户可用额度) 下发。
+ *
+ * @param whitelist 白名单命中结果(含归属公司与账户余额快照)
+ * @param userInputAmount 用户输入的充值金额
+ * @param siteId 站点ID(授权站点范围校验用)
+ * @param strategyId 计费策略ID(指定电价时段授权校验用)
+ * @return 企业账户上下文;失败时 failReason 为提示文案
+ */
+ private ChargeAccountSupport.ChargeContext resolveWhitelistEnterpriseContext(AppUserWhitelistMatchVO whitelist,
+ BigDecimal userInputAmount,
+ Integer siteId,
+ Integer strategyId) {
+ Integer companyId = whitelist.getCompanyId();
+ if (companyId == null) {
+ return ChargeAccountSupport.ChargeContext.fail(2, "账号未归属公司,无法使用企业额度充电,请选择其他支付方式");
+ }
+ TGroupCompany company;
+ try {
+ R<TGroupCompany> companyResult = groupCompanyClient.getCompanyById(companyId);
+ company = companyResult == null ? null : companyResult.getData();
+ } catch (Exception ex) {
+ log.error("[CD建单] 查询所属公司失败 companyId={}", companyId, ex);
+ return ChargeAccountSupport.ChargeContext.fail(2, "查询所属公司失败,请稍后重试或选择其他支付方式");
+ }
+ if (company == null) {
+ return ChargeAccountSupport.ChargeContext.fail(2, "所属公司不存在,无法使用企业额度充电");
+ }
+ if (company.getStatus() != null && company.getStatus() != 1) {
+ return ChargeAccountSupport.ChargeContext.fail(2, "所属公司已停用,无法使用企业额度充电");
+ }
+ int accountMode = company.getAccountMode() == null
+ ? ChargeAccountSupport.ACCOUNT_MODE_SHARED : company.getAccountMode();
+
+ ChargeAuthorizationDTO authorization = null;
+ try {
+ R<ChargeAuthorizationDTO> authResult = chargeAuthorizationClient.getDetailByCompanyId(companyId);
+ authorization = authResult == null ? null : authResult.getData();
+ } catch (Exception ex) {
+ log.error("[CD建单] 查询充电授权失败,按未配置授权放行 companyId={}", companyId, ex);
+ }
+ LocalDateTime maxEndTime = null;
+ if (authorization != null) {
+ if (authorization.getSiteIds() != null && !authorization.getSiteIds().isEmpty()
+ && (siteId == null || !authorization.getSiteIds().contains(siteId))) {
+ return ChargeAccountSupport.ChargeContext.fail(5, "当前站点不在企业充电授权范围内,请选择其他支付方式");
+ }
+ ChargeAccountSupport.AuthorizationWindow window = chargeAccountSupport
+ .resolveAuthorizationWindow(authorization, strategyId, companyId);
+ if (window == null) {
+ return ChargeAccountSupport.ChargeContext.fail(2, "当前时间不在企业充电授权时段内,请选择其他支付方式");
+ }
+ maxEndTime = window.getMaxEndTime();
+ }
+
+ BigDecimal usableAmount = accountMode == ChargeAccountSupport.ACCOUNT_MODE_ALLOCATED
+ ? whitelist.getBalance() : company.getAvailableBalance();
+ BigDecimal usable = money(usableAmount);
+ if (usable.compareTo(BigDecimal.ZERO) <= 0) {
+ return ChargeAccountSupport.ChargeContext.fail(3, "企业额度不足,请选择其他支付方式");
+ }
+ BigDecimal enterpriseLimit = authorization == null ? null : authorization.getDispatchAmount();
+ if (enterpriseLimit == null || enterpriseLimit.compareTo(BigDecimal.ZERO) <= 0) {
+ enterpriseLimit = ChargeAccountSupport.DEFAULT_DISPATCH_AMOUNT;
+ }
+ BigDecimal dispatch = money(userInputAmount);
+ dispatch = dispatch.compareTo(BigDecimal.ZERO) <= 0
+ ? enterpriseLimit.min(usable)
+ : dispatch.min(enterpriseLimit).min(usable);
+ return ChargeAccountSupport.ChargeContext.ok(accountMode, dispatch.setScale(2, RoundingMode.HALF_UP), maxEndTime);
+ }
+
+ /**
+ * 锁定企业充电额度(共享=企业账户余额,分配=白名单账户余额),返回是否锁定成功
+ *
+ * @param context 企业账户上下文(含账户模式与下发额度)
+ * @param whitelist 白名单命中结果(含白名单ID与归属公司ID)
+ * @return true=锁定成功
+ */
+ private boolean lockWhitelistEnterpriseAmount(ChargeAccountSupport.ChargeContext context,
+ AppUserWhitelistMatchVO whitelist) {
+ try {
+ R<BigDecimal> result;
+ if (Integer.valueOf(ChargeAccountSupport.ACCOUNT_MODE_ALLOCATED).equals(context.getAccountMode())) {
+ result = appUserWhitelistClient.lockBalance(whitelist.getWhitelistId(), context.getDispatchAmount());
+ } else {
+ result = groupCompanyClient.lockBalance(whitelist.getCompanyId(), context.getDispatchAmount());
+ }
+ if (result == null || result.getCode() != R.SUCCESS) {
+ log.error("[CD建单] 锁定企业额度失败 accountMode={} companyId={} whitelistId={} amount={} msg={}",
+ context.getAccountMode(), whitelist.getCompanyId(), whitelist.getWhitelistId(),
+ context.getDispatchAmount(), result == null ? "null" : result.getMsg());
+ return false;
+ }
+ return true;
+ } catch (Exception ex) {
+ log.error("[CD建单] 锁定企业额度异常 accountMode={} companyId={} whitelistId={} amount={}",
+ context.getAccountMode(), whitelist.getCompanyId(), whitelist.getWhitelistId(),
+ context.getDispatchAmount(), ex);
+ return false;
+ }
+ }
+
+ /**
* 按白名单权益拆分电费和服务费减免金额,财务统计需要分开展示两类退款金额
*
* @param chargingOrder 充电订单
@@ -774,11 +922,229 @@
* @return 数组下标0=电费减免,下标1=服务费减免
*/
private BigDecimal[] calculateWhitelistRefundSplit(TChargingOrder chargingOrder, BigDecimal electrovalence, BigDecimal serviceCharge) {
- return ChargingCouponSupport.calculateWhitelistRefundSplit(
+ return ChargingCouponSupport.calculateWhitelistRefundSplitByBenefitType(
isWhitelistOrder(chargingOrder),
- isWhitelistAllFreeOrder(chargingOrder),
+ chargingOrder == null ? null : chargingOrder.getWhitelistBenefitType(),
electrovalence,
serviceCharge);
+ }
+
+ /**
+ * 结算企业账户额度(白名单企业账户流程):
+ * 共享模式结算公司余额,分配模式结算白名单账户余额;按实际消费扣减锁定额度、差额退回原账户,
+ * 结算失败返回false,由调用方回滚订单状态等待补偿重试。
+ *
+ * @param chargingOrder 充电订单(含companyId/accountMode/lockedAmount/whitelistId)
+ * @param actualAmount 实际消费金额
+ * @return true=结算成功或非企业账户订单
+ */
+ @Override
+ public boolean settleWhitelistEnterpriseAccount(TChargingOrder chargingOrder, BigDecimal actualAmount) {
+ if (!isWhitelistEnterpriseOrder(chargingOrder)) {
+ return true;
+ }
+ BigDecimal lockedAmount = money(chargingOrder.getLockedAmount());
+ BigDecimal actual = money(actualAmount).max(BigDecimal.ZERO);
+ if (lockedAmount.compareTo(BigDecimal.ZERO) <= 0) {
+ log.info("[CD结算] 企业账户订单无锁定金额,跳过额度结算 code={}", chargingOrder.getCode());
+ return true;
+ }
+ boolean allocated = Integer.valueOf(ChargeAccountSupport.ACCOUNT_MODE_ALLOCATED).equals(chargingOrder.getAccountMode());
+ BigDecimal afterBalance;
+ try {
+ R<BigDecimal> settleResult;
+ if (allocated && null != chargingOrder.getWhitelistId()) {
+ settleResult = appUserWhitelistClient.settleBalance(chargingOrder.getWhitelistId(), lockedAmount, actual);
+ } else {
+ // 订单侧自行写消费流水,此处不传订单号(账户侧仅在订单号非空时做幂等与流水,避免重复记账)
+ settleResult = groupCompanyClient.settleBalance(chargingOrder.getCompanyId(), lockedAmount, actual,
+ null, null, null, null);
+ }
+ if (null == settleResult || R.SUCCESS != settleResult.getCode()) {
+ log.error("[CD结算] 企业账户额度结算失败 code={} accountMode={} companyId={} locked={} actual={} msg={}",
+ chargingOrder.getCode(), chargingOrder.getAccountMode(), chargingOrder.getCompanyId(),
+ lockedAmount, actual, null == settleResult ? "null" : settleResult.getMsg());
+ return false;
+ }
+ afterBalance = settleResult.getData();
+ } catch (Exception ex) {
+ log.error("[CD结算] 企业账户额度结算异常 code={} accountMode={} companyId={} locked={} actual={}",
+ chargingOrder.getCode(), chargingOrder.getAccountMode(), chargingOrder.getCompanyId(), lockedAmount, actual, ex);
+ return false;
+ }
+ recordWhitelistEnterpriseConsume(chargingOrder, actual, lockedAmount, afterBalance, allocated);
+ log.info("[CD结算] 企业账户额度结算完成 code={} accountMode={} locked={} actual={} balance={}",
+ chargingOrder.getCode(), chargingOrder.getAccountMode(), lockedAmount, actual, afterBalance);
+ return true;
+ }
+
+ /**
+ * 写企业额度分配记录-消费流水:分配模式记白名单账户,共享模式记企业账户;
+ * 消费前余额 = 结算后余额 + 实际消费 - 锁定额度(实扣冻结口径反推),流水失败不影响结算结果。
+ */
+ private void recordWhitelistEnterpriseConsume(TChargingOrder chargingOrder, BigDecimal actual, BigDecimal lockedAmount,
+ BigDecimal afterBalance, boolean allocated) {
+ if (actual.compareTo(BigDecimal.ZERO) <= 0) {
+ return;
+ }
+ String userInfo;
+ String userName;
+ if (allocated) {
+ userInfo = null == chargingOrder.getWhitelistId() ? null : String.valueOf(chargingOrder.getWhitelistId());
+ userName = userInfo;
+ } else {
+ TGroupCompany company = chargeAccountSupport.queryCompanyQuietly(chargingOrder.getCompanyId());
+ String companyName = null == company ? null : company.getName();
+ userInfo = null == companyName || companyName.isEmpty() ? String.valueOf(chargingOrder.getCompanyId()) : companyName;
+ userName = companyName;
+ }
+ BigDecimal after = null == afterBalance ? BigDecimal.ZERO : afterBalance;
+ BigDecimal before = after.add(actual).subtract(lockedAmount);
+ chargeAccountSupport.recordConsume(chargingOrder.getCompanyId(),
+ allocated ? TCompanyAllocateRecord.USER_TYPE_WHITELIST : TCompanyAllocateRecord.USER_TYPE_COMPANY,
+ userInfo, userName, before, actual.negate(), chargingOrder.getCode(), "充电桩充电结算");
+ }
+
+ /**
+ * 释放企业账户锁定额度(订单未结算/关单时调用;失败仅记录日志,不影响主流程)
+ * 释放幂等:先用条件更新抢占 lock_released 标记(0->1),保证同一订单的锁定额在多路径并发或
+ * 重复补偿下只释放一次;释放失败把标记回滚为 0,留待后续补偿或人工重试。
+ */
+ @Override
+ public void unlockWhitelistEnterpriseAmountQuietly(TChargingOrder chargingOrder, String scene) {
+ if (!isWhitelistEnterpriseOrder(chargingOrder)) {
+ return;
+ }
+ BigDecimal lockedAmount = money(chargingOrder.getLockedAmount());
+ 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())
+ && null != chargingOrder.getWhitelistId()) {
+ result = appUserWhitelistClient.unlockBalance(chargingOrder.getWhitelistId(), lockedAmount);
+ } else {
+ result = groupCompanyClient.unlockBalance(chargingOrder.getCompanyId(), lockedAmount);
+ }
+ if (null == result || R.SUCCESS != result.getCode()) {
+ log.error("[CD关单] 释放企业账户锁定额度失败 scene={} code={} companyId={} amount={} msg={}",
+ scene, chargingOrder.getCode(), chargingOrder.getCompanyId(), lockedAmount,
+ null == result ? "null" : result.getMsg());
+ } else {
+ unlocked = true;
+ 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());
+ }
+ }
+
+ /**
+ * 判断是否触发授权时间到期主动停充(白名单企业账户订单按授权截止时间提前下发停充指令)
+ */
+ private boolean needAuthorizeStop(TChargingOrder chargingOrder) {
+ if (null == chargingOrder || !isWhitelistSpecialOrder(chargingOrder) || null == chargingOrder.getMaxEndTime()) {
+ return false;
+ }
+ LocalDateTime startTime = null != chargingOrder.getStartTime() ? chargingOrder.getStartTime() : chargingOrder.getCreateTime();
+ return chargeAccountSupport.needAuthorizeStop(chargingOrder.getMaxEndTime(), startTime);
+ }
+
+ /**
+ * 授权时间到期主动停充:复用小程序主动停充口径(状态3->4、结束方式1)并下发硬件停止充电指令
+ */
+ private void stopByAuthorizeExpired(TChargingOrder chargingOrder) {
+ TChargingGun gun;
+ TChargingPile pile;
+ try {
+ R<TChargingGun> gunResult = chargingGunClient.getChargingGunById(chargingOrder.getChargingGunId());
+ gun = null == gunResult ? null : gunResult.getData();
+ R<TChargingPile> pileResult = null == chargingOrder.getChargingPileId() ? null
+ : chargingPileClient.getChargingPileById(chargingOrder.getChargingPileId());
+ pile = null == pileResult ? null : pileResult.getData();
+ } catch (Exception ex) {
+ log.error("[CD停充] 授权到期停充查询设备异常 code={}", chargingOrder.getCode(), ex);
+ return;
+ }
+ if (null == gun || null == pile) {
+ log.error("[CD停充] 授权到期停充缺少设备信息 code={} gunId={}", chargingOrder.getCode(), chargingOrder.getChargingGunId());
+ return;
+ }
+ boolean updated = this.lambdaUpdate()
+ .eq(TChargingOrder::getId, chargingOrder.getId())
+ .eq(TChargingOrder::getStatus, 3)
+ .set(TChargingOrder::getStatus, 4)
+ .set(TChargingOrder::getEndMode, 1)
+ .set(TChargingOrder::getEndTime, LocalDateTime.now())
+ .update();
+ if (!updated) {
+ return;
+ }
+ chargingOrder.setStatus(4);
+ chargingOrder.setEndMode(1);
+ chargingOrder.setEndTime(LocalDateTime.now());
+ PlatformStopCharging platformStopCharging = new PlatformStopCharging();
+ platformStopCharging.setCharging_pile_code(pile.getCode());
+ platformStopCharging.setCharging_gun_code(gun.getCode());
+ sendMessageClient.platformStopCharging(platformStopCharging);
+ log.warn("[CD停充] 授权时间到期主动停充 code={} maxEndTime={}", chargingOrder.getCode(), chargingOrder.getMaxEndTime());
}
private TVip getVipSnapshot(TAppUserVipDetail vipDetail, Integer vipId) {
@@ -834,34 +1200,44 @@
}
AppUserWhitelistMatchVO whitelist = appUserWhitelistClient.matchWhitelist(userId, tChargingGun.getSiteId()).getData();
boolean whitelistMatched = whitelist != null && Boolean.TRUE.equals(whitelist.getMatched());
- if (whitelistMatched) {
- addChargingOrder.setAppUserCouponId(null);
+ if (whitelistMatched && Integer.valueOf(2).equals(payMethod)) {
+ // 白名单用户不支持组合支付:仅支持余额支付(企业额度)与第三方支付
+ return AjaxResult.error("白名单用户不支持组合支付,请选择余额支付或第三方支付");
}
- boolean whitelistAllFree = whitelistMatched
- && Integer.valueOf(WHITELIST_BENEFIT_ALL_FREE).equals(whitelist.getBenefitType());
+ // 白名单用户按支付方式分流:余额支付=企业账户流程(锁定额度+公司优惠结算),第三方支付=按普通用户流程执行
+ boolean enterpriseCharging = whitelistMatched && Integer.valueOf(1).equals(payMethod);
BigDecimal userBalance = money(appUser.getBalance());
BigDecimal balancePayAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
- if (payMethod == 2 && userBalance.compareTo(rechargeAmount) >= 0) {
- payMethod = 1;
- addChargingOrder.setPayMethod(1);
- }
- if (isBalancePayMode(payMethod)) {
+ BigDecimal payableAmount;
+ BigDecimal thirdPayAmount;
+ if (enterpriseCharging) {
+ // 企业账户流程不使用用户余额与优惠券,额度在企业上下文解析后锁定并作为下发金额
addChargingOrder.setAppUserCouponId(null);
- } else if (!whitelistMatched && addChargingOrder.getAppUserCouponId() != null) {
- // 这里只校验优惠券,不抵扣预付金额;实际门槛和优惠金额在充电结束时计算。
- validateSelectedChargingCoupon(addChargingOrder.getAppUserCouponId(), userId);
- }
- BigDecimal payableAmount = ChargingCouponSupport.calculatePrepaymentAmount(rechargeAmount, whitelistAllFree);
- BigDecimal thirdPayAmount = payableAmount;
- if (isBalancePayMode(payMethod)) {
- if (payMethod == 1 && userBalance.compareTo(payableAmount) < 0) {
- return AjaxResult.error("余额不足,请选择组合支付或第三方支付");
+ payableAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+ thirdPayAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+ } else {
+ if (payMethod == 2 && userBalance.compareTo(rechargeAmount) >= 0) {
+ payMethod = 1;
+ addChargingOrder.setPayMethod(1);
}
- balancePayAmount = payMethod == 1 ? payableAmount : userBalance.min(payableAmount).setScale(2, RoundingMode.HALF_UP);
- thirdPayAmount = payableAmount.subtract(balancePayAmount).setScale(2, RoundingMode.HALF_UP);
+ if (isBalancePayMode(payMethod)) {
+ addChargingOrder.setAppUserCouponId(null);
+ } else if (addChargingOrder.getAppUserCouponId() != null) {
+ // 这里只校验优惠券,不抵扣预付金额;实际门槛和优惠金额在充电结束时计算。
+ validateSelectedChargingCoupon(addChargingOrder.getAppUserCouponId(), userId);
+ }
+ payableAmount = ChargingCouponSupport.calculatePrepaymentAmount(rechargeAmount, false);
+ thirdPayAmount = payableAmount;
+ if (isBalancePayMode(payMethod)) {
+ if (payMethod == 1 && userBalance.compareTo(payableAmount) < 0) {
+ return AjaxResult.error("余额不足,请选择组合支付或第三方支付");
+ }
+ balancePayAmount = payMethod == 1 ? payableAmount : userBalance.min(payableAmount).setScale(2, RoundingMode.HALF_UP);
+ thirdPayAmount = payableAmount.subtract(balancePayAmount).setScale(2, RoundingMode.HALF_UP);
+ }
+ payMethod = ChargingPaymentSupport.resolvePayMethod(balancePayAmount, thirdPayAmount, payMethod);
+ addChargingOrder.setPayMethod(payMethod);
}
- payMethod = ChargingPaymentSupport.resolvePayMethod(balancePayAmount, thirdPayAmount, payMethod);
- addChargingOrder.setPayMethod(payMethod);
//检查当前枪是否是正在使用中
TChargingOrder one = this.getOne(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getChargingGunId, addChargingOrder.getId())
.in(TChargingOrder::getStatus, Arrays.asList(3)).eq(TChargingOrder::getDelFlag, 0));
@@ -958,7 +1334,10 @@
chargingOrder.setCouponDiscountAmount(BigDecimal.ZERO);
chargingOrder.setIsWhitelist(whitelistMatched ? 1 : 0);
chargingOrder.setWhitelistId(whitelistMatched ? whitelist.getWhitelistId() : null);
- chargingOrder.setWhitelistBenefitType(whitelistMatched ? whitelist.getBenefitType() : null);
+ // 白名单权益已下线:订单不再记录权益类型,企业优惠通过 discount_* 列体现
+ chargingOrder.setWhitelistBenefitType(null);
+ chargingOrder.setCompanyId(whitelistMatched ? whitelist.getCompanyId() : null);
+ chargingOrder.setFleetId(whitelistMatched ? whitelist.getFleetId() : null);
chargingOrder.setWhitelistDiscountAmount(BigDecimal.ZERO);
chargingOrder.setWhitelistElectricRefundAmount(BigDecimal.ZERO);
chargingOrder.setWhitelistServiceRefundAmount(BigDecimal.ZERO);
@@ -981,8 +1360,26 @@
}
TAccountingStrategy accountingStrategy = accountingStrategyClient.getAccountingStrategyById(accountingStrategyId).getData();
- //会员折扣支持直营网点和加盟商站点
- if (!whitelistMatched && Integer.valueOf(1).equals(site.getMemberDiscountFlag()) && null != appUser.getVipId()) {
+ // 白名单企业账户流程:解析企业授权上下文并锁定额度(下发额度=min(用户输入充值金额, 账户可用额度, 企业设定充值金额))
+ if (enterpriseCharging) {
+ ChargeAccountSupport.ChargeContext chargeContext =
+ resolveWhitelistEnterpriseContext(whitelist, rechargeAmount, tChargingGun.getSiteId(), accountingStrategyId);
+ if (!chargeContext.isOk()) {
+ return AjaxResult.error(chargeContext.getFailReason());
+ }
+ if (!lockWhitelistEnterpriseAmount(chargeContext, whitelist)) {
+ return AjaxResult.error("企业额度锁定失败,请稍后重试或选择其他支付方式");
+ }
+ chargingOrder.setRechargeAmount(chargeContext.getDispatchAmount());
+ chargingOrder.setAccountMode(chargeContext.getAccountMode());
+ chargingOrder.setLockedAmount(chargeContext.getDispatchAmount());
+ chargingOrder.setMaxEndTime(chargeContext.getMaxEndTime());
+ // 企业优惠快照落库(建单时固化,结算/补偿优先读快照,抵抗公司优惠设置后续变更)
+ chargeAccountSupport.fillPreferentialSnapshot(chargingOrder, whitelist.getCompanyId());
+ }
+
+ //会员折扣支持直营网点和加盟商站点;余额支付(含组合支付)与企业账户流程不参与会员折扣
+ if (!enterpriseCharging && !isBalancePayMode(payMethod) && Integer.valueOf(1).equals(site.getMemberDiscountFlag()) && null != appUser.getVipId()) {
GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
getAppUserVipDetail.setAppUserId(appUser.getId());
getAppUserVipDetail.setVipId(appUser.getVipId());
@@ -998,29 +1395,37 @@
}
this.save(chargingOrder);
- //添加订单的计费策略
- List<TAccountingStrategyDetail> strategyDetailList = accountingStrategyDetailClient.getListByAccountingStrategyId(accountingStrategyId).getData();
- AccountingStrategyOrder accountingStrategyOrder = new AccountingStrategyOrder();
- BeanUtils.copyProperties(accountingStrategy, accountingStrategyOrder);
- accountingStrategyOrder.setChargingOrderId(chargingOrder.getId());
- accountingStrategyOrderService.save(accountingStrategyOrder);
- List<AccountingStrategyDetailOrder> list1 = new ArrayList<>();
- for (TAccountingStrategyDetail tAccountingStrategyDetail : strategyDetailList) {
- AccountingStrategyDetailOrder accountingStrategyDetailOrder = new AccountingStrategyDetailOrder();
- BeanUtils.copyProperties(tAccountingStrategyDetail, accountingStrategyDetailOrder);
- accountingStrategyDetailOrder.setChargingOrderId(chargingOrder.getId());
- accountingStrategyDetailOrder.setCreateTime(LocalDateTime.now());
- accountingStrategyDetailOrder.setDelFlag(false);
- list1.add(accountingStrategyDetailOrder);
- }
- accountingStrategyDetailOrderService.saveBatch(list1);
+ try {
+ //添加订单的计费策略
+ List<TAccountingStrategyDetail> strategyDetailList = accountingStrategyDetailClient.getListByAccountingStrategyId(accountingStrategyId).getData();
+ AccountingStrategyOrder accountingStrategyOrder = new AccountingStrategyOrder();
+ BeanUtils.copyProperties(accountingStrategy, accountingStrategyOrder);
+ accountingStrategyOrder.setChargingOrderId(chargingOrder.getId());
+ accountingStrategyOrderService.save(accountingStrategyOrder);
+ List<AccountingStrategyDetailOrder> list1 = new ArrayList<>();
+ for (TAccountingStrategyDetail tAccountingStrategyDetail : strategyDetailList) {
+ AccountingStrategyDetailOrder accountingStrategyDetailOrder = new AccountingStrategyDetailOrder();
+ BeanUtils.copyProperties(tAccountingStrategyDetail, accountingStrategyDetailOrder);
+ accountingStrategyDetailOrder.setChargingOrderId(chargingOrder.getId());
+ accountingStrategyDetailOrder.setCreateTime(LocalDateTime.now());
+ accountingStrategyDetailOrder.setDelFlag(false);
+ list1.add(accountingStrategyDetailOrder);
+ }
+ accountingStrategyDetailOrderService.saveBatch(list1);
- if (thirdPayAmount.compareTo(BigDecimal.ZERO) == 0 && balancePayAmount.compareTo(BigDecimal.ZERO) > 0) {
- changeUserBalance(userId, balancePayAmount.negate());
- recordBalanceConsume(chargingOrder, balancePayAmount);
- }
- if (thirdPayAmount.compareTo(BigDecimal.ZERO) == 0) {
- return completeChargingOrderPayment(chargingOrder, whitelistMatched ? "WHITELIST" : "BALANCE");
+ if (thirdPayAmount.compareTo(BigDecimal.ZERO) == 0 && balancePayAmount.compareTo(BigDecimal.ZERO) > 0) {
+ changeUserBalance(userId, balancePayAmount.negate());
+ recordBalanceConsume(chargingOrder, balancePayAmount);
+ }
+ if (thirdPayAmount.compareTo(BigDecimal.ZERO) == 0) {
+ return completeChargingOrderPayment(chargingOrder, enterpriseCharging ? "WHITELIST" : "BALANCE");
+ }
+ } catch (RuntimeException ex) {
+ if (enterpriseCharging) {
+ //企业账户流程:建单后续流程异常时释放已锁定的企业额度,避免企业额度被长期占用
+ unlockWhitelistEnterpriseAmountQuietly(chargingOrder, "建单后续流程异常");
+ }
+ throw ex;
}
//会员优惠折扣将其计入增加充电时长(增加总充电金额)
//如果充电总金额未使用完,则需要退回费用=(原金额/总金额)*(总金额-实际充电金额)
@@ -1553,8 +1958,8 @@
BigDecimal s_degrees = tChargingGun.getRatedPower().divide(new BigDecimal(3600), 6, RoundingMode.HALF_EVEN);
TAppUser appUser = appUserClient.getUserById(chargingOrder.getAppUserId()).getData();
- //判断会员是否还有充电优惠次数,计算会员优惠的折扣金额
- if (!isWhitelistOrder(chargingOrder) && null != appUser.getVipId() && isEffectiveVipDiscount(discount)) {
+ //判断会员是否还有充电优惠次数,计算会员优惠的折扣金额;余额支付(含组合支付)不参与会员折扣
+ if (!isWhitelistOrder(chargingOrder) && !isBalancePayMode(chargingOrder.getPayMethod()) && null != appUser.getVipId() && isEffectiveVipDiscount(discount)) {
GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
getAppUserVipDetail.setAppUserId(chargingOrder.getAppUserId());
getAppUserVipDetail.setVipId(appUser.getVipId());
@@ -2068,10 +2473,16 @@
changeUserBalance(chargingOrder.getAppUserId(), balanceRefundAmount);
recordBalanceRefund(chargingOrder, balanceRefundAmount, chargingOrderRefund.getRefundCode());
}
- chargingOrderRefund.setRefundStatus(2);
- chargingOrderRefund.setRefundSerialNumber(isWhitelistAllFreeOrder(chargingOrder) ? "WHITELIST" : "BALANCE");
- chargingOrderRefund.setRefundTime(LocalDateTime.now());
- chargingOrderRefundService.save(chargingOrderRefund);
+ //企业账户流程:启动失败全额释放已锁定的企业额度(订单未产生消费)
+ unlockWhitelistEnterpriseAmountQuietly(chargingOrder, "启动失败退款");
+ //企业账户订单资金全部来自企业账户额度,无用户侧渠道/余额退款,不落退款明细(保持 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;
}
@@ -2360,7 +2771,19 @@
.orderByDesc(TChargingOrder::getCreateTime)
.last(" limit 0, 1"));
if (null == one || !one.getAppUserId().equals(userId)) {
- return null;
+ // VIN 码充电订单兜底:VD 订单存于 t_vin_charging_order,且后付模式无支付动作,
+ // 不按 rechargePaymentStatus 过滤;TVinChargingOrder 继承 TChargingOrder,后续组装原样复用
+ TVinChargingOrder vinOrder = vinChargingOrderMapper.selectOne(new LambdaQueryWrapper<TVinChargingOrder>()
+ .eq(TVinChargingOrder::getAppUserId, userId)
+ .eq(TVinChargingOrder::getChargingGunId, id)
+ .in(TVinChargingOrder::getStatus, Arrays.asList(2, 3, 4))
+ .eq(TVinChargingOrder::getDelFlag, 0)
+ .orderByDesc(TVinChargingOrder::getCreateTime)
+ .last(" limit 0, 1"));
+ if (null == vinOrder) {
+ return null;
+ }
+ one = vinOrder;
}
ChargingDetails chargingDetails = new ChargingDetails();
chargingDetails.setId(one.getId().toString());
@@ -2718,6 +3141,7 @@
fillChargingOrderBalanceIncome(list);
fillChargingOrderShareIncome(list);
fillChargingOrderPageExtra(list, dto.getSiteIds(), sysUser, roleType);
+ fillChargingOrderCompanyFleetNames(list);
pageInfo.setRecords(list);
return pageInfo;
}
@@ -2793,6 +3217,8 @@
&& dto.getIsWhitelist() == null
&& dto.getPaymentType() == null
&& dto.getOrderSource() == null
+ && dto.getCompanyId() == null
+ && dto.getFleetId() == null
&& (dto.getUserIds() == null || dto.getUserIds().isEmpty());
}
@@ -2883,6 +3309,73 @@
chargingOrderVO.setCount(strategies.size());
chargingOrderVO.setLicensePlate(chargingOrderVO.getPlateNum());
}
+ }
+
+ /**
+ * 回填充电桩订单所属公司/车队名称:按订单快照 companyId/fleetId 批量远程取名(公司/车队改名后列表即时生效),
+ * 远程调用失败仅跳过回填,不影响列表其他字段。
+ */
+ private void fillChargingOrderCompanyFleetNames(List<ChargingOrderVO> list) {
+ if (list == null || list.isEmpty()) {
+ return;
+ }
+ List<Integer> companyIds = list.stream().map(ChargingOrderVO::getCompanyId)
+ .filter(Objects::nonNull).distinct().collect(Collectors.toList());
+ if (!companyIds.isEmpty()) {
+ Map<Integer, String> companyNames = loadChargingOrderCompanyNames(companyIds);
+ for (ChargingOrderVO item : list) {
+ String name = item.getCompanyId() == null ? null : companyNames.get(item.getCompanyId());
+ if (StringUtils.hasLength(name)) {
+ item.setCompanyName(name);
+ }
+ }
+ }
+ List<Integer> fleetIds = list.stream().map(ChargingOrderVO::getFleetId)
+ .filter(Objects::nonNull).distinct().collect(Collectors.toList());
+ if (fleetIds.isEmpty()) {
+ return;
+ }
+ Map<Integer, String> fleetNames = loadChargingOrderFleetNames(fleetIds);
+ for (ChargingOrderVO item : list) {
+ String name = item.getFleetId() == null ? null : fleetNames.get(item.getFleetId());
+ if (StringUtils.hasLength(name)) {
+ item.setFleetName(name);
+ }
+ }
+ }
+
+ /** 批量查询公司名称,失败返回已取到的数据 */
+ private Map<Integer, String> loadChargingOrderCompanyNames(List<Integer> companyIds) {
+ Map<Integer, String> result = new HashMap<>();
+ try {
+ List<TGroupCompany> companies = Optional.ofNullable(groupCompanyClient.listCompaniesByIds(companyIds).getData())
+ .orElse(Collections.emptyList());
+ for (TGroupCompany company : companies) {
+ if (company != null && company.getId() != null && StringUtils.hasLength(company.getName())) {
+ result.put(company.getId(), company.getName());
+ }
+ }
+ } catch (Exception e) {
+ log.warn("[充电桩订单回填] 批量查询公司名称失败 ids={}", companyIds, e);
+ }
+ return result;
+ }
+
+ /** 批量查询车队名称,失败返回已取到的数据 */
+ private Map<Integer, String> loadChargingOrderFleetNames(List<Integer> fleetIds) {
+ Map<Integer, String> result = new HashMap<>();
+ try {
+ List<TFleet> fleets = Optional.ofNullable(groupCompanyClient.listFleetsByIds(fleetIds).getData())
+ .orElse(Collections.emptyList());
+ for (TFleet fleet : fleets) {
+ if (fleet != null && fleet.getId() != null && StringUtils.hasLength(fleet.getFleetName())) {
+ result.put(fleet.getId(), fleet.getFleetName());
+ }
+ }
+ } catch (Exception e) {
+ log.warn("[充电桩订单回填] 批量查询车队名称失败 ids={}", fleetIds, e);
+ }
+ return result;
}
/**
@@ -3077,6 +3570,7 @@
platformIncomeTotal = platformIncomeTotal.add(getSummaryDecimal(batchMap, "platformIncomeTotal"));
partnerIncomeTotal = partnerIncomeTotal.add(getSummaryDecimal(batchMap, "partnerIncomeTotal"));
}
+ platformIncomeTotal = platformIncomeTotal.add(nullToZero(this.baseMapper.sumWhitelistRefundByOrderIds(batchIds)));
}
totalMap.put("platformIncomeTotal", platformIncomeTotal);
totalMap.put("partnerIncomeTotal", partnerIncomeTotal);
@@ -3844,12 +4338,12 @@
}
/**
- * 统计近六月订单收入支付方式占比;服务层聚合避免复杂CASE SQL在当前数据库驱动中转换异常。
+ * 统计近六月订单收入支付方式占比;历史订单缺少支付渠道时归入其他支付,避免漏算。
*/
@Override
public List<SixIncomeCircleDto> payTypeCircle(LocalDate sixBefore, List<Integer> siteIds, Boolean partnerView) {
if (siteIds == null || siteIds.isEmpty()) {
- return buildPayTypeCircleData(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
+ return buildPayTypeCircleData(BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO);
}
if (Boolean.TRUE.equals(partnerView)) {
return buildPartnerShareCircleData(this.baseMapper.chargingOrderShareIncomeByPayChannel(sixBefore, siteIds));
@@ -3869,28 +4363,31 @@
BigDecimal wechatAmount = BigDecimal.ZERO;
BigDecimal alipayAmount = BigDecimal.ZERO;
BigDecimal balanceAmount = BigDecimal.ZERO;
+ BigDecimal otherAmount = BigDecimal.ZERO;
for (TChargingOrder order : orders) {
BigDecimal thirdPayAmount = getThirdPayNetAmount(order);
BigDecimal balancePayAmount = getBalancePayNetAmount(order);
Integer rechargePaymentType = order.getRechargePaymentType();
if (Integer.valueOf(1).equals(rechargePaymentType)) {
wechatAmount = wechatAmount.add(thirdPayAmount);
- }
- if (Integer.valueOf(2).equals(rechargePaymentType) || Integer.valueOf(3).equals(rechargePaymentType)) {
+ } else if (Integer.valueOf(2).equals(rechargePaymentType) || Integer.valueOf(3).equals(rechargePaymentType)) {
alipayAmount = alipayAmount.add(thirdPayAmount);
+ } else {
+ otherAmount = otherAmount.add(thirdPayAmount);
}
Integer payMethod = order.getPayMethod();
if (Integer.valueOf(1).equals(payMethod) || Integer.valueOf(2).equals(payMethod)) {
balanceAmount = balanceAmount.add(balancePayAmount);
}
}
- return buildPayTypeCircleData(wechatAmount, alipayAmount, balanceAmount);
+ return buildPayTypeCircleData(wechatAmount, alipayAmount, balanceAmount, otherAmount);
}
private List<SixIncomeCircleDto> buildPartnerShareCircleData(List<Map<String, Object>> rows) {
BigDecimal wechatAmount = BigDecimal.ZERO;
BigDecimal alipayAmount = BigDecimal.ZERO;
BigDecimal balanceAmount = BigDecimal.ZERO;
+ BigDecimal otherAmount = BigDecimal.ZERO;
if (rows != null) {
for (Map<String, Object> row : rows) {
String channel = row.get("payChannel") == null ? "" : row.get("payChannel").toString();
@@ -3902,21 +4399,23 @@
} else if ("BALANCE".equalsIgnoreCase(channel) || "WHITELIST".equalsIgnoreCase(channel)) {
balanceAmount = balanceAmount.add(shareAmount);
} else {
- wechatAmount = wechatAmount.add(shareAmount);
+ otherAmount = otherAmount.add(shareAmount);
}
}
}
- return buildPayTypeCircleData(wechatAmount, alipayAmount, balanceAmount);
+ return buildPayTypeCircleData(wechatAmount, alipayAmount, balanceAmount, otherAmount);
}
/**
* 构造固定三类支付方式数据,保证前端饼图在无订单时也有稳定结构。
*/
- private List<SixIncomeCircleDto> buildPayTypeCircleData(BigDecimal wechatAmount, BigDecimal alipayAmount, BigDecimal balanceAmount) {
+ private List<SixIncomeCircleDto> buildPayTypeCircleData(BigDecimal wechatAmount, BigDecimal alipayAmount,
+ BigDecimal balanceAmount, BigDecimal otherAmount) {
List<SixIncomeCircleDto> data = new ArrayList<>();
data.add(buildIncomeCircleDto("微信支付", wechatAmount));
data.add(buildIncomeCircleDto("支付宝支付", alipayAmount));
data.add(buildIncomeCircleDto("余额支付", balanceAmount));
+ data.add(buildIncomeCircleDto("其他支付", otherAmount));
return data;
}
@@ -4558,9 +5057,52 @@
}
dto.setSiteIds(siteIds);
List<ChargingOrderListVO> list1 = this.baseMapper.chargingList(dto, startTime1, startTime2, endTime1, endTime2);
+ fillChargingPeriodCouponSource(list1);
fillChargingPeriodExportBalanceIncome(list1);
fillChargingPeriodShareIncome(list1);
return list1;
+ }
+
+ private void fillChargingPeriodCouponSource(List<ChargingOrderListVO> list) {
+ if (list == null || list.isEmpty()) {
+ return;
+ }
+ List<Long> couponIds = list.stream()
+ .map(ChargingOrderListVO::getAppCouponId)
+ .filter(Objects::nonNull)
+ .distinct()
+ .collect(Collectors.toList());
+ if (couponIds.isEmpty()) {
+ return;
+ }
+ Map<Long, TAppCoupon> couponMap = new HashMap<>();
+ int batchSize = 500;
+ for (int i = 0; i < couponIds.size(); i += batchSize) {
+ List<Long> batchIds = couponIds.subList(i, Math.min(i + batchSize, couponIds.size()));
+ R<List<TAppCoupon>> response = appCouponClient.getAppCouponByIds(batchIds);
+ List<TAppCoupon> coupons = response == null ? null : response.getData();
+ couponMap.putAll(indexByLongId(coupons, TAppCoupon::getId));
+ }
+ for (ChargingOrderListVO order : list) {
+ TAppCoupon coupon = couponMap.get(order.getAppCouponId());
+ if (coupon == null || coupon.getWaysToObtain() == null) {
+ continue;
+ }
+ switch (coupon.getWaysToObtain()) {
+ case 1:
+ order.setCouponSource("积分兑换");
+ break;
+ case 2:
+ order.setCouponSource("会员购买");
+ break;
+ case 3:
+ case 4:
+ order.setCouponSource("后台赠送");
+ break;
+ default:
+ break;
+ }
+ }
}
private void fillChargingPeriodShareIncome(List<ChargingOrderListVO> list) {
@@ -4596,7 +5138,8 @@
if (item == null) {
return;
}
- item.setPlatformIncome(getSummaryDecimal(row, "platformIncome"));
+ item.setPlatformIncome(getSummaryDecimal(row, "platformIncome")
+ .add(getSummaryDecimal(row, "whitelistRefund")));
item.setPartnerIncome(getSummaryDecimal(row, "partnerIncome"));
}
@@ -4648,7 +5191,8 @@
}
for (ChargingOrderVO item : list) {
Map<String, Object> row = incomeMap.get(item.getId());
- item.setPlatformIncome(getSummaryDecimal(row, "platformIncome"));
+ item.setPlatformIncome(getSummaryDecimal(row, "platformIncome")
+ .add(getSummaryDecimal(row, "whitelistRefund")));
item.setPartnerIncome(getSummaryDecimal(row, "partnerIncome"));
}
}
@@ -4783,6 +5327,14 @@
if (com.ruoyi.common.core.utils.StringUtils.isNotEmpty(query.getTransaction_serial_number())) {
//获取当前的计费策略
TChargingOrder chargingOrder1 = this.getOne(new LambdaQueryWrapper<TChargingOrder>().eq(TChargingOrder::getCode, query.getTransaction_serial_number()));
+ if (null == chargingOrder1) {
+ return;
+ }
+ //企业账户流程:充电中授权时间到期主动停充(按授权截止时间提前下发停充指令)
+ if (Integer.valueOf(3).equals(chargingOrder1.getStatus()) && needAuthorizeStop(chargingOrder1)) {
+ stopByAuthorizeExpired(chargingOrder1);
+ return;
+ }
BigDecimal residualAmount = chargingOrder1.getRechargeAmount().add(chargingOrder1.getVipDiscountAmount()).subtract(query.getPaid_amount());
TChargingOrder chargingOrder = new TChargingOrder();
chargingOrder.setId(chargingOrder1.getId());
@@ -5013,6 +5565,15 @@
}
}
+ //企业账户流程:白名单企业账户订单按企业优惠结算(方式2=服务费比例优惠,方式3=按计费模板同阶段单价)
+ //优惠口径优先取订单快照(建单时固化,策略后续变更不影响本单结算),快照为空的历史订单实时查询并回填
+ boolean enterpriseSettlement = isWhitelistEnterpriseOrder(chargingOrder);
+ ChargeAccountSupport.PreferentialSnapshot enterprisePreferential = enterpriseSettlement
+ ? chargeAccountSupport.resolvePreferential(chargingOrder) : null;
+ Integer enterprisePreferentialType = null == enterprisePreferential ? null : enterprisePreferential.getPreferentialType();
+ TBillingTemplateDTO enterpriseTemplate = null == enterprisePreferential ? null : enterprisePreferential.getTemplate();
+ BigDecimal enterpriseElectricDiscount = BigDecimal.ZERO;
+ BigDecimal enterpriseServiceDiscount = BigDecimal.ZERO;
//开始处理明细
for (int i = 0; i < lists.size(); i++) {
AccountingStrategyDetailOrderVo strategyDetail = lists.get(i);
@@ -5038,15 +5599,46 @@
}
//已充电总度数
- BigDecimal electrovalenc = strategyDetail.getElectrovalence().multiply(sharp_peak_charge);
+ BigDecimal finalElectricPrice = strategyDetail.getElectrovalence();
+ BigDecimal finalServicePrice = strategyDetail.getServiceCharge();
+ //企业账户流程:按企业优惠计算最终用电单价
+ if (enterpriseSettlement) {
+ if (Integer.valueOf(ChargeAccountSupport.PREFERENTIAL_TYPE_RATE).equals(enterprisePreferentialType)) {
+ //方式2:服务费按比例优惠
+ finalServicePrice = ChargeAccountSupport.applyServiceDiscountRate(finalServicePrice, enterprisePreferential.getServiceDiscountRate());
+ } else if (Integer.valueOf(ChargeAccountSupport.PREFERENTIAL_TYPE_TEMPLATE).equals(enterprisePreferentialType)) {
+ //方式3:按计费模板同电价阶段单价
+ TBillingTemplateDetail templateDetail = ChargeAccountSupport.findTemplateDetail(enterpriseTemplate, strategyDetail.getType());
+ if (null == templateDetail) {
+ log.error("[CD结算] 计费模板缺少对应电价阶段,该阶段按原价结算 code={} type={}", chargingOrder.getCode(), strategyDetail.getType());
+ } else {
+ if (null != templateDetail.getElectrovalence()) {
+ finalElectricPrice = templateDetail.getElectrovalence();
+ }
+ if (null != templateDetail.getServiceCharge()) {
+ finalServicePrice = templateDetail.getServiceCharge();
+ }
+ }
+ }
+ }
+ BigDecimal electrovalenc = finalElectricPrice.multiply(sharp_peak_charge);
BigDecimal originalServicePrice = strategyDetail.getServiceCharge().multiply(sharp_peak_charge);
- BigDecimal serviceCharge = originalServicePrice;
+ BigDecimal serviceCharge = finalServicePrice.multiply(sharp_peak_charge);
BigDecimal vipDiscountAmount = BigDecimal.ZERO;
- //计算优惠金额
- if (null != chargingOrder.getVipDiscount()) {
+ //计算优惠金额;企业账户流程按企业优惠结算,其余订单中余额支付(含组合支付)不参与会员折扣
+ if (enterpriseSettlement) {
+ //企业优惠金额(原价-优惠价),用于订单优惠统计与 whitelist_* 快照
+ enterpriseElectricDiscount = enterpriseElectricDiscount.add(strategyDetail.getElectrovalence().multiply(sharp_peak_charge).subtract(electrovalenc));
+ enterpriseServiceDiscount = enterpriseServiceDiscount.add(originalServicePrice.subtract(serviceCharge));
+ } else if (!isWhitelistOrder(chargingOrder) && !isBalancePayMode(chargingOrder.getPayMethod()) && null != chargingOrder.getVipDiscount()) {
//0.58折
vipDiscountAmount = serviceCharge.multiply(new BigDecimal(1).subtract(chargingOrder.getVipDiscount()));
serviceCharge = serviceCharge.multiply(chargingOrder.getVipDiscount());
+ }
+ //企业账户流程:明细单价记录优惠后单价,金额与单价口径一致
+ if (enterpriseSettlement) {
+ chargingOrderAccountingStrategy.setElectrovalence(finalElectricPrice);
+ chargingOrderAccountingStrategy.setServiceCharge(finalServicePrice);
}
chargingOrderAccountingStrategy.setChargingCapacity(sharp_peak_charge);
chargingOrderAccountingStrategy.setPeriodElectricPrice(electrovalenc.setScale(4, RoundingMode.HALF_EVEN));
@@ -5139,9 +5731,20 @@
payAmount = payAmount.subtract(couponDiscount).max(BigDecimal.ZERO);
serviceCharge_total = serviceCharge_total.subtract(couponDiscount).max(BigDecimal.ZERO);
chargingOrder.setCouponDiscountAmount(couponDiscount.setScale(2, RoundingMode.HALF_EVEN));
- chargingOrder.setWhitelistDiscountAmount(whitelistDiscount.setScale(2, RoundingMode.HALF_EVEN));
- chargingOrder.setWhitelistElectricRefundAmount(electricDiscount.setScale(2, RoundingMode.HALF_EVEN));
- chargingOrder.setWhitelistServiceRefundAmount(serviceDiscount.setScale(2, RoundingMode.HALF_EVEN));
+ //企业账户流程:企业优惠金额与 whitelist_* 三列快照双写(订单优惠统计沿用 whitelist_* 口径)
+ BigDecimal enterpriseElectricDiscountAmount = enterpriseElectricDiscount.max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_EVEN);
+ BigDecimal enterpriseServiceDiscountAmount = enterpriseServiceDiscount.max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_EVEN);
+ if (enterpriseSettlement) {
+ chargingOrder.setDiscountElectrovalence(enterpriseElectricDiscountAmount);
+ chargingOrder.setDiscountServiceCharge(enterpriseServiceDiscountAmount);
+ chargingOrder.setWhitelistElectricRefundAmount(enterpriseElectricDiscountAmount);
+ chargingOrder.setWhitelistServiceRefundAmount(enterpriseServiceDiscountAmount);
+ chargingOrder.setWhitelistDiscountAmount(enterpriseElectricDiscountAmount.add(enterpriseServiceDiscountAmount));
+ } else {
+ chargingOrder.setWhitelistDiscountAmount(whitelistDiscount.setScale(2, RoundingMode.HALF_EVEN));
+ chargingOrder.setWhitelistElectricRefundAmount(electricDiscount.setScale(2, RoundingMode.HALF_EVEN));
+ chargingOrder.setWhitelistServiceRefundAmount(serviceDiscount.setScale(2, RoundingMode.HALF_EVEN));
+ }
chargingOrder.setElectrovalence(periodElectricPrice_total.setScale(2, RoundingMode.HALF_EVEN));
chargingOrder.setServiceCharge(serviceCharge_total.setScale(2, RoundingMode.HALF_EVEN));
refundAmount = refundAmount.max(BigDecimal.ZERO).setScale(2, RoundingMode.HALF_UP);
@@ -5174,6 +5777,17 @@
if (actualBalancePayAmount.compareTo(BigDecimal.ZERO) < 0) {
actualBalancePayAmount = BigDecimal.ZERO;
}
+ if (enterpriseSettlement) {
+ // 企业账户流程:资金来自企业账户额度(差额结算时退回企业账户),订单无用户侧退款,实付金额=实际消费金额
+ recordedRefundAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+ refundAmount = recordedRefundAmount;
+ channelRefundAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+ thirdRefundAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+ balanceRefundAmount = BigDecimal.ZERO.setScale(2, RoundingMode.HALF_UP);
+ actualThirdPayAmount = BigDecimal.ZERO;
+ actualBalancePayAmount = payAmount.setScale(2, RoundingMode.HALF_EVEN);
+ chargingOrder.setBalancePayAmount(payAmount.setScale(2, RoundingMode.HALF_EVEN));
+ }
refundAmount = recordedRefundAmount;
chargingOrder.setBalanceRefundAmount(balanceRefundAmount.setScale(2, RoundingMode.HALF_UP));
chargingOrder.setThirdRefundAmount(thirdRefundAmount.setScale(2, RoundingMode.HALF_UP));
@@ -5189,8 +5803,26 @@
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);
+ //企业账户流程:结算企业额度(按实际消费扣减锁定额度、差额退回企业账户)并写消费流水
+ if (enterpriseSettlement && !settleWhitelistEnterpriseAccount(chargingOrder, payAmount)) {
+ // 结算失败抛异常回滚本地状态,订单保持停止中,由补偿任务重新结算
+ throw new ServiceException("企业账户额度结算失败,订单未结算,请稍后重试 code=" + chargingOrder.getCode());
+ }
chargingOrder = this.getById(chargingOrder.getId());
Site site = siteClient.getSiteByIds(Arrays.asList(chargingOrder.getSiteId())).getData().get(0);
//推送三方平台
@@ -5586,6 +6218,10 @@
private BigDecimal getManualRefundableAmount(TChargingOrder order) {
if (order == null || order.getId() == null) {
+ return BigDecimal.ZERO;
+ }
+ //企业账户流程:企业账户订单资金来自企业额度,差额在结算时退回企业账户,不支持后台手动退款
+ if (isWhitelistEnterpriseOrder(order)) {
return BigDecimal.ZERO;
}
List<TChargingOrderRefund> successfulRefunds = chargingOrderRefundService.list(
@@ -7082,26 +7718,27 @@
if (cached != null) {
return cached;
}
- LocalDate currentMonth = LocalDate.now().withDayOfMonth(1);
+ LocalDate currentDay = LocalDate.now();
List<SixChargingDto> result = new ArrayList<>();
try {
- String sixBeforeMonth = sixBefore.withDayOfMonth(1).format(LARGE_SCREEN_MONTH_FMT);
- List<SixChargingDto> incomeList = this.baseMapper.incomeTrendFromStat(siteIds, sixBeforeMonth);
- Map<String, SixChargingDto> incomeByMonth = new HashMap<>();
+ List<SixChargingDto> incomeList = this.baseMapper.incomeTrendFromOrders(siteIds,
+ sixBefore.atStartOfDay().format(LARGE_SCREEN_SQL_FMT),
+ currentDay.plusDays(1).atStartOfDay().format(LARGE_SCREEN_SQL_FMT));
+ Map<String, SixChargingDto> incomeByDay = new HashMap<>();
if (incomeList != null) {
for (SixChargingDto income : incomeList) {
if (income != null && income.getMonth() != null) {
- incomeByMonth.put(income.getMonth(), income);
+ incomeByDay.put(income.getMonth(), income);
}
}
}
- LocalDate month = sixBefore.withDayOfMonth(1);
- while (!month.isAfter(currentMonth)) {
- SixChargingDto income = incomeByMonth.get(month.format(LARGE_SCREEN_MONTH_FMT));
+ LocalDate day = sixBefore;
+ while (!day.isAfter(currentDay)) {
+ SixChargingDto income = incomeByDay.get(day.format(LARGE_SCREEN_DAY_FMT));
if (income == null) {
income = new SixChargingDto();
}
- income.setMonth(String.format("%02d", month.getMonthValue()));
+ income.setMonth(day.format(LARGE_SCREEN_DAY_FMT));
if (income.getElectrovalence() == null) {
income.setElectrovalence(BigDecimal.ZERO);
}
@@ -7109,18 +7746,18 @@
income.setServiceCharge(BigDecimal.ZERO);
}
result.add(income);
- month = month.plusMonths(1);
+ day = day.plusDays(1);
}
} catch (Exception e) {
log.warn("大屏收入趋势读取预聚合表失败, siteIds={}, sixBefore={}", siteIds, sixBefore, e);
- LocalDate month = sixBefore.withDayOfMonth(1);
- while (!month.isAfter(currentMonth)) {
+ LocalDate day = sixBefore;
+ while (!day.isAfter(currentDay)) {
SixChargingDto income = new SixChargingDto();
- income.setMonth(String.format("%02d", month.getMonthValue()));
+ income.setMonth(day.format(LARGE_SCREEN_DAY_FMT));
income.setElectrovalence(BigDecimal.ZERO);
income.setServiceCharge(BigDecimal.ZERO);
result.add(income);
- month = month.plusMonths(1);
+ day = day.plusDays(1);
}
}
redisService.setCacheObject(cacheKey, result, LARGE_SCREEN_CACHE_SECONDS, TimeUnit.SECONDS);
@@ -7333,9 +7970,11 @@
stat.put("orderCount", decimalValue(row, "order_count"));
stat.put("userCount", BigDecimal.ZERO);
stat.put("capacity", BigDecimal.ZERO);
- stat.put("electricFee", BigDecimal.ZERO);
- stat.put("serviceFee", BigDecimal.ZERO);
- stat.put("income", BigDecimal.ZERO);
+ BigDecimal electricFee = decimalValue(row, "electric_fee");
+ BigDecimal serviceFee = decimalValue(row, "service_fee");
+ stat.put("electricFee", electricFee);
+ stat.put("serviceFee", serviceFee);
+ stat.put("income", electricFee.add(serviceFee));
stat.put("totalPower", decimalValue(row, "total_power"));
stat.put("registeredUserCount", 0L);
statRows.add(stat);
@@ -7435,6 +8074,14 @@
public EndOfChargePageInfo getEndOfChargePageInfo(String id) {
EndOfChargePageInfo info = new EndOfChargePageInfo();
TChargingOrder chargingOrder = this.getById(id);
+ if (chargingOrder == null) {
+ // VIN 码充电订单兜底:VD 订单存于 t_vin_charging_order(继承 TChargingOrder,字段兼容;
+ // 结算已写 paymentAmount/startTime/endTime,后续组装原样复用)
+ chargingOrder = vinChargingOrderMapper.selectById(id);
+ }
+ if (chargingOrder == null) {
+ throw new RuntimeException("充电订单不存在");
+ }
Long userId = tokenService.getLoginUserApplet().getUserId();
if (!chargingOrder.getAppUserId().equals(userId)) {
throw new RuntimeException("权限不足");
@@ -7795,7 +8442,7 @@
for (List<Integer> chunk : chunkList(gunIds, 500)) {
Long chunkCount = this.baseMapper.selectCount(new LambdaQueryWrapper<TChargingOrder>()
.eq(TChargingOrder::getDelFlag, 0)//未删除
- .in(TChargingOrder::getStatus, Arrays.asList(3, 5))//充电中、已结束
+ .in(TChargingOrder::getStatus, Arrays.asList(3, 4, 5))//充电中、停止中、已结束
.in(TChargingOrder::getChargingGunId, chunk));//符合条件的充电枪
total += chunkCount == null ? 0L : chunkCount;
}
@@ -7804,7 +8451,7 @@
List<LocalDate> dates = new ArrayList<>();
List<Integer> counts = new ArrayList<>();
LocalDate today = LocalDate.now();
- for (int i = 7; i >= 1; i--) {//升序
+ for (int i = 6; i >= 0; i--) {//升序,最近七日(含当天)
LocalDate day = today.minusDays(i);
LocalDateTime dayStart = day.atStartOfDay();
LocalDateTime dayEnd = day.plusDays(1).atStartOfDay();
@@ -8190,8 +8837,8 @@
BigDecimal originalServicePrice = strategyDetail.getServiceCharge().multiply(sharp_peak_charge);
BigDecimal serviceCharge = originalServicePrice;
BigDecimal vipDiscountAmount = BigDecimal.ZERO;
- //计算优惠金额
- if (null != chargingOrder.getVipDiscount()) {
+ //计算优惠金额;余额支付(含组合支付)不参与会员折扣
+ if (!isWhitelistOrder(chargingOrder) && !isBalancePayMode(chargingOrder.getPayMethod()) && null != chargingOrder.getVipDiscount()) {
//0.58折
vipDiscountAmount = serviceCharge.multiply(new BigDecimal(1).subtract(chargingOrder.getVipDiscount()));
serviceCharge = serviceCharge.multiply(chargingOrder.getVipDiscount());
@@ -8379,7 +9026,7 @@
BigDecimal serviceCharge = originalServicePrice;
BigDecimal vipDiscountAmount = BigDecimal.ZERO;
//计算优惠金额
- if (null != chargingOrder.getVipDiscount()) {
+ if (!isWhitelistOrder(chargingOrder) && null != chargingOrder.getVipDiscount()) {
//0.58折
vipDiscountAmount = serviceCharge.multiply(new BigDecimal(1).subtract(chargingOrder.getVipDiscount()));
serviceCharge = serviceCharge.multiply(chargingOrder.getVipDiscount());
@@ -8682,7 +9329,7 @@
BigDecimal shareElectricityAmount = getChargingOrderShareElectricityAmount(chargingOrder);
BigDecimal shareServiceAmount = getChargingOrderShareServiceAmount(chargingOrder);
BigDecimal shareBaseAmount = shareElectricityAmount.add(shareServiceAmount).setScale(2, RoundingMode.HALF_UP);
- if (actualTotalPay.compareTo(BigDecimal.ZERO) <= 0 && isWhitelistOrder(chargingOrder)) {
+ if (actualTotalPay.compareTo(BigDecimal.ZERO) <= 0 && isWhitelistAllFreeOrder(chargingOrder)) {
actualTotalPay = shareBaseAmount;
}
if (actualTotalPay.compareTo(BigDecimal.ZERO) <= 0) {
@@ -8702,7 +9349,7 @@
records.add(buildChargingOrderShareRecord(chargingOrder, site, partner, resolveThirdPayChannel(chargingOrder), actualThirdPay,
BigDecimal.ZERO, actualTotalPay, shareElectricityAmount, shareServiceAmount));
}
- if (records.isEmpty() && isWhitelistOrder(chargingOrder)) {
+ if (records.isEmpty() && isWhitelistAllFreeOrder(chargingOrder)) {
records.add(buildChargingOrderShareRecord(chargingOrder, site, partner, "WHITELIST", actualTotalPay,
BigDecimal.ZERO, actualTotalPay, shareElectricityAmount, shareServiceAmount));
}
--
Gitblit v1.7.1