| | |
| | | summary.put("orderAmountWan", sum(dayStats, FranchiseeDayStat::getOrderAmount).divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP)); |
| | | summary.put("serviceFeeWan", sum(dayStats, FranchiseeDayStat::getServiceFee).divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP)); |
| | | summary.put("chargingPowerWan", sum(dayStats, FranchiseeDayStat::getCapacity).divide(BigDecimal.valueOf(10000), 4, RoundingMode.HALF_UP)); |
| | | summary.put("chargingAmount", sum(dayStats, FranchiseeDayStat::getPaymentAmount)); |
| | | summary.put("chargingAmount", sum(dayStats, FranchiseeDayStat::getOrderAmountEffective)); |
| | | summary.put("chargingOrderCount", dayStats.stream().mapToLong(stat -> numberValue(stat.getOrderCount())).sum()); |
| | | summary.put("powerUtilizationRate", observedPowerUtilization(dayStats)); |
| | | summary.put("demandPowerSatisfactionRate", demandPowerSatisfaction(dayStats)); |
| | |
| | | @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<FranchiseeDayStat> dayStats = franchiseeDayStats(siteIds, |
| | | new LocalDateTime[]{firstMonth.atStartOfDay(), nextMonth.atStartOfDay()}); |
| | | List<SixChargingDto> sixChargingDtos; |
| | | if (siteIds.isEmpty()) { |
| | | // 无授权站点:返回近6月全零骨架,避免空 IN 导致全站点统计 |
| | | sixChargingDtos = generateLastSixMonths(LocalDate.now()); |
| | | } else { |
| | | // 与后台 数据分析-平台总收入分析 电站收入分析口径一致:历史月读结算账单、当月补原始订单 |
| | | LocalDate sixBefore = PreviousSixMonths.get(); |
| | | List<SixChargingDto> chargingDtos = chargingOrderService.charge(sixBefore, siteIds, false); |
| | | LocalDate statisticsEndMonth = resolveStatisticsEndMonth(LocalDate.now(), chargingDtos); |
| | | sixChargingDtos = generateLastSixMonths(statisticsEndMonth); |
| | | for (SixChargingDto sixChargingDto : sixChargingDtos) { |
| | | for (SixChargingDto chargingDto : chargingDtos) { |
| | | if (sixChargingDto.getMonth().equals(chargingDto.getMonth())) { |
| | | BeanUtils.copyProperties(chargingDto, sixChargingDto); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | BigDecimal electricity = sixChargingDtos.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(SixChargingDto::getElectrovalence) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | BigDecimal service = sixChargingDtos.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(SixChargingDto::getServiceCharge) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("totalIncome", sum(dayStats, FranchiseeDayStat::getPaymentAmount)); |
| | | result.put("electricityFee", sum(dayStats, FranchiseeDayStat::getElectrovalence)); |
| | | result.put("serviceFee", sum(dayStats, FranchiseeDayStat::getServiceFee)); |
| | | result.put("incomeColumn", franchiseeIncomeTrend(dayStats, firstMonth, 6)); |
| | | result.put("totalIncome", electricity.add(service)); |
| | | result.put("electricityFee", electricity); |
| | | result.put("serviceFee", service); |
| | | result.put("incomeColumn", franchiseeIncomeTrendFromSix(sixChargingDtos)); |
| | | List<Map<String, Object>> incomePie = new ArrayList<>(); |
| | | Map<String, Object> electricityItem = new LinkedHashMap<>(); |
| | | electricityItem.put("name", "充电费"); |
| | | electricityItem.put("value", sum(dayStats, FranchiseeDayStat::getElectrovalence)); |
| | | electricityItem.put("value", electricity); |
| | | incomePie.add(electricityItem); |
| | | Map<String, Object> serviceItem = new LinkedHashMap<>(); |
| | | serviceItem.put("name", "服务费"); |
| | | serviceItem.put("value", sum(dayStats, FranchiseeDayStat::getServiceFee)); |
| | | serviceItem.put("value", service); |
| | | incomePie.add(serviceItem); |
| | | result.put("incomePie", incomePie); |
| | | return R.ok(result); |
| | |
| | | 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); |
| | | LocalDateTime[] range = franchiseeRange(params); |
| | | List<FranchiseeDayStat> operationStats = franchiseeDayStats(siteIds, range); |
| | | BigDecimal powerSatisfaction = demandPowerSatisfaction(operationStats); |
| | | Map<Integer, FranchiseeGunSat> gunSatMap = Collections.emptyMap(); |
| | | if (!siteIds.isEmpty() && range[0] != null && range[1] != null) { |
| | | gunSatMap = chargingOrderMapper.franchiseeGunSatisfaction(siteIds, range[0], range[1]).stream() |
| | | .filter(sat -> sat != null && sat.getChargingGunId() != null) |
| | | .collect(Collectors.toMap(FranchiseeGunSat::getChargingGunId, sat -> sat, (left, right) -> left)); |
| | | ChargingStatisticsQueryDto query = new ChargingStatisticsQueryDto(); |
| | | String dateType = params == null ? "today" : String.valueOf(params.getOrDefault("dateType", "today")); |
| | | int dayType = "week".equals(dateType) ? 2 : "month".equals(dateType) ? 3 : "year".equals(dateType) ? 4 : "custom".equals(dateType) ? 5 : 1; |
| | | query.setDayType(dayType); |
| | | if (dayType == 5) { |
| | | query.setStartTime(range[0].toLocalDate()); |
| | | query.setEndTime(range[1].toLocalDate().minusDays(1)); |
| | | } |
| | | 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, gunSatMap, true)); result.put("acCharts", operationCharts(guns, gunSatMap, false)); |
| | | |
| | | List<Map<String, Object>> dcAvailable = safeList(chargingOrderService.equipmentUserType1(siteIds, query)); |
| | | List<Map<String, Object>> acAvailable = safeList(chargingOrderService.equipmentUserType2(siteIds, query)); |
| | | List<Map<String, Object>> dcFault = safeList(chargingOrderService.equipmentMapbroke1(siteIds, query)); |
| | | List<Map<String, Object>> acFault = safeList(chargingOrderService.equipmentMapbroke2(siteIds, query)); |
| | | List<Map<String, Object>> dcOffline = safeList(chargingOrderService.equipmentMapOut1(siteIds, query)); |
| | | List<Map<String, Object>> acOffline = safeList(chargingOrderService.equipmentMapOut2(siteIds, query)); |
| | | List<Map<String, Object>> dcSatisfaction = safeList(chargingOrderService.needElec(siteIds, query)); |
| | | List<Map<String, Object>> acSatisfaction = safeList(chargingOrderService.needElec1(siteIds, query)); |
| | | normalizeOperationPercents(dcAvailable); |
| | | normalizeOperationPercents(acAvailable); |
| | | normalizeOperationPercents(dcFault); |
| | | normalizeOperationPercents(acFault); |
| | | normalizeOperationPercents(dcOffline); |
| | | normalizeOperationPercents(acOffline); |
| | | normalizeOperationPercents(dcSatisfaction); |
| | | normalizeOperationPercents(acSatisfaction); |
| | | |
| | | Map<String, Object> summary = new LinkedHashMap<>(); |
| | | summary.put("availableRate", calculateAveragePercent(dcAvailable, acAvailable)); |
| | | summary.put("faultRate", calculateAveragePercent(dcFault, acFault)); |
| | | summary.put("offlineRate", calculateAveragePercent(dcOffline, acOffline)); |
| | | summary.put("powerSatisfactionRate", calculateAveragePercent(dcSatisfaction, acSatisfaction)); |
| | | result.put("summary", summary); |
| | | result.put("dcCharts", Arrays.asList( |
| | | operationChart(dcAvailable, "可用率"), |
| | | operationChart(dcFault, "故障率"), |
| | | operationChart(dcOffline, "离网率"), |
| | | operationChart(dcSatisfaction, "需求电流满足率"))); |
| | | result.put("acCharts", Arrays.asList( |
| | | operationChart(acAvailable, "可用率"), |
| | | operationChart(acFault, "故障率"), |
| | | operationChart(acOffline, "离网率"), |
| | | operationChart(acSatisfaction, "需求电流满足率"))); |
| | | return R.ok(result); |
| | | } |
| | | |
| | | private Map<String, Object> operationChart(List<Map<String, Object>> rows, String name) { |
| | | List<String> categories = rows.stream().map(row -> { |
| | | String gunName = row.get("name") == null ? "" : String.valueOf(row.get("name")); |
| | | return StringUtils.isBlank(gunName) ? "未命名充电枪" : gunName; |
| | | }).collect(Collectors.toList()); |
| | | List<Object> values = rows.stream().map(row -> row.getOrDefault("percent", "0")).collect(Collectors.toList()); |
| | | Map<String, Object> chart = new LinkedHashMap<>(); |
| | | chart.put("categories", categories); |
| | | Map<String, Object> series = new LinkedHashMap<>(); |
| | | series.put("name", name); |
| | | series.put("data", values); |
| | | chart.put("series", Collections.singletonList(series)); |
| | | return chart; |
| | | } |
| | | |
| | | private void normalizeOperationPercents(List<Map<String, Object>> rows) { |
| | | for (Map<String, Object> row : rows) { |
| | | Object raw = row.get("percent"); |
| | | try { |
| | | BigDecimal percent = raw == null ? BigDecimal.ZERO : new BigDecimal(String.valueOf(raw)); |
| | | row.put("percent", percent.abs().toPlainString()); |
| | | } catch (NumberFormatException ignored) { |
| | | row.put("percent", "0"); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** 加盟商看板按日聚合(siteIds 为空直接返回空,避免空 IN)。 */ |
| | |
| | | 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<FranchiseeDayStat> stats, LocalDateTime[] range) { |
| | | if (franchiseeUseMonthlyTrend(range)) { |
| | | return franchiseeMonthlyOrderTrend(stats, range); |
| | | } |
| | | Map<String, Object> chart = franchiseeDailyTrend(stats, range, "充电金额", FranchiseeDayStat::getOrderAmountEffective); |
| | | ((List<Map<String, Object>>) chart.get("series")).get(0).put("type", "column"); |
| | | Map<LocalDate, FranchiseeDayStat> byDay = dayStatByDay(stats); |
| | |
| | | LocalDate start = range[0] == null ? LocalDate.now() : range[0].toLocalDate(); |
| | | for (int i = 0; i < categories.size(); i++) { LocalDate day = start.plusDays(i); FranchiseeDayStat stat = byDay.get(day); countData.add(BigDecimal.valueOf(stat == null ? 0L : numberValue(stat.getOrderCount()))); } |
| | | 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; |
| | | } |
| | | |
| | | /** Ranges longer than 31 days use monthly buckets so the mobile chart stays readable. */ |
| | | private boolean franchiseeUseMonthlyTrend(LocalDateTime[] range) { |
| | | if (range == null || range.length < 2 || range[0] == null || range[1] == null) { |
| | | return false; |
| | | } |
| | | return range[0].toLocalDate().plusDays(31).isBefore(range[1].toLocalDate()); |
| | | } |
| | | |
| | | private Map<String, Object> franchiseeMonthlyOrderTrend(List<FranchiseeDayStat> stats, LocalDateTime[] range) { |
| | | Map<YearMonth, List<FranchiseeDayStat>> statsByMonth = stats.stream() |
| | | .filter(stat -> stat != null && stat.getStatDay() != null) |
| | | .collect(Collectors.groupingBy(stat -> YearMonth.from(stat.getStatDay()))); |
| | | LocalDate start = range[0].toLocalDate(); |
| | | LocalDate end = range[1].toLocalDate(); |
| | | YearMonth firstMonth = YearMonth.from(start); |
| | | YearMonth lastMonth = YearMonth.from(end.minusDays(1)); |
| | | List<String> categories = new ArrayList<>(); |
| | | List<BigDecimal> amountData = new ArrayList<>(); |
| | | List<BigDecimal> countData = new ArrayList<>(); |
| | | for (YearMonth month = firstMonth; !month.isAfter(lastMonth); month = month.plusMonths(1)) { |
| | | List<FranchiseeDayStat> monthStats = statsByMonth.getOrDefault(month, Collections.emptyList()); |
| | | categories.add(month.toString()); |
| | | amountData.add(sum(monthStats, FranchiseeDayStat::getOrderAmountEffective)); |
| | | countData.add(BigDecimal.valueOf(monthStats.stream().mapToLong(stat -> numberValue(stat.getOrderCount())).sum())); |
| | | } |
| | | Map<String, Object> amountSeries = new LinkedHashMap<>(); amountSeries.put("name", "充电金额"); amountSeries.put("data", amountData); amountSeries.put("type", "column"); |
| | | 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); |
| | | Map<String, Object> chart = new LinkedHashMap<>(); chart.put("categories", categories); chart.put("series", new ArrayList<>(Arrays.asList(amountSeries, countSeries))); return chart; |
| | | } |
| | | private Map<String, Object> franchiseePhaseRing(List<Integer> siteIds, LocalDateTime[] range) { |
| | | 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<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<FranchiseeDayStat> stats, LocalDate firstMonth, int monthCount) { |
| | | /** 收支分析近6月柱状图:与后台 电站收入分析 一致(历史月结算账单 + 当月原始订单)。 */ |
| | | private Map<String, Object> franchiseeIncomeTrendFromSix(List<SixChargingDto> months) { |
| | | List<String> categories = new ArrayList<>(); |
| | | List<BigDecimal> electricity = new ArrayList<>(), service = new ArrayList<>(); |
| | | Map<YearMonth, List<FranchiseeDayStat>> byMonth = stats.stream().filter(stat -> stat != null && stat.getStatDay() != null) |
| | | .collect(Collectors.groupingBy(stat -> YearMonth.from(stat.getStatDay()))); |
| | | for (int i = 0; i < monthCount; i++) { |
| | | YearMonth month = YearMonth.from(firstMonth).plusMonths(i); |
| | | categories.add(String.format("%02d月", month.getMonthValue())); |
| | | List<FranchiseeDayStat> monthStats = byMonth.getOrDefault(month, Collections.emptyList()); |
| | | electricity.add(sum(monthStats, FranchiseeDayStat::getElectrovalence)); |
| | | service.add(sum(monthStats, FranchiseeDayStat::getServiceFee)); |
| | | for (SixChargingDto month : months) { |
| | | if (month == null) { |
| | | continue; |
| | | } |
| | | categories.add(month.getMonth() + "月"); |
| | | electricity.add(month.getElectrovalence() == null ? BigDecimal.ZERO : month.getElectrovalence()); |
| | | service.add(month.getServiceCharge() == null ? BigDecimal.ZERO : month.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; |
| | | 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; |
| | | } |
| | | |
| | | |
| | |
| | | public R<List<SixChargingDto>> charge(Integer siteId, Integer partnerId) { |
| | | Long userId = tokenService.getLoginUser().getUserid(); |
| | | SysUser sysUser = sysUserClient.getSysUser(userId).getData(); |
| | | boolean partnerView = isPartnerView(sysUser); |
| | | List<Integer> siteIds = resolvePlatformRevenueSiteIds(userId, sysUser, siteId, partnerId); |
| | | LocalDate sixBefore = PreviousSixMonths.get(); |
| | | //通过siteIds进行sql查询统计 |
| | | List<SixChargingDto> chargingDtos = chargingOrderService.charge(sixBefore, siteIds, false); |
| | | return R.ok(buildLastSixMonths(siteIds, false)); |
| | | } |
| | | |
| | | /** |
| | | * 近6月+当月电站收入:历史月读结算账单、当月补原始订单。 |
| | | * 与「电站收入分析」图表、底部数据分类及加盟商收支同口径。 |
| | | */ |
| | | private List<SixChargingDto> buildLastSixMonths(List<Integer> siteIds, boolean partnerView) { |
| | | LocalDate sixBefore = PreviousSixMonths.get(); |
| | | List<SixChargingDto> chargingDtos = chargingOrderService.charge(sixBefore, siteIds, partnerView); |
| | | LocalDate statisticsEndMonth = resolveStatisticsEndMonth(LocalDate.now(), chargingDtos); |
| | | List<SixChargingDto> sixChargingDtos = generateLastSixMonths(statisticsEndMonth); |
| | | for (SixChargingDto sixChargingDto : sixChargingDtos) { |
| | |
| | | } |
| | | |
| | | } |
| | | |
| | | return R.ok(sixChargingDtos); |
| | | |
| | | return sixChargingDtos; |
| | | } |
| | | |
| | | |
| | |
| | | SysUser sysUser = sysUserClient.getSysUser(userId).getData(); |
| | | boolean partnerView = isPartnerView(sysUser); |
| | | List<Integer> siteIds = resolvePlatformRevenueSiteIds(userId, sysUser, siteId, partnerId); |
| | | Map<String,Object> map = chargingOrderService.countAll(siteIds, false); |
| | | if (map == null) { |
| | | map = new HashMap<String,Object>(); |
| | | } |
| | | LocalDate sixBefore = PreviousSixMonths.get(); |
| | | Map<String,Object> map = new HashMap<String,Object>(); |
| | | // 电费/服务费与上方「电站收入分析」图表同口径:近6月结算账单 + 当月原始订单,保证底部合计=图表6个月相加 |
| | | List<SixChargingDto> sixChargingDtos = buildLastSixMonths(siteIds, false); |
| | | map.put("electrovalence", sixChargingDtos.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(SixChargingDto::getElectrovalence) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add)); |
| | | map.put("service_charge", sixChargingDtos.stream() |
| | | .filter(Objects::nonNull) |
| | | .map(SixChargingDto::getServiceCharge) |
| | | .filter(Objects::nonNull) |
| | | .reduce(BigDecimal.ZERO, BigDecimal::add)); |
| | | // 合作商分成/三方平台分佣按近6月结算口径统计(与图表月份窗口一致) |
| | | Map<String,Object> sub = chargingOrderService.countAll(siteIds, false, sixBefore); |
| | | map.put("commission_amount", sub == null ? BigDecimal.ZERO : sub.get("commission_amount")); |
| | | map.put("sharing_amount", sub == null ? BigDecimal.ZERO : sub.get("sharing_amount")); |
| | | boolean platformVisible = !partnerView; |
| | | if(platformVisible){ |
| | | R<BigDecimal> parkingAmountResult = parkingLotClient.getRecordAmount(siteIds); |
| | | R<BigDecimal> parkingAmountResult = parkingLotClient.getRecordAmount(siteIds, sixBefore.toString()); |
| | | BigDecimal data = parkingAmountResult == null || parkingAmountResult.getData() == null |
| | | ? BigDecimal.ZERO : parkingAmountResult.getData(); |
| | | map.put("parkingAmount",data); |
| | | BigDecimal data1 = shoppingOrderService.getSumAmount(); |
| | | BigDecimal data1 = shoppingOrderService.getSumAmount(sixBefore); |
| | | map.put("shopAmount",data1); |
| | | BigDecimal data2 = vipOrderService.getSumAmout(); |
| | | BigDecimal data2 = vipOrderService.getSumAmout(sixBefore); |
| | | map.put("vipAmount",data2); |
| | | }else{ |
| | | map.put("parkingAmount",0); |