From 51ec0edaa4acc013088974134a9ef2cf1bfcbd38 Mon Sep 17 00:00:00 2001 From: Lemon Date: Tue, 21 Apr 2026 09:13:04 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E7=BB=8F=E8=90=A5=E6=95=88=E7=8E=87=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business/BusinessFinancialController.java | 30 ++ jsowell-admin/src/main/resources/logback.xml | 2 +- .../service/BusinessFinancialService.java | 10 + .../impl/BusinessFinancialServiceImpl.java | 328 ++++++++++++++++++ .../impl/OrderBasicInfoServiceImpl.java | 2 +- 5 files changed, 370 insertions(+), 2 deletions(-) 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..d0ae8ebb4 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 @@ -8,9 +8,11 @@ import com.jsowell.common.exception.BusinessException; import com.jsowell.common.response.RestApiResponse; 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.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 +150,32 @@ 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; + } } diff --git a/jsowell-admin/src/main/resources/logback.xml b/jsowell-admin/src/main/resources/logback.xml index d293b86f2..b3591c688 100644 --- a/jsowell-admin/src/main/resources/logback.xml +++ b/jsowell-admin/src/main/resources/logback.xml @@ -1,7 +1,7 @@ - + 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..ce5dfb16d 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,9 @@ 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.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 +47,12 @@ public interface BusinessFinancialService { * @return 经营规模数据(含指标卡片和曲线图) */ BusinessScaleVO getBusinessScale(BusinessScaleQueryDTO dto); + + /** + * 查询经营效率 + * + * @param dto 查询条件 + * @return 经营效率数据(含指标卡片和曲线图) + */ + BusinessEfficiencyVO getBusinessEfficiency(BusinessEfficiencyQueryDTO 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..cd1b2c2d8 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,7 +27,9 @@ 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.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; @@ -74,6 +78,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 +794,323 @@ public class BusinessFinancialServiceImpl implements BusinessFinancialService { return BigDecimalUtils.nullToZero(report.getUseElectricity()); } } + + /** + * 查询经营效率 + * 优化:当前周期和上周期汇总并行查询,枪口数量与总额定功率并行查询 + * + * @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 de3100f7a..b0f5a2da6 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 @@ -3497,7 +3497,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);