| | |
| | | import com.ruoyi.chargingPile.api.dto.GetSiteListDTO; |
| | | import com.ruoyi.chargingPile.api.feignClient.*; |
| | | import com.ruoyi.chargingPile.api.model.*; |
| | | import com.ruoyi.chargingPile.api.support.ReservationTimeRangeSupport; |
| | | import com.ruoyi.chargingPile.api.vo.GetChargingGunByCode; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.dto.ChargingOrderGroup; |
| | |
| | | import com.ruoyi.order.service.TChargingOrderService; |
| | | import com.ruoyi.order.service.TOrderEvaluateService; |
| | | import com.ruoyi.order.service.*; |
| | | import com.ruoyi.order.support.ReservationOrderSupport; |
| | | import com.ruoyi.order.support.ReservationRecommendationCalculator; |
| | | import com.ruoyi.order.util.mongodb.service.PlatformStartChargingReplyService; |
| | | import com.ruoyi.order.util.mongodb.service.TransactionRecordService; |
| | | import com.ruoyi.order.util.mongodb.service.UploadRealTimeMonitoringDataService; |
| | |
| | | import com.ruoyi.order.vo.OrderCountByDate; |
| | | import com.ruoyi.order.vo.StatisticsOfBatteryVO; |
| | | import com.ruoyi.order.vo.LargeChargingPowerVo; |
| | | import com.ruoyi.order.vo.ReservationRecommendationVO; |
| | | import com.ruoyi.other.api.domain.*; |
| | | import com.ruoyi.order.vo.ChargingOrderListInfoVO; |
| | | import com.ruoyi.other.api.domain.TVip; |
| | |
| | | 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; |
| | | |
| | | @Override |
| | | public ReservationRecommendationVO getReservationRecommendation(Integer siteId) { |
| | | if (siteId == null) { |
| | | throw new ServiceException("站点不能为空"); |
| | | } |
| | | R<List<Site>> siteResult = siteClient.getSiteByIds(Collections.singletonList(siteId)); |
| | | List<Site> sites = siteResult == null ? null : siteResult.getData(); |
| | | if (sites == null || sites.isEmpty()) { |
| | | throw new ServiceException("站点不存在"); |
| | | } |
| | | Site site = sites.get(0); |
| | | if (site.getAccountingStrategyId() == null) { |
| | | throw new ServiceException("该站点尚未配置充电计费模板"); |
| | | } |
| | | R<List<TAccountingStrategyDetail>> detailResult = accountingStrategyDetailClient |
| | | .getListByAccountingStrategyId(site.getAccountingStrategyId()); |
| | | List<TAccountingStrategyDetail> details = detailResult == null ? null : detailResult.getData(); |
| | | if (details == null) { |
| | | throw new ServiceException("计费模板服务暂不可用"); |
| | | } |
| | | |
| | | LocalDateTime historyEnd = LocalDate.now().atStartOfDay(); |
| | | LocalDateTime historyStart = historyEnd.minusDays(RESERVATION_RECOMMENDATION_HISTORY_DAYS); |
| | | List<TChargingOrder> orders = list(Wrappers.<TChargingOrder>lambdaQuery() |
| | | .select(TChargingOrder::getStartTime, TChargingOrder::getEndTime) |
| | | .eq(TChargingOrder::getSiteId, siteId) |
| | | .eq(TChargingOrder::getStatus, 5) |
| | | .eq(TChargingOrder::getDelFlag, 0) |
| | | .isNotNull(TChargingOrder::getStartTime) |
| | | .isNotNull(TChargingOrder::getEndTime) |
| | | .ge(TChargingOrder::getEndTime, historyStart) |
| | | .lt(TChargingOrder::getStartTime, historyEnd)); |
| | | ReservationRecommendationCalculator.Recommendation recommendation = |
| | | ReservationRecommendationCalculator.recommend(details, orders, historyStart, historyEnd); |
| | | if (recommendation == null) { |
| | | throw new ServiceException("该站点计费模板未配置谷时段"); |
| | | } |
| | | return new ReservationRecommendationVO(recommendation.getStartTime(), recommendation.getEndTime(), |
| | | recommendation.getUtilizationRate()); |
| | | } |
| | | |
| | | @Resource |
| | | private ChargingGunClient chargingGunClient; |
| | |
| | | && reservationTime.isAfter(LocalDateTime.now().plusMinutes(site.getReservationAheadMinute()))) { |
| | | return AjaxResult.error("预约时间不能超过最早可提前预约时限"); |
| | | } |
| | | if (!StringUtils.hasLength(site.getReservationStartTime()) |
| | | || !StringUtils.hasLength(site.getReservationEndTime())) { |
| | | return AjaxResult.error("电站尚未配置预约时段"); |
| | | } |
| | | LocalTime start = parseReservationClock(site.getReservationStartTime()); |
| | | LocalTime end = parseReservationClock(site.getReservationEndTime()); |
| | | if (start == null || end == null) { |
| | | List<ReservationTimeRange> ranges; |
| | | try { |
| | | ranges = ReservationTimeRangeSupport.parse(site.getReservationTimeRanges(), |
| | | site.getReservationStartTime(), site.getReservationEndTime()); |
| | | } catch (IllegalArgumentException e) { |
| | | return AjaxResult.error("电站预约时段配置不正确"); |
| | | } |
| | | if (!isTimeInWindow(reservationTime.toLocalTime(), start, end)) { |
| | | return AjaxResult.error("预约时间不在允许预约时段内"); |
| | | if (ranges.isEmpty()) { |
| | | return AjaxResult.error("电站尚未配置预约时段"); |
| | | } |
| | | if (!ReservationTimeRangeSupport.contains(ranges, reservationTime.toLocalTime())) { |
| | | return AjaxResult.error("预约时间不在允许预约时段内(" |
| | | + ReservationTimeRangeSupport.describe(ranges) + ")"); |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private LocalTime parseReservationClock(String value) { |
| | | if (!StringUtils.hasLength(value)) { |
| | | return null; |
| | | } |
| | | String[] parts = value.trim().split(":"); |
| | | if (parts.length < 2) { |
| | | return null; |
| | | } |
| | | try { |
| | | return LocalTime.of(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])); |
| | | } catch (RuntimeException e) { |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private boolean isTimeInWindow(LocalTime value, LocalTime start, LocalTime end) { |
| | | if (start.equals(end)) { |
| | | return true; |
| | | } |
| | | if (start.isBefore(end)) { |
| | | return !value.isBefore(start) && !value.isAfter(end); |
| | | } |
| | | return !value.isBefore(start) || !value.isAfter(end); |
| | | } |
| | | |
| | | private boolean isSafetyCheckPassed(PreChargeCheck check) { |
| | |
| | | if (latest == null || latest.getStatus() != 2 || Boolean.TRUE.equals(check.getStartCommandSent())) { |
| | | return; |
| | | } |
| | | if (latest.getReservationTime() != null && latest.getReservationTime().isAfter(LocalDateTime.now())) { |
| | | if (!ReservationOrderSupport.isStartDue(latest.getReservationTime(), LocalDateTime.now())) { |
| | | return; |
| | | } |
| | | TChargingGun gun = chargingGunClient.getChargingGunById(latest.getChargingGunId()).getData(); |
| | |
| | | chargingDetails.setName(site.getName() + "-" + chargingPile.getName()); |
| | | chargingDetails.setCode(one.getCode()); |
| | | chargingDetails.setReservationTime(one.getReservationTime()); |
| | | boolean reservationPending = one.getReservationTime() != null |
| | | && one.getReservationTime().isAfter(LocalDateTime.now()) |
| | | && Integer.valueOf(2).equals(one.getStatus()); |
| | | boolean reservationPending = ReservationOrderSupport.isPending( |
| | | one.getStatus(), one.getReservationTime(), LocalDateTime.now()); |
| | | chargingDetails.setStatus(reservationPending ? 1 : one.getStatus()); |
| | | chargingDetails.setChargingCost(one.getResidualAmount()); |
| | | UploadRealTimeMonitoringData data = uploadRealTimeMonitoringDataService.getLastDataById(one.getCode()); |
| | |
| | | @Override |
| | | public AjaxResult stopCharging(String id) { |
| | | TChargingOrder order = this.getById(id); |
| | | Integer status = order.getStatus(); |
| | | if (status != 3) { |
| | | return AjaxResult.error("还未开始充电"); |
| | | } |
| | | if (status == 4 || status == 5) { |
| | | return AjaxResult.error("不能重复操作"); |
| | | if (order == null) { |
| | | return AjaxResult.error("充电订单不存在"); |
| | | } |
| | | Long userId = tokenService.getLoginUserApplet().getUserId(); |
| | | if (!order.getAppUserId().equals(userId)) { |
| | | log.info("【" + order.getCode() + "】主动停止充电失败,用户权限不足。当前登录用户id:{},订单用户id:{}", userId, order.getAppUserId()); |
| | | log.info("【" + order.getCode() + "】停止或取消充电失败,用户权限不足。当前登录用户id:{},订单用户id:{}", userId, order.getAppUserId()); |
| | | return AjaxResult.error("权限不足"); |
| | | } |
| | | if (ReservationOrderSupport.isPending(order.getStatus(), order.getReservationTime(), LocalDateTime.now())) { |
| | | return cancelPendingReservation(order); |
| | | } |
| | | Integer status = order.getStatus(); |
| | | if (status == 4 || status == 5) { |
| | | return AjaxResult.error("不能重复操作"); |
| | | } |
| | | if (status != 3) { |
| | | return AjaxResult.error("还未开始充电"); |
| | | } |
| | | order.setEndTime(LocalDateTime.now()); |
| | | order.setStatus(4); |
| | |
| | | chengDuJianGuanClient.notificationConnectorChargeStatus(vo); |
| | | }); |
| | | return AjaxResult.success(); |
| | | } |
| | | |
| | | private AjaxResult cancelPendingReservation(TChargingOrder order) { |
| | | RLock lock = redissonClient.getLock("START_AFTER_SAFETY:" + order.getId()); |
| | | boolean locked = false; |
| | | try { |
| | | locked = lock.tryLock(1, 120, TimeUnit.SECONDS); |
| | | if (!locked) { |
| | | return AjaxResult.error("预约订单正在处理中,请稍后重试"); |
| | | } |
| | | TChargingOrder latest = this.getById(order.getId()); |
| | | if (latest == null || !ReservationOrderSupport.isPending( |
| | | latest.getStatus(), latest.getReservationTime(), LocalDateTime.now())) { |
| | | return AjaxResult.error("预约已到时间或订单状态已变化,无法取消"); |
| | | } |
| | | refund(latest.getCode()); |
| | | TChargingOrder cancelled = this.getById(latest.getId()); |
| | | if (cancelled != null) { |
| | | cancelled.setStatus(-1); |
| | | cancelled.setEndMode(1); |
| | | cancelled.setEndTime(LocalDateTime.now()); |
| | | this.updateById(cancelled); |
| | | } |
| | | redisService.deleteObject("AQJC_" + latest.getChargingGunId()); |
| | | log.info("【{}】用户取消预约充电,预约时间:{}", latest.getCode(), latest.getReservationTime()); |
| | | return AjaxResult.success("预约充电已取消,退款将原路退回"); |
| | | } catch (InterruptedException e) { |
| | | Thread.currentThread().interrupt(); |
| | | return AjaxResult.error("取消预约被中断,请稍后重试"); |
| | | } finally { |
| | | if (locked && lock.isHeldByCurrentThread()) { |
| | | lock.unlock(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | |
| | | payOrderQueryDto = new PayOrderQueryDto(); |
| | | } |
| | | PageInfo<PayOrderDto> pageInfo = new PageInfo<>(payOrderQueryDto.getPageCurr(), payOrderQueryDto.getPageSize()); |
| | | // 开票申请没有关联明细时必须返回空结果,不能退化成支付订单全量查询。 |
| | | if (payOrderQueryDto.getInvoiceId() != null |
| | | && (payOrderQueryDto.getIds() == null || payOrderQueryDto.getIds().isEmpty())) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | | return R.ok(pageInfo); |
| | | } |
| | | List<PayOrderDto> list = this.baseMapper.payOrderQuery(pageInfo, payOrderQueryDto); |
| | | if (list == null || list.isEmpty()) { |
| | | pageInfo.setRecords(Collections.emptyList()); |
| | |
| | | |
| | | @Override |
| | | public List<SixChargingDto> charge(LocalDate sixBefore, List<Integer> siteIds, Boolean partnerView) { |
| | | List<SixChargingDto> result = this.baseMapper.charge(sixBefore, siteIds, partnerView); |
| | | String sixBeforeValue = sixBefore == null ? null : sixBefore.toString(); |
| | | List<SixChargingDto> result = new ArrayList<>(); |
| | | if (Boolean.TRUE.equals(partnerView)) { |
| | | List<SixChargingDto> partnerIncome = this.baseMapper.chargePartner(sixBeforeValue, siteIds); |
| | | if (partnerIncome != null) { |
| | | result.addAll(partnerIncome); |
| | | } |
| | | } else { |
| | | List<SixChargingDto> settlementIncome = this.baseMapper.chargeSettlement(sixBeforeValue, siteIds); |
| | | if (settlementIncome != null) { |
| | | result.addAll(settlementIncome); |
| | | } |
| | | List<SixChargingDto> currentMonthIncome = this.baseMapper.chargeCurrentMonth(siteIds); |
| | | if (currentMonthIncome != null) { |
| | | result.addAll(currentMonthIncome); |
| | | } |
| | | } |
| | | if (result != null) { |
| | | for (SixChargingDto item : result) { |
| | | if (item == null) { |