| | |
| | | } |
| | | |
| | | /** |
| | | * 设备停机明细报表:按采集间隔推算每台设备的停机区间与合计。 |
| | | * |
| | | * <p>与单设备离线查询保持同一口径(在线超时阈值、保留期、七天上限), |
| | | * 供 OEE 报表定位停机短板使用。 |
| | | */ |
| | | public cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO |
| | | getDowntimeDetail(LocalDateTime start, LocalDateTime end) { |
| | | LocalDateTime now = LocalDateTime.now(); |
| | | if (start == null || end == null || !start.isBefore(end) || end.isAfter(now) |
| | | || Duration.between(start, end).compareTo(Duration.ofDays(31)) > 0) { |
| | | throw invalidParamException("请选择有效的时间范围,单次最多查询三十一天,结束时间不能晚于当前时间"); |
| | | } |
| | | if (start.isBefore(now.minusDays(collectProperties.getRetention().getRealtimeDays()))) { |
| | | throw invalidParamException("查询时间超出数采历史保留期,无法可靠计算停机记录"); |
| | | } |
| | | var result = new cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO(); |
| | | result.setStartTime(start); |
| | | result.setEndTime(end); |
| | | result.setTimeoutMinutes((int) ONLINE_TIMEOUT_MINUTES); |
| | | long totalMinutes = Duration.between(start, end).toMinutes(); |
| | | result.setTotalMinutes(totalMinutes); |
| | | |
| | | var machineries = machineryMapper.selectListWithCollectDeviceCode(); |
| | | List<cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO.Item> items = new ArrayList<>(); |
| | | long sumDowntime = 0L; |
| | | for (var machinery : machineries) { |
| | | String deviceCode = machinery.getCollectDeviceCode(); |
| | | var prior = realtimeHistoryMapper.selectOne(new LambdaQueryWrapperX<MesCollectRealtimeHistoryDO>() |
| | | .eq(MesCollectRealtimeHistoryDO::getDeviceCode, deviceCode) |
| | | .ge(MesCollectRealtimeHistoryDO::getCollectTime, now.minusDays(collectProperties.getRetention().getRealtimeDays())) |
| | | .le(MesCollectRealtimeHistoryDO::getCollectTime, start) |
| | | .orderByDesc(MesCollectRealtimeHistoryDO::getCollectTime).last("LIMIT 1")); |
| | | var history = realtimeHistoryMapper.selectList(new LambdaQueryWrapperX<MesCollectRealtimeHistoryDO>() |
| | | .eq(MesCollectRealtimeHistoryDO::getDeviceCode, deviceCode) |
| | | .gt(MesCollectRealtimeHistoryDO::getCollectTime, start) |
| | | .le(MesCollectRealtimeHistoryDO::getCollectTime, end) |
| | | .orderByAsc(MesCollectRealtimeHistoryDO::getCollectTime).last("LIMIT 100001")); |
| | | if (history.size() > 100000) throw invalidParamException("采样数据过多,请缩短查询时间范围"); |
| | | List<LocalDateTime> samples = new ArrayList<>(); |
| | | int count = 0; |
| | | if (prior != null) { samples.add(prior.getCollectTime()); count++; } |
| | | history.forEach(item -> samples.add(item.getCollectTime())); |
| | | count += history.size(); |
| | | var intervals = CollectOfflinePolicy.calculate(samples, start, end, Duration.ofMinutes(ONLINE_TIMEOUT_MINUTES)).stream() |
| | | .map(i -> new cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO.Interval( |
| | | i.start(), i.end(), Duration.between(i.start(), i.end()).getSeconds())) |
| | | .toList(); |
| | | long minutes = intervals.stream().mapToLong(i -> i.getSeconds() / 60).sum(); |
| | | sumDowntime += minutes; |
| | | items.add(new cn.iocoder.yudao.module.mes.controller.admin.collect.vo.MesCollectDowntimeDetailRespVO.Item( |
| | | machinery.getId(), machinery.getCode(), machinery.getName(), deviceCode, |
| | | count, intervals.size(), minutes, intervals)); |
| | | } |
| | | items.sort((a, b) -> Long.compare(b.getDowntimeMinutes(), a.getDowntimeMinutes())); |
| | | result.setItems(items); |
| | | result.setDowntimeMinutes(sumDowntime); |
| | | result.setDowntimeRate(totalMinutes > 0 && !items.isEmpty() |
| | | ? Math.round(sumDowntime * 10000D / (totalMinutes * items.size())) / 100D : 0D); |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 获取已配置数采编码的设备实时状态列表 |
| | | */ |
| | | public List<MesCollectDeviceRespVO> getDeviceList() { |