package cn.iocoder.yudao.module.mes.service.em.statistics; import cn.hutool.core.convert.Convert; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.mes.controller.admin.em.statistics.vo.MesEmOverviewRespVO; import cn.iocoder.yudao.module.mes.controller.admin.em.statistics.vo.MesEmReportRespVO; import cn.iocoder.yudao.module.mes.dal.dataobject.em.alert.MesEmAlertDO; import cn.iocoder.yudao.module.mes.dal.dataobject.em.meter.MesEmMeterDO; import cn.iocoder.yudao.module.mes.dal.mysql.em.alert.MesEmAlertMapper; import cn.iocoder.yudao.module.mes.dal.mysql.em.meter.MesEmMeterMapper; import cn.iocoder.yudao.module.mes.dal.mysql.em.record.MesEmRecordMapper; import cn.iocoder.yudao.module.mes.enums.em.*; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.YearMonth; import java.util.*; /** * MES 能耗统计 Service 实现类 * * @author 超级管理员 */ @Service @Validated public class MesEmStatisticsServiceImpl implements MesEmStatisticsService { private static final BigDecimal ZERO = BigDecimal.ZERO; private static final BigDecimal HUNDRED = new BigDecimal("100"); @Resource private MesEmRecordMapper recordMapper; @Resource private MesEmMeterMapper meterMapper; @Resource private MesEmAlertMapper alertMapper; @Override public MesEmOverviewRespVO getOverview(LocalDate beginDate, LocalDate endDate) { LocalDate begin = beginDate == null ? LocalDate.now().withDayOfMonth(1) : beginDate; LocalDate end = endDate == null ? LocalDate.now() : endDate; if (begin.isAfter(end)) { LocalDate temp = begin; begin = end; end = temp; } MesEmOverviewRespVO respVO = new MesEmOverviewRespVO(); // 1. 能源类型统计 Map> typeMap = toMapBy(recordMapper.selectTypeStats(begin, end), "energyType"); List typeStats = new ArrayList<>(); BigDecimal totalTce = ZERO; BigDecimal totalCost = ZERO; for (MesEmEnergyTypeEnum energyType : MesEmEnergyTypeEnum.values()) { Map row = typeMap.getOrDefault(energyType.getType(), Collections.emptyMap()); MesEmOverviewRespVO.TypeStat stat = new MesEmOverviewRespVO.TypeStat(); stat.setEnergyType(energyType.getType()); stat.setEnergyTypeName(energyType.getName()); stat.setUnit(energyType.getUnit()); stat.setUsage(toDecimal(row, "usageAmount")); stat.setCost(toDecimal(row, "costAmount")); stat.setTce(toDecimal(row, "tce")); typeStats.add(stat); totalTce = totalTce.add(stat.getTce()); totalCost = totalCost.add(stat.getCost()); } respVO.setTypeStats(typeStats); respVO.setTotalTce(scale(totalTce)); respVO.setTotalCost(scale(totalCost)); // 2. 车间统计 respVO.setWorkshopStats(parseWorkshopStats(recordMapper.selectWorkshopStats(begin, end))); // 3. 能耗趋势 respVO.setTrend(parseTrend(recordMapper.selectTrendRows(begin, end))); // 4. 重点用能设备排行 respVO.setTopMeters(parseTopMeters(recordMapper.selectTopMeterStats(begin, end, 10))); // 5. 预警概览 respVO.setMeterCount(Math.toIntExact(meterMapper.selectCount( new LambdaQueryWrapperX() .eq(MesEmMeterDO::getStatus, MesEmMeterStatusEnum.ENABLE.getStatus())))); respVO.setPendingAlertCount(Math.toIntExact(alertMapper.selectPendingCount())); respVO.setRecentAlerts(parseRecentAlerts(alertMapper.selectRecent(begin, end, 5))); return respVO; } @Override public MesEmReportRespVO getReport(String period) { YearMonth yearMonth = StrUtil.isBlank(period) ? YearMonth.now() : YearMonth.parse(period); LocalDate begin = yearMonth.atDay(1); LocalDate end = yearMonth.atEndOfMonth(); YearMonth lastMonth = yearMonth.minusMonths(1); YearMonth lastYear = yearMonth.minusYears(1); Map> currentTypeMap = toMapBy(recordMapper.selectTypeStats(begin, end), "energyType"); Map> lastMonthTypeMap = toMapBy( recordMapper.selectTypeStats(lastMonth.atDay(1), lastMonth.atEndOfMonth()), "energyType"); Map> lastYearTypeMap = toMapBy( recordMapper.selectTypeStats(lastYear.atDay(1), lastYear.atEndOfMonth()), "energyType"); MesEmReportRespVO respVO = new MesEmReportRespVO(); respVO.setPeriod(yearMonth.toString()); respVO.setBeginDate(begin); respVO.setEndDate(end); // 1. 按能源类型 List typeRows = new ArrayList<>(); BigDecimal totalTce = ZERO; BigDecimal totalCost = ZERO; BigDecimal lastMonthTce = ZERO; BigDecimal lastYearTce = ZERO; for (MesEmEnergyTypeEnum energyType : MesEmEnergyTypeEnum.values()) { Map current = currentTypeMap.getOrDefault(energyType.getType(), Collections.emptyMap()); Map lastMonthRow = lastMonthTypeMap.getOrDefault(energyType.getType(), Collections.emptyMap()); Map lastYearRow = lastYearTypeMap.getOrDefault(energyType.getType(), Collections.emptyMap()); MesEmReportRespVO.TypeRow row = new MesEmReportRespVO.TypeRow(); row.setEnergyType(energyType.getType()); row.setEnergyTypeName(energyType.getName()); row.setUnit(energyType.getUnit()); row.setUsage(toDecimal(current, "usageAmount")); row.setCost(toDecimal(current, "costAmount")); row.setTce(toDecimal(current, "tce")); row.setLastMonthUsage(toDecimal(lastMonthRow, "usageAmount")); row.setMomRate(rate(row.getUsage(), row.getLastMonthUsage())); row.setLastYearUsage(toDecimal(lastYearRow, "usageAmount")); row.setYoyRate(rate(row.getUsage(), row.getLastYearUsage())); typeRows.add(row); totalTce = totalTce.add(row.getTce()); totalCost = totalCost.add(row.getCost()); lastMonthTce = lastMonthTce.add(toDecimal(lastMonthRow, "tce")); lastYearTce = lastYearTce.add(toDecimal(lastYearRow, "tce")); } respVO.setTypeRows(typeRows); // 2. 按车间 respVO.setWorkshopRows(parseReportWorkshopRows( recordMapper.selectWorkshopStats(begin, end), recordMapper.selectWorkshopStats(lastMonth.atDay(1), lastMonth.atEndOfMonth()), recordMapper.selectWorkshopStats(lastYear.atDay(1), lastYear.atEndOfMonth()))); // 3. 合计 respVO.setTotalTce(scale(totalTce)); respVO.setTotalCost(scale(totalCost)); respVO.setLastMonthTce(scale(lastMonthTce)); respVO.setMomRate(rate(totalTce, lastMonthTce)); respVO.setLastYearTce(scale(lastYearTce)); respVO.setYoyRate(rate(totalTce, lastYearTce)); return respVO; } // ==================== 数据转换 ==================== private Map> toMapBy(List> rows, String key) { Map> result = new HashMap<>(); for (Map row : rows) { Integer type = Convert.toInt(row.get(key)); if (type != null) { result.put(type, row); } } return result; } private List parseWorkshopStats(List> rows) { List result = new ArrayList<>(); for (Map row : rows) { MesEmOverviewRespVO.WorkshopStat stat = new MesEmOverviewRespVO.WorkshopStat(); stat.setWorkshopId(Convert.toLong(row.get("workshopId"))); stat.setWorkshopName(Convert.toStr(row.get("workshopName"))); stat.setTce(scale(Convert.toBigDecimal(row.get("tce"), ZERO))); stat.setCost(scale(Convert.toBigDecimal(row.get("costAmount"), ZERO))); result.add(stat); } return result; } private List parseTrend(List> rows) { Map pointMap = new TreeMap<>(); for (Map row : rows) { String date = toDateStr(row.get("recordDate")); if (StrUtil.isBlank(date)) { continue; } MesEmOverviewRespVO.TrendPoint point = pointMap.computeIfAbsent(date, key -> { MesEmOverviewRespVO.TrendPoint item = new MesEmOverviewRespVO.TrendPoint(); item.setDate(key); item.setTce(ZERO); item.setCost(ZERO); item.setElectricity(ZERO); item.setWater(ZERO); item.setFuel(ZERO); item.setGas(ZERO); return item; }); Integer energyType = Convert.toInt(row.get("energyType")); BigDecimal usage = Convert.toBigDecimal(row.get("usageAmount"), ZERO); point.setTce(point.getTce().add(Convert.toBigDecimal(row.get("tce"), ZERO))); point.setCost(point.getCost().add(Convert.toBigDecimal(row.get("costAmount"), ZERO))); if (MesEmEnergyTypeEnum.ELECTRICITY.getType().equals(energyType)) { point.setElectricity(usage); } else if (MesEmEnergyTypeEnum.WATER.getType().equals(energyType)) { point.setWater(usage); } else if (MesEmEnergyTypeEnum.FUEL.getType().equals(energyType)) { point.setFuel(usage); } else if (MesEmEnergyTypeEnum.GAS.getType().equals(energyType)) { point.setGas(usage); } } pointMap.values().forEach(point -> { point.setTce(scale(point.getTce())); point.setCost(scale(point.getCost())); }); return new ArrayList<>(pointMap.values()); } private List parseTopMeters(List> rows) { List result = new ArrayList<>(); for (Map row : rows) { MesEmOverviewRespVO.MeterStat stat = new MesEmOverviewRespVO.MeterStat(); stat.setMeterId(Convert.toLong(row.get("meterId"))); stat.setMeterName(Convert.toStr(row.get("meterName"))); stat.setEnergyType(Convert.toInt(row.get("energyType"))); stat.setEnergyTypeName(MesEmEnergyTypeEnum.getName(stat.getEnergyType())); stat.setUnit(Convert.toStr(row.get("unit"))); stat.setUsage(Convert.toBigDecimal(row.get("usageAmount"), ZERO)); stat.setCost(scale(Convert.toBigDecimal(row.get("costAmount"), ZERO))); stat.setTce(scale(Convert.toBigDecimal(row.get("tce"), ZERO))); result.add(stat); } return result; } private List parseRecentAlerts(List alerts) { List result = new ArrayList<>(); for (MesEmAlertDO alert : alerts) { MesEmOverviewRespVO.RecentAlert item = new MesEmOverviewRespVO.RecentAlert(); item.setId(alert.getId()); item.setContent(alert.getContent()); item.setAlertLevel(alert.getAlertLevel()); item.setAlertLevelName(MesEmAlertLevelEnum.getName(alert.getAlertLevel())); item.setAlertDate(alert.getAlertDate() == null ? null : alert.getAlertDate().toString()); item.setStatus(alert.getStatus()); item.setStatusName(MesEmAlertStatusEnum.getName(alert.getStatus())); result.add(item); } return result; } private List parseReportWorkshopRows( List> currentRows, List> lastMonthRows, List> lastYearRows) { Map> currentMap = toMapByWorkshop(currentRows); Map> lastMonthMap = toMapByWorkshop(lastMonthRows); Map> lastYearMap = toMapByWorkshop(lastYearRows); Set workshopIds = new LinkedHashSet<>(currentMap.keySet()); workshopIds.addAll(lastMonthMap.keySet()); workshopIds.addAll(lastYearMap.keySet()); List result = new ArrayList<>(); for (Long workshopId : workshopIds) { Map current = currentMap.getOrDefault(workshopId, Collections.emptyMap()); Map lastMonth = lastMonthMap.getOrDefault(workshopId, Collections.emptyMap()); Map lastYear = lastYearMap.getOrDefault(workshopId, Collections.emptyMap()); MesEmReportRespVO.WorkshopRow row = new MesEmReportRespVO.WorkshopRow(); row.setWorkshopId(workshopId); row.setWorkshopName(Convert.toStr(current.getOrDefault("workshopName", lastMonth.getOrDefault("workshopName", lastYear.get("workshopName"))))); row.setCost(scale(Convert.toBigDecimal(current.get("costAmount"), ZERO))); row.setTce(scale(Convert.toBigDecimal(current.get("tce"), ZERO))); row.setLastMonthTce(scale(Convert.toBigDecimal(lastMonth.get("tce"), ZERO))); row.setMomRate(rate(row.getTce(), row.getLastMonthTce())); row.setLastYearTce(scale(Convert.toBigDecimal(lastYear.get("tce"), ZERO))); row.setYoyRate(rate(row.getTce(), row.getLastYearTce())); result.add(row); } return result; } private Map> toMapByWorkshop(List> rows) { Map> result = new HashMap<>(); for (Map row : rows) { Long workshopId = Convert.toLong(row.get("workshopId")); if (workshopId != null) { result.put(workshopId, row); } } return result; } private BigDecimal toDecimal(Map row, String key) { return scale(Convert.toBigDecimal(row.get(key), ZERO)); } private String toDateStr(Object value) { if (value == null) { return null; } if (value instanceof LocalDate localDate) { return localDate.toString(); } if (value instanceof java.sql.Date sqlDate) { return sqlDate.toLocalDate().toString(); } String text = Convert.toStr(value); return text != null && text.length() > 10 ? text.substring(0, 10) : text; } /** * 计算环比/同比(%),基数为 0 时返回 null,前端显示为 - */ private BigDecimal rate(BigDecimal current, BigDecimal base) { if (base == null || base.compareTo(ZERO) == 0) { return null; } return current.subtract(base).multiply(HUNDRED).divide(base, 2, RoundingMode.HALF_UP); } private BigDecimal scale(BigDecimal value) { return value == null ? ZERO : value.setScale(2, RoundingMode.HALF_UP); } }