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
*
*
提供首页 KPI 与趋势数据,全部基于现有台账实时计算,不做冗余统计表。
*
* @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 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 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 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 buildTrend(List times) {
Map counts = new LinkedHashMap<>();
YearMonth current = YearMonth.from(LocalDate.now());
List 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();
}
}