Просмотр исходного кода

pms 瑞都当天生成前一天的生产日报简报 定时任务

zhangcl 2 недель назад
Родитель
Сommit
6eb1e43d82

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/constant/PmsConstants.java

@@ -49,4 +49,7 @@ public interface PmsConstants {
 
     // 瑞恒日报汇总生产动态消息提醒
     String RH_PRODUCTION_STATUS = "rh-production-status";
+
+    // 瑞都日报汇总生产动态消息提醒
+    String RD_PRODUCTION_STATUS = "rd-production-status";
 }

+ 8 - 58
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrddailyreport/IotRdDailyReportController.java

@@ -50,6 +50,7 @@ import cn.iocoder.yudao.module.pms.service.iotdailyreportfuel.IotDailyReportFuel
 import cn.iocoder.yudao.module.pms.service.iotprojectinfo.IotProjectInfoService;
 import cn.iocoder.yudao.module.pms.service.iotprojecttask.IotProjectTaskService;
 import cn.iocoder.yudao.module.pms.service.iotrddailyreport.IotRdDailyReportService;
+import cn.iocoder.yudao.module.pms.util.DailyReportUtils;
 import cn.iocoder.yudao.module.pms.util.ZHBDUtil;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -914,8 +915,10 @@ public class IotRdDailyReportController {
             }
         }
         PageResult<IotRdDailyReportDO> pageResult = iotRdDailyReportService.getIotRdDailyReportPage(pageReqVO);
+        // 数据转换与清洗
+        List<IotRdDailyReportRespVO> reportVOs = iotRdDailyReportService.getIotRdDailyReports(pageResult.getList());
         // 设置日报的关联信息 部门(施工队伍) 项目 任务 带班干部 日报填报人
-        return success(new PageResult<>(buildDailyReportList(pageResult.getList()), pageResult.getTotal()));
+        return success(new PageResult<>(reportVOs, pageResult.getTotal()));
     }
 
     @GetMapping("/reportSummary")
@@ -3054,7 +3057,7 @@ public class IotRdDailyReportController {
                         }
                         // 查询关联井的 非生产时间填写情况 只要有1个关联井填写了 非生产时间 就显示为 已填写
                         IotRdDailyReportRespVO reportRespVO = BeanUtils.toBean(report, IotRdDailyReportRespVO.class);
-                        Boolean flag = setNoProductFlag(reportRespVO);
+                        Boolean flag = DailyReportUtils.setNoProductFlag(reportRespVO);
                         if (flag) {
                             reportNonProductFlagPair.put(report.getPlatformGroup(), true);
                         }
@@ -3062,7 +3065,7 @@ public class IotRdDailyReportController {
                         if (CollUtil.isNotEmpty(report.getExtProperty()) || StrUtil.isNotBlank(report.getRdStatus())) {
                             // 说明当前日报设置过 工作量 施工状态
                             IotRdDailyReportRespVO rdDailyReportResp = BeanUtils.toBean(report, IotRdDailyReportRespVO.class);
-                            BigDecimal nonProductTime = calculateNonProductTime(rdDailyReportResp);
+                            BigDecimal nonProductTime = DailyReportUtils.calculateNonProductTimeResp(rdDailyReportResp);
                             if (platformNonProductPair.containsKey(report.getPlatformGroup())) {
                                 List<BigDecimal> tempSumNpts = platformNonProductPair.get(report.getPlatformGroup());
                                 tempSumNpts.add(nonProductTime);
@@ -3343,7 +3346,7 @@ public class IotRdDailyReportController {
                 }
             }
             // 计算非生产时效 如果是平台井 需要计算 所有平台井 的 非生产时效
-            BigDecimal nonProductionTimes = calculateNonProductTime(reportVO);
+            BigDecimal nonProductionTimes = DailyReportUtils.calculateNonProductTimeResp(reportVO);
             reportVO.setNonProductionRate(nonProductionTimes.divide(BigDecimal.valueOf(24), 4, RoundingMode.HALF_UP ));
 
             // 非生产时效 百分比格式 导出使用
@@ -3360,7 +3363,7 @@ public class IotRdDailyReportController {
             }
 
             // 设置非生产时间填写标识 noProductFlag 为 true 或者 processInstanceId 为 2 的时候 显示 已填写
-            reportVO.setNonProductFlag(setNoProductFlag(reportVO));
+            reportVO.setNonProductFlag(DailyReportUtils.setNoProductFlag(reportVO));
             // 创建时间 格式化 yyyy-MM-dd
             if (ObjUtil.isNotEmpty(reportVO.getCreateTime())) {
                 reportVO.setCreateTimeStr(LocalDateTimeUtil.format(reportVO.getCreateTime(), DatePattern.NORM_DATE_PATTERN));
@@ -3413,59 +3416,6 @@ public class IotRdDailyReportController {
         });
     }
 
-    /**
-     *  检测是否已经维护了生产时效
-     * @param dailyReport
-     * @return
-     */
-    private Boolean setNoProductFlag(IotRdDailyReportRespVO dailyReport){
-        BigDecimal accidentTime = dailyReport.getAccidentTime();
-        BigDecimal repairTime = dailyReport.getRepairTime();
-        BigDecimal selfStopTime = dailyReport.getSelfStopTime();
-        BigDecimal complexityTime = dailyReport.getComplexityTime();
-        BigDecimal relocationTime = dailyReport.getRelocationTime();
-        BigDecimal rectificationTime = dailyReport.getRectificationTime();
-        BigDecimal waitingStopTime = dailyReport.getWaitingStopTime();
-        BigDecimal winterBreakTime = dailyReport.getWinterBreakTime();
-        BigDecimal partyaDesignTime = dailyReport.getPartyaDesign();
-        BigDecimal partyaPrepareTime = dailyReport.getPartyaPrepare();
-        BigDecimal partyaResourceTime = dailyReport.getPartyaResource();
-        BigDecimal otherNptTime = dailyReport.getOtherNptTime();
-        if (accidentTime.compareTo(BigDecimal.ZERO) > 0 || repairTime.compareTo(BigDecimal.ZERO) > 0 || selfStopTime.compareTo(BigDecimal.ZERO) > 0
-                || complexityTime.compareTo(BigDecimal.ZERO) > 0 || relocationTime.compareTo(BigDecimal.ZERO) > 0 || rectificationTime.compareTo(BigDecimal.ZERO) > 0
-                || waitingStopTime.compareTo(BigDecimal.ZERO) > 0 || winterBreakTime.compareTo(BigDecimal.ZERO) > 0 || partyaDesignTime.compareTo(BigDecimal.ZERO) > 0
-                || partyaPrepareTime.compareTo(BigDecimal.ZERO) > 0 || partyaResourceTime.compareTo(BigDecimal.ZERO) > 0 || otherNptTime.compareTo(BigDecimal.ZERO) > 0) {
-            // 设置非生产时间填写标识
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     *  计算非生产时间累加值 计算非生产时效
-     *  NPT 归类 设备故障repair_time 工程质量accident_time 技术受限complexity_time 生产组织rectification_time 不可抗力waiting_stop_time
-     *      甲方设计partya_design 甲方准备partya_prepare 甲方资源partya_resource 其他非生产时间other_npt_time
-     * @param dailyReport
-     * @return
-     */
-    private BigDecimal calculateNonProductTime(IotRdDailyReportRespVO dailyReport){
-        return Stream.of(
-                        dailyReport.getAccidentTime(),
-                        dailyReport.getRepairTime(),
-                        dailyReport.getComplexityTime(),
-                        dailyReport.getRectificationTime(),
-                        dailyReport.getWaitingStopTime(),
-                        dailyReport.getPartyaDesign(),    // 注:字段名虽无Time但属于非生产时间,保留
-                        dailyReport.getPartyaPrepare(),
-                        dailyReport.getPartyaResource(),
-                        dailyReport.getOtherNptTime()
-                )
-                // 处理null值,避免调用add()时抛出NullPointerException
-                .map(bd -> Objects.isNull(bd) ? BigDecimal.ZERO : bd)
-                // Stream.reduce:从初始值0开始,依次累加所有元素
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-    }
-
     @GetMapping("/taskActualProgress")
     @Operation(summary = "查询日报所属任务的实际进度")
     @PreAuthorize("@ss.hasPermission('pms:iot-rd-daily-report:query')")

+ 245 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/dailyreport/CreateRdDailyReportStatisticsJob.java

@@ -0,0 +1,245 @@
+package cn.iocoder.yudao.module.pms.job.dailyreport;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.module.pms.constant.PmsConstants;
+import cn.iocoder.yudao.module.pms.controller.admin.iotrddailyreport.vo.IotRdDailyReportPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotrddailyreport.vo.IotRdDailyReportRespVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotrddailyreport.IotRdDailyReportDO;
+import cn.iocoder.yudao.module.pms.message.PmsMessage;
+import cn.iocoder.yudao.module.pms.service.iotrddailyreport.IotRdDailyReportService;
+import cn.iocoder.yudao.module.pms.service.iotrhdailyreport.IotRhDailyReportService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
+import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
+import cn.iocoder.yudao.module.system.service.dept.DeptService;
+import cn.iocoder.yudao.module.system.service.permission.RoleService;
+import cn.iocoder.yudao.module.system.service.user.AdminUserService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.module.pms.framework.config.MultiThreadConfiguration.PMS_THREAD_POOL_TASK_EXECUTOR;
+
+/**
+ * 瑞都每天生成昨日的日报汇总 钉钉 推送消息
+ */
+@Component
+@Slf4j
+public class CreateRdDailyReportStatisticsJob implements JobHandler {
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private IotRhDailyReportService iotRhDailyReportService;
+    @Resource
+    private IotRdDailyReportService iotRdDailyReportService;
+    @Resource(name = PMS_THREAD_POOL_TASK_EXECUTOR)
+    private ThreadPoolTaskExecutor pmsThreadPoolTaskExecutor;
+    @Resource
+    private PmsMessage pmsMessage;
+    @Resource
+    private DeptService deptService;
+    @Resource
+    private AdminUserService adminUserService;
+    @Resource
+    private RoleService roleService;
+    @Resource
+    private UserRoleMapper userRoleMapper;
+
+    @Override
+    @TenantIgnore
+    public String execute(String param) throws Exception {
+        // 获取当前时间
+        LocalDateTime currentDate = LocalDateTime.now();
+        // 根据当前日期生成前一天的日期
+        LocalDateTime yesterday = currentDate.minusDays(1);
+
+        LocalDateTime createTimeStart = yesterday.toLocalDate().atStartOfDay();
+        LocalDateTime createTimeEnd = yesterday.toLocalDate().atTime(LocalTime.MAX);
+
+        // 查询瑞恒昨天的日报列表
+        // IotRhDailyReportPageReqVO reqVO = new IotRhDailyReportPageReqVO();
+        // 测试数据
+        /* reqVO.setCreateTime(new LocalDateTime[]{
+                LocalDate.of(2026, 6, 15).atStartOfDay(),
+                LocalDate.of(2026, 6, 15).atTime(23, 59, 59)
+        }); */
+
+        IotRdDailyReportPageReqVO reqVO = new IotRdDailyReportPageReqVO();
+        reqVO.setCreateTime(new LocalDateTime[]{createTimeStart, createTimeEnd});
+        reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        PageResult<IotRdDailyReportDO> pageReports = iotRdDailyReportService.getIotRdDailyReportPage(reqVO);
+        List<IotRdDailyReportDO> dailyReports = pageReports.getList();
+        if (CollUtil.isEmpty(dailyReports)) {
+            return "没有数据";
+        }
+
+        List<IotRdDailyReportRespVO> reports = iotRdDailyReportService.getIotRdDailyReports(dailyReports);
+        // 先 按照 项目部projectSort 排序 再按照 队伍teamSort 排序
+        // todo 只筛选 时效维护完成的日报记录
+        // 1. 排序:先按项目部 sort,再按队伍 sort(null 排最后)
+        reports.sort(Comparator
+                .comparing(IotRdDailyReportRespVO::getProjectSort,
+                        Comparator.nullsLast(Comparator.naturalOrder()))
+                .thenComparing(IotRdDailyReportRespVO::getTeamSort,
+                        Comparator.nullsLast(Comparator.naturalOrder())));
+
+        // 2. 按项目名称分组(保持顺序)
+        Map<String, List<IotRdDailyReportRespVO>> groupedByProject = reports.stream()
+                .collect(Collectors.groupingBy(
+                        IotRdDailyReportRespVO::getProjectName,
+                        LinkedHashMap::new,
+                        Collectors.toList()
+                ));
+
+        // 构建包含所有项目部的消息(每个项目部一块)
+        StringBuilder finalMsgBuilder = new StringBuilder();
+
+        reports.forEach(report -> {
+            List<String> injectLines = new ArrayList<>();
+            List<String> rateLines = new ArrayList<>();
+            // 以项目部分组各队伍的日报 施工简报
+            String projectName = report.getProjectName();
+            String teamName = report.getDeptName();
+            String taskName = report.getTaskName();
+            String rdStatusLabel = report.getRdStatusLabel();
+            String techniqueNames = report.getTechniqueNames();
+            String brief = report.getConstructionBrief();
+        });
+
+        int projectIndex = 0;
+        int totalProjects = groupedByProject.size();
+        for (Map.Entry<String, List<IotRdDailyReportRespVO>> entry : groupedByProject.entrySet()) {
+            String projectName = entry.getKey();
+            List<IotRdDailyReportRespVO> projectReports = entry.getValue();
+
+            // 项目部标题
+            finalMsgBuilder.append("**").append(projectName).append(":**  \n");
+
+            // 项目部内部循环日报条目
+            for (int i = 0; i < projectReports.size(); i++) {
+                IotRdDailyReportRespVO report = projectReports.get(i);
+
+                finalMsgBuilder.append(report.getDeptName()).append(":  \n");
+                finalMsgBuilder.append("井号:").append(StrUtil.blankToDefault(report.getTaskName(), "无")).append(";  \n");
+                finalMsgBuilder.append("状态:").append(StrUtil.blankToDefault(report.getRdStatusLabel(), "无")).append(";  \n");
+                finalMsgBuilder.append("工艺:").append(StrUtil.blankToDefault(report.getTechniqueNames(), "无")).append(";  \n");
+                finalMsgBuilder.append("施工简要:").append(StrUtil.blankToDefault(report.getConstructionBrief(), "无")).append("。  \n");
+
+                // 同项目内,条目之间添加虚线(最后一条不加)
+                if (i < projectReports.size() - 1) {
+                    finalMsgBuilder.append("……………………  \n");
+                }
+            }
+
+            // 不同项目之间添加实线(最后一个项目后不加)
+            if (projectIndex < totalProjects - 1) {
+                finalMsgBuilder.append("——————————————  \n");
+            }
+            projectIndex++;
+        }
+
+        // 日报汇总生产动态精简内容
+        /* String PRODUCTION_STATUS_MSG_TEMPLATE =
+                "**{}:**  \n" +
+                "队伍:{}  \n" +
+                "井号:{}  \n" +
+                "状态:{}  \n" +
+                "工艺:  \n{}  \n" +
+                "施工简要:  \n{}  \n"; */
+
+        /* for (int i = 0; i < productionStatuses.size(); i++) {
+            ProjectUtilizationRateVo status = productionStatuses.get(i);
+            String block = StrUtil.format(PRODUCTION_STATUS_MSG_TEMPLATE,
+                    status.getProjectDeptName(),
+                    status.getTeamCount(),
+                    status.getDmTeamCount(),
+                    status.getZbTeamCount(),
+                    status.getSgTeamCount(),
+                    // 注入量和运行时效已包含换行,直接放入
+                    status.getInjectionVolume(),
+                    status.getRunningAvailability()
+            );
+            finalMsgBuilder.append(block);
+            // 块之间用分隔线隔开(最后一块不加)
+            // 块之间添加纯文本分隔线,避免被识别为Markdown水平线
+            if (i < productionStatuses.size() - 1) {
+                finalMsgBuilder.append("  \n==========  \n");
+            }
+        } */
+
+        String finalMsgTitle = finalMsgBuilder.toString();
+
+        Set<Long> deptIds = new HashSet<>();
+        Set<Long> userIds = new HashSet<>();
+
+        // 查询瑞恒 163l 下具有 瑞都日报汇总推送 角色的人员
+        deptIds.add(163l);
+        List<AdminUserDO> companyUsers = adminUserService.getUserListByDeptIds(deptIds);
+        if (CollUtil.isNotEmpty(companyUsers)) {
+            companyUsers.forEach(user -> {
+                userIds.add(user.getId());
+            });
+        }
+
+        // 将当前日期的前一天转换成 long 类型的数字
+        Long timeStamp = cn.hutool.core.date.DateUtil.toInstant(yesterday).toEpochMilli();
+
+        RoleDO role = roleService.getRoleByCode("瑞都日报汇总推送");
+        if (ObjUtil.isNotEmpty(role)) {
+            Set<Long> roleIds = new HashSet<>();
+            roleIds.add(role.getId());
+            List<UserRoleDO> userRoles = userRoleMapper.selectListByRoleIds(roleIds);
+            if (CollUtil.isNotEmpty(userRoles)) {
+                // 提取有审批角色的所有用户ID(去重+空值过滤)
+                Set<Long> roleUserIds = userRoles.stream()
+                        .map(UserRoleDO::getUserId)
+                        .filter(Objects::nonNull) // 过滤空userId
+                        .collect(Collectors.toSet());
+                // 计算两个集合的交集,得到最终目标用户ID
+                Set<Long> targetUserIds = userIds.stream()
+                        .filter(roleUserIds::contains)
+                        .collect(Collectors.toSet());
+                if (CollUtil.isNotEmpty(targetUserIds)) {
+                    Map<Long, AdminUserRespDTO> users = adminUserApi.getUserMap(targetUserIds);
+                    // 给多个用户发送 相同 的 提醒消息
+                    if (CollUtil.isNotEmpty(users)) {
+                        // 生成消息提醒 标题内容
+                        CountDownLatch latch = new CountDownLatch(users.size());
+                        users.forEach((userId, user) -> {
+                            pmsThreadPoolTaskExecutor.execute(() -> {
+                                try {
+                                    String mobile = user.getMobile();
+                                    // 没有手机号也发送站内信消息
+                                    if (StrUtil.isNotBlank(finalMsgTitle)) {
+                                        pmsMessage.sendMessage(timeStamp, finalMsgTitle, PmsConstants.RD_PRODUCTION_STATUS,
+                                                userId, mobile);
+                                    }
+                                } finally {
+                                    latch.countDown();
+                                }
+                            });
+                        });
+                    }
+                }
+            }
+        }
+        return "创建成功";
+    }
+}

+ 0 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/dailyreport/CreateRhDailyReportStatisticsJob.java

@@ -27,7 +27,6 @@ import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.DecimalFormat;
-import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.util.*;

+ 10 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/message/PmsMessage.java

@@ -126,6 +126,14 @@ public class PmsMessage {
                 msg = "**"+yesterdayPattern+"瑞恒生产动态**  \n" + name + "["+ DateUtil.format(new Date(),"yyyy-MM-dd hh:mm:ss")+"]";
             }
 
+            // 瑞都日报汇总生产动态提醒
+            if (PmsConstants.RD_PRODUCTION_STATUS.equals(businessType)) {
+                // businessId 时间戳 转换成 yyyy-MM-dd
+                LocalDateTime yesterday = LocalDateTime.ofInstant(Instant.ofEpochMilli(businessId),ZoneId.of("Asia/Shanghai"));
+                String yesterdayPattern = LocalDateTimeUtil.format(yesterday, DatePattern.NORM_DATE_PATTERN);
+                msg = "**四川瑞都 "+yesterdayPattern+" 施工简报**  \n" + name + "["+ DateUtil.format(new Date(),"yyyy-MM-dd hh:mm:ss")+"]";
+            }
+
             // 瑞鹰项目启动设备整改
             if (PmsConstants.RY_IMPROVE_DAILY_REPORT.equals(businessType)) {
                 msg = "您有新的项目启动设备整改日报 " + name + "需要填报,请处理。"+"["+ DateUtil.format(new Date(),"yyyy-MM-dd hh:mm:ss")+"]";
@@ -173,7 +181,8 @@ public class PmsMessage {
                     || PmsConstants.GENERATE_QHSE_CERT.equals(businessType) || PmsConstants.WEEK_ALARM_MESSAGE.equals(businessType)) {
                 //通知设备状态变更为观察使用
                 dingtalkSendApi.send(mobile, msg, "text",url,  title);
-            } else if (PmsConstants.PRODUCTION_EXCEPTION.equals(businessType) || PmsConstants.RH_PRODUCTION_STATUS.equals(businessType)) {
+            } else if (PmsConstants.PRODUCTION_EXCEPTION.equals(businessType) || PmsConstants.RH_PRODUCTION_STATUS.equals(businessType)
+                    || PmsConstants.RD_PRODUCTION_STATUS.equals(businessType)) {
                 // markdown 类型消息 支持 文字加粗
                 dingtalkSendApi.send(mobile, msg, "markdown",url,  title);
             } else {

+ 8 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrddailyreport/IotRdDailyReportService.java

@@ -59,6 +59,14 @@ public interface IotRdDailyReportService {
      */
     PageResult<IotRdDailyReportDO> getIotRdDailyReportPage(IotRdDailyReportPageReqVO pageReqVO);
 
+    /**
+     * 获得瑞都日报列表 看板生产动态
+     *
+     * @param reports 日报列表
+     * @return 瑞都日报分页
+     */
+    List<IotRdDailyReportRespVO> getIotRdDailyReports(List<IotRdDailyReportDO> reports);
+
     /**
      * 获得瑞都日报列表
      *

+ 616 - 26
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrddailyreport/IotRdDailyReportServiceImpl.java

@@ -19,8 +19,11 @@ import cn.iocoder.yudao.module.pms.controller.admin.iotproductionexception.vo.Io
 import cn.iocoder.yudao.module.pms.controller.admin.iotprojectinfo.vo.IotProjectInfoPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotprojecttask.vo.IotProjectTaskPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotrddailyreport.vo.*;
+import cn.iocoder.yudao.module.pms.controller.admin.iotrddailyreportdetail.vo.IotRdDailyReportDetailPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotrddailyreportdetail.vo.IotRdDailyReportDetailRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotrddailyreportdetail.vo.IotRdDailyReportDetailSaveReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDevicePageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotProductClassifyListReqVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.IotProductClassifyDO;
@@ -46,6 +49,8 @@ import cn.iocoder.yudao.module.pms.dal.mysql.iotrddailyreportdetail.IotRdDailyRe
 import cn.iocoder.yudao.module.pms.enums.AttachmentCategoryEnum;
 import cn.iocoder.yudao.module.pms.enums.AttachmentTypeEnum;
 import cn.iocoder.yudao.module.pms.message.PmsMessage;
+import cn.iocoder.yudao.module.pms.service.IotDeviceService;
+import cn.iocoder.yudao.module.pms.util.DailyReportUtils;
 import cn.iocoder.yudao.module.pms.util.LocalTimeUtils;
 import cn.iocoder.yudao.module.supplier.enums.common.SupplierAuditStatusEnum;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -71,6 +76,7 @@ import org.springframework.validation.annotation.Validated;
 import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.text.DecimalFormat;
 import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -86,6 +92,7 @@ import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstant.*;
 import static cn.iocoder.yudao.module.pms.framework.config.MultiThreadConfiguration.PMS_THREAD_POOL_TASK_EXECUTOR;
 
@@ -134,6 +141,8 @@ public class IotRdDailyReportServiceImpl implements IotRdDailyReportService {
     private IotDeviceAssociateMapper iotDeviceAssociateMapper;
     @Resource
     private IotProductionExceptionMapper iotProductionExceptionMapper;
+    @Resource
+    private IotDeviceService iotDeviceService;
 
     @Override
     public Long createIotRdDailyReport(IotRdDailyReportSaveReqVO createReqVO) {
@@ -664,6 +673,612 @@ public class IotRdDailyReportServiceImpl implements IotRdDailyReportService {
         return iotRdDailyReportMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public List<IotRdDailyReportRespVO> getIotRdDailyReports(List<IotRdDailyReportDO> reports) {
+        if (CollUtil.isEmpty(reports)) {
+            return Collections.emptyList();
+        }
+        // 设备部门信息
+        Map<Long, DeptDO> tempDeptMap = deptService.getDeptMap(convertList(reports, IotRdDailyReportDO::getDeptId));
+        // key项目id   value项目合同号
+        Map<Long, String> projectPair = new HashMap<>();
+        //  key任务id     value任务井号
+        Map<Long, String> taskPair = new HashMap<>();
+        //  key任务id     value任务施工区域
+        Map<Long, String> taskLocationPair = new HashMap<>();
+        //  key任务id     value任务关联的带班干部名称
+        Map<Long, String> taskResponsiblePair = new HashMap<>();
+        //  key任务id     value临时新建日报的带班干部名称 多个逗号分隔
+        Map<Long, String> tempReportResponsiblePair = new HashMap<>();
+        //  key任务id     value任务关联的填报人名称
+        Map<Long, String> taskSubmitterPair = new HashMap<>();
+        //  key任务id     value临时新建的日报的填报人名称
+        Map<Long, String> tempSubmitterPair = new HashMap<>();
+        //  key任务id     value工作量数据集合
+        Map<Long, List<IotTaskAttrModelProperty>> taskWorkloadPair = new HashMap<>();
+        //  key任务id     value任务关联的施工队伍名称 多个逗号分隔
+        Map<Long, String> taskTeamsPair = new HashMap<>();
+
+        Set<Long> userIds = new HashSet<>();
+        // 当前页所有日报关联任务的 施工队伍id 集合
+        Set<Long> currentPageDeptIds = new HashSet<>();
+        // key主井任务platformGroup/taskPlatform     value关联井号名称
+        Map<String, String> relatedPlatformPair = new HashMap<>();
+        // key主井任务id     value关联井任务施工状态
+        Map<String, String> relatedWellStatusPair = new HashMap<>();
+        // key关联井任务id     value关联井号
+        Map<Long, String> relatedPlatformsPair = new HashMap<>();
+        // key施工状态数据字典value   value施工状态数据字典label
+        Map<String, String> constructStatusPair = new HashMap<>();
+        // key日报id   value生产动态明细
+        Map<Long, List<IotRdDailyReportDetailRespVO>> reportDetailsPair = new HashMap<>();
+
+        // 施工状态 字典数据
+        List<DictDataDO> rdStatusData = dictDataService.getDictDataListByDictType("rdStatus");
+        if (CollUtil.isNotEmpty(rdStatusData)) {
+            rdStatusData.forEach(data -> {
+                constructStatusPair.put(data.getValue(), data.getLabel());
+            });
+        }
+        // 设置非生产时间标识
+        Map<String, Boolean> reportNonProductFlagPair = new HashMap<>();
+        // key平台井标识  value非生产时间累计和列表
+        Map<String, List<BigDecimal>> platformNonProductPair = new HashMap<>();
+        // key平台井标识  value平台井非生产时效集合
+        Map<String, BigDecimal> platformNptPair = new HashMap<>();
+        // key项目部id  value项目部排序值
+        Map<Long, Integer> projectSortPair = new HashMap<>();
+        // key项目部id  value项目部名称
+        Map<Long, String> projectNamePair = new HashMap<>();
+        // key施工队伍id  value队伍所属项目部
+        Map<Long, Long> projectIdPair = new HashMap<>();
+        //  施工工艺 key字典键值     value字典标签
+        Map<String, String> techniqueDictPair = new HashMap<>();
+        // key日报id  value日报关联的施工工艺名称集合
+        Map<Long, String> techniqueNamesPair = new HashMap<>();
+        // key设备id  value日报关联的设备名称集合
+        Map<String, String> deviceNamesPair = new HashMap<>();
+        // key日报id  value日报关联的设备名称集合
+        Map<Long, String> reportDeviceNamesPair = new HashMap<>();
+
+        // 设备id集合
+        Set<Long> deviceIds = new HashSet<>();
+
+        // 查询施工工艺字典数据
+        List<DictDataDO> rdTechniques = dictDataService.getDictDataListByDictType("rq_iot_project_technology_rd");
+        if (CollUtil.isNotEmpty(rdTechniques)) {
+            rdTechniques.forEach(tech -> {
+                techniqueDictPair.put(tech.getValue(), tech.getLabel());
+            });
+        }
+
+        // 平台井 筛选关联的井的生产动态明细 赋值到 平台井列表 的 日报对象
+
+        DataPermissionUtils.executeIgnore(() -> {
+            // 查询日报关联的项目信息
+            IotProjectInfoPageReqVO reqVO = new IotProjectInfoPageReqVO();
+            reqVO.setProjectIds(convertList(reports, IotRdDailyReportDO::getProjectId));
+            List<IotProjectInfoDO> projects = iotProjectInfoMapper.selectList(reqVO);
+            if (CollUtil.isNotEmpty(projects)) {
+                projects.forEach(project -> {
+                    projectPair.put(project.getId(), project.getContractName());
+                });
+            }
+            // 查询日报关联的任务信息
+            IotProjectTaskPageReqVO taskReqVO = new IotProjectTaskPageReqVO();
+            taskReqVO.setTaskIds(convertList(reports, IotRdDailyReportDO::getTaskId));
+            List<IotProjectTaskDO> tasks = iotProjectTaskMapper.selectList(taskReqVO);
+            // 已经施工完成的主井任务 platformGroup
+            Set<String> finishedPlatformGroups = new HashSet<>();
+            // 已经施工完成的主井任务 id
+            Set<Long> finishedMainPlatformIds = new HashSet<>();
+            // 施工状态为空的主井任务 id
+            Set<Long> nullStatusPlatformIds = new HashSet<>();
+            // 施工完成的任务id集合
+            Set<Long> finishedTaskIds = new HashSet<>();
+            // 查询主井的关联井列表 当主井已经施工完成时,显示 关联井的井号 施工状态
+            // 如果主井已经施工完成,并且主井日报 ‘施工状态’ 字段为空值,则显示关联井的井号、施工状态
+            IotRdDailyReportPageReqVO relatedWellReqVO = new IotRdDailyReportPageReqVO();
+            relatedWellReqVO.setPlatformGroups(convertList(reports, IotRdDailyReportDO::getPlatformGroup));
+            // relatedWellReqVO.setPlatformWell(2);
+            List<IotRdDailyReportDO> relatedWellReports = iotRdDailyReportMapper.dailyReports(relatedWellReqVO);
+
+            // key日报id  value平台井platformGroup
+            Map<Long, String> platformWellPair = new HashMap<>();
+
+            // 查询每个日报的生产动态明细 平台井查询 主井 的生产动态明细
+            IotRdDailyReportDetailPageReqVO detailReqVO = new IotRdDailyReportDetailPageReqVO();
+            detailReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+            detailReqVO.setReportIds(convertList(relatedWellReports, IotRdDailyReportDO::getId));
+            PageResult<IotRdDailyReportDetailDO> reportDetailsPage = iotRdDailyReportDetailMapper.selectPage(detailReqVO);
+            List<IotRdDailyReportDetailDO> reportDetails = reportDetailsPage.getList();
+            if (CollUtil.isNotEmpty(reportDetails)) {
+                // 设置每个日报的生产动态明细
+                reportDetails.forEach(detail -> {
+                    if (reportDetailsPair.containsKey(detail.getReportId())) {
+                        List<IotRdDailyReportDetailRespVO> tempDetails = reportDetailsPair.get(detail.getReportId());
+                        tempDetails.add(BeanUtils.toBean(detail, IotRdDailyReportDetailRespVO.class));
+                        reportDetailsPair.put(detail.getReportId(), tempDetails);
+                    } else {
+                        List<IotRdDailyReportDetailRespVO> tempDetails = new ArrayList<>();
+                        tempDetails.add(BeanUtils.toBean(detail, IotRdDailyReportDetailRespVO.class));
+                        reportDetailsPair.put(detail.getReportId(), tempDetails);
+                    }
+                });
+            }
+
+            // 按照日报创建时间倒序排列
+            if (CollUtil.isNotEmpty(relatedWellReports)) {
+                relatedWellReports = relatedWellReports.stream()
+                        .sorted(Comparator.comparing(IotRdDailyReportDO::getCreateTime).reversed())
+                        .collect(Collectors.toList());
+                // 筛选 平台井 类型的日报
+                platformWellPair = relatedWellReports.stream()
+                        .collect(Collectors.toMap(
+                                IotRdDailyReportDO::getId,        // key:日报ID
+                                IotRdDailyReportDO::getPlatformGroup, // value:platformGroup
+                                (oldValue, newValue) -> oldValue  // 重复key时保留旧值(避免重复ID报错)
+                        ));
+            }
+
+            // 组装没有关联生产动态明细的 日报
+            // 1. 按 platformGroup 分组,存储每组的明细列表
+            Map<String, List<IotRdDailyReportDetailRespVO>> platformGroupDetailsMap = new HashMap<>();
+            // 2. 把已有明细的日报,按 platformGroup 汇总明细
+            if (CollUtil.isNotEmpty(platformWellPair) && CollUtil.isNotEmpty(reportDetailsPair)) {
+                platformWellPair.forEach((reportId, platformGroup) -> {
+                    List<IotRdDailyReportDetailRespVO> details = reportDetailsPair.get(reportId);
+                    if (CollUtil.isNotEmpty(details)) {
+                        platformGroupDetailsMap
+                                .computeIfAbsent(platformGroup, k -> new ArrayList<>())
+                                .addAll(details);
+                    }
+                });
+            }
+
+            // 3. 给没有明细的日报,赋值同平台组的明细(实现共享)
+            for (IotRdDailyReportDO report : reports) {
+                Long reportId = report.getId();
+                String platformGroup = platformWellPair.get(reportId);
+
+                Set<Long> thisDeviceIds = report.getDeviceIds();
+                if (CollUtil.isNotEmpty(thisDeviceIds)) {
+                    deviceIds.addAll(thisDeviceIds);
+                }
+
+                Set<Long> techniqueIds = report.getTechniqueIds();
+                String thisTechniqueNames = Optional.ofNullable(techniqueIds)
+                        .filter(CollUtil::isNotEmpty)
+                        .map(techIds -> techIds.stream()
+                                .map(String::valueOf)  // 将 Long 转为 String
+                                .map(techniqueDictPair::get)  // 从字典获取名称
+                                .filter(Objects::nonNull)  // 过滤空值
+                                .filter(StrUtil::isNotBlank)  // 过滤空白值
+                                .collect(Collectors.joining(",")))  // 用逗号连接
+                        .orElse("");  // 如果为空,返回空字符串
+                techniqueNamesPair.put(report.getId(), thisTechniqueNames);
+                // 如果当前日报没有明细 && 同组有共享明细 → 赋值
+                if (!reportDetailsPair.containsKey(reportId)
+                        && StrUtil.isNotBlank(platformGroup)
+                        && platformGroupDetailsMap.containsKey(platformGroup)) {
+
+                    List<IotRdDailyReportDetailRespVO> sharedDetails = platformGroupDetailsMap.get(platformGroup);
+                    if (CollUtil.isNotEmpty(sharedDetails)) {
+                        reportDetailsPair.put(reportId, sharedDetails);
+                    }
+                }
+            }
+
+            if (CollUtil.isNotEmpty(tasks)) {
+                tasks.forEach(task -> {
+                    Set<Long> personIds = task.getResponsiblePerson();
+                    Set<Long> submitterIds = task.getSubmitter();
+                    // 施工队伍id集合
+                    Set<Long> deptIds = task.getDeptIds();
+                    currentPageDeptIds.addAll(Optional.ofNullable(deptIds).orElse(Collections.emptySet()));
+                    taskPair.put(task.getId(), task.getWellName());
+                    taskLocationPair.put(task.getId(), task.getLocation());
+                    userIds.addAll(Optional.ofNullable(personIds).orElse(Collections.emptySet()));
+                    userIds.addAll(Optional.ofNullable(submitterIds).orElse(Collections.emptySet()));
+                    // 获取所有任务的工作量数据
+                    List<IotTaskAttrModelProperty> extProperties = task.getExtProperty();
+                    taskWorkloadPair.put(task.getId(), extProperties);
+                    // 找到已经施工完成的主井任务 查询对应的关联井任务 用关联井号作为日报任务井号
+                    if (1 == task.getPlatformWell() && "wg".equals(task.getStatus())) {
+                        finishedPlatformGroups.add(task.getPlatformGroup());
+                        finishedMainPlatformIds.add(task.getId());
+                    }
+                    // 收集施工完成的任务id集合
+                    if ("wg".equals(task.getStatus())) {
+                        finishedTaskIds.add(task.getId());
+                    }
+                });
+                // 查询 finishedPlatformGroups 相关的未施工完成的关联井任务井
+                if (CollUtil.isNotEmpty(finishedPlatformGroups)) {
+                    IotProjectTaskPageReqVO unfinishedTaskReqVO = new IotProjectTaskPageReqVO();
+                    unfinishedTaskReqVO.setPlatformGroups(new ArrayList<>(finishedPlatformGroups));
+                    unfinishedTaskReqVO.setPlatformWell(2);
+                    // unfinishedTaskReqVO.setJobFlag("Y");
+                    List<IotProjectTaskDO> unfinishedRelatedTasks = iotProjectTaskMapper.selectList(unfinishedTaskReqVO);
+                    if (CollUtil.isNotEmpty(unfinishedRelatedTasks)) {
+                        unfinishedRelatedTasks.forEach(task -> {
+                            // relatedPlatformPair.put(task.getPlatformGroup(), task.getWellName());
+                            relatedPlatformsPair.put(task.getId(), task.getWellName());
+                        });
+                    }
+                }
+            }
+
+            // 平台井 主井施工完成 需要显示 关联井的施工状态 井号
+            if (CollUtil.isNotEmpty(relatedWellReports)) {
+                Map<String, IotRdDailyReportDO> relatedPlatformGroupPair = new HashMap<>();
+                relatedWellReports.forEach(report -> {
+                    if (2 == report.getPlatformWell()) {
+                        // 找到当前页所有的关联井 如果有多个关联井 取排列在最前面的关联井
+                        if (!relatedPlatformGroupPair.containsKey(report.getPlatformGroup())) {
+                            relatedPlatformGroupPair.put(report.getPlatformGroup(), report);
+                        }
+                        // 查询关联井的 非生产时间填写情况 只要有1个关联井填写了 非生产时间 就显示为 已填写
+                        IotRdDailyReportRespVO reportRespVO = BeanUtils.toBean(report, IotRdDailyReportRespVO.class);
+                        Boolean flag = DailyReportUtils.setNoProductFlag(reportRespVO);
+                        if (flag) {
+                            reportNonProductFlagPair.put(report.getPlatformGroup(), true);
+                        }
+                        // 设置平台井 的非生产时效 所有 井数据 累加平均
+                        if (CollUtil.isNotEmpty(report.getExtProperty()) || StrUtil.isNotBlank(report.getRdStatus())) {
+                            // 说明当前日报设置过 工作量 施工状态
+                            IotRdDailyReportRespVO rdDailyReportResp = BeanUtils.toBean(report, IotRdDailyReportRespVO.class);
+                            BigDecimal nonProductTime = DailyReportUtils.calculateNonProductTimeResp(rdDailyReportResp);
+                            if (platformNonProductPair.containsKey(report.getPlatformGroup())) {
+                                List<BigDecimal> tempSumNpts = platformNonProductPair.get(report.getPlatformGroup());
+                                tempSumNpts.add(nonProductTime);
+                                platformNonProductPair.put(report.getPlatformGroup(), tempSumNpts);
+                            } else {
+                                List<BigDecimal> tempSumNpts = new ArrayList<>();
+                                tempSumNpts.add(nonProductTime);
+                                platformNonProductPair.put(report.getPlatformGroup(), tempSumNpts);
+                            }
+                        }
+                    }
+                });
+                relatedWellReports.forEach(report -> {
+                    // 找到施工状态为空的主井
+                    if (1 == report.getPlatformWell() && StrUtil.isBlank(report.getRdStatus())
+                            && finishedMainPlatformIds.contains(report.getTaskId())) {
+                        nullStatusPlatformIds.add(report.getTaskId());
+                        // 找到当前主井对应的关联井
+                        if (relatedPlatformGroupPair.containsKey(report.getPlatformGroup())) {
+                            IotRdDailyReportDO tempReport = relatedPlatformGroupPair.get(report.getPlatformGroup());
+                            tempReport.getRdStatus();
+                            tempReport.getTaskId();
+                            if (relatedPlatformsPair.containsKey(tempReport.getTaskId())) {
+                                String wellName = relatedPlatformsPair.get(tempReport.getTaskId());
+                                relatedPlatformPair.put(tempReport.getPlatformGroup(), wellName);
+                                relatedWellStatusPair.put(tempReport.getPlatformGroup(), tempReport.getRdStatus());
+                            }
+                        }
+                    }
+
+                });
+            }
+
+            // 添加临时创建的日报相关的 带班干部 创建人 id
+            reports.forEach(report -> {
+                if (CollUtil.isNotEmpty(report.getResponsiblePerson())) {
+                    userIds.addAll(report.getResponsiblePerson());
+                }
+                if (StrUtil.isNotBlank(report.getCreator())) {
+                    userIds.add(Long.valueOf(report.getCreator()));
+                }
+                // 查询所有设备信息 设备名称 设备编码
+                if (CollUtil.isNotEmpty(deviceIds)) {
+                    Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(new ArrayList<>(deviceIds));
+                    deviceMap.forEach((deviceId, device) -> {
+                        deviceNamesPair.put(String.valueOf(deviceId), StrUtil.join("-", device.getDeviceCode(), device.getDeviceName()));
+                    });
+                }
+
+            });
+
+            // 查询当前页所有日报关联的任务的施工队伍名称
+            Map<Long, DeptDO> deptMap = deptService.getDeptMap(currentPageDeptIds);
+            // 查询所有 带班干部 填报人 的姓名
+            Map<Long, AdminUserRespDTO> userMap;
+            if (CollUtil.isNotEmpty(userIds)) {
+                userMap = adminUserApi.getUserMap(userIds);
+            } else {
+                userMap = Collections.emptyMap();
+            }
+            if (CollUtil.isNotEmpty(tasks)) {
+                tasks.forEach(task -> {
+                    // 安全获取施工队伍ID集合(避免null)
+                    Set<Long> deptIds = Optional.ofNullable(task.getDeptIds())
+                            .orElse(Collections.emptySet());
+                    // 转换ID集合为队伍名称字符串(用逗号分隔)
+                    String deptNames = deptIds.stream()
+                            // 映射ID到队伍名称(队伍不存在时用空字符串)
+                            .map(deptId -> Optional.ofNullable(deptMap.get(deptId))
+                                    .map(DeptDO::getName)
+                                    .orElse(""))
+                            // 过滤空字符串(避免多余逗号)
+                            .filter(name -> !name.isEmpty())
+                            // 拼接部门名称
+                            .collect(Collectors.joining(","));
+                    // 存入映射关系
+                    taskTeamsPair.put(task.getId(), deptNames);
+                    // 安全获取带班干部ID集合(避免null)
+                    Set<Long> responsibleIds = Optional.ofNullable(task.getResponsiblePerson())
+                            .orElse(Collections.emptySet());
+                    // 转换ID集合为姓名字符串(用逗号分隔)
+                    String responsibleNames = responsibleIds.stream()
+                            // 映射ID到姓名(用户不存在时用空字符串)
+                            .map(userId -> Optional.ofNullable(userMap.get(userId))
+                                    .map(AdminUserRespDTO::getNickname)
+                                    .orElse(""))
+                            // 过滤空字符串(避免多余逗号)
+                            .filter(name -> !name.isEmpty())
+                            // 拼接姓名
+                            .collect(Collectors.joining(","));
+                    // 存入映射关系
+                    taskResponsiblePair.put(task.getId(), responsibleNames);
+                    // 工单填报人
+                    Set<Long> submitterIds = Optional.ofNullable(task.getSubmitter())
+                            .orElse(Collections.emptySet());
+                    // 转换ID集合为姓名字符串(用逗号分隔)
+                    String submitterNames = submitterIds.stream()
+                            // 映射ID到姓名(用户不存在时用空字符串)
+                            .map(userId -> Optional.ofNullable(userMap.get(userId))
+                                    .map(AdminUserRespDTO::getNickname)
+                                    .orElse(""))
+                            // 过滤空字符串(避免多余逗号)
+                            .filter(name -> !name.isEmpty())
+                            // 拼接姓名
+                            .collect(Collectors.joining(","));
+                    // 存入映射关系
+                    taskSubmitterPair.put(task.getId(), submitterNames);
+                });
+            } else {
+                // 临时新建的日报 没有关联项目 任务
+            }
+            // 统计计算出平台井中每个井的 非生产时效
+            if (CollUtil.isNotEmpty(platformNonProductPair)) {
+                platformNonProductPair.forEach((platformGroup, npts) -> {
+                    // npts是各平台井的非生产时间累计和集合
+                    // 1. 计算分子:集合中所有BigDecimal元素的总和
+                    BigDecimal numerator = npts.stream()
+                            .reduce(BigDecimal.ZERO, BigDecimal::add);
+                    int denominatorInt = npts.size() * 24;
+                    BigDecimal denominator = BigDecimal.valueOf(denominatorInt);
+                    BigDecimal nonProductionRate;
+                    if (denominator.compareTo(BigDecimal.ZERO) == 0) {
+                        nonProductionRate = BigDecimal.ZERO; // 分母为0时比率设为0
+                    } else {
+                        // 除法:指定精度(保留4位小数)和舍入模式(四舍五入),可根据业务调整
+                        nonProductionRate = numerator.divide(denominator, 4, RoundingMode.HALF_UP);
+                    }
+                    platformNptPair.put(platformGroup, nonProductionRate);
+                } );
+            }
+            // 查询所有任务的工作量数据 单位相同的工作量属性值合并处理
+            for (IotRdDailyReportDO report : reports) {
+                // 设置每个日报关联的 设备信息集合
+                Set<Long> thisDeviceIds = report.getDeviceIds();
+                String thisDeviceCodeNames = Optional.ofNullable(thisDeviceIds)
+                        .filter(CollUtil::isNotEmpty)
+                        .map(devIds -> devIds.stream()
+                                .map(String::valueOf)  // 将 Long 转为 String
+                                .map(deviceNamesPair::get)  // 从字典获取名称
+                                .filter(Objects::nonNull)  // 过滤空值
+                                .filter(StrUtil::isNotBlank)  // 过滤空白值
+                                .collect(Collectors.joining(",")))  // 用逗号连接
+                        .orElse("");  // 如果为空,返回空字符串
+                reportDeviceNamesPair.put(report.getId(), thisDeviceCodeNames);
+
+                // 处理临时创建的日报 带班干部 填报人 有单独的逻辑规则
+                Set<Long> personIds = report.getResponsiblePerson();
+                if (CollUtil.isNotEmpty(personIds)) {
+                    // tempRespPersonIds.addAll(personIds);
+                    // 转换ID集合为姓名字符串(用逗号分隔)
+                    String respNames = personIds.stream()
+                            // 映射ID到姓名(用户不存在时用空字符串)
+                            .map(userId -> Optional.ofNullable(userMap.get(userId))
+                                    .map(AdminUserRespDTO::getNickname)
+                                    .orElse(""))
+                            // 过滤空字符串(避免多余逗号)
+                            .filter(name -> !name.isEmpty())
+                            // 拼接姓名
+                            .collect(Collectors.joining(","));
+                    tempReportResponsiblePair.put(report.getId(), respNames);
+                }
+                String creator = report.getCreator();
+                if (StrUtil.isNotBlank(creator)) {
+                    Long creatorId = Long.valueOf(creator);
+                    Set<Long> creatorIds = new HashSet<>();
+                    creatorIds.add(creatorId);
+                    String creatorNames = creatorIds.stream()
+                            // 映射ID到姓名(用户不存在时用空字符串)
+                            .map(userId -> Optional.ofNullable(userMap.get(userId))
+                                    .map(AdminUserRespDTO::getNickname)
+                                    .orElse(""))
+                            // 过滤空字符串(避免多余逗号)
+                            .filter(name -> !name.isEmpty())
+                            // 拼接姓名
+                            .collect(Collectors.joining(","));
+                    tempSubmitterPair.put(report.getId(), creatorNames);
+                }
+                // 设置每个任务的工作量数据  单位相同的工作量值作合并处理
+                List<IotTaskAttrModelProperty> taskAttrs = report.getExtProperty();
+                // 这里暂时使用枚举 统计每个单位下的 工作量
+                // 桥塞(个数)      钻可溶桥塞  钻复合桥塞           bridge_plug
+                // 趟数           通刮洗 冲砂                    run_count
+                // 井数           累计施工-井                    cumulative_working_well
+                // 小时H                                       hour_count
+                // 天数D
+                // 水方量(方)                                   water_volume
+                // 台次           当日泵车台次 当日仪表/混砂        daily_tools_sand   daily_pump_trips
+                // 段数           累计施工-层                    cumulative_working_layers
+                // 暂存不同单位的工作量属性值
+                BigDecimal tempTotalBridgePlug = BigDecimal.ZERO;
+                BigDecimal tempTotalRunCount = BigDecimal.ZERO;
+                BigDecimal tempTotalCumulativeWorkingWell = BigDecimal.ZERO;
+                BigDecimal tempTotalHourCount = BigDecimal.ZERO;
+                BigDecimal tempTotalWaterVolume = BigDecimal.ZERO;
+                BigDecimal tempTotalPumpTrips = BigDecimal.ZERO;
+                BigDecimal tempTotalCumulativeWorkingLayers = BigDecimal.ZERO;
+                BigDecimal tempTotalMixSand = BigDecimal.ZERO;
+                if (CollUtil.isNotEmpty(taskAttrs)) {
+                    for (IotTaskAttrModelProperty attr : taskAttrs) {
+                        String unit = attr.getUnit();   // 工作量单位
+                        String actualValueStr = attr.getActualValue();  // 工作量属性的实际值
+                        // 处理实际值:避免null或非数字字符串导致的异常
+                        BigDecimal actualValue = BigDecimal.ZERO;
+                        if (StrUtil.isNotBlank(actualValueStr)) {  // 假设使用Hutool的StrUtil,或自行判断null/空
+                            try {
+                                actualValue = new BigDecimal(actualValueStr);
+                            } catch (NumberFormatException e) {
+                                // 若字符串格式错误,默认按0处理(可根据业务调整)
+                                actualValue = BigDecimal.ZERO;
+                            }
+                        }
+                        if ("个数".equals(unit)) {
+                            // 钻可溶桥塞  钻复合桥塞
+                            tempTotalBridgePlug = tempTotalBridgePlug.add(actualValue);
+                        }
+                        if ("趟数".equals(unit)) {
+                            tempTotalRunCount = tempTotalRunCount.add(actualValue);
+                        }
+                        if ("小时".equals(unit)) {
+                            tempTotalHourCount = tempTotalHourCount.add(actualValue);
+                        }
+                        if ("天数".equals(unit)) {
+                            // 将 actualValue 换算成 H
+                            BigDecimal hours = actualValue.multiply(new BigDecimal("24"));
+                            tempTotalHourCount = tempTotalHourCount.add(hours);
+                        }
+                        if ("方".equals(unit)) {
+                            tempTotalWaterVolume = tempTotalWaterVolume.add(actualValue);
+                        }
+                        if ("井数".equals(unit)) {
+                            tempTotalCumulativeWorkingWell = tempTotalCumulativeWorkingWell.add(actualValue);
+                        }
+                        if ("段数".equals(unit)) {
+                            tempTotalCumulativeWorkingLayers = tempTotalCumulativeWorkingLayers.add(actualValue);
+                        }
+                        if ("台次".equals(unit) && "当日泵车台次".equals(attr.getName())) {
+                            tempTotalPumpTrips = tempTotalPumpTrips.add(actualValue);
+                        }
+                        if ("台次".equals(unit) && ("当日仪表/混砂".equals(attr.getName()) || "当日仪表".equals(attr.getName()) || "当日混砂".equals(attr.getName()))) {
+                            tempTotalMixSand = tempTotalMixSand.add(actualValue);
+                        }
+                    }
+                    // 关联的任务完工 井数 +1
+                    if (finishedTaskIds.contains(report.getTaskId())) {
+                        report.setCumulativeWorkingWell(new BigDecimal(1));
+                    }
+                    report.setCumulativeWorkingLayers(tempTotalCumulativeWorkingLayers);
+                    report.setDailyPumpTrips(tempTotalPumpTrips);
+                    report.setDailyToolsSand(tempTotalMixSand);
+                    report.setRunCount(tempTotalRunCount);
+                    report.setBridgePlug(tempTotalBridgePlug);
+                    report.setWaterVolume(tempTotalWaterVolume);
+                    report.setHourCount(tempTotalHourCount);
+                }
+            }
+            // 查询所有队伍对应的上级部门 sort
+            Set<Long> parentDeptIds = new HashSet<>();
+            if (CollUtil.isNotEmpty(tempDeptMap)) {
+                tempDeptMap.forEach((deptId, dept) -> {
+                    parentDeptIds.add(dept.getParentId());
+                    projectIdPair.put(deptId, dept.getParentId());
+                });
+                // 查询所有上级项目部信息
+                Map<Long, DeptDO> projectDeptMap = deptService.getDeptMap(parentDeptIds);
+                if (CollUtil.isNotEmpty(projectDeptMap)) {
+                    projectDeptMap.forEach((deptId, dept) -> {
+                        projectSortPair.put(deptId, dept.getSort());
+                        projectNamePair.put(deptId, dept.getName());
+                    });
+                }
+            }
+        });
+        return BeanUtils.toBean(reports, IotRdDailyReportRespVO.class, (reportVO) -> {
+            // 施工状态 导出使用
+            if (StrUtil.isNotBlank(reportVO.getRdStatus())) {
+                if (constructStatusPair.containsKey(reportVO.getRdStatus())) {
+                    reportVO.setRdStatusLabel(constructStatusPair.get(reportVO.getRdStatus()));
+                }
+            }
+            // 计算非生产时效 如果是平台井 需要计算 所有平台井 的 非生产时效
+            BigDecimal nonProductionTimes = DailyReportUtils.calculateNonProductTimeResp(reportVO);
+            reportVO.setNonProductionRate(nonProductionTimes.divide(BigDecimal.valueOf(24), 4, RoundingMode.HALF_UP ));
+
+            // 非生产时效 百分比格式 导出使用
+            if (ObjUtil.isNotEmpty(reportVO.getNonProductionRate())) {
+                // 获取原始小数
+                BigDecimal nptRate = reportVO.getNonProductionRate();
+                // 乘以100转换为百分比数值
+                BigDecimal percentage = nptRate.multiply(BigDecimal.valueOf(100));
+                // 格式化保留2位小数
+                DecimalFormat df = new DecimalFormat("0.00");
+                String nonProductionRate = df.format(percentage) + "%";
+                // 赋值
+                reportVO.setNonProductionRateFormat(nonProductionRate);
+            }
+
+            // 设置非生产时间填写标识 noProductFlag 为 true 或者 processInstanceId 为 2 的时候 显示 已填写
+            reportVO.setNonProductFlag(DailyReportUtils.setNoProductFlag(reportVO));
+            // 创建时间 格式化 yyyy-MM-dd
+            if (ObjUtil.isNotEmpty(reportVO.getCreateTime())) {
+                reportVO.setCreateTimeStr(LocalDateTimeUtil.format(reportVO.getCreateTime(), DatePattern.NORM_DATE_PATTERN));
+            }
+
+            // 日报 关联的施工工艺名称集合
+            findAndThen(techniqueNamesPair, reportVO.getId(), techniqueNames -> reportVO.setTechniqueNames(techniqueNames));
+            // 日报 关联的设备名称集合
+            findAndThen(reportDeviceNamesPair, reportVO.getId(), deviceNames -> reportVO.setDeviceNames(deviceNames));
+            // 日报 所属 上级项目部
+            findAndThen(projectIdPair, reportVO.getDeptId(), projectId -> reportVO.setProjectDeptId(projectId));
+            // 生产动态明细
+            findAndThen(reportDetailsPair, reportVO.getId(), details -> reportVO.setReportDetails(details));
+            // 平台井 非生产时效
+            findAndThen(platformNptPair, reportVO.getPlatformGroup(), npt -> reportVO.setNonProductionRate(npt));
+            // 部门信息 任务中关联的施工队伍
+            findAndThen(taskTeamsPair, reportVO.getTaskId(), deptNames -> reportVO.setDeptName(deptNames));
+            // 临时新建的日报的 施工队伍
+            findAndThen(tempDeptMap, reportVO.getDeptId(), dept -> reportVO.setDeptName(dept.getName()));
+            // 所有施工队伍的排序值
+            findAndThen(tempDeptMap, reportVO.getDeptId(), dept -> reportVO.setTeamSort(dept.getSort()));
+            // 所有施工队伍所属项目部的名称
+            findAndThen(projectNamePair, reportVO.getProjectDeptId(), name -> reportVO.setProjectName(name));
+            // 所有施工队伍所属项目部的排序值
+            findAndThen(projectSortPair, reportVO.getProjectDeptId(), sort -> reportVO.setProjectSort(sort));
+            // 日报关联的项目信息
+            findAndThen(projectPair, reportVO.getProjectId(), contractName -> reportVO.setContractName(contractName));
+            // 日报关联的任务井号
+            findAndThen(taskPair, reportVO.getTaskId(), taskName -> reportVO.setTaskName(taskName));
+            // 日报关联的任务施工区域
+            findAndThen(taskLocationPair, reportVO.getTaskId(), location -> reportVO.setLocation(location));
+            // 日报关联的任务信息(兼容主井完工 关联井未完工的情况)
+            findAndThen(relatedPlatformPair, reportVO.getPlatformGroup(), relatedTaskName -> reportVO.setTaskName(relatedTaskName));
+            // 平台井相关的非生产时间标识
+            findAndThen(reportNonProductFlagPair, reportVO.getPlatformGroup(), nonProductFlag -> reportVO.setNonProductFlag(nonProductFlag));
+            // 日报关联的任务施工状态(兼容主井完工 关联井未完工的情况)
+            findAndThen(relatedWellStatusPair, reportVO.getPlatformGroup(), rdStatus -> reportVO.setRdStatus(rdStatus));
+            // 日报关联的责任人
+            findAndThen(taskResponsiblePair, reportVO.getTaskId(), responsibleNames -> reportVO.setResponsiblePersonNames(responsibleNames));
+            // 临时新增日报关联的带班干部
+            findAndThen(tempReportResponsiblePair, reportVO.getId(), responsibleNames -> reportVO.setResponsiblePersonNames(responsibleNames));
+            // 日报关联的填报人
+            findAndThen(taskSubmitterPair, reportVO.getTaskId(), submitterNames -> reportVO.setSubmitterNames(submitterNames));
+            // 临时新建的日报关联的填报人
+            findAndThen(tempSubmitterPair, reportVO.getId(), submitterNames -> reportVO.setSubmitterNames(submitterNames));
+            if (ObjUtil.isEmpty(reportVO.getProjectId()) && ObjUtil.isEmpty(reportVO.getTaskId())) {
+                reportVO.setContractName("无工作量");
+                reportVO.setTaskName("无工作量");
+            }
+        });
+    }
+
     @Override
     public List<IotRdDailyReportDO> dailyReports(IotRdDailyReportPageReqVO pageReqVO) {
         return iotRdDailyReportMapper.dailyReports(pageReqVO);
@@ -895,7 +1510,7 @@ public class IotRdDailyReportServiceImpl implements IotRdDailyReportService {
                 }
 
                 // 统计每个日报的 非生产时效 任务的非生产时效 任务关联的日报的 sum(非生产时间)/24n
-                BigDecimal sumNpt = calculateNonProductTime(report);
+                BigDecimal sumNpt = DailyReportUtils.calculateNonProductTime(report);
                 if (taskNonProductValuesPair.containsKey(report.getTaskId())) {
                     List<BigDecimal> tempNptValues = taskNonProductValuesPair.get(report.getTaskId());
                     tempNptValues.add(sumNpt);
@@ -1175,31 +1790,6 @@ public class IotRdDailyReportServiceImpl implements IotRdDailyReportService {
         return result;
     }
 
-    /**
-     *  计算非生产时间累加值 计算非生产时效
-     *  NPT 归类 设备故障repair_time 工程质量accident_time 技术受限complexity_time 生产组织rectification_time 不可抗力waiting_stop_time
-     *      甲方设计partya_design 甲方准备partya_prepare 甲方资源partya_resource 其他非生产时间other_npt_time
-     * @param dailyReport
-     * @return
-     */
-    private BigDecimal calculateNonProductTime(IotRdDailyReportDO dailyReport){
-        return Stream.of(
-                        dailyReport.getAccidentTime(),
-                        dailyReport.getRepairTime(),
-                        dailyReport.getComplexityTime(),
-                        dailyReport.getRectificationTime(),
-                        dailyReport.getWaitingStopTime(),
-                        dailyReport.getPartyaDesign(),    // 注:字段名虽无Time但属于非生产时间,保留
-                        dailyReport.getPartyaPrepare(),
-                        dailyReport.getPartyaResource(),
-                        dailyReport.getOtherNptTime()
-                )
-                // 处理null值,避免调用add()时抛出NullPointerException
-                .map(bd -> Objects.isNull(bd) ? BigDecimal.ZERO : bd)
-                // Stream.reduce:从初始值0开始,依次累加所有元素
-                .reduce(BigDecimal.ZERO, BigDecimal::add);
-    }
-
     @Override
     public List<IotRdDailyReportStatisticsRespVO> summaryStatistics(IotRdDailyReportPageReqVO pageReqVO) {
         List<IotRdDailyReportStatisticsRespVO> result = new ArrayList<>();

+ 90 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/util/DailyReportUtils.java

@@ -0,0 +1,90 @@
+package cn.iocoder.yudao.module.pms.util;
+
+import cn.iocoder.yudao.module.pms.controller.admin.iotrddailyreport.vo.IotRdDailyReportRespVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotrddailyreport.IotRdDailyReportDO;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+public class DailyReportUtils {
+
+    /**
+     *  检测是否已经维护了生产时效
+     * @param dailyReport
+     * @return
+     */
+    public static Boolean setNoProductFlag(IotRdDailyReportRespVO dailyReport){
+        BigDecimal accidentTime = dailyReport.getAccidentTime();
+        BigDecimal repairTime = dailyReport.getRepairTime();
+        BigDecimal selfStopTime = dailyReport.getSelfStopTime();
+        BigDecimal complexityTime = dailyReport.getComplexityTime();
+        BigDecimal relocationTime = dailyReport.getRelocationTime();
+        BigDecimal rectificationTime = dailyReport.getRectificationTime();
+        BigDecimal waitingStopTime = dailyReport.getWaitingStopTime();
+        BigDecimal winterBreakTime = dailyReport.getWinterBreakTime();
+        BigDecimal partyaDesignTime = dailyReport.getPartyaDesign();
+        BigDecimal partyaPrepareTime = dailyReport.getPartyaPrepare();
+        BigDecimal partyaResourceTime = dailyReport.getPartyaResource();
+        BigDecimal otherNptTime = dailyReport.getOtherNptTime();
+        if (accidentTime.compareTo(BigDecimal.ZERO) > 0 || repairTime.compareTo(BigDecimal.ZERO) > 0 || selfStopTime.compareTo(BigDecimal.ZERO) > 0
+                || complexityTime.compareTo(BigDecimal.ZERO) > 0 || relocationTime.compareTo(BigDecimal.ZERO) > 0 || rectificationTime.compareTo(BigDecimal.ZERO) > 0
+                || waitingStopTime.compareTo(BigDecimal.ZERO) > 0 || winterBreakTime.compareTo(BigDecimal.ZERO) > 0 || partyaDesignTime.compareTo(BigDecimal.ZERO) > 0
+                || partyaPrepareTime.compareTo(BigDecimal.ZERO) > 0 || partyaResourceTime.compareTo(BigDecimal.ZERO) > 0 || otherNptTime.compareTo(BigDecimal.ZERO) > 0) {
+            // 设置非生产时间填写标识
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     *  计算非生产时间累加值 计算非生产时效
+     *  NPT 归类 设备故障repair_time 工程质量accident_time 技术受限complexity_time 生产组织rectification_time 不可抗力waiting_stop_time
+     *      甲方设计partya_design 甲方准备partya_prepare 甲方资源partya_resource 其他非生产时间other_npt_time
+     * @param dailyReport
+     * @return
+     */
+    public static BigDecimal calculateNonProductTime(IotRdDailyReportDO dailyReport){
+        return Stream.of(
+                        dailyReport.getAccidentTime(),
+                        dailyReport.getRepairTime(),
+                        dailyReport.getComplexityTime(),
+                        dailyReport.getRectificationTime(),
+                        dailyReport.getWaitingStopTime(),
+                        dailyReport.getPartyaDesign(),    // 注:字段名虽无Time但属于非生产时间,保留
+                        dailyReport.getPartyaPrepare(),
+                        dailyReport.getPartyaResource(),
+                        dailyReport.getOtherNptTime()
+                )
+                // 处理null值,避免调用add()时抛出NullPointerException
+                .map(bd -> Objects.isNull(bd) ? BigDecimal.ZERO : bd)
+                // Stream.reduce:从初始值0开始,依次累加所有元素
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
+
+    /**
+     *  计算非生产时间累加值 计算非生产时效
+     *  NPT 归类 设备故障repair_time 工程质量accident_time 技术受限complexity_time 生产组织rectification_time 不可抗力waiting_stop_time
+     *      甲方设计partya_design 甲方准备partya_prepare 甲方资源partya_resource 其他非生产时间other_npt_time
+     * @param dailyReport
+     * @return
+     */
+    public static BigDecimal calculateNonProductTimeResp(IotRdDailyReportRespVO dailyReport){
+        return Stream.of(
+                        dailyReport.getAccidentTime(),
+                        dailyReport.getRepairTime(),
+                        dailyReport.getComplexityTime(),
+                        dailyReport.getRectificationTime(),
+                        dailyReport.getWaitingStopTime(),
+                        dailyReport.getPartyaDesign(),    // 注:字段名虽无Time但属于非生产时间,保留
+                        dailyReport.getPartyaPrepare(),
+                        dailyReport.getPartyaResource(),
+                        dailyReport.getOtherNptTime()
+                )
+                // 处理null值,避免调用add()时抛出NullPointerException
+                .map(bd -> Objects.isNull(bd) ? BigDecimal.ZERO : bd)
+                // Stream.reduce:从初始值0开始,依次累加所有元素
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+    }
+
+}