| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | 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; |
| | |
| | | |
| | | 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; |
| | |
| | | private AppCouponClient appCouponClient; |
| | | @Resource |
| | | private AppUserWhitelistClient appUserWhitelistClient; |
| | | @Resource |
| | | private GroupCompanyClient groupCompanyClient; |
| | | |
| | | @Resource |
| | | private AppUserVipDetailClient appUserVipDetailClient; |
| | |
| | | |
| | | @Resource |
| | | private UploadRealTimeMonitoringDataService uploadRealTimeMonitoringDataService; |
| | | |
| | | @Resource |
| | | private TVinChargingOrderMapper vinChargingOrderMapper; |
| | | |
| | | @Resource |
| | | private TCECClient tcecClient; |
| | |
| | | |
| | | @Resource |
| | | private AccountingStrategyClient accountingStrategyClient; |
| | | |
| | | @Resource |
| | | private ChargeAccountSupport chargeAccountSupport; |
| | | |
| | | @Resource |
| | | private ChargeAuthorizationClient chargeAuthorizationClient; |
| | | |
| | | @Resource |
| | | private PlatformStartChargingReplyService platformStartChargingReplyService; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 判断订单是否为白名单专属订单(企业账户流程订单或历史白名单权益订单): |
| | | * 此类订单按企业优惠/历史权益结算,不参与会员折扣、优惠券等普通用户特权; |
| | | * 白名单用户选择第三方支付的订单(无企业账户模式、无权益类型)不在此列,按普通用户流程执行。 |
| | | * |
| | | * @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 充电订单 |
| | |
| | | * @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) { |
| | |
| | | } |
| | | 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)); |
| | |
| | | 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); |
| | |
| | | } |
| | | 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()); |
| | |
| | | } |
| | | 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; |
| | | } |
| | | //会员优惠折扣将其计入增加充电时长(增加总充电金额) |
| | | //如果充电总金额未使用完,则需要退回费用=(原金额/总金额)*(总金额-实际充电金额) |
| | |
| | | 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()); |
| | |
| | | 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; |
| | | } |
| | |
| | | .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()); |
| | |
| | | String startTime2 = timeRange[1]; |
| | | String endTime1 = timeRange[2]; |
| | | String endTime2 = timeRange[3]; |
| | | PageInfo<ChargingOrderVO> pageInfo = new PageInfo<>(dto.getPageCurr(), dto.getPageSize()); |
| | | int pageCurr = dto.getPageCurr() == null ? 1 : dto.getPageCurr(); |
| | | int pageSize = dto.getPageSize() == null ? 50 : dto.getPageSize(); |
| | | PageInfo<ChargingOrderVO> pageInfo = new PageInfo<>(pageCurr, pageSize); |
| | | Long userId = tokenService.getLoginUser().getUserid(); |
| | | SysUser sysUser = sysUserClient.getSysUser(userId).getData(); |
| | | Integer roleType = sysUser.getRoleType(); |
| | | setChargingOrderAuthSiteIds(dto, sysUser, userId, roleType); |
| | | List<ChargingOrderVO> list = this.baseMapper.chargingOrder(pageInfo, dto, startTime1, startTime2, endTime1, endTime2); |
| | | if (StringUtils.hasLength(dto.getPhone()) && (dto.getUserIds() == null || dto.getUserIds().isEmpty())) { |
| | | List<Long> data = appUserClient.getUserIdsByPhone(dto.getPhone()).getData(); |
| | | if (data == null || data.isEmpty()) { |
| | | pageInfo.setTotal(0L); |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | return pageInfo; |
| | | } |
| | | dto.setUserIds(data); |
| | | } |
| | | // 手动分页:不传 IPage(避免分页拦截器生成无法下推的全量 COUNT),COUNT 与 ORDER BY + LIMIT 均由 SQL 下推到分片 |
| | | long offset = (long) (pageCurr - 1) * pageSize; |
| | | long total = this.baseMapper.chargingOrderCount(dto, startTime1, startTime2, endTime1, endTime2); |
| | | pageInfo.setTotal(total); |
| | | List<ChargingOrderVO> list = total == 0L ? Collections.emptyList() |
| | | : this.baseMapper.chargingOrder(dto, startTime1, startTime2, endTime1, endTime2, offset, pageSize); |
| | | fillChargingOrderBalanceIncome(list); |
| | | fillChargingOrderShareIncome(list); |
| | | fillChargingOrderPageExtra(list, dto.getSiteIds(), sysUser, roleType); |
| | | fillChargingOrderCompanyFleetNames(list); |
| | | pageInfo.setRecords(list); |
| | | return pageInfo; |
| | | } |
| | |
| | | public TCharingOrderVO chargingOrderSummary(ChargingOrderQuery dto) { |
| | | TCharingOrderVO tCharingOrderVO = new TCharingOrderVO(); |
| | | fillEmptyChargingOrderSummary(tCharingOrderVO); |
| | | if (isChargingOrderFullSweep(dto)) { |
| | | // 无任何筛选即全量扫描:汇总聚合跨分片 JOIN(accounting_strategy,分片键 charging_order_id 与 t_charging_order 不一致) |
| | | // 且需把全量订单 id 反查 share/balance,MyCat 只能全量拉回代理层,必然超时。汇总没有 LIMIT 可下推,直接返回空汇总。 |
| | | return tCharingOrderVO; |
| | | } |
| | | String[] timeRange = parseChargingOrderTimeRange(dto); |
| | | String startTime1 = timeRange[0]; |
| | | String startTime2 = timeRange[1]; |
| | |
| | | return new String[]{startTime1, startTime2, endTime1, endTime2}; |
| | | } |
| | | |
| | | /** 是否全量扫描:用户未提供任何可收窄的筛选条件(时间/订单号/手机号/站点/桩/枪/状态等),无界聚合无法下推。 */ |
| | | private boolean isChargingOrderFullSweep(ChargingOrderQuery dto) { |
| | | return !StringUtils.hasLength(dto.getStartTime()) |
| | | && !StringUtils.hasLength(dto.getEndTime()) |
| | | && !StringUtils.hasLength(dto.getCode()) |
| | | && !StringUtils.hasLength(dto.getPhone()) |
| | | && dto.getSiteId() == null |
| | | && dto.getPileId() == null |
| | | && dto.getGunId() == null |
| | | && dto.getOrderType() == null |
| | | && dto.getStatus() == null |
| | | && dto.getIsWhitelist() == null |
| | | && dto.getPaymentType() == null |
| | | && dto.getOrderSource() == null |
| | | && dto.getCompanyId() == null |
| | | && dto.getFleetId() == null |
| | | && (dto.getUserIds() == null || dto.getUserIds().isEmpty()); |
| | | } |
| | | |
| | | private void fillChargingOrderPageExtra(List<ChargingOrderVO> list, List<Integer> siteIds, SysUser sysUser, Integer roleType) { |
| | | if (list == null || list.isEmpty()) { |
| | | return; |
| | |
| | | Map<Integer, Site> siteMap = pageSites.stream() |
| | | .filter(item -> item.getId() != null) |
| | | .collect(Collectors.toMap(Site::getId, item -> item, (a, b) -> a)); |
| | | // 列表 SQL 不再跨库 JOIN app_user/partner(避免分片表无筛选全量广播),手机号与合作商名在此回填 |
| | | Map<Long, String> phoneMap = new HashMap<>(); |
| | | List<Long> pageUserIds = list.stream().map(ChargingOrderVO::getAppUserId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | if (!pageUserIds.isEmpty()) { |
| | | List<TAppUser> pageUsers = Optional.ofNullable(appUserClient.getUserByIds(pageUserIds).getData()).orElse(new ArrayList<>()); |
| | | for (TAppUser user : pageUsers) { |
| | | if (user != null && user.getId() != null) { |
| | | phoneMap.put(user.getId(), user.getPhone()); |
| | | } |
| | | } |
| | | } |
| | | Map<Integer, String> partnerNameMap = new HashMap<>(); |
| | | List<Integer> pagePartnerIds = siteMap.values().stream().map(Site::getPartnerId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | for (Integer partnerId : pagePartnerIds) { |
| | | Partner partner = Optional.ofNullable(partnerClient.getPartnerById(partnerId).getData()).orElse(null); |
| | | if (partner != null && partner.getId() != null) { |
| | | partnerNameMap.put(partner.getId(), partner.getName()); |
| | | } |
| | | } |
| | | Map<Long, List<TChargingOrderAccountingStrategy>> strategyMap = orderIds.isEmpty() ? new HashMap<>() : |
| | | chargingOrderAccountingStrategyService.lambdaQuery() |
| | | .in(TChargingOrderAccountingStrategy::getChargingOrderId, orderIds) |
| | |
| | | if (roleType == 2 && chargingOrderVO.getSiteId() != null) { |
| | | chargingOrderVO.setAuthRecord(Boolean.TRUE.equals(authRecordMap.get(chargingOrderVO.getSiteId()))); |
| | | } |
| | | chargingOrderVO.setPhone(phoneMap.get(chargingOrderVO.getAppUserId())); |
| | | chargingOrderVO.setCommissionAmount(chargingOrderVO.getRechargeAmount() != null ? chargingOrderVO.getRechargeAmount().multiply(new BigDecimal("0.006")) : new BigDecimal("0")); |
| | | chargingOrderVO.setPlatFormMoney(chargingOrderVO.getRechargeAmount() != null ? chargingOrderVO.getRechargeAmount().multiply(new BigDecimal("0.006")).setScale(2, BigDecimal.ROUND_DOWN) : new BigDecimal("0")); |
| | | chargingOrderVO.setUid(chargingOrderVO.getId() + ""); |
| | | Site site = siteMap.get(chargingOrderVO.getSiteId()); |
| | | if (site != null) { |
| | | chargingOrderVO.setSiteName(site.getName()); |
| | | chargingOrderVO.setPartnerName(partnerNameMap.get(site.getPartnerId())); |
| | | } |
| | | TChargingGun gun = gunMap.get(chargingOrderVO.getChargingGunId()); |
| | | TChargingPile pile = pileMap.get(chargingOrderVO.getChargingPileId()); |
| | |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @Override |
| | | public List<ChargingOrderBalanceIncomeDetailVO> balanceIncomeDetail(ChargingOrderQuery dto) { |
| | | if (isChargingOrderFullSweep(dto)) { |
| | | // 与汇总同根因:全量扫描会把全量订单 id 反查 exchange_code_use_record,MyCat 超时,直接返回空明细 |
| | | return new ArrayList<>(); |
| | | } |
| | | String startTime1 = null; |
| | | String startTime2 = null; |
| | | String endTime1 = null; |
| | |
| | | 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); |
| | |
| | | |
| | | @Override |
| | | public R<PageInfo<EnterpriseChargingAnalysisVO>> enterpriseChargingAnalysis(EnterpriseChargingAnalysisQuery query) { |
| | | PageInfo<EnterpriseChargingAnalysisVO> pageInfo = new PageInfo<>(query.getPageCurr(), query.getPageSize()); |
| | | List<EnterpriseChargingAnalysisVO> list = this.baseMapper.enterpriseChargingAnalysis(pageInfo, query); |
| | | if (list == null || list.isEmpty()) { |
| | | int pageCurr = query.getPageCurr() == null ? 1 : query.getPageCurr(); |
| | | int pageSize = query.getPageSize() == null ? 50 : query.getPageSize(); |
| | | PageInfo<EnterpriseChargingAnalysisVO> pageInfo = new PageInfo<>(pageCurr, pageSize); |
| | | // 逆反查聚合(不再跨分片 JOIN,构建块见 TChargingOrderMapper.xml enterpriseInvoiceRows 等): |
| | | // 1) t_order_invoice 单表查企业发票(10 片按 app_user_id 分片,抬头级条件可下推) |
| | | // 2) t_order_invoice_detail 按 order_invoice_id IN 路由查充电明细 |
| | | // 3) 命中订单级条件(站点/时间等)→ 命中订单 id |
| | | // 4) t_charging_order 按 id IN 走主键取 start_time/site_id |
| | | // 5) t_site 单表取站点名 |
| | | // 6) 按公司聚合 + 手工分页(不再传 IPage,避免分页拦截器生成无法下推的全量 COUNT) |
| | | List<EnterpriseChargingAggRow.Invoice> invoices = this.baseMapper.enterpriseInvoiceRows(query); |
| | | if (invoices == null || invoices.isEmpty()) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | return R.ok(pageInfo); |
| | | } |
| | | List<String> companyNames = list.stream() |
| | | .map(EnterpriseChargingAnalysisVO::getCompanyName) |
| | | Map<Long, List<EnterpriseChargingAggRow.Detail>> detailsByInvoice = new HashMap<>(); |
| | | Map<Long, EnterpriseChargingAggRow.Detail> detailByOrder = new LinkedHashMap<>(); |
| | | List<Long> invoiceIds = invoices.stream() |
| | | .map(EnterpriseChargingAggRow.Invoice::getId) |
| | | .filter(Objects::nonNull) |
| | | .distinct() |
| | | .collect(Collectors.toList()); |
| | | Map<String, List<Map<String, Object>>> topSitesByCompany = groupByCompany( |
| | | this.baseMapper.enterpriseTopSitesByCompanies(query, companyNames)); |
| | | Map<String, List<Map<String, Object>>> chargingTimesByCompany = groupByCompany( |
| | | this.baseMapper.enterpriseChargingTimesByCompanies(query, companyNames)); |
| | | for (EnterpriseChargingAnalysisVO row : list) { |
| | | String companyKey = companyKey(row.getCompanyName(), row.getSocialCreditCode()); |
| | | List<Map<String, Object>> topSites = topSitesByCompany.getOrDefault(companyKey, Collections.emptyList()); |
| | | String topSiteNames = topSites.stream() |
| | | .sorted(Comparator.comparing((Map<String, Object> item) -> ((Number) item.getOrDefault("counts", 0)).longValue()).reversed()) |
| | | .limit(3) |
| | | .map(item -> Objects.toString(item.get("siteName"), "")) |
| | | .filter(org.springframework.util.StringUtils::hasText) |
| | | .collect(Collectors.joining("、")); |
| | | row.setTopSiteNames(topSiteNames); |
| | | List<LocalDateTime> chargingTimes = chargingTimesByCompany.getOrDefault(companyKey, Collections.emptyList()).stream() |
| | | .map(item -> toLocalDateTime(item.get("startTime"))) |
| | | .filter(Objects::nonNull) |
| | | .sorted() |
| | | .collect(Collectors.toList()); |
| | | row.setAverageChargingInterval(calculateAverageChargingInterval(chargingTimes)); |
| | | for (int start = 0; start < invoiceIds.size(); start += ENTERPRISE_INVOICE_BATCH_SIZE) { |
| | | int end = Math.min(start + ENTERPRISE_INVOICE_BATCH_SIZE, invoiceIds.size()); |
| | | List<EnterpriseChargingAggRow.Detail> details = |
| | | this.baseMapper.enterpriseInvoiceDetailRows(invoiceIds.subList(start, end)); |
| | | if (details == null) { |
| | | continue; |
| | | } |
| | | for (EnterpriseChargingAggRow.Detail detail : details) { |
| | | if (detail.getOrderId() == null) { |
| | | continue; |
| | | } |
| | | detailsByInvoice.computeIfAbsent(detail.getInvoiceId(), k -> new ArrayList<>()).add(detail); |
| | | detailByOrder.putIfAbsent(detail.getOrderId(), detail); |
| | | } |
| | | } |
| | | pageInfo.setRecords(list); |
| | | if (detailByOrder.isEmpty()) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | return R.ok(pageInfo); |
| | | } |
| | | List<Long> candidateOrderIds = new ArrayList<>(detailByOrder.keySet()); |
| | | boolean orderFilter = hasEnterpriseOrderFilter(query); |
| | | Set<Long> matchedOrderIds = null; |
| | | if (orderFilter) { |
| | | matchedOrderIds = new HashSet<>(); |
| | | for (int start = 0; start < candidateOrderIds.size(); start += ENTERPRISE_ORDER_ID_BATCH_SIZE) { |
| | | int end = Math.min(start + ENTERPRISE_ORDER_ID_BATCH_SIZE, candidateOrderIds.size()); |
| | | List<Long> matched = this.baseMapper.enterpriseAnalysisMatchingOrderIds( |
| | | candidateOrderIds.subList(start, end), query); |
| | | if (matched != null) { |
| | | matchedOrderIds.addAll(matched); |
| | | } |
| | | } |
| | | if (matchedOrderIds.isEmpty()) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | return R.ok(pageInfo); |
| | | } |
| | | } |
| | | List<Long> effectiveOrderIds = orderFilter ? new ArrayList<>(matchedOrderIds) : candidateOrderIds; |
| | | Map<Long, EnterpriseChargingAggRow.Order> orderMap = new HashMap<>(); |
| | | for (int start = 0; start < effectiveOrderIds.size(); start += ENTERPRISE_ORDER_ID_BATCH_SIZE) { |
| | | int end = Math.min(start + ENTERPRISE_ORDER_ID_BATCH_SIZE, effectiveOrderIds.size()); |
| | | List<EnterpriseChargingAggRow.Order> orders = |
| | | this.baseMapper.enterpriseAnalysisChargingOrders(effectiveOrderIds.subList(start, end)); |
| | | if (orders == null) { |
| | | continue; |
| | | } |
| | | for (EnterpriseChargingAggRow.Order order : orders) { |
| | | orderMap.put(order.getOrderId(), order); |
| | | } |
| | | } |
| | | Set<Long> siteIds = orderMap.values().stream() |
| | | .map(EnterpriseChargingAggRow.Order::getSiteId) |
| | | .filter(Objects::nonNull) |
| | | .collect(Collectors.toSet()); |
| | | Map<Long, String> siteNameMap = new HashMap<>(); |
| | | if (!siteIds.isEmpty()) { |
| | | List<EnterpriseChargingAggRow.Site> sites = this.baseMapper.enterpriseAnalysisSites(new ArrayList<>(siteIds)); |
| | | if (sites != null) { |
| | | for (EnterpriseChargingAggRow.Site site : sites) { |
| | | siteNameMap.put(site.getSiteId(), site.getSiteName()); |
| | | } |
| | | } |
| | | } |
| | | Map<String, EnterpriseCompanyAgg> companyAggs = new LinkedHashMap<>(); |
| | | for (EnterpriseChargingAggRow.Invoice invoice : invoices) { |
| | | List<EnterpriseChargingAggRow.Detail> details = |
| | | detailsByInvoice.getOrDefault(invoice.getId(), Collections.emptyList()); |
| | | if (details.isEmpty()) { |
| | | // 无充电明细(order_type=1)的发票不计,等同原 INNER JOIN tod.order_type=1 语义 |
| | | continue; |
| | | } |
| | | List<EnterpriseChargingAggRow.Order> invoiceOrders = new ArrayList<>(); |
| | | BigDecimal detailAmount = BigDecimal.ZERO; |
| | | for (EnterpriseChargingAggRow.Detail detail : details) { |
| | | if (orderFilter && !matchedOrderIds.contains(detail.getOrderId())) { |
| | | continue; |
| | | } |
| | | detailAmount = detailAmount.add(nullToZero(detail.getInvoiceAmount())); |
| | | EnterpriseChargingAggRow.Order order = orderMap.get(detail.getOrderId()); |
| | | if (order != null) { |
| | | invoiceOrders.add(order); |
| | | } |
| | | } |
| | | if (orderFilter && invoiceOrders.isEmpty()) { |
| | | // 订单级筛选后该发票无命中订单 → 剔除,等同原 LEFT JOIN + 订单级 WHERE 语义 |
| | | continue; |
| | | } |
| | | BigDecimal invoiceAmount = detailAmount.signum() > 0 ? detailAmount : nullToZero(invoice.getTotalAmount()); |
| | | String key = companyKey(invoice.getCompanyName(), invoice.getSocialCreditCode()); |
| | | EnterpriseCompanyAgg agg = companyAggs.get(key); |
| | | if (agg == null) { |
| | | agg = new EnterpriseCompanyAgg(invoice.getCompanyName(), invoice.getSocialCreditCode(), |
| | | invoice.getCompanyAddress(), invoice.getCompanyPhone()); |
| | | companyAggs.put(key, agg); |
| | | } |
| | | agg.invoiceCount++; |
| | | agg.invoiceAmount = agg.invoiceAmount.add(invoiceAmount); |
| | | for (EnterpriseChargingAggRow.Order order : invoiceOrders) { |
| | | if (order.getStartTime() != null) { |
| | | agg.startTimes.add(order.getStartTime()); |
| | | if (agg.latestChargingTime == null || order.getStartTime().isAfter(agg.latestChargingTime)) { |
| | | agg.latestChargingTime = order.getStartTime(); |
| | | } |
| | | } |
| | | if (order.getSiteId() != null) { |
| | | agg.siteOrderIds.computeIfAbsent(order.getSiteId(), k -> new HashSet<>()).add(order.getOrderId()); |
| | | } |
| | | } |
| | | } |
| | | if (companyAggs.isEmpty()) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | return R.ok(pageInfo); |
| | | } |
| | | List<EnterpriseChargingAnalysisVO> all = companyAggs.values().stream() |
| | | .sorted(Comparator.comparingLong((EnterpriseCompanyAgg agg) -> agg.invoiceCount).reversed()) |
| | | .map(agg -> buildEnterpriseAnalysisVO(agg, siteNameMap)) |
| | | .collect(Collectors.toList()); |
| | | pageInfo.setTotal(all.size()); |
| | | long offset = (long) (pageCurr - 1L) * pageSize; |
| | | if (offset >= all.size()) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | } else { |
| | | int toIndex = (int) Math.min(offset + pageSize, all.size()); |
| | | pageInfo.setRecords(all.subList((int) offset, toIndex)); |
| | | } |
| | | return R.ok(pageInfo); |
| | | } |
| | | |
| | | private Map<String, List<Map<String, Object>>> groupByCompany(List<Map<String, Object>> rows) { |
| | | if (rows == null || rows.isEmpty()) { |
| | | return Collections.emptyMap(); |
| | | private boolean hasEnterpriseOrderFilter(EnterpriseChargingAnalysisQuery query) { |
| | | return query.getSiteType() != null |
| | | || query.getPartnerId() != null |
| | | || query.getSiteId() != null |
| | | || query.getCreateTime1() != null |
| | | || query.getCreateTime2() != null |
| | | || (query.getCode() != null && !query.getCode().isEmpty()) |
| | | || query.getOrderType() != null |
| | | || (query.getPhone() != null && !query.getPhone().isEmpty()) |
| | | || query.getStatus() != null |
| | | || (query.getPartnerName() != null && !query.getPartnerName().isEmpty()) |
| | | || query.getOrderSource() != null |
| | | || query.getStartTime1() != null |
| | | || query.getStartTime2() != null |
| | | || query.getEndTime1() != null |
| | | || query.getEndTime2() != null; |
| | | } |
| | | |
| | | private EnterpriseChargingAnalysisVO buildEnterpriseAnalysisVO(EnterpriseCompanyAgg agg, |
| | | Map<Long, String> siteNameMap) { |
| | | EnterpriseChargingAnalysisVO vo = new EnterpriseChargingAnalysisVO(); |
| | | vo.setCompanyName(agg.companyName); |
| | | vo.setSocialCreditCode(agg.socialCreditCode); |
| | | vo.setCompanyAddress(agg.companyAddress); |
| | | vo.setCompanyPhone(agg.companyPhone); |
| | | vo.setInvoiceCount(agg.invoiceCount); |
| | | vo.setInvoiceAmount(agg.invoiceAmount); |
| | | vo.setLatestChargingTime(agg.latestChargingTime); |
| | | String topSiteNames = agg.siteOrderIds.entrySet().stream() |
| | | .sorted(Comparator.comparingInt((Map.Entry<Long, Set<Long>> e) -> e.getValue().size()).reversed()) |
| | | .limit(3) |
| | | .map(e -> siteNameMap.getOrDefault(e.getKey(), "")) |
| | | .filter(org.springframework.util.StringUtils::hasText) |
| | | .collect(Collectors.joining("、")); |
| | | vo.setTopSiteNames(topSiteNames); |
| | | vo.setAverageChargingInterval(calculateAverageChargingInterval(new ArrayList<>(agg.startTimes))); |
| | | return vo; |
| | | } |
| | | |
| | | private static class EnterpriseCompanyAgg { |
| | | private final String companyName; |
| | | private final String socialCreditCode; |
| | | private final String companyAddress; |
| | | private final String companyPhone; |
| | | private long invoiceCount; |
| | | private BigDecimal invoiceAmount = BigDecimal.ZERO; |
| | | private LocalDateTime latestChargingTime; |
| | | private final Map<Long, Set<Long>> siteOrderIds = new HashMap<>(); |
| | | private final TreeSet<LocalDateTime> startTimes = new TreeSet<>(); |
| | | |
| | | private EnterpriseCompanyAgg(String companyName, String socialCreditCode, |
| | | String companyAddress, String companyPhone) { |
| | | this.companyName = companyName; |
| | | this.socialCreditCode = socialCreditCode; |
| | | this.companyAddress = companyAddress; |
| | | this.companyPhone = companyPhone; |
| | | } |
| | | return rows.stream().collect(Collectors.groupingBy(row -> companyKey( |
| | | Objects.toString(row.get("companyName"), null), |
| | | Objects.toString(row.get("socialCreditCode"), "")))); |
| | | } |
| | | |
| | | private String companyKey(String companyName, String socialCreditCode) { |
| | | return Objects.toString(companyName, "") + "\u0000" + Objects.toString(socialCreditCode, ""); |
| | | } |
| | | |
| | | private LocalDateTime toLocalDateTime(Object value) { |
| | | if (value instanceof LocalDateTime) { |
| | | return (LocalDateTime) value; |
| | | } |
| | | if (value instanceof java.sql.Timestamp) { |
| | | return ((java.sql.Timestamp) value).toLocalDateTime(); |
| | | } |
| | | if (value instanceof Date) { |
| | | return LocalDateTime.ofInstant(((Date) value).toInstant(), ZoneId.systemDefault()); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | @Override |
| | |
| | | .divide(BigDecimal.valueOf(3600), 2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private static final int SERIAL_ORDER_ID_SEARCH_LIMIT = 5000; |
| | | private static final int SITE_ORDER_ID_SEARCH_LIMIT = 50000; |
| | | |
| | | @Override |
| | | public R<PageInfo<TChargingOrderRefund>> getRefundList(ChargingRefundDto chargingRefundDto) { |
| | | PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(chargingRefundDto.getPageCurr(), chargingRefundDto.getPageSize()); |
| | | boolean withoutOrderJoin = !StringUtils.hasLength(chargingRefundDto.getSerialNumber()) |
| | | && (chargingRefundDto.getSiteIds() == null || chargingRefundDto.getSiteIds().isEmpty()); |
| | | List<TChargingOrderRefund> list = withoutOrderJoin |
| | | ? this.baseMapper.getRefundListWithoutOrderJoin(pageInfo, chargingRefundDto) |
| | | : this.baseMapper.getRefundList(pageInfo, chargingRefundDto); |
| | | if (withoutOrderJoin) { |
| | | enrichRefundOrderInfo(list); |
| | | int pageCurr = chargingRefundDto.getPageCurr() == null ? 1 : chargingRefundDto.getPageCurr(); |
| | | int pageSize = chargingRefundDto.getPageSize() == null ? 50 : chargingRefundDto.getPageSize(); |
| | | PageInfo<TChargingOrderRefund> pageInfo = new PageInfo<>(pageCurr, pageSize); |
| | | long offset = (long) (pageCurr - 1) * pageSize; |
| | | // 每个 UNION 分支须取 offset+pageSize 行,外层再按 offset,pageSize 切页,否则翻页时池子不足导致丢数据 |
| | | int branchLimit = (int) Math.min(offset + pageSize, Integer.MAX_VALUE); |
| | | boolean filterBySerial = StringUtils.hasLength(chargingRefundDto.getSerialNumber()); |
| | | boolean filterBySite = chargingRefundDto.getSiteIds() != null && !chargingRefundDto.getSiteIds().isEmpty(); |
| | | |
| | | List<TChargingOrderRefund> list; |
| | | if (!filterBySerial && !filterBySite) { |
| | | // 无筛选:手工分页。分页拦截器会对 UNION 查询生成全量 COUNT 且无法把 ORDER BY/LIMIT 下推到分片, |
| | | // 这里改为分支内下推(getRefundListPage)加分支 COUNT 求和(countRefundList) |
| | | pageInfo.setTotal(this.baseMapper.countRefundList(chargingRefundDto)); |
| | | list = this.baseMapper.getRefundListPage(chargingRefundDto, offset, pageSize, branchLimit); |
| | | } else { |
| | | // 筛选:逆反查。先按条件查订单 id(t_charging_order 等可下推/走索引), |
| | | // 再按退款表分片键 IN 过滤(可路由),替代跨分片 LEFT JOIN |
| | | RefundOrderIds ids = resolveRefundOrderIds(chargingRefundDto); |
| | | if (ids.isEmpty()) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | return R.ok(pageInfo); |
| | | } |
| | | pageInfo.setTotal(this.baseMapper.countRefundListByOrderIds(chargingRefundDto, |
| | | ids.type1OrderIds, ids.type2OrderIds, ids.type3OrderIds)); |
| | | list = this.baseMapper.getRefundListByOrderIds(chargingRefundDto, |
| | | ids.type1OrderIds, ids.type2OrderIds, ids.type3OrderIds, offset, pageSize, branchLimit); |
| | | } |
| | | enrichRefundOrderInfo(list); |
| | | for (TChargingOrderRefund refund : list) { |
| | | refund.setUid(refund.getType() + "-" + refund.getId()); |
| | | } |
| | | pageInfo.setRecords(list); |
| | | |
| | | return R.ok(pageInfo); |
| | | } |
| | | |
| | | /** |
| | | * 逆反查订单 id:serialNumber 匹配充电/商城/会员订单 id(各自 LIMIT 防爆), |
| | | * siteIds 匹配充电订单 id;两者同时存在时取交集。 |
| | | * 返回约定:null = 该类型不过滤;空列表 = 该类型无匹配(SQL 侧 AND 1=0)。 |
| | | */ |
| | | private RefundOrderIds resolveRefundOrderIds(ChargingRefundDto chargingRefundDto) { |
| | | List<Long> type1OrderIds = null; |
| | | List<Long> type2OrderIds = null; |
| | | List<Long> type3OrderIds = null; |
| | | if (StringUtils.hasLength(chargingRefundDto.getSerialNumber())) { |
| | | // 模糊匹配结果可能很大(短流水号),截断上限防 IN 列表爆炸;匹配不到则列表为空 |
| | | type1OrderIds = safeIds(this.baseMapper.listChargingOrderIdsBySerialNumber( |
| | | chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT)); |
| | | type2OrderIds = safeIds(this.baseMapper.listShoppingOrderIdsBySerialNumber( |
| | | chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT)); |
| | | type3OrderIds = safeIds(this.baseMapper.listVipOrderIdsBySerialNumber( |
| | | chargingRefundDto.getSerialNumber(), SERIAL_ORDER_ID_SEARCH_LIMIT)); |
| | | warnIfTruncated(chargingRefundDto.getSerialNumber(), type1OrderIds, |
| | | SERIAL_ORDER_ID_SEARCH_LIMIT, "充电订单"); |
| | | warnIfTruncated(chargingRefundDto.getSerialNumber(), type2OrderIds, |
| | | SERIAL_ORDER_ID_SEARCH_LIMIT, "商城订单"); |
| | | warnIfTruncated(chargingRefundDto.getSerialNumber(), type3OrderIds, |
| | | SERIAL_ORDER_ID_SEARCH_LIMIT, "会员订单"); |
| | | } |
| | | if (chargingRefundDto.getSiteIds() != null && !chargingRefundDto.getSiteIds().isEmpty()) { |
| | | List<Long> siteMatched = safeIds(this.baseMapper.listChargingOrderIdsBySiteIds( |
| | | chargingRefundDto.getSiteIds(), SITE_ORDER_ID_SEARCH_LIMIT)); |
| | | warnIfTruncated(String.valueOf(chargingRefundDto.getSiteIds()), siteMatched, |
| | | SITE_ORDER_ID_SEARCH_LIMIT, "站点订单"); |
| | | if (type1OrderIds == null) { |
| | | type1OrderIds = siteMatched; |
| | | } else { |
| | | type1OrderIds.retainAll(new HashSet<>(siteMatched)); |
| | | } |
| | | } |
| | | return new RefundOrderIds(type1OrderIds, type2OrderIds, type3OrderIds); |
| | | } |
| | | |
| | | /** 反查结果达到上限说明可能被截断,告警便于排查(截断会导致部分退款不在列表内) */ |
| | | private void warnIfTruncated(String condition, List<Long> ids, int limit, String orderType) { |
| | | if (ids.size() >= limit) { |
| | | log.warn("退款列表逆反查:条件[{}]匹配{}数达到上限{},结果可能被截断", condition, orderType, limit); |
| | | } |
| | | } |
| | | |
| | | private static List<Long> safeIds(List<Long> ids) { |
| | | return ids == null ? new ArrayList<>() : new ArrayList<>(ids); |
| | | } |
| | | |
| | | /** 逆反查结果:null = 不过滤;空列表 = 无匹配 */ |
| | | private static class RefundOrderIds { |
| | | private final List<Long> type1OrderIds; |
| | | private final List<Long> type2OrderIds; |
| | | private final List<Long> type3OrderIds; |
| | | |
| | | private RefundOrderIds(List<Long> type1OrderIds, List<Long> type2OrderIds, List<Long> type3OrderIds) { |
| | | this.type1OrderIds = type1OrderIds; |
| | | this.type2OrderIds = type2OrderIds; |
| | | this.type3OrderIds = type3OrderIds; |
| | | } |
| | | |
| | | /** 三个类型全部明确无匹配(非 null 且为空)时,整页为空,可短路 */ |
| | | private boolean isEmpty() { |
| | | return type1OrderIds != null && type1OrderIds.isEmpty() |
| | | && type2OrderIds != null && type2OrderIds.isEmpty() |
| | | && type3OrderIds != null && type3OrderIds.isEmpty(); |
| | | } |
| | | } |
| | | |
| | | private void enrichRefundOrderInfo(List<TChargingOrderRefund> refunds) { |
| | |
| | | } |
| | | } else { |
| | | List<SixChargingDto> settlementIncome = this.baseMapper.chargeSettlement(sixBeforeValue, siteIds); |
| | | if (settlementIncome != null) { |
| | | result.addAll(settlementIncome); |
| | | } |
| | | List<SixChargingDto> orderHistoryIncome = this.baseMapper.chargeOrderHistory(sixBeforeValue, siteIds); |
| | | result.addAll(mergeMissingMonthlyIncome(settlementIncome, orderHistoryIncome)); |
| | | String siteIdsCsv = siteIds == null ? null : siteIds.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(String::valueOf) |
| | |
| | | return result; |
| | | } |
| | | |
| | | static List<SixChargingDto> mergeMissingMonthlyIncome(List<SixChargingDto> settlementIncome, |
| | | List<SixChargingDto> orderHistoryIncome) { |
| | | List<SixChargingDto> merged = new ArrayList<>(); |
| | | Set<Integer> settledMonths = new HashSet<>(); |
| | | if (settlementIncome != null) { |
| | | for (SixChargingDto item : settlementIncome) { |
| | | if (item == null) { |
| | | continue; |
| | | } |
| | | merged.add(item); |
| | | Integer month = incomeMonth(item); |
| | | if (month != null) { |
| | | settledMonths.add(month); |
| | | } |
| | | } |
| | | } |
| | | if (orderHistoryIncome != null) { |
| | | for (SixChargingDto item : orderHistoryIncome) { |
| | | Integer month = incomeMonth(item); |
| | | if (item != null && month != null && settledMonths.add(month)) { |
| | | merged.add(item); |
| | | } |
| | | } |
| | | } |
| | | return merged; |
| | | } |
| | | |
| | | private static Integer incomeMonth(SixChargingDto item) { |
| | | if (item == null || item.getMonth() == null) { |
| | | return null; |
| | | } |
| | | try { |
| | | int month = Integer.parseInt(item.getMonth()); |
| | | return month >= 1 && month <= 12 ? month : null; |
| | | } catch (NumberFormatException ignored) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public List<SixCircleDto> circle(List<Integer> siteIds, LocalDate sixBefore, Boolean partnerView) { |
| | | List<SixCircleDto> result = this.baseMapper.circle(siteIds, sixBefore, partnerView); |
| | |
| | | } |
| | | |
| | | @Override |
| | | public Map<String, Object> countAll(List<Integer> siteIds, Boolean partnerView) { |
| | | Map<String, Object> result = this.baseMapper.countAll(siteIds, partnerView); |
| | | public Map<String, Object> countAll(List<Integer> siteIds, Boolean partnerView, LocalDate sixBefore) { |
| | | Map<String, Object> result = this.baseMapper.countAll(siteIds, partnerView, sixBefore); |
| | | if (!Boolean.TRUE.equals(partnerView) && result != null) { |
| | | result.put("commission_amount", this.baseMapper.sumSuccessfulShareAmount(siteIds)); |
| | | result.put("commission_amount", this.baseMapper.sumSuccessfulShareAmount(siteIds, sixBefore)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 统计近六月订单收入支付方式占比;服务层聚合避免复杂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)); |
| | |
| | | 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(); |
| | |
| | | } 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; |
| | | } |
| | | |
| | |
| | | } |
| | | 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) { |
| | |
| | | if (item == null) { |
| | | return; |
| | | } |
| | | item.setPlatformIncome(getSummaryDecimal(row, "platformIncome")); |
| | | item.setPlatformIncome(getSummaryDecimal(row, "platformIncome") |
| | | .add(getSummaryDecimal(row, "whitelistRefund"))); |
| | | item.setPartnerIncome(getSummaryDecimal(row, "partnerIncome")); |
| | | } |
| | | |
| | |
| | | } |
| | | 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")); |
| | | } |
| | | } |
| | |
| | | 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()); |
| | |
| | | } |
| | | } |
| | | |
| | | //企业账户流程:白名单企业账户订单按企业优惠结算(方式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); |
| | |
| | | } |
| | | |
| | | //已充电总度数 |
| | | 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)); |
| | |
| | | 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); |
| | |
| | | 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)); |
| | |
| | | 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); |
| | | //推送三方平台 |
| | |
| | | |
| | | 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( |
| | |
| | | if (summary != null) { |
| | | result.putAll(summary); |
| | | } |
| | | // 充电用户数精确去重(用户-站点对表, 跨站不重复); 对表未回填时回退到按站点求和 |
| | | Long pairCount = this.baseMapper.userSiteTableCount(); |
| | | if (pairCount != null && pairCount > 0) { |
| | | Long exactUserCount = this.baseMapper.userCountFromStat(siteIds); |
| | | if (exactUserCount != null) { |
| | | result.put("chargingUserCount", exactUserCount); |
| | | } |
| | | } |
| | | Long registered = this.baseMapper.registeredFromStat(); |
| | | result.put("registeredUserCount", registered == null ? 0L : registered); |
| | | } catch (Exception e) { |
| | |
| | | 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); |
| | | } |
| | |
| | | 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); |
| | |
| | | LocalDate currentMonth = LocalDate.now().withDayOfMonth(1); |
| | | while (!month.isAfter(currentMonth)) { |
| | | writeMonthSlice(month, month.plusMonths(1)); |
| | | writeUserSitePairs(month, month.plusMonths(1)); |
| | | month = month.plusMonths(1); |
| | | } |
| | | // 2. 近90天日切片 |
| | |
| | | } |
| | | // 3. 全局注册用户数 |
| | | writeGlobal(); |
| | | clearLargeScreenCache(); |
| | | log.info("大屏预聚合表全量重建完成, 耗时 {}ms", System.currentTimeMillis() - start); |
| | | } |
| | | |
| | |
| | | LocalDate currentMonth = LocalDate.now().withDayOfMonth(1); |
| | | writeMonthSlice(currentMonth, currentMonth.plusMonths(1)); |
| | | writeDaySlice(LocalDate.now()); |
| | | writeUserSitePairs(LocalDate.now(), LocalDate.now().plusDays(1)); |
| | | writeGlobal(); |
| | | clearLargeScreenCache(); |
| | | log.info("大屏预聚合表刷新完成, 耗时 {}ms", System.currentTimeMillis() - start); |
| | | } |
| | | |
| | | /** |
| | | * 写入一个月度切片聚合(M 行) |
| | | * 写入时间切片内的 (site_id, app_user_id) 对(充电用户精确去重用, INSERT IGNORE 幂等) |
| | | */ |
| | | private void writeMonthSlice(LocalDate monthStart, LocalDate monthEnd) { |
| | | List<Map<String, Object>> rows = this.baseMapper.monthSliceAgg( |
| | | monthStart.atStartOfDay().format(LARGE_SCREEN_SQL_FMT), |
| | | monthEnd.atStartOfDay().format(LARGE_SCREEN_SQL_FMT)); |
| | | if (rows == null || rows.isEmpty()) { |
| | | private void writeUserSitePairs(LocalDate sliceStart, LocalDate sliceEnd) { |
| | | List<Map<String, Object>> pairs = this.baseMapper.userSitePairsAgg( |
| | | sliceStart.atStartOfDay().format(LARGE_SCREEN_SQL_FMT), |
| | | sliceEnd.atStartOfDay().format(LARGE_SCREEN_SQL_FMT)); |
| | | if (pairs == null || pairs.isEmpty()) { |
| | | return; |
| | | } |
| | | List<Map<String, Object>> statRows = new ArrayList<>(); |
| | | for (Map<String, Object> row : rows) { |
| | | if (row == null || row.get("site_id") == null) { |
| | | List<Map<String, Object>> rows = new ArrayList<>(); |
| | | for (Map<String, Object> pair : pairs) { |
| | | if (pair == null || pair.get("site_id") == null || pair.get("app_user_id") == null) { |
| | | continue; |
| | | } |
| | | BigDecimal electricFee = decimalValue(row, "electric_fee"); |
| | | BigDecimal serviceFee = decimalValue(row, "service_fee"); |
| | | Map<String, Object> stat = new HashMap<>(); |
| | | stat.put("statType", "M"); |
| | | stat.put("period", monthStart.format(LARGE_SCREEN_MONTH_FMT)); |
| | | stat.put("siteId", row.get("site_id")); |
| | | stat.put("orderCount", decimalValue(row, "order_count")); |
| | | stat.put("userCount", decimalValue(row, "user_count")); |
| | | stat.put("capacity", decimalValue(row, "capacity")); |
| | | stat.put("electricFee", electricFee); |
| | | stat.put("serviceFee", serviceFee); |
| | | stat.put("income", electricFee.add(serviceFee)); |
| | | stat.put("totalPower", BigDecimal.ZERO); |
| | | stat.put("registeredUserCount", 0L); |
| | | statRows.add(stat); |
| | | Map<String, Object> row = new HashMap<>(); |
| | | row.put("siteId", pair.get("site_id")); |
| | | row.put("appUserId", pair.get("app_user_id")); |
| | | rows.add(row); |
| | | } |
| | | if (!rows.isEmpty()) { |
| | | this.baseMapper.insertUserSitePairs(rows); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 写入一个月度切片聚合(M 行) |
| | | * 订单数取未删除、状态1-5口径;金额取已结束且已支付口径。 |
| | | * 充电用户数取 monthSliceUserAgg(工作台口径: 该站点所有订单的去重用户, 不区分状态/删除标记) |
| | | */ |
| | | private void writeMonthSlice(LocalDate monthStart, LocalDate monthEnd) { |
| | | String startTime = monthStart.atStartOfDay().format(LARGE_SCREEN_SQL_FMT); |
| | | String endTime = monthEnd.atStartOfDay().format(LARGE_SCREEN_SQL_FMT); |
| | | List<Map<String, Object>> rows = this.baseMapper.monthSliceAgg(startTime, endTime); |
| | | List<Map<String, Object>> userRows = this.baseMapper.monthSliceUserAgg(startTime, endTime); |
| | | if ((rows == null || rows.isEmpty()) && (userRows == null || userRows.isEmpty())) { |
| | | return; |
| | | } |
| | | Map<Object, BigDecimal> userCountBySite = new HashMap<>(); |
| | | if (userRows != null) { |
| | | for (Map<String, Object> userRow : userRows) { |
| | | if (userRow != null && userRow.get("site_id") != null) { |
| | | userCountBySite.put(userRow.get("site_id"), decimalValue(userRow, "user_count")); |
| | | } |
| | | } |
| | | } |
| | | String period = monthStart.format(LARGE_SCREEN_MONTH_FMT); |
| | | List<Map<String, Object>> statRows = new ArrayList<>(); |
| | | Map<Object, Boolean> written = new HashMap<>(); |
| | | if (rows != null) { |
| | | for (Map<String, Object> row : rows) { |
| | | if (row == null || row.get("site_id") == null) { |
| | | continue; |
| | | } |
| | | BigDecimal electricFee = decimalValue(row, "electric_fee"); |
| | | BigDecimal serviceFee = decimalValue(row, "service_fee"); |
| | | Map<String, Object> stat = new HashMap<>(); |
| | | stat.put("statType", "M"); |
| | | stat.put("period", period); |
| | | stat.put("siteId", row.get("site_id")); |
| | | stat.put("orderCount", decimalValue(row, "order_count")); |
| | | stat.put("userCount", userCountBySite.getOrDefault(row.get("site_id"), BigDecimal.ZERO)); |
| | | stat.put("capacity", decimalValue(row, "capacity")); |
| | | stat.put("electricFee", electricFee); |
| | | stat.put("serviceFee", serviceFee); |
| | | stat.put("income", electricFee.add(serviceFee)); |
| | | stat.put("totalPower", BigDecimal.ZERO); |
| | | stat.put("registeredUserCount", 0L); |
| | | statRows.add(stat); |
| | | written.put(row.get("site_id"), Boolean.TRUE); |
| | | } |
| | | } |
| | | // 当月仅有充电用户、暂无已结束订单的站点, 补一行保用户数(金额为0) |
| | | for (Map.Entry<Object, BigDecimal> entry : userCountBySite.entrySet()) { |
| | | if (!written.containsKey(entry.getKey())) { |
| | | Map<String, Object> stat = new HashMap<>(); |
| | | stat.put("statType", "M"); |
| | | stat.put("period", period); |
| | | stat.put("siteId", entry.getKey()); |
| | | stat.put("orderCount", BigDecimal.ZERO); |
| | | stat.put("userCount", entry.getValue()); |
| | | stat.put("capacity", BigDecimal.ZERO); |
| | | stat.put("electricFee", BigDecimal.ZERO); |
| | | stat.put("serviceFee", BigDecimal.ZERO); |
| | | stat.put("income", BigDecimal.ZERO); |
| | | stat.put("totalPower", BigDecimal.ZERO); |
| | | stat.put("registeredUserCount", 0L); |
| | | statRows.add(stat); |
| | | } |
| | | } |
| | | if (!statRows.isEmpty()) { |
| | | this.baseMapper.replaceSiteStatBatch(statRows); |
| | |
| | | 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); |
| | |
| | | siteIds.stream().sorted().map(String::valueOf).collect(Collectors.joining(",")) |
| | | .getBytes(StandardCharsets.UTF_8)); |
| | | return LARGE_SCREEN_CACHE_PREFIX + endpoint + ":" + idPart; |
| | | } |
| | | |
| | | private void clearLargeScreenCache() { |
| | | Collection<String> keys = redisService.keys(LARGE_SCREEN_CACHE_PREFIX + "*"); |
| | | if (keys != null && !keys.isEmpty()) { |
| | | redisService.deleteObject(keys); |
| | | } |
| | | } |
| | | |
| | | private BigDecimal decimalValue(Map<String, Object> values, String key) { |
| | |
| | | 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("权限不足"); |
| | |
| | | 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; |
| | | } |
| | |
| | | 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(); |
| | |
| | | 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()); |
| | |
| | | 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()); |
| | |
| | | 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) { |
| | |
| | | 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)); |
| | | } |