| | |
| | | import java.time.LocalTime; |
| | | import java.time.format.DateTimeFormatter; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.Collections; |
| | | import java.util.HashSet; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | /** |
| | | * VIN码/刷卡充电公共账户支持(VIN 与刷卡共用,防止两套逻辑漂移): |
| | | * 1) 企业充电授权窗口解析(时间方式=全天/指定时段/指定电价时段)与授权截止时间计算; |
| | | * 2) 企业优惠设置装载(1=不优惠,2=服务费按比例优惠,3=按计费模板优惠单价收费); |
| | | * 3) 授权时间到期停充判定(提前量 = min(60秒, 授权时长/2)); |
| | | * 4) 企业额度分配记录-消费流水写入(charge_type=2,按订单号+变更类型幂等,失败不影响主流程); |
| | | * 3) 授权时间到期停充判定(提前量 = min(60秒, 授权总时长)); |
| | | * 4) 企业额度分配记录-消费流水写入与已结算订单号查询(charge_type=2,按订单号+变更类型幂等,失败不影响主流程); |
| | | * 5) 充电账户上下文(账户模式/锁定额度/授权截止时间/失败码)承载。 |
| | | */ |
| | | @Component |
| | |
| | | public static final int ACCOUNT_MODE_ALLOCATED = 2; |
| | | /** 未配置充电授权时的默认下发金额上限(元) */ |
| | | public static final BigDecimal DEFAULT_DISPATCH_AMOUNT = new BigDecimal("200"); |
| | | /** 授权时间到期停充提前量(秒):剩余授权时间不足该值时提前下发停充,规避数据传输延迟 */ |
| | | /** 授权时间到期停充提前量(秒):固定提前量,剩余授权时间不足该值即下发停充(授权总时长更短时按总时长提前) */ |
| | | public static final int AUTHORIZE_STOP_AHEAD_SECONDS = 60; |
| | | /** 授权时间方式:全天 */ |
| | | public static final int AUTHORIZE_TIME_MODE_ALL_DAY = 1; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 授权时间到期停充判定:剩余授权时间不足提前量时需主动停充; |
| | | * 授权时长不足提前量时按授权时长的一半提前,避免刚启动即被停充; |
| | | * 授权时间到期停充判定:剩余授权时间不足固定提前量(60 秒)时需主动停充,规避数据传输延迟; |
| | | * 授权总时长不足提前量时按整个授权时长提前(表现为启动后即触发停充),确保授权截止时间前结束充电; |
| | | * maxEndTime 为空(全天授权/未开启自动停充)时不介入。 |
| | | */ |
| | | public boolean needAuthorizeStop(LocalDateTime maxEndTime, LocalDateTime startTime) { |
| | |
| | | return true; |
| | | } |
| | | long authorizeSeconds = Duration.between(startTime, maxEndTime).getSeconds(); |
| | | long aheadSeconds = Math.min(AUTHORIZE_STOP_AHEAD_SECONDS, Math.max(authorizeSeconds / 2, 1)); |
| | | long aheadSeconds = Math.min(AUTHORIZE_STOP_AHEAD_SECONDS, Math.max(authorizeSeconds, 1)); |
| | | return remainSeconds <= aheadSeconds; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 批量查询已落消费流水的订单号(订单侧兜底扫描双判据使用) |
| | | * |
| | | * @return 已结算订单号集合;查询失败返回 null 表示无法确认,调用方须按「不得回退订单状态」保守处理 |
| | | */ |
| | | public Set<String> listConsumedOrderCodesQuietly(List<String> orderCodes) { |
| | | if (orderCodes == null || orderCodes.isEmpty()) { |
| | | return Collections.emptySet(); |
| | | } |
| | | try { |
| | | R<List<String>> result = companyAllocateRecordClient.listConsumedOrderCodes(orderCodes); |
| | | if (result == null || result.getCode() != R.SUCCESS || result.getData() == null) { |
| | | log.error("[充电账户] 查询已结算订单号失败 msg={}", result == null ? "null" : result.getMsg()); |
| | | return null; |
| | | } |
| | | return new HashSet<>(result.getData()); |
| | | } catch (Exception ex) { |
| | | log.error("[充电账户] 查询已结算订单号异常", ex); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 写企业额度分配记录-消费流水(订单号+change_type 幂等):失败不影响结算主流程 |
| | | * |
| | | * @param amount 本次变更金额(消费传负数) |