From 206b53ece041fe2b6846e5cd0cbbeab84da189d7 Mon Sep 17 00:00:00 2001 From: Lemon Date: Mon, 11 May 2026 13:40:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=95=88=E7=8E=87=E5=88=86=E6=9E=90=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../business/BusinessFinancialController.java | 60 ++++++ .../service/BusinessFinancialService.java | 20 ++ .../impl/BusinessFinancialServiceImpl.java | 178 ++++++++++++++++++ .../BusinessEfficiencyAnalysisVO.java | 84 +++++++++ .../business/BusinessStationRankVO.java | 38 ++++ 5 files changed, 380 insertions(+) create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessEfficiencyAnalysisVO.java create mode 100644 jsowell-pile/src/main/java/com/jsowell/pile/vo/uniapp/business/BusinessStationRankVO.java 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 d0ae8ebb4..177fcec15 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 @@ -12,10 +12,12 @@ 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; +import com.jsowell.pile.vo.uniapp.business.BusinessStationRankVO; import com.jsowell.pile.vo.web.MerchantOrderReportVO; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +26,8 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import java.util.List; + /** * 运营端财务信息相关Controller * @@ -178,4 +182,60 @@ public class BusinessFinancialController extends BaseController { } 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 查询参数 + * @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); + } + List result = businessFinancialService.getStationOrderRank(dto); + response = new RestApiResponse<>(result); + logger.info("查询场站订单排行成功 startTime:{}, endTime:{}, 站点数量:{}", + dto.getStartTime(), dto.getEndTime(), result.size()); + } 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/service/BusinessFinancialService.java b/jsowell-pile/src/main/java/com/jsowell/pile/service/BusinessFinancialService.java index ce5dfb16d..487c4655d 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 @@ -6,13 +6,17 @@ 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; +import com.jsowell.pile.vo.uniapp.business.BusinessStationRankVO; import com.jsowell.pile.vo.web.MerchantOrderReportVO; import com.jsowell.pile.vo.web.ParkingCouponRecordVO; +import java.util.List; + public interface BusinessFinancialService { MerchantOrderReportVO getMyWallet(MerchantOrderReportDTO dto); @@ -55,4 +59,20 @@ public interface BusinessFinancialService { * @return 经营效率数据(含指标卡片和曲线图) */ BusinessEfficiencyVO getBusinessEfficiency(BusinessEfficiencyQueryDTO dto); + + /** + * 查询场站订单排行 + * + * @param dto 查询条件 + * @return 场站订单排行列表(按订单数量降序) + */ + List 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 cd1b2c2d8..7dd06f6d7 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 @@ -28,6 +28,7 @@ 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; @@ -36,6 +37,7 @@ 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; @@ -795,6 +797,182 @@ public class BusinessFinancialServiceImpl implements BusinessFinancialService { } } + /** + * 查询效率分析(单均效率 + 枪均效率) + * 单均效率:按订单维度计算平均指标 + * 枪均效率:按枪口×天数维度计算日均指标 + * + * @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 查询条件 + * @return 场站订单排行列表 + */ + @Override + public List getStationOrderRank(BusinessOperationAnalysisQueryDTO dto) { + if (dto == null) { + return new ArrayList<>(); + } + 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()); + + // 查询结算报表原始数据 + List reportList = queryRawReports( + stationIdList, dto.getStartTime(), dto.getEndTime()); + + if (CollectionUtils.isEmpty(reportList)) { + return new ArrayList<>(); + } + + // 按站点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); + } + + // 批量查询站点名称 + Map stationNameMap = new LinkedHashMap<>(); + for (String sId : stationOrderCountMap.keySet()) { + try { + PileStationInfo stationInfo = pileStationInfoService.selectPileStationInfoById(Long.valueOf(sId)); + if (stationInfo != null) { + stationNameMap.put(sId, stationInfo.getStationName()); + } else { + stationNameMap.put(sId, "未知站点"); + } + } catch (Exception e) { + log.warn("查询站点名称失败 stationId:{}", sId, e); + stationNameMap.put(sId, "未知站点"); + } + } + + // 按订单数量降序排列并构建排行列表 + 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); + } + + return rankList; + } + /** * 查询经营效率 * 优化:当前周期和上周期汇总并行查询,枪口数量与总额定功率并行查询 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/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; +}