Merge branch 'feature-business-minigram' into dev

This commit is contained in:
Lemon
2026-05-25 14:35:31 +08:00
9 changed files with 860 additions and 1 deletions

View File

@@ -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;
}
}

View File

@@ -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<String> stationIdList;
/**
* 当前选中的指标编码,用于查询对应的曲线图数据
* AVG_SERVICE_FEE_PER_DEGREE-度均服务费,
* TIME_UTILIZATION-时间利用率,
* GUN_AVG_ELECTRICITY-枪均电量,
* POWER_UTILIZATION-功率利用率
*/
private String selectedMetricCode;
}

View File

@@ -38,4 +38,14 @@ public class BusinessOperationAnalysisQueryDTO {
* 当前选中的指标编码
*/
private String selectedMetricCode;
/**
* 页码(用于场站排行分页)
*/
private Integer pageNum;
/**
* 每页条数(用于场站排行分页)
*/
private Integer pageSize;
}

View File

@@ -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);
}

View File

@@ -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<String> stationIdList = resolveStationIds(dto.getStationIdList());
// 并行查询订单汇总和枪口数量
CompletableFuture<BusinessOperationSummaryDTO> summaryFuture = CompletableFuture.supplyAsync(
() -> buildSummaryFromReports(
queryRawReports(stationIdList, dto.getStartTime(), dto.getEndTime())),
FINANCIAL_THREAD_POOL);
CompletableFuture<Integer> 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<String> stationIdList = resolveStationIds(dto.getStationIdList());
// 查询结算报表原始数据
List<SettleOrderReport> 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<String, Integer> 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<PileStationInfo> allStations = pileStationInfoService.selectPileStationInfoList(new PileStationInfo());
Map<String, String> stationNameMap = new LinkedHashMap<>();
for (PileStationInfo station : allStations) {
stationNameMap.put(String.valueOf(station.getId()), station.getStationName());
}
// 按订单数量降序排列并构建排行列表
List<BusinessStationRankVO> 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<BusinessStationRankVO> 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<String> stationIdList = resolveStationIds(dto.getStationIdList());
// 并行查询当前周期和上周期原始报表数据
CompletableFuture<List<SettleOrderReport>> currentReportFuture = CompletableFuture.supplyAsync(
() -> queryRawReports(stationIdList, range.getCurrentStart(), range.getCurrentEnd()),
FINANCIAL_THREAD_POOL);
CompletableFuture<List<SettleOrderReport>> previousReportFuture = CompletableFuture.supplyAsync(
() -> queryRawReports(stationIdList, range.getPreviousStart(), range.getPreviousEnd()),
FINANCIAL_THREAD_POOL);
// 等待报表查询完成
List<SettleOrderReport> currentReports = currentReportFuture.join();
List<SettleOrderReport> previousReports = previousReportFuture.join();
BusinessOperationSummaryDTO currentSummary = buildSummaryFromReports(currentReports);
BusinessOperationSummaryDTO previousSummary = buildSummaryFromReports(previousReports);
Map<String, SettleOrderReport> currentDailyMap = buildDailyMapFromReports(currentReports);
Map<String, SettleOrderReport> 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<BusinessScaleMetricVO> 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<String> stationIdList) {
List<ConnectorInfoVO> connectors;
if (stationIdList == null) {
// 平台账号先查询所有站点ID再批量查询枪口信息
List<PileStationInfo> allStations = pileStationInfoService.selectPileStationInfoList(new PileStationInfo());
List<String> 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<String, SettleOrderReport> currentDailyMap,
Map<String, SettleOrderReport> 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<BusinessScaleChartVO.ChartPoint> 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;
}
}
}

View File

@@ -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<RealTimeMonitorData> dataList = JSON.parseArray(monitorData, RealTimeMonitorData.class);
resultList.addAll(dataList);

View File

@@ -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;
}

View File

@@ -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<BusinessScaleMetricVO> metricList;
/**
* 当前选中指标对应的曲线图数据
*/
private BusinessScaleChartVO chartData;
}

View File

@@ -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;
}