diff --git a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/business/BusinessFinancialController.java b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/business/BusinessFinancialController.java index f80ae21e1..c3dbf5f90 100644 --- a/jsowell-admin/src/main/java/com/jsowell/api/uniapp/business/BusinessFinancialController.java +++ b/jsowell-admin/src/main/java/com/jsowell/api/uniapp/business/BusinessFinancialController.java @@ -6,11 +6,15 @@ import com.jsowell.common.core.page.PageResponse; import com.jsowell.common.enums.ykc.ReturnCodeEnum; import com.jsowell.common.exception.BusinessException; import com.jsowell.common.response.RestApiResponse; +import com.google.common.collect.ImmutableMap; import com.jsowell.pile.dto.MerchantOrderReportDTO; import com.jsowell.pile.dto.ParkingCouponRecordQueryDTO; +import com.jsowell.pile.dto.business.BusinessEfficiencyQueryDTO; import com.jsowell.pile.dto.business.BusinessOperationAnalysisQueryDTO; import com.jsowell.pile.dto.business.BusinessScaleQueryDTO; import com.jsowell.pile.service.BusinessFinancialService; +import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyAnalysisVO; +import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyVO; import com.jsowell.pile.vo.uniapp.business.BusinessGunEfficiencyAnalysisVO; import com.jsowell.pile.vo.uniapp.business.BusinessOperationAnalysisVO; import com.jsowell.pile.vo.uniapp.business.BusinessScaleVO; @@ -148,4 +152,88 @@ public class BusinessFinancialController extends BaseController { } return response; } + + /** + * 查询经营效率 + * + * @param dto 查询参数 + * @return 经营效率数据(含指标卡片和曲线图) + */ + @PostMapping("/businessEfficiency") + public RestApiResponse getBusinessEfficiency(@RequestBody BusinessEfficiencyQueryDTO dto) { + logger.info("查询经营效率 params:{}", JSONObject.toJSONString(dto)); + RestApiResponse response; + try { + if (dto == null) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + BusinessEfficiencyVO result = businessFinancialService.getBusinessEfficiency(dto); + response = new RestApiResponse<>(result); + logger.info("查询经营效率成功 startTime:{}, endTime:{}, selectedMetricCode:{}", + dto.getStartTime(), dto.getEndTime(), dto.getSelectedMetricCode()); + } catch (BusinessException e) { + logger.warn("查询经营效率业务异常 code:{}, message:{}", e.getCode(), e.getMessage(), e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); + } catch (Exception e) { + logger.error("查询经营效率系统异常 params:{}", JSONObject.toJSONString(dto), e); + response = new RestApiResponse<>(e); + } + return response; + } + + /** + * 查询效率分析(单均效率 + 枪均效率) + * + * @param dto 查询参数 + * @return 效率分析数据(含单均效率和枪均效率两个维度) + */ + @PostMapping("/efficiencyAnalysis") + public RestApiResponse getEfficiencyAnalysis(@RequestBody BusinessOperationAnalysisQueryDTO dto) { + logger.info("查询效率分析 params:{}", JSONObject.toJSONString(dto)); + RestApiResponse response; + try { + if (dto == null) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + BusinessEfficiencyAnalysisVO result = businessFinancialService.getEfficiencyAnalysis(dto); + response = new RestApiResponse<>(result); + logger.info("查询效率分析成功 startTime:{}, endTime:{}", + dto.getStartTime(), dto.getEndTime()); + } catch (BusinessException e) { + logger.warn("查询效率分析业务异常 code:{}, message:{}", e.getCode(), e.getMessage(), e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); + } catch (Exception e) { + logger.error("查询效率分析系统异常 params:{}", JSONObject.toJSONString(dto), e); + response = new RestApiResponse<>(e); + } + return response; + } + + /** + * 分页查询场站订单排行 + * + * @param dto 查询参数(含pageNum、pageSize) + * @return 分页结果(场站订单排行列表,按订单数量降序) + */ + @PostMapping("/stationOrderRank") + public RestApiResponse getStationOrderRank(@RequestBody BusinessOperationAnalysisQueryDTO dto) { + logger.info("查询场站订单排行 params:{}", JSONObject.toJSONString(dto)); + RestApiResponse response; + try { + if (dto == null) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + PageResponse pageResponse = businessFinancialService.getStationOrderRank(dto); + response = new RestApiResponse<>(ImmutableMap.of("pageResponse", pageResponse)); + logger.info("查询场站订单排行成功 startTime:{}, endTime:{}, total:{}", + dto.getStartTime(), dto.getEndTime(), pageResponse.getTotal()); + } catch (BusinessException e) { + logger.warn("查询场站订单排行业务异常 code:{}, message:{}", e.getCode(), e.getMessage(), e); + response = new RestApiResponse<>(e.getCode(), e.getMessage()); + } catch (Exception e) { + logger.error("查询场站订单排行系统异常 params:{}", JSONObject.toJSONString(dto), e); + response = new RestApiResponse<>(e); + } + return response; + } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/BusinessEfficiencyQueryDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/BusinessEfficiencyQueryDTO.java new file mode 100644 index 000000000..421098732 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/BusinessEfficiencyQueryDTO.java @@ -0,0 +1,45 @@ +package com.jsowell.pile.dto.business; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 经营效率查询DTO + * + * @author zhangziao + * @date 2026/4/16 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BusinessEfficiencyQueryDTO { + + /** + * 开始时间,格式:yyyy-MM-dd + */ + private String startTime; + + /** + * 结束时间,格式:yyyy-MM-dd + */ + private String endTime; + + /** + * 站点id列表 + */ + private List stationIdList; + + /** + * 当前选中的指标编码,用于查询对应的曲线图数据 + * AVG_SERVICE_FEE_PER_DEGREE-度均服务费, + * TIME_UTILIZATION-时间利用率, + * GUN_AVG_ELECTRICITY-枪均电量, + * POWER_UTILIZATION-功率利用率 + */ + private String selectedMetricCode; +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/BusinessOperationAnalysisQueryDTO.java b/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/BusinessOperationAnalysisQueryDTO.java index e172e2891..0fb35717a 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/BusinessOperationAnalysisQueryDTO.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/dto/business/BusinessOperationAnalysisQueryDTO.java @@ -38,4 +38,14 @@ public class BusinessOperationAnalysisQueryDTO { * 当前选中的指标编码 */ private String selectedMetricCode; + + /** + * 页码(用于场站排行分页) + */ + private Integer pageNum; + + /** + * 每页条数(用于场站排行分页) + */ + private Integer pageSize; } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/BusinessFinancialService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/BusinessFinancialService.java index ea94965fc..87fb249f6 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/BusinessFinancialService.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/BusinessFinancialService.java @@ -4,7 +4,10 @@ import com.jsowell.common.core.page.PageResponse; import com.jsowell.pile.dto.MerchantOrderReportDTO; import com.jsowell.pile.dto.ParkingCouponRecordQueryDTO; import com.jsowell.pile.dto.business.BusinessOperationAnalysisQueryDTO; +import com.jsowell.pile.dto.business.BusinessEfficiencyQueryDTO; import com.jsowell.pile.dto.business.BusinessScaleQueryDTO; +import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyAnalysisVO; +import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyVO; import com.jsowell.pile.vo.uniapp.business.BusinessGunEfficiencyAnalysisVO; import com.jsowell.pile.vo.uniapp.business.BusinessOperationAnalysisVO; import com.jsowell.pile.vo.uniapp.business.BusinessScaleVO; @@ -45,4 +48,28 @@ public interface BusinessFinancialService { * @return 经营规模数据(含指标卡片和曲线图) */ BusinessScaleVO getBusinessScale(BusinessScaleQueryDTO dto); + + /** + * 查询经营效率 + * + * @param dto 查询条件 + * @return 经营效率数据(含指标卡片和曲线图) + */ + BusinessEfficiencyVO getBusinessEfficiency(BusinessEfficiencyQueryDTO dto); + + /** + * 分页查询场站订单排行 + * + * @param dto 查询条件(含pageNum、pageSize) + * @return 分页结果(场站订单排行列表,按订单数量降序) + */ + PageResponse getStationOrderRank(BusinessOperationAnalysisQueryDTO dto); + + /** + * 查询效率分析(单均效率 + 枪均效率) + * + * @param dto 查询条件 + * @return 效率分析数据(含单均效率和枪均效率两个维度) + */ + BusinessEfficiencyAnalysisVO getEfficiencyAnalysis(BusinessOperationAnalysisQueryDTO dto); } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/BusinessFinancialServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/BusinessFinancialServiceImpl.java index 8cbe41293..a896626e2 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/BusinessFinancialServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/BusinessFinancialServiceImpl.java @@ -12,11 +12,13 @@ import com.jsowell.common.enums.ykc.ReturnCodeEnum; import com.jsowell.common.exception.BusinessException; import com.jsowell.pile.dto.MerchantOrderReportDTO; import com.jsowell.pile.dto.ParkingCouponRecordQueryDTO; +import com.jsowell.pile.dto.business.BusinessEfficiencyQueryDTO; import com.jsowell.pile.dto.business.BusinessOperationAnalysisQueryDTO; import com.jsowell.pile.dto.business.BusinessOperationDateRangeDTO; import com.jsowell.pile.dto.business.BusinessOperationSummaryDTO; import com.jsowell.pile.dto.business.BusinessScaleQueryDTO; import com.jsowell.pile.domain.PileConnectorInfo; +import com.jsowell.pile.domain.PileStationInfo; import com.jsowell.pile.domain.SettleOrderReport; import com.jsowell.pile.service.BusinessFinancialService; import com.jsowell.pile.service.CarCouponRecordService; @@ -25,13 +27,17 @@ import com.jsowell.pile.service.PileConnectorInfoService; import com.jsowell.pile.service.PileStationInfoService; import com.jsowell.pile.service.SettleOrderReportService; import com.jsowell.pile.util.UserUtils; +import com.jsowell.pile.vo.base.ConnectorInfoVO; +import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyAnalysisVO; import com.jsowell.pile.vo.uniapp.business.BusinessGunEfficiencyAnalysisVO; +import com.jsowell.pile.vo.uniapp.business.BusinessEfficiencyVO; import com.jsowell.pile.vo.uniapp.business.BusinessOperationAnalysisVO; import com.jsowell.pile.vo.uniapp.business.BusinessOperationDiagnosisItemVO; import com.jsowell.pile.vo.uniapp.business.BusinessOperationMetricVO; import com.jsowell.pile.vo.uniapp.business.BusinessScaleChartVO; import com.jsowell.pile.vo.uniapp.business.BusinessScaleMetricVO; import com.jsowell.pile.vo.uniapp.business.BusinessScaleVO; +import com.jsowell.pile.vo.uniapp.business.BusinessStationRankVO; import com.jsowell.pile.vo.web.MerchantOrderReportVO; import com.jsowell.pile.vo.web.ParkingCouponRecordVO; import lombok.extern.slf4j.Slf4j; @@ -74,6 +80,11 @@ public class BusinessFinancialServiceImpl implements BusinessFinancialService { private static final String SCALE_METRIC_SERVICE_AMOUNT = "SERVICE_AMOUNT"; private static final String SCALE_METRIC_USE_ELECTRICITY = "USE_ELECTRICITY"; + private static final String EFFICIENCY_METRIC_AVG_SERVICE_FEE_PER_DEGREE = "AVG_SERVICE_FEE_PER_DEGREE"; + private static final String EFFICIENCY_METRIC_TIME_UTILIZATION = "TIME_UTILIZATION"; + private static final String EFFICIENCY_METRIC_GUN_AVG_ELECTRICITY = "GUN_AVG_ELECTRICITY"; + private static final String EFFICIENCY_METRIC_POWER_UTILIZATION = "POWER_UTILIZATION"; + /** * 财务查询通用线程池(支持多方法并行查询) * 核心线程数4:覆盖经营规模(2路)、我的钱包(3路)、经营分析(2路)、枪均效率(2路)等场景 @@ -785,4 +796,509 @@ public class BusinessFinancialServiceImpl implements BusinessFinancialService { return BigDecimalUtils.nullToZero(report.getUseElectricity()); } } + + /** + * 查询效率分析(单均效率 + 枪均效率) + * 单均效率:按订单维度计算平均指标 + * 枪均效率:按枪口×天数维度计算日均指标 + * + * @param dto 查询条件 + * @return 效率分析数据 + */ + @Override + public BusinessEfficiencyAnalysisVO getEfficiencyAnalysis(BusinessOperationAnalysisQueryDTO dto) { + if (dto == null) { + return null; + } + if (StringUtils.isBlank(dto.getStartTime()) && StringUtils.isBlank(dto.getEndTime())) { + LocalDate endDate = LocalDate.now(); + LocalDate startDate = endDate.minusDays(6); + dto.setStartTime(startDate.toString()); + dto.setEndTime(endDate.toString()); + } + + List stationIdList = resolveStationIds(dto.getStationIdList()); + + // 并行查询订单汇总和枪口数量 + CompletableFuture summaryFuture = CompletableFuture.supplyAsync( + () -> buildSummaryFromReports( + queryRawReports(stationIdList, dto.getStartTime(), dto.getEndTime())), + FINANCIAL_THREAD_POOL); + CompletableFuture connectorCountFuture = CompletableFuture.supplyAsync( + () -> countConnectors(stationIdList), FINANCIAL_THREAD_POOL); + + BusinessOperationSummaryDTO summary = summaryFuture.join(); + int connectorCount = connectorCountFuture.join(); + + long dayCount = calculateInclusiveDays(dto.getStartTime(), dto.getEndTime()); + + // ===== 单均效率指标 ===== + // 单均服务费 = 服务费总额 / 订单数量 + BigDecimal avgServiceFeePerOrder = BigDecimalUtils.safeDivide( + summary.getServiceAmount(), summary.getOrderCount(), 2); + // 单均充电量 = 充电电量总量 / 订单数量 + BigDecimal avgElectricityPerOrder = BigDecimalUtils.safeDivide( + summary.getUseElectricity(), summary.getOrderCount(), 4); + // 单均充电时长 = 总充电时长 / 订单数量 + BigDecimal avgChargeTimeMinutesPerOrder = BigDecimalUtils.safeDivide( + summary.getChargeTime(), summary.getOrderCount(), 2); + String avgChargeTimePerOrder = formatMinutesOnly(avgChargeTimeMinutesPerOrder); + + // ===== 枪均效率指标 ===== + BigDecimal connectorCountBd = BigDecimal.valueOf(connectorCount); + BigDecimal dayCountBd = BigDecimal.valueOf(dayCount); + BigDecimal connectorDayCount = connectorCountBd.multiply(dayCountBd); + + // 枪日均服务费 = 服务费总额 / (枪口数量 × 天数) + BigDecimal gunDailyAvgServiceFee = BigDecimalUtils.safeDivide( + summary.getServiceAmount(), connectorDayCount, 2); + // 枪日均充电量 = 充电电量总量 / (枪口数量 × 天数) + BigDecimal gunDailyAvgElectricity = BigDecimalUtils.safeDivide( + summary.getUseElectricity(), connectorDayCount, 4); + // 枪日均充电时长 = 总充电时长 / (枪口数量 × 天数) + BigDecimal gunDailyAvgChargeTimeMinutes = BigDecimalUtils.safeDivide( + summary.getChargeTime(), connectorDayCount, 2); + String gunDailyAvgChargeTime = formatMinutesToHourMinute(gunDailyAvgChargeTimeMinutes); + + // ===== 共用指标 ===== + // 度均服务费 = 服务费总额 / 充电电量总量 + BigDecimal avgServiceFeePerDegree = BigDecimalUtils.safeDivide( + summary.getServiceAmount(), summary.getUseElectricity(), 4); + // 平均功率 = 充电电量总量 × 60 / 总充电时长(分钟) + BigDecimal avgPower = BigDecimalUtils.safeDivide( + summary.getUseElectricity().multiply(new BigDecimal("60")), + summary.getChargeTime(), + 4); + + return BusinessEfficiencyAnalysisVO.builder() + .startTime(dto.getStartTime()) + .endTime(dto.getEndTime()) + .avgServiceFeePerOrder(BigDecimalUtils.normalize(avgServiceFeePerOrder)) + .avgElectricityPerOrder(BigDecimalUtils.normalize(avgElectricityPerOrder)) + .avgChargeTimePerOrder(avgChargeTimePerOrder) + .gunDailyAvgServiceFee(BigDecimalUtils.normalize(gunDailyAvgServiceFee)) + .gunDailyAvgElectricity(BigDecimalUtils.normalize(gunDailyAvgElectricity)) + .gunDailyAvgChargeTime(gunDailyAvgChargeTime) + .avgServiceFeePerDegree(BigDecimalUtils.normalize(avgServiceFeePerDegree)) + .avgPower(BigDecimalUtils.normalize(avgPower)) + .build(); + } + + /** + * 将分钟数格式化为xxm格式(用于单均充电时长) + * + * @param minutes 分钟数 + * @return 格式化结果 + */ + private String formatMinutesOnly(BigDecimal minutes) { + long totalMinutes = BigDecimalUtils.nullToZero(minutes) + .setScale(0, RoundingMode.HALF_UP) + .longValue(); + return totalMinutes + "m"; + } + + /** + * 分页查询场站订单排行 + * 按站点分组统计订单数量,按订单数量降序排列,支持分页 + * + * @param dto 查询条件(含pageNum、pageSize) + * @return 分页结果 + */ + @Override + public PageResponse getStationOrderRank(BusinessOperationAnalysisQueryDTO dto) { + if (dto == null) { + return PageResponse.builder().pageNum(1).pageSize(10).list(new ArrayList<>()).total(0).pages(0).build(); + } + if (StringUtils.isBlank(dto.getStartTime()) && StringUtils.isBlank(dto.getEndTime())) { + LocalDate endDate = LocalDate.now(); + LocalDate startDate = endDate.minusDays(6); + dto.setStartTime(startDate.toString()); + dto.setEndTime(endDate.toString()); + } + + // 设置默认分页参数 + int pageNum = dto.getPageNum() != null && dto.getPageNum() > 0 ? dto.getPageNum() : 1; + int pageSize = dto.getPageSize() != null && dto.getPageSize() > 0 ? dto.getPageSize() : 10; + + List stationIdList = resolveStationIds(dto.getStationIdList()); + + // 查询结算报表原始数据 + List reportList = queryRawReports( + stationIdList, dto.getStartTime(), dto.getEndTime()); + + if (CollectionUtils.isEmpty(reportList)) { + return PageResponse.builder().pageNum(pageNum).pageSize(pageSize).list(new ArrayList<>()).total(0).pages(0).build(); + } + + // 按站点ID分组,累加订单数量 + Map stationOrderCountMap = new LinkedHashMap<>(); + for (SettleOrderReport report : reportList) { + String sId = report.getStationId(); + if (StringUtils.isBlank(sId)) { + continue; + } + int chargeNum = BigDecimalUtils.parseOrZero(report.getChargeNum()).intValue(); + stationOrderCountMap.merge(sId, chargeNum, Integer::sum); + } + + // 批量查询站点名称(一次性查出所有站点,内存中按ID匹配,避免N+1查询) + List allStations = pileStationInfoService.selectPileStationInfoList(new PileStationInfo()); + Map stationNameMap = new LinkedHashMap<>(); + for (PileStationInfo station : allStations) { + stationNameMap.put(String.valueOf(station.getId()), station.getStationName()); + } + + // 按订单数量降序排列并构建排行列表 + List rankList = stationOrderCountMap.entrySet().stream() + .map(entry -> BusinessStationRankVO.builder() + .stationId(entry.getKey()) + .stationName(stationNameMap.getOrDefault(entry.getKey(), "未知站点")) + .orderCount(entry.getValue()) + .build()) + .sorted((a, b) -> Integer.compare( + b.getOrderCount() != null ? b.getOrderCount() : 0, + a.getOrderCount() != null ? a.getOrderCount() : 0)) + .collect(Collectors.toList()); + + // 设置排行名次(基于全量数据的排名) + for (int i = 0; i < rankList.size(); i++) { + rankList.get(i).setRank(i + 1); + } + + // 手动分页 + long total = rankList.size(); + int pages = (int) Math.ceil((double) total / pageSize); + int fromIndex = (pageNum - 1) * pageSize; + int toIndex = Math.min(fromIndex + pageSize, rankList.size()); + List pageList = fromIndex < rankList.size() + ? rankList.subList(fromIndex, toIndex) + : new ArrayList<>(); + + return PageResponse.builder() + .pageNum(pageNum) + .pageSize(pageSize) + .list(pageList) + .total(total) + .pages(pages) + .build(); + } + + /** + * 查询经营效率 + * 优化:当前周期和上周期汇总并行查询,枪口数量与总额定功率并行查询 + * + * @param dto 查询条件 + * @return 经营效率数据 + */ + @Override + public BusinessEfficiencyVO getBusinessEfficiency(BusinessEfficiencyQueryDTO dto) { + if (dto == null) { + return null; + } + if (StringUtils.isBlank(dto.getStartTime()) && StringUtils.isBlank(dto.getEndTime())) { + LocalDate endDate = LocalDate.now(); + LocalDate startDate = endDate.minusDays(6); + dto.setStartTime(startDate.toString()); + dto.setEndTime(endDate.toString()); + } + + BusinessOperationDateRangeDTO range = buildDateRangeFromEfficiency(dto); + List stationIdList = resolveStationIds(dto.getStationIdList()); + + // 并行查询当前周期和上周期原始报表数据 + CompletableFuture> currentReportFuture = CompletableFuture.supplyAsync( + () -> queryRawReports(stationIdList, range.getCurrentStart(), range.getCurrentEnd()), + FINANCIAL_THREAD_POOL); + CompletableFuture> previousReportFuture = CompletableFuture.supplyAsync( + () -> queryRawReports(stationIdList, range.getPreviousStart(), range.getPreviousEnd()), + FINANCIAL_THREAD_POOL); + + // 等待报表查询完成 + List currentReports = currentReportFuture.join(); + List previousReports = previousReportFuture.join(); + + BusinessOperationSummaryDTO currentSummary = buildSummaryFromReports(currentReports); + BusinessOperationSummaryDTO previousSummary = buildSummaryFromReports(previousReports); + + Map currentDailyMap = buildDailyMapFromReports(currentReports); + Map previousDailyMap = buildDailyMapFromReports(previousReports); + + long dayCount = calculateInclusiveDays(range.getCurrentStart(), range.getCurrentEnd()); + + // 查询枪口数量和总额定功率 + ConnectorStats connectorStats = buildConnectorStats(stationIdList); + int connectorCount = connectorStats.getConnectorCount(); + BigDecimal totalRatedPower = connectorStats.getTotalRatedPower(); + + // 构建指标卡片 + BigDecimal currentAvgServiceFeePerDegree = BigDecimalUtils.safeDivide( + currentSummary.getServiceAmount(), currentSummary.getUseElectricity(), 2); + BigDecimal previousAvgServiceFeePerDegree = BigDecimalUtils.safeDivide( + previousSummary.getServiceAmount(), previousSummary.getUseElectricity(), 2); + + BigDecimal currentTimeUtilization = calculateTimeUtilization( + currentSummary.getChargeTime(), connectorCount, dayCount); + BigDecimal previousTimeUtilization = calculateTimeUtilization( + previousSummary.getChargeTime(), connectorCount, dayCount); + + BigDecimal currentGunAvgElectricity = connectorCount > 0 + ? BigDecimalUtils.safeDivide(currentSummary.getUseElectricity(), + BigDecimal.valueOf(connectorCount), 2) + : BigDecimal.ZERO; + BigDecimal previousGunAvgElectricity = connectorCount > 0 + ? BigDecimalUtils.safeDivide(previousSummary.getUseElectricity(), + BigDecimal.valueOf(connectorCount), 2) + : BigDecimal.ZERO; + + BigDecimal currentPowerUtilization = calculatePowerUtilization( + currentSummary.getUseElectricity(), currentSummary.getChargeTime(), + connectorCount, dayCount, totalRatedPower); + BigDecimal previousPowerUtilization = calculatePowerUtilization( + previousSummary.getUseElectricity(), previousSummary.getChargeTime(), + connectorCount, dayCount, totalRatedPower); + + List metricList = new ArrayList<>(); + metricList.add(buildScaleMetric(EFFICIENCY_METRIC_AVG_SERVICE_FEE_PER_DEGREE, "度均服务费", "元", + currentAvgServiceFeePerDegree, previousAvgServiceFeePerDegree)); + metricList.add(buildScaleMetric(EFFICIENCY_METRIC_TIME_UTILIZATION, "时间利用率", "%", + currentTimeUtilization, previousTimeUtilization)); + metricList.add(buildScaleMetric(EFFICIENCY_METRIC_GUN_AVG_ELECTRICITY, "枪均电量", "度", + currentGunAvgElectricity, previousGunAvgElectricity)); + metricList.add(buildScaleMetric(EFFICIENCY_METRIC_POWER_UTILIZATION, "功率利用率", "%", + currentPowerUtilization, previousPowerUtilization)); + + // 构建选中指标的曲线图数据 + String selectedMetricCode = StringUtils.isBlank(dto.getSelectedMetricCode()) + ? EFFICIENCY_METRIC_AVG_SERVICE_FEE_PER_DEGREE : dto.getSelectedMetricCode(); + BusinessScaleChartVO chartData = buildEfficiencyChartFromMaps( + selectedMetricCode, range, currentDailyMap, previousDailyMap, + connectorCount, totalRatedPower); + + return BusinessEfficiencyVO.builder() + .currentStartTime(range.getCurrentStart()) + .currentEndTime(range.getCurrentEnd()) + .previousStartTime(range.getPreviousStart()) + .previousEndTime(range.getPreviousEnd()) + .metricList(metricList) + .chartData(chartData) + .build(); + } + + /** + * 构建经营效率日期区间 + */ + private BusinessOperationDateRangeDTO buildDateRangeFromEfficiency(BusinessEfficiencyQueryDTO dto) { + try { + LocalDate currentStart = LocalDate.parse(dto.getStartTime()); + LocalDate currentEnd = LocalDate.parse(dto.getEndTime()); + if (currentEnd.isBefore(currentStart)) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + long days = ChronoUnit.DAYS.between(currentStart, currentEnd) + 1; + LocalDate previousEnd = currentStart.minusDays(1); + LocalDate previousStart = previousEnd.minusDays(days - 1); + return BusinessOperationDateRangeDTO.builder() + .currentStart(currentStart.toString()) + .currentEnd(currentEnd.toString()) + .previousStart(previousStart.toString()) + .previousEnd(previousEnd.toString()) + .build(); + } catch (BusinessException e) { + throw e; + } catch (Exception e) { + throw new BusinessException(ReturnCodeEnum.CODE_PARAM_NOT_NULL_ERROR); + } + } + + /** + * 构建枪口统计信息(枪口数量 + 总额定功率) + */ + private ConnectorStats buildConnectorStats(List stationIdList) { + List connectors; + if (stationIdList == null) { + // 平台账号:先查询所有站点ID,再批量查询枪口信息 + List allStations = pileStationInfoService.selectPileStationInfoList(new PileStationInfo()); + List allStationIds = allStations.stream() + .map(s -> String.valueOf(s.getId())) + .collect(Collectors.toList()); + if (allStationIds.isEmpty()) { + return new ConnectorStats(0, BigDecimal.ZERO); + } + connectors = pileConnectorInfoService.batchSelectConnectorList(allStationIds); + } else if (stationIdList.isEmpty()) { + return new ConnectorStats(0, BigDecimal.ZERO); + } else { + connectors = pileConnectorInfoService.batchSelectConnectorList(stationIdList); + } + + if (CollectionUtils.isEmpty(connectors)) { + return new ConnectorStats(0, BigDecimal.ZERO); + } + + BigDecimal totalRatedPower = BigDecimal.ZERO; + for (ConnectorInfoVO connector : connectors) { + totalRatedPower = totalRatedPower.add(BigDecimalUtils.parseOrZero(connector.getRatedPower())); + } + + return new ConnectorStats(connectors.size(), totalRatedPower); + } + + /** + * 计算时间利用率(%) + * = 总充电时长(分钟) / (枪口数 × 天数 × 1440分钟) × 100 + */ + private BigDecimal calculateTimeUtilization(BigDecimal chargeTime, int connectorCount, long dayCount) { + if (connectorCount == 0 || dayCount == 0) { + return BigDecimal.ZERO; + } + BigDecimal totalAvailableMinutes = BigDecimal.valueOf(connectorCount) + .multiply(BigDecimal.valueOf(dayCount)) + .multiply(new BigDecimal("1440")); + BigDecimal utilization = BigDecimalUtils.safeDivide( + BigDecimalUtils.nullToZero(chargeTime), totalAvailableMinutes, 4); + return utilization.multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP); + } + + /** + * 计算功率利用率(%) + * = 实际总能量 / 理论最大总能量 × 100 + * = useElectricity / (总额定功率 × 天数 × 24) × 100 + */ + private BigDecimal calculatePowerUtilization(BigDecimal useElectricity, BigDecimal chargeTime, + int connectorCount, long dayCount, BigDecimal totalRatedPower) { + if (totalRatedPower.compareTo(BigDecimal.ZERO) == 0 || dayCount == 0) { + return BigDecimal.ZERO; + } + BigDecimal theoreticalMaxEnergy = totalRatedPower + .multiply(BigDecimal.valueOf(dayCount)) + .multiply(new BigDecimal("24")); + BigDecimal utilization = BigDecimalUtils.safeDivide( + BigDecimalUtils.nullToZero(useElectricity), theoreticalMaxEnergy, 4); + return utilization.multiply(new BigDecimal("100")).setScale(2, RoundingMode.HALF_UP); + } + + /** + * 从dailyMap构建经营效率曲线图数据 + * 每个指标按天计算(度均服务费=当天服务费/当天电量, 时间利用率=当天充电时长/可用时长, 等) + */ + private BusinessScaleChartVO buildEfficiencyChartFromMaps(String metricCode, + BusinessOperationDateRangeDTO range, + Map currentDailyMap, + Map previousDailyMap, + int connectorCount, + BigDecimal totalRatedPower) { + String metricName; + String unit; + switch (metricCode) { + case EFFICIENCY_METRIC_TIME_UTILIZATION: + metricName = "时间利用率"; + unit = "%"; + break; + case EFFICIENCY_METRIC_GUN_AVG_ELECTRICITY: + metricName = "枪均电量"; + unit = "度"; + break; + case EFFICIENCY_METRIC_POWER_UTILIZATION: + metricName = "功率利用率"; + unit = "%"; + break; + default: + metricName = "度均服务费"; + unit = "元"; + break; + } + + LocalDate currentStart = LocalDate.parse(range.getCurrentStart()); + LocalDate currentEnd = LocalDate.parse(range.getCurrentEnd()); + LocalDate previousStart = LocalDate.parse(range.getPreviousStart()); + + long dayCount = ChronoUnit.DAYS.between(currentStart, currentEnd) + 1; + DateTimeFormatter displayFormatter = DateTimeFormatter.ofPattern("MM/dd"); + + List chartPoints = new ArrayList<>(); + for (long i = 0; i < dayCount; i++) { + LocalDate currentDate = currentStart.plusDays(i); + LocalDate previousDate = previousStart.plusDays(i); + + SettleOrderReport currentReport = currentDailyMap.get(currentDate.toString()); + SettleOrderReport previousReport = previousDailyMap.get(previousDate.toString()); + + BigDecimal currentValue = calculateEfficiencyMetricValue( + metricCode, currentReport, connectorCount, 1, totalRatedPower); + BigDecimal previousValue = calculateEfficiencyMetricValue( + metricCode, previousReport, connectorCount, 1, totalRatedPower); + + chartPoints.add(BusinessScaleChartVO.ChartPoint.builder() + .date(currentDate.format(displayFormatter)) + .fullDate(currentDate.toString()) + .value(BigDecimalUtils.normalize(currentValue)) + .previousValue(BigDecimalUtils.normalize(previousValue)) + .changeRate(BigDecimalUtils.calculateRate( + BigDecimalUtils.nullToZero(currentValue), + BigDecimalUtils.nullToZero(previousValue))) + .build()); + } + + return BusinessScaleChartVO.builder() + .metricCode(metricCode) + .metricName(metricName) + .unit(unit) + .chartData(chartPoints) + .build(); + } + + /** + * 根据指标编码计算单条报表记录对应的经营效率指标值 + */ + private BigDecimal calculateEfficiencyMetricValue(String metricCode, SettleOrderReport report, + int connectorCount, long dayCount, + BigDecimal totalRatedPower) { + if (report == null) { + return BigDecimal.ZERO; + } + BigDecimal serviceAmount = BigDecimalUtils.nullToZero(report.getServiceAmount()); + BigDecimal useElectricity = BigDecimalUtils.nullToZero(report.getUseElectricity()); + BigDecimal chargeTime = BigDecimalUtils.parseOrZero(report.getChargeTime()); + + switch (metricCode) { + case EFFICIENCY_METRIC_AVG_SERVICE_FEE_PER_DEGREE: + // 度均服务费 = 服务费 / 用电量 + return BigDecimalUtils.safeDivide(serviceAmount, useElectricity, 2); + case EFFICIENCY_METRIC_TIME_UTILIZATION: + // 时间利用率 = 充电时长 / (枪口数 × 1天 × 1440) × 100 + return calculateTimeUtilization(chargeTime, connectorCount, dayCount); + case EFFICIENCY_METRIC_GUN_AVG_ELECTRICITY: + // 枪均电量 = 用电量 / 枪口数 + return connectorCount > 0 + ? BigDecimalUtils.safeDivide(useElectricity, BigDecimal.valueOf(connectorCount), 2) + : BigDecimal.ZERO; + case EFFICIENCY_METRIC_POWER_UTILIZATION: + // 功率利用率 = 用电量 / (总额定功率 × 1天 × 24) × 100 + return calculatePowerUtilization(useElectricity, chargeTime, + connectorCount, dayCount, totalRatedPower); + default: + return BigDecimal.ZERO; + } + } + + /** + * 枪口统计信息内部类 + */ + private static class ConnectorStats { + private final int connectorCount; + private final BigDecimal totalRatedPower; + + ConnectorStats(int connectorCount, BigDecimal totalRatedPower) { + this.connectorCount = connectorCount; + this.totalRatedPower = totalRatedPower; + } + + int getConnectorCount() { + return connectorCount; + } + + BigDecimal getTotalRatedPower() { + return totalRatedPower; + } + } } diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java index e850fa2e0..63bc66d93 100644 --- a/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java +++ b/jsowell-pile/src/main/java/com/jsowell/pile/service/impl/OrderBasicInfoServiceImpl.java @@ -3503,7 +3503,7 @@ public class OrderBasicInfoServiceImpl implements OrderBasicInfoService { logger.debug("redis中没有实时数据了,去数据库查"); // redis中为空,去查库 OrderMonitorData orderMonitorData = orderMonitorDataService.selectByTransactionCode(transactionCode); - if (orderMonitorData != null) { + if (orderMonitorData != null && StringUtils.isNotBlank(orderMonitorData.getMonitorData())) { String monitorData = orderMonitorData.getMonitorData(); List dataList = JSON.parseArray(monitorData, RealTimeMonitorData.class); resultList.addAll(dataList); diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessEfficiencyAnalysisVO.java b/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessEfficiencyAnalysisVO.java new file mode 100644 index 000000000..256a4ef2c --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessEfficiencyAnalysisVO.java @@ -0,0 +1,84 @@ +package com.jsowell.pile.vo.uniapp.business; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +/** + * 效率分析VO(包含单均效率和枪均效率两个维度的数据) + * + * @author Lemon + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BusinessEfficiencyAnalysisVO { + + /** + * 开始时间 + */ + private String startTime; + + /** + * 结束时间 + */ + private String endTime; + + // ========== 单均效率指标 ========== + + /** + * 单均服务费(元) + * = 服务费总额 / 订单数量 + */ + private BigDecimal avgServiceFeePerOrder; + + /** + * 单均充电量(度) + * = 充电电量总量 / 订单数量 + */ + private BigDecimal avgElectricityPerOrder; + + /** + * 单均充电时长,格式:xxm + * = 总充电时长(分钟) / 订单数量 + */ + private String avgChargeTimePerOrder; + + // ========== 枪均效率指标 ========== + + /** + * 枪日均服务费(元) + * = 服务费总额 / (枪口数量 × 天数) + */ + private BigDecimal gunDailyAvgServiceFee; + + /** + * 枪日均充电量(度) + * = 充电电量总量 / (枪口数量 × 天数) + */ + private BigDecimal gunDailyAvgElectricity; + + /** + * 枪日均充电时长,格式:xxhxxm + * = 总充电时长(分钟) / (枪口数量 × 天数) + */ + private String gunDailyAvgChargeTime; + + // ========== 共用指标 ========== + + /** + * 度均服务费(元) + * = 服务费总额 / 充电电量总量 + */ + private BigDecimal avgServiceFeePerDegree; + + /** + * 平均功率(KW) + * = 充电电量总量 × 60 / 总充电时长(分钟) + */ + private BigDecimal avgPower; +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessEfficiencyVO.java b/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessEfficiencyVO.java new file mode 100644 index 000000000..038542503 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessEfficiencyVO.java @@ -0,0 +1,51 @@ +package com.jsowell.pile.vo.uniapp.business; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +/** + * 经营效率VO + * + * @author zhangziao + * @date 2026/4/16 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BusinessEfficiencyVO { + + /** + * 当前周期开始时间 + */ + private String currentStartTime; + + /** + * 当前周期结束时间 + */ + private String currentEndTime; + + /** + * 上周期开始时间 + */ + private String previousStartTime; + + /** + * 上周期结束时间 + */ + private String previousEndTime; + + /** + * 指标卡片列表(度均服务费、时间利用率、枪均电量、功率利用率) + */ + private List metricList; + + /** + * 当前选中指标对应的曲线图数据 + */ + private BusinessScaleChartVO chartData; +} diff --git a/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessStationRankVO.java b/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessStationRankVO.java new file mode 100644 index 000000000..48d97ce19 --- /dev/null +++ b/jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessStationRankVO.java @@ -0,0 +1,38 @@ +package com.jsowell.pile.vo.uniapp.business; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 场站排行VO + * + * @author zhangziao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class BusinessStationRankVO { + + /** + * 排行名次 + */ + private Integer rank; + + /** + * 站点ID + */ + private String stationId; + + /** + * 场站名称 + */ + private String stationName; + + /** + * 订单总数 + */ + private Integer orderCount; +}