| | |
| | | page.setRecords(Collections.emptyList()); |
| | | return page; |
| | | } |
| | | long total = baseMapper.countAppletOrders(vinIds); |
| | | long total = baseMapper.countAppletOrders(appUserId, vinIds); |
| | | page.setTotal(total); |
| | | List<MyVinChargingOrderList> records = total == 0 ? Collections.emptyList() |
| | | : baseMapper.pageAppletOrders(vinIds, (long) (current - 1) * size, size); |
| | | : baseMapper.pageAppletOrders(appUserId, vinIds, (long) (current - 1) * size, size); |
| | | fillAppletExtra(records); |
| | | page.setRecords(records); |
| | | return page; |
| | |
| | | tokenService.getLoginUserApplet().getUserId()); |
| | | List<Long> vinIds = response == null ? Collections.emptyList() |
| | | : Optional.ofNullable(response.getData()).orElse(Collections.emptyList()); |
| | | return vinIds.contains(order.getVinId()) ? order : null; |
| | | // VIN 可换绑:除校验 VIN 当前归属外,还必须校验订单下单用户与当前登录用户一致,防止新绑定用户查看他人历史订单 |
| | | Long appUserId = tokenService.getLoginUserApplet().getUserId(); |
| | | return vinIds.contains(order.getVinId()) && appUserId.equals(order.getAppUserId()) ? order : null; |
| | | } |
| | | |
| | | private Long toEpochMilli(LocalDateTime time) { |
| | |
| | | accountUpdate.setAccountMode(chargeContext.getAccountMode()); |
| | | accountUpdate.setLockedAmount(chargeContext.getDispatchAmount()); |
| | | accountUpdate.setMaxEndTime(chargeContext.getMaxEndTime()); |
| | | // 企业优惠快照落库(建单时固化,结算/补偿优先读快照,抵抗公司优惠设置后续变更) |
| | | chargeAccountSupport.fillPreferentialSnapshot(accountUpdate, vinRecord.getCompanyId()); |
| | | updateById(accountUpdate); |
| | | // 同步内存对象,保证后续失败回滚能按账户模式/锁定额释放额度 |
| | | order.setAccountMode(chargeContext.getAccountMode()); |
| | |
| | | // 避免无快照订单在结算时按 0 价计费):关单(状态-1)并释放锁定额度 |
| | | if (!saveStrategySnapshot(order.getId(), strategyId)) { |
| | | log.error("[VD建单] 计费策略快照保存失败,终止本次启动 code={} strategyId={}", order.getCode(), strategyId); |
| | | closeTimeoutStartingOrder(order); |
| | | closeStartingOrderWithoutGunReset(order); |
| | | return DeviceStartChargingVO.fail(null); |
| | | } |
| | | log.info("[VD建单] VIN码充电订单创建成功 code={} vinId={} gun={} accountMode={} lockAmount={} maxEndTime={}", |
| | |
| | | private PeriodTypeWindow findCurrentPeriodType(Integer strategyId, LocalTime current) { |
| | | ChargeAccountSupport.PeriodTypeWindow period = chargeAccountSupport.findCurrentPeriodType(strategyId, current); |
| | | return period == null ? null : new PeriodTypeWindow(period.getType(), period.getEndTime()); |
| | | } |
| | | |
| | | /** |
| | | * 查询企业优惠设置(失败或未配置返回 null,按不优惠结算;实现见公共支持类) |
| | | */ |
| | | private CompanyPreferentialDTO loadCompanyPreferential(Integer companyId) { |
| | | return chargeAccountSupport.loadCompanyPreferential(companyId); |
| | | } |
| | | |
| | | /** |
| | | * 按优惠方式加载计费模板(仅方式3需要,取不到时该段按原价结算;实现见公共支持类) |
| | | */ |
| | | private TBillingTemplateDTO loadBillingTemplate(CompanyPreferentialDTO preferential) { |
| | | return chargeAccountSupport.loadBillingTemplate(preferential); |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | /** |
| | | * 判断是否触发授权时间到期主动停充(提前 AUTHORIZE_STOP_AHEAD_SECONDS 秒下发,规避数据传输延迟): |
| | | * 授权时长不足提前量时按授权时长的一半提前,避免刚启动即被停充; |
| | | * 授权总时长不足固定提前量时按整个授权时长提前(启动后即触发停充),确保授权截止时间前结束充电; |
| | | * maxEndTime 为空(全天授权/未开启自动停充)时不介入。 |
| | | */ |
| | | private boolean needAuthorizeStop(TVinChargingOrder order) { |
| | |
| | | snapshots.sort(Comparator.comparing(TVinAccountingStrategyDetailOrder::getStartTime, |
| | | Comparator.nullsLast(String::compareTo))); |
| | | |
| | | // 企业优惠设置与计费模板(1=不优惠,2=服务费按比例优惠,3=按计费模板优惠单价收费) |
| | | CompanyPreferentialDTO preferential = loadCompanyPreferential(order.getCompanyId()); |
| | | TBillingTemplateDTO billingTemplate = loadBillingTemplate(preferential); |
| | | // 企业优惠设置与计费模板(1=不优惠,2=服务费按比例优惠,3=按计费模板优惠单价收费): |
| | | // 订单快照优先(建单时固化,策略后续变更不影响本单结算口径),快照为空的历史订单实时查询并在本事务回填 |
| | | ChargeAccountSupport.PreferentialSnapshot preferential = chargeAccountSupport.resolvePreferential(order); |
| | | TBillingTemplateDTO billingTemplate = preferential == null ? null : preferential.getTemplate(); |
| | | |
| | | BigDecimal electrovalenceTotal = BigDecimal.ZERO; |
| | | BigDecimal serviceChargeTotal = BigDecimal.ZERO; |
| | |
| | | final BigDecimal discountSvcTotal = discountServiceTotal; |
| | | final BigDecimal startSoc = getMonitoringSocQuietly(code, "asc"); |
| | | final BigDecimal endSoc = getMonitoringSocQuietly(code, "desc"); |
| | | final ChargeAccountSupport.PreferentialSnapshot preferentialSnapshot = preferential; |
| | | final boolean preferentialBackfill = preferentialSnapshot != null && preferentialSnapshot.needBackfill(); |
| | | |
| | | // 本地事务:写计费明细 + 更新订单(状态5;账户结算在事务外执行,避免长事务占用链接) |
| | | transactionTemplate.execute(status -> { |
| | |
| | | update.setStatus(ORDER_STATUS_FINISHED); |
| | | if (order.getEndMode() == null) { |
| | | update.setEndMode(1); |
| | | } |
| | | if (preferentialBackfill) { |
| | | // 历史订单(快照为空)实时查询结果回填订单快照列,保证后续补偿重算口径一致 |
| | | update.setPreferentialType(preferentialSnapshot.getPreferentialType()); |
| | | update.setServiceDiscountRate(preferentialSnapshot.getServiceDiscountRate()); |
| | | update.setBillingTemplateId(preferentialSnapshot.getBillingTemplateId()); |
| | | update.setBillingTemplateSnapshot( |
| | | ChargeAccountSupport.serializeTemplateSnapshot(preferentialSnapshot.getTemplate())); |
| | | update.setPreferentialSnapshotTime(LocalDateTime.now()); |
| | | } |
| | | updateById(update); |
| | | return true; |
| | |
| | | log.warn("[VD补偿] 启动超时关单 code={} vinId={} gunId={}", order.getCode(), order.getVinId(), order.getChargingGunId()); |
| | | } |
| | | |
| | | /** |
| | | * 严格模式启动失败关单:仅关闭订单并释放锁定额度,不复位充电枪 |
| | | * (枪可能处于离线/故障等业务态,避免误复位为可用;订单已关单,不会残留占用) |
| | | */ |
| | | private void closeStartingOrderWithoutGunReset(TVinChargingOrder order) { |
| | | TVinChargingOrder update = new TVinChargingOrder(); |
| | | update.setStatus(ORDER_STATUS_START_FAILED); |
| | | update.setEndMode(0); |
| | | update.setEndTime(LocalDateTime.now()); |
| | | boolean closed = update(update, new com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper<TVinChargingOrder>() |
| | | .eq(TVinChargingOrder::getId, order.getId()) |
| | | .eq(TVinChargingOrder::getStatus, ORDER_STATUS_STARTING)); |
| | | if (!closed) { |
| | | log.info("[VD建单] 严格模式关单跳过,订单状态已变化 code={}", order.getCode()); |
| | | return; |
| | | } |
| | | // 释放申请时锁定的账户额度(未产生消费) |
| | | unlockVinChargeContextQuietly(order, "启动关单"); |
| | | pushRegulatoryAsync(order, false, false); |
| | | log.warn("[VD建单] 严格模式启动失败关单(不复位充电枪) code={} vinId={} gunId={}", order.getCode(), order.getVinId(), order.getChargingGunId()); |
| | | } |
| | | |
| | | @Override |
| | | public void compensationSettle(TVinChargingOrder order) { |
| | | // 无设备交易记录:以订单累计电量构造全平段账单,复用统一结算入口 |