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<Integer, Map<String, Object>> typeMap = toMapBy(recordMapper.selectTypeStats(begin, end), "energyType");
|
List<MesEmOverviewRespVO.TypeStat> typeStats = new ArrayList<>();
|
BigDecimal totalTce = ZERO;
|
BigDecimal totalCost = ZERO;
|
for (MesEmEnergyTypeEnum energyType : MesEmEnergyTypeEnum.values()) {
|
Map<String, Object> 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<MesEmMeterDO>()
|
.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<Integer, Map<String, Object>> currentTypeMap = toMapBy(recordMapper.selectTypeStats(begin, end), "energyType");
|
Map<Integer, Map<String, Object>> lastMonthTypeMap = toMapBy(
|
recordMapper.selectTypeStats(lastMonth.atDay(1), lastMonth.atEndOfMonth()), "energyType");
|
Map<Integer, Map<String, Object>> 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<MesEmReportRespVO.TypeRow> typeRows = new ArrayList<>();
|
BigDecimal totalTce = ZERO;
|
BigDecimal totalCost = ZERO;
|
BigDecimal lastMonthTce = ZERO;
|
BigDecimal lastYearTce = ZERO;
|
for (MesEmEnergyTypeEnum energyType : MesEmEnergyTypeEnum.values()) {
|
Map<String, Object> current = currentTypeMap.getOrDefault(energyType.getType(), Collections.emptyMap());
|
Map<String, Object> lastMonthRow = lastMonthTypeMap.getOrDefault(energyType.getType(), Collections.emptyMap());
|
Map<String, Object> 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<Integer, Map<String, Object>> toMapBy(List<Map<String, Object>> rows, String key) {
|
Map<Integer, Map<String, Object>> result = new HashMap<>();
|
for (Map<String, Object> row : rows) {
|
Integer type = Convert.toInt(row.get(key));
|
if (type != null) {
|
result.put(type, row);
|
}
|
}
|
return result;
|
}
|
|
private List<MesEmOverviewRespVO.WorkshopStat> parseWorkshopStats(List<Map<String, Object>> rows) {
|
List<MesEmOverviewRespVO.WorkshopStat> result = new ArrayList<>();
|
for (Map<String, Object> 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<MesEmOverviewRespVO.TrendPoint> parseTrend(List<Map<String, Object>> rows) {
|
Map<String, MesEmOverviewRespVO.TrendPoint> pointMap = new TreeMap<>();
|
for (Map<String, Object> 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<MesEmOverviewRespVO.MeterStat> parseTopMeters(List<Map<String, Object>> rows) {
|
List<MesEmOverviewRespVO.MeterStat> result = new ArrayList<>();
|
for (Map<String, Object> 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<MesEmOverviewRespVO.RecentAlert> parseRecentAlerts(List<MesEmAlertDO> alerts) {
|
List<MesEmOverviewRespVO.RecentAlert> 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<MesEmReportRespVO.WorkshopRow> parseReportWorkshopRows(
|
List<Map<String, Object>> currentRows, List<Map<String, Object>> lastMonthRows,
|
List<Map<String, Object>> lastYearRows) {
|
Map<Long, Map<String, Object>> currentMap = toMapByWorkshop(currentRows);
|
Map<Long, Map<String, Object>> lastMonthMap = toMapByWorkshop(lastMonthRows);
|
Map<Long, Map<String, Object>> lastYearMap = toMapByWorkshop(lastYearRows);
|
Set<Long> workshopIds = new LinkedHashSet<>(currentMap.keySet());
|
workshopIds.addAll(lastMonthMap.keySet());
|
workshopIds.addAll(lastYearMap.keySet());
|
|
List<MesEmReportRespVO.WorkshopRow> result = new ArrayList<>();
|
for (Long workshopId : workshopIds) {
|
Map<String, Object> current = currentMap.getOrDefault(workshopId, Collections.emptyMap());
|
Map<String, Object> lastMonth = lastMonthMap.getOrDefault(workshopId, Collections.emptyMap());
|
Map<String, Object> 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<Long, Map<String, Object>> toMapByWorkshop(List<Map<String, Object>> rows) {
|
Map<Long, Map<String, Object>> result = new HashMap<>();
|
for (Map<String, Object> row : rows) {
|
Long workshopId = Convert.toLong(row.get("workshopId"));
|
if (workshopId != null) {
|
result.put(workshopId, row);
|
}
|
}
|
return result;
|
}
|
|
private BigDecimal toDecimal(Map<String, Object> 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);
|
}
|
|
}
|