| | |
| | | |
| | | /** |
| | | * 释放企业账户锁定额度(订单未结算/关单时调用;失败仅记录日志,不影响主流程) |
| | | * 释放幂等:先用条件更新抢占 lock_released 标记(0->1),保证同一订单的锁定额在多路径并发或 |
| | | * 重复补偿下只释放一次;释放失败把标记回滚为 0,留待后续补偿或人工重试。 |
| | | */ |
| | | @Override |
| | | public void unlockWhitelistEnterpriseAmountQuietly(TChargingOrder chargingOrder, String scene) { |
| | |
| | | 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()) |
| | |
| | | log.error("[CD关单] 释放企业账户锁定额度失败 scene={} code={} companyId={} amount={} msg={}", |
| | | scene, chargingOrder.getCode(), chargingOrder.getCompanyId(), lockedAmount, |
| | | null == result ? "null" : result.getMsg()); |
| | | return; |
| | | } else { |
| | | unlocked = true; |
| | | log.info("[CD关单] 释放企业账户锁定额度完成 scene={} code={} amount={}", scene, chargingOrder.getCode(), lockedAmount); |
| | | } |
| | | 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()); |
| | | } |
| | | } |
| | | |
| | |
| | | chargingOrder.setAccountMode(chargeContext.getAccountMode()); |
| | | chargingOrder.setLockedAmount(chargeContext.getDispatchAmount()); |
| | | chargingOrder.setMaxEndTime(chargeContext.getMaxEndTime()); |
| | | // 企业优惠快照落库(建单时固化,结算/补偿优先读快照,抵抗公司优惠设置后续变更) |
| | | chargeAccountSupport.fillPreferentialSnapshot(chargingOrder, whitelist.getCompanyId()); |
| | | } |
| | | |
| | | //会员折扣支持直营网点和加盟商站点;余额支付(含组合支付)与企业账户流程不参与会员折扣 |
| | |
| | | } |
| | | //企业账户流程:启动失败全额释放已锁定的企业额度(订单未产生消费) |
| | | unlockWhitelistEnterpriseAmountQuietly(chargingOrder, "启动失败退款"); |
| | | chargingOrderRefund.setRefundStatus(2); |
| | | chargingOrderRefund.setRefundSerialNumber(isWhitelistAllFreeOrder(chargingOrder) ? "WHITELIST" : "BALANCE"); |
| | | chargingOrderRefund.setRefundTime(LocalDateTime.now()); |
| | | chargingOrderRefundService.save(chargingOrderRefund); |
| | | //企业账户订单资金全部来自企业账户额度,无用户侧渠道/余额退款,不落退款明细(保持 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; |
| | | } |
| | |
| | | } |
| | | |
| | | //企业账户流程:白名单企业账户订单按企业优惠结算(方式2=服务费比例优惠,方式3=按计费模板同阶段单价) |
| | | //优惠口径优先取订单快照(建单时固化,策略后续变更不影响本单结算),快照为空的历史订单实时查询并回填 |
| | | boolean enterpriseSettlement = isWhitelistEnterpriseOrder(chargingOrder); |
| | | CompanyPreferentialDTO enterprisePreferential = enterpriseSettlement |
| | | ? chargeAccountSupport.loadCompanyPreferential(chargingOrder.getCompanyId()) : null; |
| | | ChargeAccountSupport.PreferentialSnapshot enterprisePreferential = enterpriseSettlement |
| | | ? chargeAccountSupport.resolvePreferential(chargingOrder) : null; |
| | | Integer enterprisePreferentialType = null == enterprisePreferential ? null : enterprisePreferential.getPreferentialType(); |
| | | TBillingTemplateDTO enterpriseTemplate = enterpriseSettlement |
| | | ? chargeAccountSupport.loadBillingTemplate(enterprisePreferential) : null; |
| | | TBillingTemplateDTO enterpriseTemplate = null == enterprisePreferential ? null : enterprisePreferential.getTemplate(); |
| | | BigDecimal enterpriseElectricDiscount = BigDecimal.ZERO; |
| | | BigDecimal enterpriseServiceDiscount = BigDecimal.ZERO; |
| | | //开始处理明细 |
| | |
| | | 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); |
| | | |
| | | //企业账户流程:结算企业额度(按实际消费扣减锁定额度、差额退回企业账户)并写消费流水 |