| | |
| | | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| | | import com.ruoyi.account.api.feignClient.AppUserCarClient; |
| | | import com.ruoyi.account.api.feignClient.AppUserClient; |
| | | import com.ruoyi.account.api.model.TAppUser; |
| | | import com.ruoyi.account.api.model.TAppUserCar; |
| | | import com.ruoyi.chargingPile.api.domain.SiteMenu; |
| | | import com.ruoyi.chargingPile.api.dto.GetSiteListDTO; |
| | |
| | | import com.ruoyi.chargingPile.api.model.TChargingGun; |
| | | import com.ruoyi.chargingPile.api.model.TChargingPile; |
| | | import com.ruoyi.chargingPile.api.model.TParkingRecord; |
| | | import com.ruoyi.chargingPile.api.vo.GetChargingGunByChargingPileIdsVo; |
| | | import com.ruoyi.common.core.domain.R; |
| | | import com.ruoyi.common.core.dto.ChargingOrderGroup; |
| | | import com.ruoyi.common.core.dto.ChargingPercentProvinceDto; |
| | |
| | | import com.ruoyi.order.vo.WatchChargingOrderVo; |
| | | import com.ruoyi.other.api.feignClient.RoleSiteClient; |
| | | import com.ruoyi.other.api.feignClient.UserSiteClient; |
| | | import com.ruoyi.other.api.feignClient.VipClient; |
| | | import com.ruoyi.other.api.feignClient.OtherClient; |
| | | import com.ruoyi.other.api.domain.TCompany; |
| | | import com.ruoyi.other.api.domain.TVip; |
| | | import com.ruoyi.payment.api.feignClient.AliPaymentClient; |
| | | import com.ruoyi.payment.api.feignClient.WxPaymentClient; |
| | | import com.ruoyi.payment.api.vo.IcbcPaymentResp; |
| | |
| | | @Resource |
| | | private RoleSiteClient roleSiteClient; |
| | | @Resource |
| | | private VipClient vipClient; |
| | | @Resource |
| | | private OtherClient otherClient; |
| | | @Resource |
| | | private SysUserRoleClient sysUserRoleClient; |
| | | |
| | | /** 小程序加盟商看板:站点范围始终从登录账号绑定关系解析。 */ |
| | | @PostMapping("/app/franchisee/charge-data") |
| | | public R<Map<String, Object>> franchiseeChargeData(@RequestBody(required = false) Map<String, Object> params) { |
| | | List<TChargingOrder> orders = franchiseeOrders(params); |
| | | LocalDateTime[] range = franchiseeRange(params); |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("orderAmountWan", sum(orders, TChargingOrder::getOrderAmount).divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP)); |
| | | summary.put("serviceFeeWan", sum(orders, TChargingOrder::getServiceCharge).divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP)); |
| | | summary.put("chargingPowerWan", sum(orders, TChargingOrder::getChargingCapacity).divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP)); |
| | | summary.put("chargingAmount", sum(orders, TChargingOrder::getPaymentAmount)); |
| | | summary.put("chargingOrderCount", orders.size()); |
| | | summary.put("powerUtilizationRate", observedPowerUtilization(orders)); |
| | | summary.put("demandPowerSatisfactionRate", demandPowerSatisfaction(orders)); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("summary", summary); |
| | | result.put("keyDataLine", franchiseeDailyTrend(orders, range, "订单金额", order -> firstNonNull(order.getOrderAmount(), order.getPaymentAmount()))); |
| | | result.put("keyDataRing", franchiseePhaseRing(orders)); |
| | | result.put("orderStats", franchiseeOrderTrend(orders, range)); |
| | | result.put("efficiency", franchiseeEfficiencyTrend(orders, range)); |
| | | result.put("powerDistribution", franchiseePowerDistribution(orders)); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | @PostMapping("/app/franchisee/income") |
| | | public R<Map<String, Object>> franchiseeIncome(@RequestBody(required = false) Map<String, Object> params) { |
| | | List<Integer> siteIds = franchiseeSiteIds(params); |
| | | LocalDate firstMonth = YearMonth.now().minusMonths(5).atDay(1); |
| | | LocalDate nextMonth = YearMonth.now().plusMonths(1).atDay(1); |
| | | List<TChargingOrder> orders = franchiseeOrders(siteIds, firstMonth.atStartOfDay(), nextMonth.atStartOfDay()); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("totalIncome", sum(orders, TChargingOrder::getPaymentAmount)); |
| | | result.put("electricityFee", sum(orders, TChargingOrder::getElectrovalence)); |
| | | result.put("serviceFee", sum(orders, TChargingOrder::getServiceCharge)); |
| | | result.put("incomeColumn", franchiseeIncomeTrend(orders, firstMonth, 6)); |
| | | List<Map<String, Object>> incomePie = new ArrayList<>(); |
| | | Map<String, Object> electricityItem = new LinkedHashMap<>(); |
| | | electricityItem.put("name", "充电费"); |
| | | electricityItem.put("value", sum(orders, TChargingOrder::getElectrovalence)); |
| | | incomePie.add(electricityItem); |
| | | Map<String, Object> serviceItem = new LinkedHashMap<>(); |
| | | serviceItem.put("name", "服务费"); |
| | | serviceItem.put("value", sum(orders, TChargingOrder::getServiceCharge)); |
| | | incomePie.add(serviceItem); |
| | | result.put("incomePie", incomePie); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | @PostMapping("/app/franchisee/user") |
| | | public R<Map<String, Object>> franchiseeUser(@RequestBody(required = false) Map<String, Object> params) { |
| | | List<TChargingOrder> orders = franchiseeOrders(params); |
| | | Set<Long> users = orders.stream().map(TChargingOrder::getAppUserId).filter(Objects::nonNull).collect(Collectors.toSet()); |
| | | List<Integer> siteIds = franchiseeSiteIds(params); |
| | | LocalDateTime[] range = franchiseeRange(params); |
| | | Set<Long> newUsers = new HashSet<>(); |
| | | if (!siteIds.isEmpty() && range[0] != null && range[1] != null) { |
| | | List<TChargingOrder> allOrders = franchiseeOrders(siteIds, null, range[1]); |
| | | Map<Long, LocalDateTime> firstOrder = new HashMap<>(); |
| | | for (TChargingOrder order : allOrders) { |
| | | if (order.getAppUserId() == null || order.getEndTime() == null) continue; |
| | | firstOrder.merge(order.getAppUserId(), order.getEndTime(), (oldTime, newTime) -> oldTime.isBefore(newTime) ? oldTime : newTime); |
| | | } |
| | | firstOrder.forEach((userId, firstTime) -> { if (!firstTime.isBefore(range[0])) newUsers.add(userId); }); |
| | | } |
| | | |
| | | List<TAppUser> appUsers = Collections.emptyList(); |
| | | if (!users.isEmpty()) { |
| | | R<List<TAppUser>> userResult = appUserClient.getUserByIds(new ArrayList<>(users)); |
| | | if (userResult != null && userResult.getData() != null) appUsers = userResult.getData(); |
| | | } |
| | | Map<Long, TAppUser> userMap = appUsers.stream().filter(user -> user.getId() != null) |
| | | .collect(Collectors.toMap(TAppUser::getId, user -> user, (left, right) -> left)); |
| | | |
| | | List<Long> carIds = orders.stream().map(TChargingOrder::getAppUserCarId).filter(Objects::nonNull).distinct().collect(Collectors.toList()); |
| | | List<TAppUserCar> cars = Collections.emptyList(); |
| | | if (!carIds.isEmpty()) { |
| | | R<List<TAppUserCar>> carResult = appUserCarClient.getCarByIds(carIds); |
| | | if (carResult != null && carResult.getData() != null) cars = carResult.getData(); |
| | | } |
| | | |
| | | Map<String, Object> evaluation = franchiseeEvaluation(siteIds); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | Map<String, Object> summary = new LinkedHashMap<>(); summary.put("chargeUserCount", users.size()); summary.put("newUserCount", newUsers.size()); |
| | | result.put("summary", summary); |
| | | result.put("userStats", franchiseeUserTrend(orders, newUsers, range)); |
| | | result.put("userTags", franchiseeUserTags(orders)); |
| | | Map<Integer, String> vipNames = new HashMap<>(); |
| | | R<List<TVip>> vipResult = vipClient.getAllVip(); |
| | | if (vipResult != null && vipResult.getData() != null) vipResult.getData().forEach(vip -> vipNames.put(vip.getId(), vip.getName())); |
| | | result.put("memberType", franchiseeMemberTypes(appUsers, vipNames)); |
| | | result.put("corpRank", franchiseeCompanyRank(orders, userMap)); |
| | | result.put("evaluation", evaluation.get("summary")); |
| | | result.put("goodTags", evaluation.get("goodTags")); |
| | | result.put("badTags", evaluation.get("badTags")); |
| | | result.put("traffic", franchiseeTraffic(orders)); |
| | | result.put("vehicleClass", franchiseeCarGroup(cars, TAppUserCar::getVehicleUse, "未知用途", 5)); |
| | | result.put("vehicleBrand", franchiseeCarGroup(cars, TAppUserCar::getVehicleBrand, "其他", 5)); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | @PostMapping("/app/franchisee/operation") |
| | | public R<Map<String, Object>> franchiseeOperation(@RequestBody(required = false) Map<String, Object> params) { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | List<Integer> siteIds = franchiseeSiteIds(params); |
| | | List<TChargingPile> piles = Collections.emptyList(); |
| | | if (!siteIds.isEmpty()) { R<List<TChargingPile>> pileResult = chargingPileClient.getChargingPileBySiteIds(siteIds); if (pileResult != null && pileResult.getData() != null) piles = pileResult.getData(); } |
| | | List<TChargingGun> guns = Collections.emptyList(); |
| | | List<Integer> pileIds = piles.stream().map(TChargingPile::getId).filter(Objects::nonNull).collect(Collectors.toList()); |
| | | if (!pileIds.isEmpty()) { |
| | | GetChargingGunByChargingPileIdsVo query = new GetChargingGunByChargingPileIdsVo(); query.setChargingPileIds(pileIds); |
| | | R<List<TChargingGun>> gunResult = chargingGunClient.getChargingGunByChargingPileIds(query); |
| | | if (gunResult != null && gunResult.getData() != null) guns = gunResult.getData(); |
| | | } |
| | | BigDecimal total = BigDecimal.valueOf(guns.size()); |
| | | BigDecimal available = BigDecimal.valueOf(guns.stream().filter(this::isAvailableGun).count()); |
| | | BigDecimal fault = countGuns(guns, 7), offline = countGuns(guns, 1); |
| | | List<TChargingOrder> operationOrders = franchiseeOrders(params); |
| | | BigDecimal powerSatisfaction = demandPowerSatisfaction(operationOrders); |
| | | Map<String, Object> summary = new LinkedHashMap<>(); summary.put("availableRate", rate(available, total)); summary.put("faultRate", rate(fault, total)); summary.put("offlineRate", rate(offline, total)); summary.put("powerSatisfactionRate", powerSatisfaction); |
| | | result.put("summary", summary); result.put("dcCharts", operationCharts(guns, operationOrders, true)); result.put("acCharts", operationCharts(guns, operationOrders, false)); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | private List<TChargingOrder> franchiseeOrders(Map<String, Object> params) { |
| | | List<Integer> siteIds = franchiseeSiteIds(params); |
| | | if (siteIds.isEmpty()) return Collections.emptyList(); |
| | | LocalDateTime[] range = franchiseeRange(params); |
| | | return franchiseeOrders(siteIds, range[0], range[1]); |
| | | } |
| | | |
| | | private List<TChargingOrder> franchiseeOrders(List<Integer> siteIds, LocalDateTime start, LocalDateTime end) { |
| | | if (siteIds == null || siteIds.isEmpty()) return Collections.emptyList(); |
| | | LambdaQueryWrapper<TChargingOrder> query = Wrappers.lambdaQuery(TChargingOrder.class).in(TChargingOrder::getSiteId, siteIds).eq(TChargingOrder::getDelFlag, 0).eq(TChargingOrder::getRechargePaymentStatus, 2); |
| | | if (start != null) query.ge(TChargingOrder::getEndTime, start); |
| | | if (end != null) query.lt(TChargingOrder::getEndTime, end); |
| | | return chargingOrderService.list(query); |
| | | } |
| | | |
| | | private List<Integer> franchiseeSiteIds(Map<String, Object> params) { |
| | | if (tokenService.getLoginUserApplet() == null || tokenService.getLoginUserApplet().getUserId() == null) return Collections.emptyList(); |
| | | Long appUserId = tokenService.getLoginUserApplet().getUserId(); |
| | | R<com.ruoyi.chargingPile.api.model.Partner> partnerResult = partnerClient.getPartnerByAppUserId(appUserId); |
| | | com.ruoyi.chargingPile.api.model.Partner partner = partnerResult == null ? null : partnerResult.getData(); |
| | | if (partner == null || partner.getId() == null) return Collections.emptyList(); |
| | | R<List<Site>> siteResult = siteClient.getSiteAll(); |
| | | List<Site> sites = siteResult == null ? null : siteResult.getData(); |
| | | List<Integer> siteIds = sites == null ? new ArrayList<>() : sites.stream().filter(s -> Objects.equals(s.getPartnerId(), partner.getId())).map(Site::getId).collect(Collectors.toList()); |
| | | Object requestedSite = params == null ? null : params.get("siteId"); |
| | | if (requestedSite != null && !String.valueOf(requestedSite).isEmpty()) { |
| | | Integer siteId = Integer.valueOf(String.valueOf(requestedSite)); siteIds = siteIds.contains(siteId) ? Collections.singletonList(siteId) : Collections.singletonList(-1); |
| | | } |
| | | return siteIds; |
| | | } |
| | | |
| | | private Map<String, Object> franchiseeUserTrend(List<TChargingOrder> orders, Set<Long> newUsers, LocalDateTime[] range) { |
| | | LocalDate start = range[0] == null ? LocalDate.now() : range[0].toLocalDate(); |
| | | LocalDate end = range[1] == null ? start.plusDays(1) : range[1].toLocalDate(); |
| | | List<String> categories = new ArrayList<>(); |
| | | List<BigDecimal> chargeUsers = new ArrayList<>(), newUserCounts = new ArrayList<>(); |
| | | for (LocalDate day = start; day.isBefore(end); day = day.plusDays(1)) { |
| | | LocalDateTime dayStart = day.atStartOfDay(), dayEnd = day.plusDays(1).atStartOfDay(); |
| | | Set<Long> dayUsers = orders.stream().filter(order -> order.getAppUserId() != null && order.getEndTime() != null |
| | | && !order.getEndTime().isBefore(dayStart) && order.getEndTime().isBefore(dayEnd)) |
| | | .map(TChargingOrder::getAppUserId).collect(Collectors.toSet()); |
| | | long newCount = dayUsers.stream().filter(newUsers::contains).count(); |
| | | categories.add(day.toString().substring(5)); |
| | | chargeUsers.add(BigDecimal.valueOf(dayUsers.size())); |
| | | newUserCounts.add(BigDecimal.valueOf(newCount)); |
| | | } |
| | | Map<String, Object> chart = new LinkedHashMap<>(); chart.put("categories", categories); |
| | | Map<String, Object> chargeSeries = new LinkedHashMap<>(); chargeSeries.put("name", "充电用户"); chargeSeries.put("data", chargeUsers); |
| | | Map<String, Object> newSeries = new LinkedHashMap<>(); newSeries.put("name", "新增用户"); newSeries.put("data", newUserCounts); |
| | | chart.put("series", Arrays.asList(chargeSeries, newSeries)); return chart; |
| | | } |
| | | |
| | | private List<Map<String, Object>> franchiseeUserTags(List<TChargingOrder> orders) { |
| | | Map<Long, Long> counts = orders.stream().filter(order -> order.getAppUserId() != null) |
| | | .collect(Collectors.groupingBy(TChargingOrder::getAppUserId, Collectors.counting())); |
| | | long active = counts.values().stream().filter(count -> count >= 5).count(); |
| | | long normal = counts.values().stream().filter(count -> count >= 2 && count < 5).count(); |
| | | long lowFrequency = counts.values().stream().filter(count -> count == 1).count(); |
| | | return namedCounts(new String[]{"活跃用户", "普通用户", "低频用户"}, new long[]{active, normal, lowFrequency}); |
| | | } |
| | | |
| | | private List<Map<String, Object>> franchiseeMemberTypes(List<TAppUser> users, Map<Integer, String> vipNames) { |
| | | long ordinary = users.stream().filter(user -> user.getVipId() == null || user.getVipId() == 0).count(); |
| | | Map<Integer, Long> vipCounts = users.stream().filter(user -> user.getVipId() != null && user.getVipId() != 0) |
| | | .collect(Collectors.groupingBy(TAppUser::getVipId, Collectors.counting())); |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | if (ordinary > 0) result.add(namedCount("普通用户", ordinary)); |
| | | vipCounts.forEach((vipId, count) -> result.add(namedCount(vipNames.getOrDefault(vipId, "会员" + vipId), count))); |
| | | return result; |
| | | } |
| | | |
| | | private Map<String, Object> franchiseeCompanyRank(List<TChargingOrder> orders, Map<Long, TAppUser> userMap) { |
| | | Map<Integer, BigDecimal> totals = new HashMap<>(); |
| | | orders.forEach(order -> { |
| | | TAppUser user = userMap.get(order.getAppUserId()); |
| | | if (user != null && user.getCompanyId() != null) { |
| | | totals.merge(user.getCompanyId(), value(order.getChargingCapacity()), BigDecimal::add); |
| | | } |
| | | }); |
| | | List<Map.Entry<Integer, BigDecimal>> sorted = totals.entrySet().stream() |
| | | .sorted(Map.Entry.<Integer, BigDecimal>comparingByValue().reversed()).limit(5).collect(Collectors.toList()); |
| | | Map<String, Object> chart = new LinkedHashMap<>(); |
| | | chart.put("categories", sorted.stream().map(entry -> { |
| | | R<TCompany> companyResult = otherClient.unitDetail(entry.getKey()); |
| | | TCompany company = companyResult == null ? null : companyResult.getData(); |
| | | return company == null || StringUtils.isBlank(company.getName()) ? "单位" + entry.getKey() : company.getName(); |
| | | }).collect(Collectors.toList())); |
| | | Map<String, Object> series = new LinkedHashMap<>(); series.put("name", "充电量"); |
| | | series.put("data", sorted.stream().map(Map.Entry::getValue).collect(Collectors.toList())); |
| | | chart.put("series", Collections.singletonList(series)); return chart; |
| | | } |
| | | |
| | | private Map<String, Object> franchiseeEvaluation(List<Integer> siteIds) { |
| | | if (siteIds == null || siteIds.isEmpty()) { |
| | | Map<String, Object> emptySummary = new LinkedHashMap<>(); |
| | | emptySummary.put("score", 0D); emptySummary.put("goodCount", 0); emptySummary.put("mediumCount", 0); emptySummary.put("badCount", 0); |
| | | emptySummary.put("goodRate", BigDecimal.ZERO); emptySummary.put("mediumRate", BigDecimal.ZERO); emptySummary.put("badRate", BigDecimal.ZERO); |
| | | Map<String, Object> empty = new LinkedHashMap<>(); empty.put("summary", emptySummary); empty.put("goodTags", Collections.emptyList()); empty.put("badTags", Collections.emptyList()); return empty; |
| | | } |
| | | Double score = chargingOrderService.getAver(siteIds); |
| | | List<Map<String, Object>> levels = chargingOrderService.getLevelEvaluate(siteIds); |
| | | long good = 0, medium = 0, bad = 0; |
| | | if (levels != null) for (Map<String, Object> level : levels) { |
| | | String range = String.valueOf(level.get("score_range")); long count = numberValue(level.get("count")); |
| | | if ("4-5".equals(range)) good += count; else if ("3".equals(range)) medium += count; else if ("1-2".equals(range)) bad += count; |
| | | } |
| | | long total = good + medium + bad; |
| | | Map<String, Object> summary = new LinkedHashMap<>(); summary.put("score", score == null ? 0D : score); |
| | | summary.put("goodCount", good); summary.put("mediumCount", medium); summary.put("badCount", bad); |
| | | summary.put("goodRate", ratePercent(good, total)); summary.put("mediumRate", ratePercent(medium, total)); summary.put("badRate", ratePercent(bad, total)); |
| | | Map<String, Object> result = new LinkedHashMap<>(); result.put("summary", summary); |
| | | result.put("goodTags", safeList(orderEvaluateService.goodTop(siteIds))); result.put("badTags", safeList(orderEvaluateService.badTop(siteIds))); return result; |
| | | } |
| | | |
| | | private List<Map<String, Object>> franchiseeTraffic(List<TChargingOrder> orders) { |
| | | Map<String, Long> counts = orders.stream().collect(Collectors.groupingBy(order -> { |
| | | String source = order.getTripartitePlatformName(); return source == null || source.isEmpty() ? "明星充电" : source; |
| | | }, Collectors.counting())); |
| | | return counts.entrySet().stream().map(entry -> namedCount(entry.getKey(), entry.getValue())).collect(Collectors.toList()); |
| | | } |
| | | |
| | | private List<Map<String, Object>> franchiseeCarGroup(List<TAppUserCar> cars, java.util.function.Function<TAppUserCar, String> getter, String emptyName, int limit) { |
| | | Map<String, Long> counts = cars.stream().collect(Collectors.groupingBy(car -> { |
| | | String value = getter.apply(car); return value == null || value.trim().isEmpty() ? emptyName : value; |
| | | }, Collectors.counting())); |
| | | return counts.entrySet().stream().sorted(Map.Entry.<String, Long>comparingByValue().reversed()).limit(limit) |
| | | .map(entry -> namedCount(entry.getKey(), entry.getValue())).collect(Collectors.toList()); |
| | | } |
| | | |
| | | private List<Map<String, Object>> namedCounts(String[] names, long[] values) { |
| | | List<Map<String, Object>> result = new ArrayList<>(); |
| | | for (int i = 0; i < names.length; i++) if (values[i] > 0) result.add(namedCount(names[i], values[i])); |
| | | return result; |
| | | } |
| | | |
| | | private Map<String, Object> namedCount(String name, long value) { Map<String, Object> result = new LinkedHashMap<>(); result.put("name", name); result.put("value", value); return result; } |
| | | private long numberValue(Object value) { return value instanceof Number ? ((Number) value).longValue() : 0L; } |
| | | private BigDecimal ratePercent(long numerator, long denominator) { return denominator == 0 ? BigDecimal.ZERO : BigDecimal.valueOf(numerator * 100D / denominator).setScale(2, RoundingMode.HALF_UP); } |
| | | private <T> List<T> safeList(List<T> values) { return values == null ? Collections.emptyList() : values; } |
| | | |
| | | private BigDecimal countGuns(List<TChargingGun> guns, int status) { return BigDecimal.valueOf(guns.stream().filter(gun -> Objects.equals(gun.getStatus(), status)).count()); } |
| | | private boolean isAvailableGun(TChargingGun gun) { return gun.getStatus() != null && gun.getStatus() >= 2 && gun.getStatus() <= 6; } |
| | | private BigDecimal rate(BigDecimal numerator, BigDecimal denominator) { return denominator.signum() == 0 ? BigDecimal.ZERO : numerator.multiply(BigDecimal.valueOf(100)).divide(denominator, 2, RoundingMode.HALF_UP); } |
| | | private List<Map<String, Object>> operationCharts(List<TChargingGun> guns, List<TChargingOrder> orders, boolean dc) { |
| | | List<TChargingGun> selected = guns.stream().filter(gun -> dc ? Objects.equals(gun.getType(), 4) : gun.getType() != null && gun.getType() >= 1 && gun.getType() <= 3).collect(Collectors.toList()); |
| | | List<Map<String, Object>> charts = new ArrayList<>(); |
| | | String[] keys = {"availableRate", "faultRate", "offlineRate", "powerSatisfactionRate"}; int[] statuses = {0, 7, 1, 0}; |
| | | String[] names = {"可用率", "故障率", "离网率", "电量满足率"}; |
| | | for (int i = 0; i < keys.length; i++) { |
| | | final int status = statuses[i]; Map<String, Object> chart = new LinkedHashMap<>(); chart.put("key", keys[i]); |
| | | List<String> categories = selected.stream().map(TChargingGun::getName).map(name -> name == null ? "未命名充电枪" : name).collect(Collectors.toList()); |
| | | final int metricIndex = i; |
| | | List<BigDecimal> values = selected.stream().map(gun -> metricIndex == 3 |
| | | ? demandPowerSatisfaction(orders.stream().filter(order -> Objects.equals(order.getChargingGunId(), gun.getId())).collect(Collectors.toList())) |
| | | : BigDecimal.valueOf(metricIndex == 0 ? (isAvailableGun(gun) ? 100 : 0) : (Objects.equals(gun.getStatus(), status) ? 100 : 0))).collect(Collectors.toList()); |
| | | chart.put("categories", categories); |
| | | Map<String, Object> series = new LinkedHashMap<>(); series.put("name", names[i]); series.put("data", values); chart.put("series", Collections.singletonList(series)); charts.add(chart); |
| | | } |
| | | return charts; |
| | | } |
| | | |
| | | private LocalDateTime[] franchiseeRange(Map<String, Object> params) { |
| | | LocalDate today = LocalDate.now(); |
| | | if (params != null && params.get("startTime") != null && params.get("endTime") != null) { |
| | | String startText = String.valueOf(params.get("startTime")); |
| | | String endText = String.valueOf(params.get("endTime")); |
| | | if (!startText.isEmpty() && !endText.isEmpty()) { |
| | | try { |
| | | return new LocalDateTime[]{LocalDate.parse(startText).atStartOfDay(), LocalDate.parse(endText).plusDays(1).atStartOfDay()}; |
| | | } catch (RuntimeException ignored) { |
| | | // 日期格式异常时回退到快捷日期范围。 |
| | | } |
| | | } |
| | | } |
| | | String type = params == null ? "today" : String.valueOf(params.getOrDefault("dateType", "today")); |
| | | if ("year".equals(type)) return new LocalDateTime[]{today.withDayOfYear(1).atStartOfDay(), today.plusDays(1).atStartOfDay()}; |
| | | if ("month".equals(type)) return new LocalDateTime[]{today.withDayOfMonth(1).atStartOfDay(), today.plusDays(1).atStartOfDay()}; |
| | | if ("week".equals(type)) { LocalDate start = today.minusDays(today.getDayOfWeek().getValue() - 1); return new LocalDateTime[]{start.atStartOfDay(), today.plusDays(1).atStartOfDay()}; } |
| | | return new LocalDateTime[]{today.atStartOfDay(), today.plusDays(1).atStartOfDay()}; |
| | | } |
| | | |
| | | private BigDecimal sum(List<TChargingOrder> orders, java.util.function.Function<TChargingOrder, BigDecimal> getter) { return orders.stream().map(getter).map(this::value).reduce(BigDecimal.ZERO, BigDecimal::add); } |
| | | private BigDecimal firstNonNull(BigDecimal first, BigDecimal second) { return first != null ? first : value(second); } |
| | | private BigDecimal value(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } |
| | | private Map<String, Object> franchiseeTrend(List<TChargingOrder> orders, String name, java.util.function.Function<TChargingOrder, BigDecimal> getter) { Map<String, Object> chart = new LinkedHashMap<>(); chart.put("categories", Collections.singletonList(LocalDate.now().toString())); Map<String, Object> series = new LinkedHashMap<>(); series.put("name", name); series.put("data", Collections.singletonList(sum(orders, getter))); chart.put("series", new ArrayList<>(Collections.singletonList(series))); return chart; } |
| | | private Map<String, Object> franchiseeDailyTrend(List<TChargingOrder> orders, LocalDateTime[] range, String name, java.util.function.Function<TChargingOrder, BigDecimal> getter) { |
| | | List<String> categories = new ArrayList<>(); List<BigDecimal> data = new ArrayList<>(); |
| | | LocalDate start = range[0] == null ? LocalDate.now() : range[0].toLocalDate(); |
| | | LocalDate end = range[1] == null ? start.plusDays(1) : range[1].toLocalDate(); |
| | | for (LocalDate day = start; day.isBefore(end); day = day.plusDays(1)) { |
| | | categories.add(day.toString().substring(5)); |
| | | LocalDateTime dayStart = day.atStartOfDay(), dayEnd = day.plusDays(1).atStartOfDay(); |
| | | data.add(sum(orders.stream().filter(order -> order.getEndTime() != null && !order.getEndTime().isBefore(dayStart) && order.getEndTime().isBefore(dayEnd)).collect(Collectors.toList()), getter)); |
| | | } |
| | | Map<String, Object> chart = new LinkedHashMap<>(); chart.put("categories", categories); |
| | | Map<String, Object> series = new LinkedHashMap<>(); series.put("name", name); series.put("data", data); chart.put("series", new ArrayList<>(Collections.singletonList(series))); return chart; |
| | | } |
| | | private Map<String, Object> franchiseeOrderTrend(List<TChargingOrder> orders, LocalDateTime[] range) { |
| | | Map<String, Object> chart = franchiseeDailyTrend(orders, range, "充电金额", order -> firstNonNull(order.getPaymentAmount(), order.getOrderAmount())); |
| | | ((List<Map<String, Object>>) chart.get("series")).get(0).put("type", "column"); |
| | | List<BigDecimal> countData = new ArrayList<>(); List<String> categories = (List<String>) chart.get("categories"); |
| | | LocalDate start = range[0] == null ? LocalDate.now() : range[0].toLocalDate(); |
| | | for (int i = 0; i < categories.size(); i++) { LocalDate day = start.plusDays(i); LocalDateTime dayStart = day.atStartOfDay(), dayEnd = day.plusDays(1).atStartOfDay(); countData.add(BigDecimal.valueOf(orders.stream().filter(order -> order.getEndTime() != null && !order.getEndTime().isBefore(dayStart) && order.getEndTime().isBefore(dayEnd)).count())); } |
| | | Map<String, Object> countSeries = new LinkedHashMap<>(); countSeries.put("name", "充电订单"); countSeries.put("data", countData); countSeries.put("type", "line"); countSeries.put("style", "curve"); countSeries.put("index", 1); ((List<Map<String, Object>>) chart.get("series")).add(countSeries); return chart; |
| | | } |
| | | private Map<String, Object> franchiseePhaseRing(List<TChargingOrder> orders) { |
| | | Map<Integer, BigDecimal> phase = new LinkedHashMap<>(); phase.put(1, BigDecimal.ZERO); phase.put(2, BigDecimal.ZERO); phase.put(3, BigDecimal.ZERO); phase.put(4, BigDecimal.ZERO); |
| | | List<Long> ids = orders.stream().map(TChargingOrder::getId).filter(Objects::nonNull).collect(Collectors.toList()); |
| | | if (!ids.isEmpty()) { chargingOrderAccountingStrategyService.lambdaQuery().in(TChargingOrderAccountingStrategy::getChargingOrderId, ids).list().forEach(item -> phase.computeIfPresent(item.getType(), (key, total) -> total.add(value(item.getChargingCapacity())))); } |
| | | String[] names = {"尖", "峰", "平", "谷"}; List<Map<String, Object>> data = new ArrayList<>(); |
| | | for (int i = 1; i <= 4; i++) { Map<String, Object> item = new LinkedHashMap<>(); item.put("name", names[i - 1]); item.put("value", phase.get(i)); data.add(item); } |
| | | Map<String, Object> chart = new LinkedHashMap<>(); chart.put("series", Collections.singletonList(Collections.singletonMap("data", data))); return chart; |
| | | } |
| | | private Map<String, Object> franchiseeEfficiencyTrend(List<TChargingOrder> orders, LocalDateTime[] range) { |
| | | Map<String, Object> chart = franchiseeDailyTrend(orders, range, "功率", order -> value(order.getChargingPower() == null ? order.getPower() : order.getChargingPower())); |
| | | List<BigDecimal> powerData = new ArrayList<>(), utilization = new ArrayList<>(); List<String> categories = (List<String>) chart.get("categories"); |
| | | LocalDate start = range[0] == null ? LocalDate.now() : range[0].toLocalDate(); |
| | | for (int i = 0; i < categories.size(); i++) { LocalDate day = start.plusDays(i); LocalDateTime dayStart = day.atStartOfDay(), dayEnd = day.plusDays(1).atStartOfDay(); List<TChargingOrder> dayOrders = orders.stream().filter(order -> order.getEndTime() != null && !order.getEndTime().isBefore(dayStart) && order.getEndTime().isBefore(dayEnd)).collect(Collectors.toList()); powerData.add(averagePower(dayOrders)); utilization.add(observedPowerUtilization(dayOrders)); } |
| | | ((List<Map<String, Object>>) chart.get("series")).get(0).put("data", powerData); |
| | | Map<String, Object> utilizationSeries = new LinkedHashMap<>(); utilizationSeries.put("name", "功率利用率"); utilizationSeries.put("data", utilization); ((List<Map<String, Object>>) chart.get("series")).add(utilizationSeries); return chart; |
| | | } |
| | | private BigDecimal averagePower(List<TChargingOrder> orders) { |
| | | List<BigDecimal> powers = orders.stream().map(order -> order.getChargingPower() == null ? order.getPower() : order.getChargingPower()).filter(Objects::nonNull).collect(Collectors.toList()); |
| | | return powers.isEmpty() ? BigDecimal.ZERO : powers.stream().reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(powers.size()), 2, RoundingMode.HALF_UP); |
| | | } |
| | | private BigDecimal observedPowerUtilization(List<TChargingOrder> orders) { |
| | | List<BigDecimal> powers = orders.stream().map(order -> order.getChargingPower() == null ? order.getPower() : order.getChargingPower()).filter(Objects::nonNull).filter(power -> power.signum() > 0).collect(Collectors.toList()); |
| | | if (powers.isEmpty()) return BigDecimal.ZERO; BigDecimal maximum = powers.stream().max(BigDecimal::compareTo).orElse(BigDecimal.ZERO); |
| | | return maximum.signum() == 0 ? BigDecimal.ZERO : powers.stream().reduce(BigDecimal.ZERO, BigDecimal::add).divide(BigDecimal.valueOf(powers.size()), 4, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(100)).divide(maximum, 2, RoundingMode.HALF_UP); |
| | | } |
| | | private BigDecimal demandPowerSatisfaction(List<TChargingOrder> orders) { |
| | | List<TChargingOrder> valid = orders.stream().filter(order -> order.getCurrent() != null && order.getNeedElec() != null && order.getNeedElec().signum() > 0).collect(Collectors.toList()); |
| | | if (valid.isEmpty()) return BigDecimal.ZERO; |
| | | BigDecimal total = valid.stream().map(order -> order.getCurrent().multiply(BigDecimal.valueOf(100)).divide(order.getNeedElec(), 4, RoundingMode.HALF_UP).min(BigDecimal.valueOf(100))).reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | return total.divide(BigDecimal.valueOf(valid.size()), 2, RoundingMode.HALF_UP); |
| | | } |
| | | private Map<String, Object> franchiseePowerDistribution(List<TChargingOrder> orders) { |
| | | String[] names = {"0-30kW", "30-60kW", "60-120kW", "120-300kW", "300kW以上"}; long[] counts = new long[5]; |
| | | orders.forEach(order -> { BigDecimal power = order.getChargingPower() == null ? order.getPower() : order.getChargingPower(); if (power == null) return; double value = power.doubleValue(); int index = value < 30 ? 0 : value < 60 ? 1 : value < 120 ? 2 : value < 300 ? 3 : 4; counts[index]++; }); |
| | | List<Map<String, Object>> data = new ArrayList<>(); for (int i = 0; i < names.length; i++) { Map<String, Object> item = new LinkedHashMap<>(); item.put("name", names[i]); item.put("value", counts[i]); data.add(item); } |
| | | return Collections.singletonMap("series", Collections.singletonList(Collections.singletonMap("data", data))); |
| | | } |
| | | private Map<String, Object> franchiseeIncomeTrend(List<TChargingOrder> orders, LocalDate firstMonth, int monthCount) { |
| | | List<String> categories = new ArrayList<>(); |
| | | List<BigDecimal> electricity = new ArrayList<>(), service = new ArrayList<>(); |
| | | for (int i = 0; i < monthCount; i++) { |
| | | YearMonth month = YearMonth.from(firstMonth).plusMonths(i); |
| | | categories.add(String.format("%02d月", month.getMonthValue())); |
| | | LocalDateTime start = month.atDay(1).atStartOfDay(), end = month.plusMonths(1).atDay(1).atStartOfDay(); |
| | | electricity.add(sum(orders.stream().filter(o -> o.getEndTime() != null && !o.getEndTime().isBefore(start) && o.getEndTime().isBefore(end)).collect(Collectors.toList()), TChargingOrder::getElectrovalence)); |
| | | service.add(sum(orders.stream().filter(o -> o.getEndTime() != null && !o.getEndTime().isBefore(start) && o.getEndTime().isBefore(end)).collect(Collectors.toList()), TChargingOrder::getServiceCharge)); |
| | | } |
| | | Map<String, Object> chart = new LinkedHashMap<>(); chart.put("categories", categories); |
| | | Map<String, Object> electricitySeries = new LinkedHashMap<>(); electricitySeries.put("name", "充电费"); electricitySeries.put("data", electricity); |
| | | Map<String, Object> serviceSeries = new LinkedHashMap<>(); serviceSeries.put("name", "服务费"); serviceSeries.put("data", service); |
| | | chart.put("series", new ArrayList<>(Arrays.asList(electricitySeries, serviceSeries))); return chart; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |