|
@@ -0,0 +1,230 @@
|
|
|
|
|
+package cn.start.tz.module.pressure2.service.riskalert;
|
|
|
|
|
+
|
|
|
|
|
+import cn.hutool.core.collection.CollUtil;
|
|
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
|
|
+import cn.start.tz.module.pressure2.controller.admin.riskalert.vo.*;
|
|
|
|
|
+import cn.start.tz.module.pressure2.dal.dataobject.useralertconfig.UserAlertConfigDO;
|
|
|
|
|
+import cn.start.tz.module.pressure2.dal.mysql.riskalert.RiskAlertMapper;
|
|
|
|
|
+import cn.start.tz.module.pressure2.dal.mysql.useralertconfig.UserAlertConfigMapper;
|
|
|
|
|
+import cn.start.tz.module.system.api.dept.DeptApi;
|
|
|
|
|
+import cn.start.tz.module.system.api.dept.dto.DeptRespDTO;
|
|
|
|
|
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
|
|
+import jakarta.annotation.Resource;
|
|
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
|
|
+
|
|
|
|
|
+import java.time.DayOfWeek;
|
|
|
|
|
+import java.time.LocalDate;
|
|
|
|
|
+import java.util.*;
|
|
|
|
|
+import java.util.stream.Collectors;
|
|
|
|
|
+
|
|
|
|
|
+import static cn.start.tz.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
|
|
|
|
+
|
|
|
|
|
+@Slf4j
|
|
|
|
|
+@Service
|
|
|
|
|
+public class RiskAlertServiceImpl implements RiskAlertService {
|
|
|
|
|
+
|
|
|
|
|
+ private static final int DEFAULT_ALERT_DAYS = 5;
|
|
|
|
|
+
|
|
|
|
|
+ // 锅炉检验性质:100内部检验 200外部检验 300耐压检验
|
|
|
|
|
+ private static final Map<Integer, String> BOILER_CHECK_TYPE_MAP = Map.of(
|
|
|
|
|
+ 100, "内部检验", 200, "外部检验", 300, "耐压检验"
|
|
|
|
|
+ );
|
|
|
|
|
+ // 管道检验性质:100法定检验 200年度检查
|
|
|
|
|
+ private static final Map<Integer, String> PIPE_CHECK_TYPE_MAP = Map.of(
|
|
|
|
|
+ 100, "定期检验", 200, "年度检查"
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private RiskAlertMapper riskAlertMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private UserAlertConfigMapper userAlertConfigMapper;
|
|
|
|
|
+
|
|
|
|
|
+ @Resource
|
|
|
|
|
+ private DeptApi deptApi;
|
|
|
|
|
+
|
|
|
|
|
+ // ============ 锅炉预警 ============
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<TaskOrderReportAlertCountRespVO> getMyBoilerAlert() {
|
|
|
|
|
+ String userId = getLoginUserId();
|
|
|
|
|
+ if (StrUtil.isBlank(userId)) return Collections.emptyList();
|
|
|
|
|
+ return getMyAlertStats(userId, "boiler");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<DeptAlertCountRespVO> getDeptBoilerAlert(List<String> deptIds) {
|
|
|
|
|
+ if (CollUtil.isEmpty(deptIds)) return Collections.emptyList();
|
|
|
|
|
+ return getDeptAlertStats(deptIds, "boiler");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============ 管道预警 ============
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<TaskOrderReportAlertCountRespVO> getMyPipeAlert() {
|
|
|
|
|
+ String userId = getLoginUserId();
|
|
|
|
|
+ if (StrUtil.isBlank(userId)) return Collections.emptyList();
|
|
|
|
|
+ return getMyAlertStats(userId, "pipe");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public List<DeptAlertCountRespVO> getDeptPipeAlert(List<String> deptIds) {
|
|
|
|
|
+ if (CollUtil.isEmpty(deptIds)) return Collections.emptyList();
|
|
|
|
|
+ return getDeptAlertStats(deptIds, "pipe");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ============ 核心逻辑 ============
|
|
|
|
|
+
|
|
|
|
|
+ private List<TaskOrderReportAlertCountRespVO> getMyAlertStats(String userId, String equipType) {
|
|
|
|
|
+ int alertDays = getUserAlertDays(userId);
|
|
|
|
|
+
|
|
|
|
|
+ List<Map<String, Object>> rows;
|
|
|
|
|
+ try {
|
|
|
|
|
+ rows = "boiler".equals(equipType)
|
|
|
|
|
+ ? riskAlertMapper.selectMyBoilerAlert(userId)
|
|
|
|
|
+ : riskAlertMapper.selectMyPipeAlert(userId);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("[getMyAlertStats] 查询失败, equipType={}", equipType, e);
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return aggregateByCheckType(rows, alertDays, equipType);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private List<DeptAlertCountRespVO> getDeptAlertStats(List<String> deptIds, String equipType) {
|
|
|
|
|
+ int alertDays = DEFAULT_ALERT_DAYS;
|
|
|
|
|
+
|
|
|
|
|
+ List<Map<String, Object>> rows;
|
|
|
|
|
+ try {
|
|
|
|
|
+ rows = "boiler".equals(equipType)
|
|
|
|
|
+ ? riskAlertMapper.selectDeptBoilerAlert(deptIds)
|
|
|
|
|
+ : riskAlertMapper.selectDeptPipeAlert(deptIds);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("[getDeptAlertStats] 查询失败, equipType={}", equipType, e);
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 批量获取部门名称
|
|
|
|
|
+ Map<String, String> deptNameMap = getDeptNameMap(deptIds);
|
|
|
|
|
+
|
|
|
|
|
+ // 按部门分组
|
|
|
|
|
+ Map<String, List<Map<String, Object>>> deptGrouped = rows.stream()
|
|
|
|
|
+ .collect(Collectors.groupingBy(row -> String.valueOf(row.get("dept_id"))));
|
|
|
|
|
+
|
|
|
|
|
+ List<DeptAlertCountRespVO> result = new ArrayList<>();
|
|
|
|
|
+ for (Map.Entry<String, List<Map<String, Object>>> entry : deptGrouped.entrySet()) {
|
|
|
|
|
+ String deptId = entry.getKey();
|
|
|
|
|
+ List<TaskOrderReportAlertCountRespVO> stats = aggregateByCheckType(entry.getValue(), alertDays, equipType);
|
|
|
|
|
+ if (!stats.isEmpty()) {
|
|
|
|
|
+ DeptAlertCountRespVO deptVO = new DeptAlertCountRespVO();
|
|
|
|
|
+ deptVO.setDeptId(deptId);
|
|
|
|
|
+ deptVO.setDeptName(deptNameMap.getOrDefault(deptId, deptId));
|
|
|
|
|
+ deptVO.setCheckTypeStats(stats);
|
|
|
|
|
+ deptVO.setTotalCount(stats.stream().mapToInt(TaskOrderReportAlertCountRespVO::getCount).sum());
|
|
|
|
|
+ result.add(deptVO);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // ---------- 工具方法 ----------
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 按检验性质分组统计,只保留剩余工作日 <= alertDays 的记录
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<TaskOrderReportAlertCountRespVO> aggregateByCheckType(List<Map<String, Object>> rows, int alertDays, String equipType) {
|
|
|
|
|
+ Map<Integer, String> checkTypeNameMap = "boiler".equals(equipType) ? BOILER_CHECK_TYPE_MAP : PIPE_CHECK_TYPE_MAP;
|
|
|
|
|
+ // 收集唯一 checkDate → 批量计算剩余工作日
|
|
|
|
|
+ Set<LocalDate> dateSet = new HashSet<>();
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ Object checkDateObj = row.get("check_date");
|
|
|
|
|
+ if (checkDateObj instanceof java.sql.Date) {
|
|
|
|
|
+ dateSet.add(((java.sql.Date) checkDateObj).toLocalDate());
|
|
|
|
|
+ } else if (checkDateObj instanceof LocalDate) {
|
|
|
|
|
+ dateSet.add((LocalDate) checkDateObj);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ Map<LocalDate, Integer> remainingMap = new HashMap<>();
|
|
|
|
|
+ for (LocalDate date : dateSet) {
|
|
|
|
|
+ remainingMap.put(date, calcRemainingWorkDays(date));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 分类统计
|
|
|
|
|
+ Map<Integer, Set<String>> checkTypeMap = new LinkedHashMap<>();
|
|
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
|
|
+ Integer checkType = toInt(row.get("check_type"));
|
|
|
|
|
+ Object checkDateObj = row.get("check_date");
|
|
|
|
|
+ String equipCode = (String) row.get("equip_code");
|
|
|
|
|
+ if (checkType == null || checkDateObj == null || StrUtil.isBlank(equipCode)) continue;
|
|
|
|
|
+
|
|
|
|
|
+ LocalDate checkDate = toLocalDate(checkDateObj);
|
|
|
|
|
+ if (checkDate == null) continue;
|
|
|
|
|
+
|
|
|
|
|
+ Integer remain = remainingMap.get(checkDate);
|
|
|
|
|
+ if (remain != null && remain <= alertDays) {
|
|
|
|
|
+ checkTypeMap.computeIfAbsent(checkType, k -> new LinkedHashSet<>()).add(equipCode);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ List<TaskOrderReportAlertCountRespVO> result = new ArrayList<>();
|
|
|
|
|
+ for (Map.Entry<Integer, Set<String>> entry : checkTypeMap.entrySet()) {
|
|
|
|
|
+ TaskOrderReportAlertCountRespVO vo = new TaskOrderReportAlertCountRespVO();
|
|
|
|
|
+ vo.setCheckType(entry.getKey());
|
|
|
|
|
+ vo.setCheckTypeName(checkTypeNameMap.getOrDefault(entry.getKey(), "未知"));
|
|
|
|
|
+ vo.setEquipCodes(new ArrayList<>(entry.getValue()));
|
|
|
|
|
+ vo.setCount(entry.getValue().size());
|
|
|
|
|
+ result.add(vo);
|
|
|
|
|
+ }
|
|
|
|
|
+ result.sort(Comparator.comparing(TaskOrderReportAlertCountRespVO::getCheckType));
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private int getUserAlertDays(String userId) {
|
|
|
|
|
+ UserAlertConfigDO config = userAlertConfigMapper.selectOne(
|
|
|
|
|
+ Wrappers.<UserAlertConfigDO>lambdaQuery().eq(UserAlertConfigDO::getUserId, userId));
|
|
|
|
|
+ return config != null && config.getAlertNum() != null ? config.getAlertNum() : DEFAULT_ALERT_DAYS;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private Map<String, String> getDeptNameMap(List<String> deptIds) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
|
|
|
|
|
+ return deptMap.entrySet().stream()
|
|
|
|
|
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getName()));
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("[getDeptNameMap] 查询部门名称失败", e);
|
|
|
|
|
+ return new HashMap<>();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 计算剩余工作日(不含周末)
|
|
|
|
|
+ */
|
|
|
|
|
+ private static int calcRemainingWorkDays(LocalDate checkDate) {
|
|
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
|
|
+ if (!checkDate.isAfter(today)) return 0;
|
|
|
|
|
+ int workDays = 0;
|
|
|
|
|
+ LocalDate d = today;
|
|
|
|
|
+ while (d.isBefore(checkDate)) {
|
|
|
|
|
+ d = d.plusDays(1);
|
|
|
|
|
+ if (d.getDayOfWeek() != DayOfWeek.SATURDAY && d.getDayOfWeek() != DayOfWeek.SUNDAY) {
|
|
|
|
|
+ workDays++;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return workDays;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Integer toInt(Object obj) {
|
|
|
|
|
+ if (obj == null) return null;
|
|
|
|
|
+ if (obj instanceof Number) return ((Number) obj).intValue();
|
|
|
|
|
+ try { return Integer.parseInt(obj.toString()); } catch (Exception e) { return null; }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static LocalDate toLocalDate(Object obj) {
|
|
|
|
|
+ if (obj == null) return null;
|
|
|
|
|
+ if (obj instanceof java.sql.Date) return ((java.sql.Date) obj).toLocalDate();
|
|
|
|
|
+ if (obj instanceof LocalDate) return (LocalDate) obj;
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+}
|