package cn.iocoder.yudao.module.mes.service.home; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.module.mes.controller.admin.home.vo.MesHomeProductionTrendRespVO; import cn.iocoder.yudao.module.mes.controller.admin.home.vo.MesHomeSummaryRespVO; import cn.iocoder.yudao.module.mes.controller.admin.home.vo.MesHomeWorkOrderStatusRespVO; import cn.iocoder.yudao.module.mes.dal.mysql.home.MesHomeStatisticsMapper; import cn.iocoder.yudao.module.mes.enums.dv.MesDvMachineryStatusEnum; import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; /** * MES 首页统计 Service 实现类 * * @author 超级管理员 */ @Service public class MesHomeStatisticsServiceImpl implements MesHomeStatisticsService { @Resource private MesHomeStatisticsMapper homeStatisticsMapper; @Override public MesHomeSummaryRespVO getHomeSummary() { MesHomeSummaryRespVO summary = new MesHomeSummaryRespVO(); // 1. 工单统计 List> workOrderStats = homeStatisticsMapper.selectWorkOrderCountGroupByStatus(); long activeCount = 0L; long prepareCount = 0L; long finishedCount = 0L; for (Map stat : workOrderStats) { int status = ((Number) stat.get("status")).intValue(); long count = ((Number) stat.get("count")).longValue(); if (status == MesProWorkOrderStatusEnum.CONFIRMED.getStatus()) { activeCount = count; } else if (status == MesProWorkOrderStatusEnum.PREPARE.getStatus()) { prepareCount = count; } else if (status == MesProWorkOrderStatusEnum.FINISHED.getStatus()) { finishedCount = count; } } summary.setWorkOrderActiveCount(activeCount); summary.setWorkOrderPrepareCount(prepareCount); summary.setWorkOrderFinishedCount(finishedCount); // 2. 今日产量 LocalDateTime today = LocalDateTimeUtils.getToday(); LocalDateTime yesterday = LocalDateTimeUtils.getYesterday(); Map todayFeedback = homeStatisticsMapper.selectFeedbackSummary(today, null); summary.setTodayOutput(getDecimal(todayFeedback, "feedbackQuantity")); summary.setTodayQualifiedQuantity(getDecimal(todayFeedback, "qualifiedQuantity")); summary.setTodayUnqualifiedQuantity(getDecimal(todayFeedback, "unqualifiedQuantity")); // 3. 昨日产量 Map yesterdayFeedback = homeStatisticsMapper.selectFeedbackSummary(yesterday, today); summary.setYesterdayOutput(getDecimal(yesterdayFeedback, "feedbackQuantity")); // 4. 设备统计 List> machineryStats = homeStatisticsMapper.selectMachineryCountGroupByStatus(); long total = 0L; long producing = 0L; long stop = 0L; long maintenance = 0L; for (Map stat : machineryStats) { int status = ((Number) stat.get("status")).intValue(); long count = ((Number) stat.get("count")).longValue(); total += count; if (status == MesDvMachineryStatusEnum.PRODUCING.getStatus()) { producing = count; } else if (status == MesDvMachineryStatusEnum.STOP.getStatus()) { stop = count; } else if (status == MesDvMachineryStatusEnum.MAINTENANCE.getStatus()) { maintenance = count; } } summary.setMachineryTotal(total); summary.setMachineryProducing(producing); summary.setMachineryStop(stop); summary.setMachineryMaintenance(maintenance); // 5. 异常/待办 summary.setAndonActiveCount(homeStatisticsMapper.selectAndonActiveCount()); summary.setRepairActiveCount(homeStatisticsMapper.selectRepairActiveCount()); // 6. 质检合格率(来料 / 过程 / 出货) // 数量口径 = 合格数量 / 检验数量;不过滤单据状态,也不做时间范围限制(全量累计) Map> qcSummary = new HashMap<>(); for (Map row : homeStatisticsMapper.selectQcQualifiedSummary()) { Map metrics = new HashMap<>(); metrics.put("checkQuantity", getDecimalFromObj(row.get("checkQuantity"))); metrics.put("qualifiedQuantity", getDecimalFromObj(row.get("qualifiedQuantity"))); qcSummary.put(String.valueOf(row.get("type")), metrics); } summary.setIqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("IQC"))); summary.setIpqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("IPQC"))); summary.setOqcQualifiedRate(calcQcQualifiedRate(qcSummary.get("OQC"))); // 三个环节各自的检验数量合计(合格率的分母),供卡片副行展示样本量 summary.setIqcCheckQuantity(calcCheckQuantity(qcSummary.get("IQC"))); summary.setIpqcCheckQuantity(calcCheckQuantity(qcSummary.get("IPQC"))); summary.setOqcCheckQuantity(calcCheckQuantity(qcSummary.get("OQC"))); return summary; } @Override public List getWorkOrderStatusDistribution() { List> stats = homeStatisticsMapper.selectWorkOrderCountGroupByStatus(); List result = new ArrayList<>(); for (MesProWorkOrderStatusEnum statusEnum : MesProWorkOrderStatusEnum.values()) { long count = 0L; for (Map stat : stats) { if (((Number) stat.get("status")).intValue() == statusEnum.getStatus()) { count = ((Number) stat.get("count")).longValue(); break; } } result.add(new MesHomeWorkOrderStatusRespVO(statusEnum.getStatus(), statusEnum.getName(), count)); } return result; } @Override public List getProductionTrend(Integer days) { LocalDate endDate = LocalDate.now().plusDays(1); LocalDate startDate = endDate.minusDays(days); LocalDateTime beginTime = startDate.atStartOfDay(); LocalDateTime endTime = endDate.atStartOfDay(); // 查询数据库 List> dbData = homeStatisticsMapper.selectDailyFeedbackTrend(beginTime, endTime); Map> dateMap = new LinkedHashMap<>(); for (Map row : dbData) { dateMap.put((String) row.get("date"), row); } // 补全所有日期(无数据的日期填 0) DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd"); List result = new ArrayList<>(); for (LocalDate d = startDate; d.isBefore(endDate); d = d.plusDays(1)) { String dateStr = d.format(fmt); Map row = dateMap.get(dateStr); MesHomeProductionTrendRespVO vo = new MesHomeProductionTrendRespVO(); vo.setDate(dateStr); if (row != null) { vo.setQuantity(getDecimalFromObj(row.get("quantity"))); vo.setQualifiedQuantity(getDecimalFromObj(row.get("qualifiedQuantity"))); vo.setUnqualifiedQuantity(getDecimalFromObj(row.get("unqualifiedQuantity"))); } else { vo.setQuantity(BigDecimal.ZERO); vo.setQualifiedQuantity(BigDecimal.ZERO); vo.setUnqualifiedQuantity(BigDecimal.ZERO); } result.add(vo); } return result; } // ========== 工具方法 ========== private BigDecimal getDecimal(Map map, String key) { if (map == null || map.get(key) == null) { return BigDecimal.ZERO; } return map.get(key); } private BigDecimal getDecimalFromObj(Object obj) { if (obj == null) { return BigDecimal.ZERO; } if (obj instanceof BigDecimal) { return (BigDecimal) obj; } return new BigDecimal(obj.toString()); } /** * 计算质检合格率(百分比,保留两位小数) * * 检验数量为 0(即没有已完成的质检单)时返回 null, * 让页面显示「—」,而不是误导性的 0%。 */ private BigDecimal calcQcQualifiedRate(Map qcSummary) { if (qcSummary == null) { return null; } BigDecimal checkedQuantity = getDecimal(qcSummary, "checkQuantity"); if (checkedQuantity.compareTo(BigDecimal.ZERO) <= 0) { return null; } return getDecimal(qcSummary, "qualifiedQuantity") .multiply(BigDecimal.valueOf(100)) .divide(checkedQuantity, 2, RoundingMode.HALF_UP); } /** * 取该检验环节的检验数量合计(合格率的分母,即卡片副行展示的样本量) * * 该环节没有任何质检单时返回 0。 */ private Long calcCheckQuantity(Map qcSummary) { if (qcSummary == null) { return 0L; } return getDecimal(qcSummary, "checkQuantity").longValue(); } }