package cn.iocoder.yudao.module.mes.service.safety;
|
|
import cn.hutool.core.util.StrUtil;
|
import cn.iocoder.yudao.module.mes.dal.dataobject.safety.*;
|
import cn.iocoder.yudao.module.mes.dal.mysql.safety.*;
|
import cn.iocoder.yudao.module.mes.controller.admin.safety.vo.ledger.SafetyOverviewRespVO;
|
import jakarta.annotation.Resource;
|
import org.springframework.stereotype.Service;
|
|
import java.time.LocalDate;
|
import java.time.LocalDateTime;
|
import java.time.YearMonth;
|
import java.time.format.DateTimeFormatter;
|
import java.util.ArrayList;
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Map;
|
|
/**
|
* 安全生产 - 总览统计 Service
|
*
|
* <p>提供首页 KPI 与趋势数据,全部基于现有台账实时计算,不做冗余统计表。</p>
|
*
|
* @author 超级管理员
|
*/
|
@Service
|
public class SafetyOverviewService {
|
|
private static final DateTimeFormatter MONTH_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM");
|
/** 趋势展示月数 */
|
private static final int TREND_MONTHS = 6;
|
|
@Resource
|
private SafetyCertificateMapper certificateMapper;
|
@Resource
|
private SafetyHazardMapper hazardMapper;
|
@Resource
|
private SafetyWorkPermitMapper permitMapper;
|
@Resource
|
private SafetyMaterialMapper materialMapper;
|
@Resource
|
private SafetyTrainingMapper trainingMapper;
|
@Resource
|
private SafetyDrillMapper drillMapper;
|
@Resource
|
private SafetyAccidentMapper accidentMapper;
|
|
public SafetyOverviewRespVO getOverview() {
|
SafetyOverviewRespVO overview = new SafetyOverviewRespVO();
|
LocalDate today = LocalDate.now();
|
|
// ---------- 特种作业证 ----------
|
List<SafetyCertificateDO> certs = certificateMapper.selectList();
|
long expiring = 0;
|
long expired = 0;
|
for (SafetyCertificateDO cert : certs) {
|
String status = SafetyCertificateService.resolveStatus(
|
cert.getExpireDate(), cert.getWarnDays(), cert.getStatus());
|
if ("EXPIRED".equals(status)) {
|
expired++;
|
} else if ("EXPIRING".equals(status)) {
|
expiring++;
|
}
|
}
|
overview.setCertTotal((long) certs.size());
|
overview.setCertExpiring(expiring);
|
overview.setCertExpired(expired);
|
|
// ---------- 隐患 ----------
|
List<SafetyHazardDO> hazards = hazardMapper.selectList();
|
long hazardOpen = hazards.stream()
|
.filter(h -> !"CLOSED".equals(h.getStatus())).count();
|
long hazardOverdue = hazardMapper.selectOverdue(today).size();
|
long hazardClosed = hazards.size() - hazardOpen;
|
overview.setHazardTotal((long) hazards.size());
|
overview.setHazardOpen(hazardOpen);
|
overview.setHazardOverdue(hazardOverdue);
|
overview.setHazardCloseRate(hazards.isEmpty() ? 0
|
: (int) Math.round(hazardClosed * 100.0 / hazards.size()));
|
|
// ---------- 作业票 ----------
|
List<SafetyWorkPermitDO> permits = permitMapper.selectList();
|
overview.setPermitPending(permits.stream()
|
.filter(p -> "PENDING".equals(p.getStatus())).count());
|
overview.setPermitInProgress(permits.stream()
|
.filter(p -> "IN_PROGRESS".equals(p.getStatus())).count());
|
|
// ---------- 物资 ----------
|
overview.setMaterialBelowSafeStock((long) materialMapper.selectBelowSafeStock().size());
|
overview.setMaterialExpiring((long) materialMapper.selectExpiringBefore(today.plusDays(30)).size());
|
|
// ---------- 本月活动 ----------
|
YearMonth currentMonth = YearMonth.from(today);
|
LocalDate monthStart = currentMonth.atDay(1);
|
LocalDate monthEnd = currentMonth.atEndOfMonth();
|
overview.setTrainingThisMonth((long) trainingMapper.selectListBetween(monthStart, monthEnd).size());
|
overview.setDrillThisMonth((long) drillMapper.selectListBetween(monthStart, monthEnd).size());
|
overview.setAccidentThisMonth((long) accidentMapper.selectListBetween(
|
monthStart.atStartOfDay(), monthEnd.atTime(23, 59, 59)).size());
|
|
// ---------- 趋势(近 6 个月) ----------
|
overview.setHazardTrend(buildTrend(hazards.stream().map(SafetyHazardDO::getReportTime).toList()));
|
overview.setAccidentTrend(buildTrend(hazards.isEmpty() ? List.of()
|
: accidentMapper.selectList().stream().map(SafetyAccidentDO::getOccurTime).toList()));
|
|
return overview;
|
}
|
|
/**
|
* 按月聚合时间点,输出近 N 个月的连续序列(无数据的月份补 0)
|
*/
|
private List<SafetyOverviewRespVO.MonthlyCount> buildTrend(List<LocalDateTime> times) {
|
Map<String, Long> counts = new LinkedHashMap<>();
|
YearMonth current = YearMonth.from(LocalDate.now());
|
List<YearMonth> months = new ArrayList<>();
|
for (int i = TREND_MONTHS - 1; i >= 0; i--) {
|
YearMonth month = current.minusMonths(i);
|
months.add(month);
|
counts.put(month.format(MONTH_FORMAT), 0L);
|
}
|
for (LocalDateTime time : times) {
|
if (time == null) {
|
continue;
|
}
|
String key = YearMonth.from(time).format(MONTH_FORMAT);
|
if (counts.containsKey(key)) {
|
counts.put(key, counts.get(key) + 1);
|
}
|
}
|
return months.stream().map(month -> {
|
SafetyOverviewRespVO.MonthlyCount item = new SafetyOverviewRespVO.MonthlyCount();
|
item.setMonth(month.format(MONTH_FORMAT));
|
item.setCount(counts.get(month.format(MONTH_FORMAT)));
|
return item;
|
}).toList();
|
}
|
|
}
|