|  | @@ -9,6 +9,7 @@ import cn.iocoder.yudao.module.pms.controller.admin.iotdevicecategorytemplateatt
 | 
	
		
			
				|  |  |  import cn.iocoder.yudao.module.pms.controller.admin.iotprojecttask.vo.IotProjectTaskPageReqVO;
 | 
	
		
			
				|  |  |  import cn.iocoder.yudao.module.pms.controller.admin.iotrhdailyreport.vo.IotRhDailyReportPageReqVO;
 | 
	
		
			
				|  |  |  import cn.iocoder.yudao.module.pms.controller.admin.iotrhdailyreport.vo.IotRhDailyReportSaveReqVO;
 | 
	
		
			
				|  |  | +import cn.iocoder.yudao.module.pms.controller.admin.iotrhdailyreport.vo.IotRhDailyReportStatisticsVO;
 | 
	
		
			
				|  |  |  import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDevicePageReqVO;
 | 
	
		
			
				|  |  |  import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
 | 
	
		
			
				|  |  |  import cn.iocoder.yudao.module.pms.dal.dataobject.iotopeationfill.IotOpeationFillDO;
 | 
	
	
		
			
				|  | @@ -37,9 +38,13 @@ import javax.annotation.Resource;
 | 
	
		
			
				|  |  |  import java.lang.reflect.Type;
 | 
	
		
			
				|  |  |  import java.math.BigDecimal;
 | 
	
		
			
				|  |  |  import java.math.RoundingMode;
 | 
	
		
			
				|  |  | +import java.time.LocalDate;
 | 
	
		
			
				|  |  |  import java.time.LocalDateTime;
 | 
	
		
			
				|  |  | +import java.time.format.DateTimeFormatter;
 | 
	
		
			
				|  |  | +import java.time.temporal.ChronoUnit;
 | 
	
		
			
				|  |  |  import java.util.*;
 | 
	
		
			
				|  |  |  import java.util.concurrent.atomic.AtomicReference;
 | 
	
		
			
				|  |  | +import java.util.stream.Collectors;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 | 
	
		
			
				|  |  |  import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstant.*;
 | 
	
	
		
			
				|  | @@ -361,6 +366,193 @@ public class IotRhDailyReportServiceImpl implements IotRhDailyReportService {
 | 
	
		
			
				|  |  |          return new PageResult<>(page.getRecords(), page.getTotal());
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    @Override
 | 
	
		
			
				|  |  | +    public List<IotRhDailyReportStatisticsVO> rhDailyReportStatistics(IotRhDailyReportPageReqVO reqVO) {
 | 
	
		
			
				|  |  | +        if (reqVO.getCreateTime().length == 0) {
 | 
	
		
			
				|  |  | +            // 必须传递时间区间参数
 | 
	
		
			
				|  |  | +            throw exception(IOT_DAILY_REPORT_TIME_NOT_EXISTS);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        List<IotRhDailyReportStatisticsVO> results = new ArrayList<>();
 | 
	
		
			
				|  |  | +        // 查询 瑞恒157l 所有存在设备的队伍
 | 
	
		
			
				|  |  | +        // 查询瑞恒下所有部门
 | 
	
		
			
				|  |  | +        List<IotDeviceDO> devices = devices();
 | 
	
		
			
				|  |  | +        // 所有需要填报日报的 瑞恒 队伍集合
 | 
	
		
			
				|  |  | +        Set<Long> deptIds = new HashSet<>();
 | 
	
		
			
				|  |  | +        if (CollUtil.isNotEmpty(devices)){
 | 
	
		
			
				|  |  | +            devices.forEach(device -> {
 | 
	
		
			
				|  |  | +                deptIds.add(device.getDeptId());
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  | +            // 比如有20个队伍 某个时间区间D内 必须有20*D份日报 时间区间最好是1天
 | 
	
		
			
				|  |  | +            // 查询出指定时间区间内 已经填写的日报数量
 | 
	
		
			
				|  |  | +            List<IotRhDailyReportDO> dailyReports = iotRhDailyReportMapper.dailyReports(reqVO);
 | 
	
		
			
				|  |  | +            // 根据指定的时间区间 计算出天数
 | 
	
		
			
				|  |  | +            LocalDateTime[] createTimes = reqVO.getCreateTime();
 | 
	
		
			
				|  |  | +            // 获取开始时间和结束时间
 | 
	
		
			
				|  |  | +            LocalDateTime startTime = createTimes[0];
 | 
	
		
			
				|  |  | +            LocalDateTime endTime = createTimes[1];
 | 
	
		
			
				|  |  | +            // 截断到日期(忽略时分秒)
 | 
	
		
			
				|  |  | +            LocalDate startDate = startTime.toLocalDate();
 | 
	
		
			
				|  |  | +            LocalDate endDate = endTime.toLocalDate();
 | 
	
		
			
				|  |  | +            // 计算天数:日期差 + 1(包含首尾两天)
 | 
	
		
			
				|  |  | +            long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
 | 
	
		
			
				|  |  | +            // 计算指定时间区间内所有日报数量总数
 | 
	
		
			
				|  |  | +            long totalRequiredReports = (long) deptIds.size() * days;
 | 
	
		
			
				|  |  | +            // 获取【实际已报的日报数量】
 | 
	
		
			
				|  |  | +            long totalReportedReports = CollUtil.isNotEmpty(dailyReports) ? dailyReports.size() : 0l;
 | 
	
		
			
				|  |  | +            // 计算指定时间区间内未填报的日报数量
 | 
	
		
			
				|  |  | +            long notReportedCount = totalRequiredReports - totalReportedReports;
 | 
	
		
			
				|  |  | +            IotRhDailyReportStatisticsVO allReports = new IotRhDailyReportStatisticsVO();
 | 
	
		
			
				|  |  | +            allReports.setGroupName("总数");
 | 
	
		
			
				|  |  | +            allReports.setCount(totalRequiredReports);
 | 
	
		
			
				|  |  | +            IotRhDailyReportStatisticsVO haveReported = new IotRhDailyReportStatisticsVO();
 | 
	
		
			
				|  |  | +            haveReported.setGroupName("已填报");
 | 
	
		
			
				|  |  | +            haveReported.setCount(totalReportedReports);
 | 
	
		
			
				|  |  | +            IotRhDailyReportStatisticsVO notReported = new IotRhDailyReportStatisticsVO();
 | 
	
		
			
				|  |  | +            notReported.setGroupName("未填报");
 | 
	
		
			
				|  |  | +            notReported.setCount(notReportedCount);
 | 
	
		
			
				|  |  | +            results.add(allReports);
 | 
	
		
			
				|  |  | +            results.add(haveReported);
 | 
	
		
			
				|  |  | +            results.add(notReported);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return results;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @Override
 | 
	
		
			
				|  |  | +    public List<IotRhDailyReportStatisticsVO> rhUnReportedDetails(IotRhDailyReportPageReqVO reqVO) {
 | 
	
		
			
				|  |  | +        // 查询所有未填报日报的队伍名称 HY-A1,HY-A2...
 | 
	
		
			
				|  |  | +        // 首先必须传递了日期范围
 | 
	
		
			
				|  |  | +        if (reqVO.getCreateTime().length == 0) {
 | 
	
		
			
				|  |  | +            // 必须传递时间区间参数
 | 
	
		
			
				|  |  | +            throw exception(IOT_DAILY_REPORT_TIME_NOT_EXISTS);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        List<IotRhDailyReportStatisticsVO> results = new ArrayList<>();
 | 
	
		
			
				|  |  | +        List<IotDeviceDO> devices = devices();
 | 
	
		
			
				|  |  | +        // 所有需要填报日报的 瑞恒 队伍集合
 | 
	
		
			
				|  |  | +        Set<Long> deptIds = new HashSet<>();
 | 
	
		
			
				|  |  | +        List<String> reportDates = new ArrayList<>();
 | 
	
		
			
				|  |  | +        if (CollUtil.isNotEmpty(devices)){
 | 
	
		
			
				|  |  | +            devices.forEach(device -> {
 | 
	
		
			
				|  |  | +                deptIds.add(device.getDeptId());
 | 
	
		
			
				|  |  | +            });
 | 
	
		
			
				|  |  | +            // 根据指定的时间区间 计算出天数
 | 
	
		
			
				|  |  | +            LocalDateTime[] createTimes = reqVO.getCreateTime();
 | 
	
		
			
				|  |  | +            // 获取开始时间和结束时间
 | 
	
		
			
				|  |  | +            LocalDateTime startTime = createTimes[0];
 | 
	
		
			
				|  |  | +            LocalDateTime endTime = createTimes[1];
 | 
	
		
			
				|  |  | +            // 截断到日期(忽略时分秒)
 | 
	
		
			
				|  |  | +            LocalDate startDate = startTime.toLocalDate();
 | 
	
		
			
				|  |  | +            LocalDate endDate = endTime.toLocalDate();
 | 
	
		
			
				|  |  | +            // 将时间区间拆分成具体的每天 计算每天未填报日报的队伍明细
 | 
	
		
			
				|  |  | +            Set<LocalDate> dateSet = new HashSet<>();
 | 
	
		
			
				|  |  | +            LocalDate currentDate = startDate;
 | 
	
		
			
				|  |  | +            // 循环添加从开始日期到结束日期的所有日期(包含两端)
 | 
	
		
			
				|  |  | +            while (!currentDate.isAfter(endDate)) {
 | 
	
		
			
				|  |  | +                dateSet.add(currentDate);
 | 
	
		
			
				|  |  | +                currentDate = currentDate.plusDays(1); // 日期加1天
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // 转换为字符串格式的日期集合(如:2025-10-06)
 | 
	
		
			
				|  |  | +            reportDates = dateSet.stream()
 | 
	
		
			
				|  |  | +                    .map(date -> date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
 | 
	
		
			
				|  |  | +                    .collect(Collectors.toList());
 | 
	
		
			
				|  |  | +            // 比如有20个队伍 某个时间区间D内 必须有20*D份日报 时间区间最好是1天
 | 
	
		
			
				|  |  | +            // 查询出指定时间区间内 已经填写的日报数量
 | 
	
		
			
				|  |  | +            List<IotRhDailyReportDO> dailyReports = iotRhDailyReportMapper.dailyReports(reqVO);
 | 
	
		
			
				|  |  | +            // 已经填报的队伍名称集合
 | 
	
		
			
				|  |  | +            Map<String, List<String>> reportedDeptNames = new HashMap<>();
 | 
	
		
			
				|  |  | +            // 未填报的队伍名称集合
 | 
	
		
			
				|  |  | +            Map<String, List<String>> unReportedDeptNames = new HashMap<>();
 | 
	
		
			
				|  |  | +            // 查询所有部门信息
 | 
	
		
			
				|  |  | +            Map<Long, DeptDO> allDepts = deptService.getDeptMap(deptIds);
 | 
	
		
			
				|  |  | +            // 过滤后的所有部门信息
 | 
	
		
			
				|  |  | +            Map<Long, DeptDO> filteredDepts = new HashMap<>();
 | 
	
		
			
				|  |  | +            // 过滤掉部门中的 ‘项目部’
 | 
	
		
			
				|  |  | +            if (CollUtil.isNotEmpty(allDepts)) {
 | 
	
		
			
				|  |  | +                allDepts.forEach((deptId, dept) -> {
 | 
	
		
			
				|  |  | +                    if (!dept.getName().contains("项目部") && !dept.getName().contains("瑞恒兴域")
 | 
	
		
			
				|  |  | +                            && !dept.getName().contains("基地") && !dept.getName().contains("服务部")) {
 | 
	
		
			
				|  |  | +                        filteredDepts.put(deptId, dept);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if (CollUtil.isNotEmpty(dailyReports)) {
 | 
	
		
			
				|  |  | +                // 组装每天填写的日报集合 从而统计出每天未填写日报的队伍
 | 
	
		
			
				|  |  | +                dailyReports.forEach(report -> {
 | 
	
		
			
				|  |  | +                    Long tempDeptId = report.getDeptId();
 | 
	
		
			
				|  |  | +                    if (filteredDepts.containsKey(tempDeptId)) {
 | 
	
		
			
				|  |  | +                        String deptName = filteredDepts.get(tempDeptId).getName();
 | 
	
		
			
				|  |  | +                        LocalDateTime createTime = report.getCreateTime();
 | 
	
		
			
				|  |  | +                        LocalDate tempDate = createTime.toLocalDate();
 | 
	
		
			
				|  |  | +                        String tempReportDate = tempDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
 | 
	
		
			
				|  |  | +                        if (reportedDeptNames.containsKey(tempReportDate)) {
 | 
	
		
			
				|  |  | +                            List<String> tempDeptNames = reportedDeptNames.get(tempReportDate);
 | 
	
		
			
				|  |  | +                            tempDeptNames.add(deptName);
 | 
	
		
			
				|  |  | +                            reportedDeptNames.put(tempReportDate, tempDeptNames);
 | 
	
		
			
				|  |  | +                        } else {
 | 
	
		
			
				|  |  | +                            List<String> tempDeptNames = new ArrayList<>();
 | 
	
		
			
				|  |  | +                            tempDeptNames.add(deptName);
 | 
	
		
			
				|  |  | +                            reportedDeptNames.put(tempReportDate, tempDeptNames);
 | 
	
		
			
				|  |  | +                        }
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // 遍历所有时间区间的日期 组装每个日期中 未填写日报的队伍名称集合
 | 
	
		
			
				|  |  | +            if (CollUtil.isNotEmpty(reportDates)) {
 | 
	
		
			
				|  |  | +                reportDates.forEach(dateStr -> {
 | 
	
		
			
				|  |  | +                    // 对时间区间内每个日期 都经过已经填报日报的过滤
 | 
	
		
			
				|  |  | +                    if (reportedDeptNames.containsKey(dateStr)) {
 | 
	
		
			
				|  |  | +                        List<String> tempReportedDeptNames = reportedDeptNames.get(dateStr);
 | 
	
		
			
				|  |  | +                        filteredDepts.forEach((deptId, dept) -> {
 | 
	
		
			
				|  |  | +                            if (!tempReportedDeptNames.contains(dept.getName())) {
 | 
	
		
			
				|  |  | +                                if (unReportedDeptNames.containsKey(dateStr)) {
 | 
	
		
			
				|  |  | +                                    List<String> tempDeptNames = unReportedDeptNames.get(dateStr);
 | 
	
		
			
				|  |  | +                                    tempDeptNames.add(dept.getName());
 | 
	
		
			
				|  |  | +                                    unReportedDeptNames.put(dateStr, tempDeptNames);
 | 
	
		
			
				|  |  | +                                } else {
 | 
	
		
			
				|  |  | +                                    List<String> tempDeptNames = new ArrayList<>();
 | 
	
		
			
				|  |  | +                                    tempDeptNames.add(dept.getName());
 | 
	
		
			
				|  |  | +                                    unReportedDeptNames.put(dateStr, tempDeptNames);
 | 
	
		
			
				|  |  | +                                }
 | 
	
		
			
				|  |  | +                            }
 | 
	
		
			
				|  |  | +                        });
 | 
	
		
			
				|  |  | +                    } else {
 | 
	
		
			
				|  |  | +                        // 当天没有任何队伍填写日报
 | 
	
		
			
				|  |  | +                        filteredDepts.forEach((deptId, dept) -> {
 | 
	
		
			
				|  |  | +                            List<String> tempDeptNames = new ArrayList<>();
 | 
	
		
			
				|  |  | +                            tempDeptNames.add(dept.getName());
 | 
	
		
			
				|  |  | +                            unReportedDeptNames.put(dateStr, tempDeptNames);
 | 
	
		
			
				|  |  | +                        });
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            // 将集合中的未填报日报的队伍集合转换成 逗号 分隔的字符串
 | 
	
		
			
				|  |  | +            if (CollUtil.isNotEmpty(unReportedDeptNames)) {
 | 
	
		
			
				|  |  | +                unReportedDeptNames.forEach((reportDate, deptList) -> {
 | 
	
		
			
				|  |  | +                    IotRhDailyReportStatisticsVO dailyReportVO = new IotRhDailyReportStatisticsVO();
 | 
	
		
			
				|  |  | +                    dailyReportVO.setReportDate(reportDate);
 | 
	
		
			
				|  |  | +                    dailyReportVO.setDeptNames(deptList.stream().collect(Collectors.joining(",")));
 | 
	
		
			
				|  |  | +                    results.add(dailyReportVO);
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +                // 使用Comparator进行排序
 | 
	
		
			
				|  |  | +                results.sort(Comparator
 | 
	
		
			
				|  |  | +                        .comparing(IotRhDailyReportStatisticsVO::getReportDate,
 | 
	
		
			
				|  |  | +                                Comparator.nullsLast(Comparator.reverseOrder())));
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        return results;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private List<IotDeviceDO> devices() {
 | 
	
		
			
				|  |  | +        // 查询 瑞恒157l 所有存在设备的队伍
 | 
	
		
			
				|  |  | +        // 查询瑞恒下所有部门
 | 
	
		
			
				|  |  | +        Set<Long> ids = new HashSet<>();
 | 
	
		
			
				|  |  | +        ids = deptService.getChildDeptIdListFromCache(157l);
 | 
	
		
			
				|  |  | +        ids.add(157l);
 | 
	
		
			
				|  |  | +        // 查询ids下包含的所有设备 设备对应的部门就是 小队全集
 | 
	
		
			
				|  |  | +        IotDevicePageReqVO deviceReqVO = new IotDevicePageReqVO();
 | 
	
		
			
				|  |  | +        List<IotDeviceDO> devices = iotDeviceMapper.selectSimpleList(deviceReqVO, ids);
 | 
	
		
			
				|  |  | +        return devices;
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      @Override
 | 
	
		
			
				|  |  |      public List<IotRhDailyReportDO> taskActualProgress(IotRhDailyReportPageReqVO reqVO) {
 | 
	
		
			
				|  |  |          if (ObjUtil.isEmpty(reqVO.getTaskId())) {
 |