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(); } }