Przeglądaj źródła

pms 瑞鹰定时任务生成 生产动态 列表 钉钉消息

zhangcl 17 godzin temu
rodzic
commit
fbfbb14943
12 zmienionych plików z 645 dodań i 326 usunięć
  1. 3 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/constant/PmsConstants.java
  2. 5 323
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/IotRyDailyReportController.java
  3. 3 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/vo/IotRyDailyReportRespVO.java
  4. 1 1
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/dailyreport/CreateRdDailyReportStatisticsJob.java
  5. 1 1
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/dailyreport/CreateRhDailyReportStatisticsJob.java
  6. 203 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/dailyreport/CreateRyDailyReportStatisticsJob.java
  7. 9 1
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/message/PmsMessage.java
  8. 13 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrydailyreport/IotRyDailyReportService.java
  9. 397 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrydailyreport/IotRyDailyReportServiceImpl.java
  10. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptRespVO.java
  11. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java
  12. 4 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dept/DeptDO.java

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

@@ -55,4 +55,7 @@ public interface PmsConstants {
 
     // 油服日报汇总生产动态消息提醒
     String YF_PRODUCTION_STATUS = "yf-production-status";
+
+    // 瑞鹰日报汇总生产动态消息提醒
+    String RY_PRODUCTION_STATUS = "ry-production-status";
 }

+ 5 - 323
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/IotRyDailyReportController.java

@@ -56,7 +56,6 @@ import java.time.LocalDateTime;
 import java.util.*;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -305,8 +304,9 @@ public class IotRyDailyReportController {
         }
 
         PageResult<IotRyDailyReportDO> pageResult = iotRyDailyReportService.getIotRyDailyReportPage(pageReqVO);
-
-        return success(new PageResult<>(buildRyDailyReports(pageResult.getList(), pageReqVO, "list"), pageResult.getTotal()));
+        // 数据转换与清洗
+        List<IotRyDailyReportRespVO> reportVOs = iotRyDailyReportService.getIotRyDailyReports(pageResult.getList(), pageReqVO);
+        return success(new PageResult<>(reportVOs, pageResult.getTotal()));
     }
 
     @GetMapping("/productionBriefs")
@@ -761,7 +761,7 @@ public class IotRyDailyReportController {
                 reportVO.setOtherProductionTime(reportVO.getRelocationTime());
             }
             // 计算非生产时效
-            BigDecimal sumNpt = calculateNonProductTime(reportVO);
+            BigDecimal sumNpt = iotRyDailyReportService.calculateNonProductTime(reportVO);
             // 非生产时间加和
             reportVO.setNonProductionTime(sumNpt);
             reportVO.setNonProductionRate(sumNpt.divide(BigDecimal.valueOf(24), 4, RoundingMode.HALF_UP ));
@@ -830,276 +830,6 @@ public class IotRyDailyReportController {
                 .collect(Collectors.toList());
     }
 
-    /**
-     * 瑞恒日报分页 设置关联查询信息
-     * @param reports
-     * @return
-     */
-    private List<IotRyDailyReportRespVO> buildRyDailyReports(List<IotRyDailyReportDO> reports, IotRyDailyReportPageReqVO pageReqVO, String type) {
-        if (CollUtil.isEmpty(reports)) {
-            return Collections.emptyList();
-        }
-        // 设备部门信息
-        Map<Long, DeptDO> deptMap = deptService.getDeptMap(convertList(reports, IotRyDailyReportDO::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, BigDecimal> taskExtPropertyPair = new HashMap<>();
-        // key任务id   value额定生产时间
-        Map<Long, BigDecimal> taskRatedProductionTimePair = new HashMap<>();
-        // key任务id   value套生段产管尺寸
-        Map<Long, String> taskCasingPipeSizePair = new HashMap<>();
-        // key任务id   value井控级别
-        Map<Long, String> taskWellControlLevelPair = new HashMap<>();
-        // key任务id   value施工工艺
-        Map<Long, String> taskTechniquePair = new HashMap<>();
-        // key任务id   value井别
-        Map<Long, String> taskWellCategoryPair = new HashMap<>();
-        // key任务id   value设计井身结构
-        Map<Long, String> taskWellStructPair = new HashMap<>();
-        // key部门id  value设备型号编号
-        AtomicReference<Map<Long, String>> equipmentTypePair = new AtomicReference<>(new HashMap<>());
-        // key施工队伍id    value最近完工日期
-        Map<Long, LocalDateTime> latestWellDoneTimePair = new HashMap<>();
-        // key施工队伍id    value施工井数量
-        Map<Long, Integer> totalTasksPair = new HashMap<>();
-        // key施工队伍id    value完工井数量
-        Map<Long, Integer> completedTasksPair = new HashMap<>();
-        // key非生产时间原因数据字典value   value非生产时间原因数据字典label
-        Map<String, String> nptReasonPair = new HashMap<>();
-        // key施工状态数据字典value   value施工状态数据字典label
-        Map<String, String> constructStatusPair = new HashMap<>();
-        // key日报id   value生产动态明细
-        Map<Long, List<IotRyDailyReportDetailRespVO>> reportDetailsPair = new HashMap<>();
-
-        // 钻井 施工状态 字典数据
-        List<DictDataDO> rigStatusDictData = dictDataService.getDictDataListByDictType("rigStatus");
-        // 修井 施工状态 字典数据
-        List<DictDataDO> repairStatusDictData = dictDataService.getDictDataListByDictType("repairStatus");
-        // 非生产时间原因 字典数据
-        List<DictDataDO> nptReasonDictData = dictDataService.getDictDataListByDictType("ryNptReason");
-
-        if (CollUtil.isNotEmpty(nptReasonDictData)) {
-            nptReasonDictData.forEach(data -> {
-                nptReasonPair.put(data.getValue(), data.getLabel());
-            });
-        }
-        // 钻井施工状态
-        if ("1".equals(pageReqVO.getProjectClassification()) && CollUtil.isNotEmpty(rigStatusDictData)) {
-            rigStatusDictData.forEach(data -> {
-                constructStatusPair.put(data.getValue(), data.getLabel());
-            });
-        }
-        // 修井施工状态
-        if ("2".equals(pageReqVO.getProjectClassification()) && CollUtil.isNotEmpty(repairStatusDictData)) {
-            repairStatusDictData.forEach(data -> {
-                constructStatusPair.put(data.getValue(), data.getLabel());
-            });
-        }
-        // 查询每个日报的生产动态明细
-        IotRyDailyReportDetailPageReqVO detailReqVO = new IotRyDailyReportDetailPageReqVO();
-        detailReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
-        detailReqVO.setReportIds(convertList(reports, IotRyDailyReportDO::getId));
-        PageResult<IotRyDailyReportDetailDO> reportDetailsPage = iotRyDailyReportDetailMapper.selectPage(detailReqVO);
-        List<IotRyDailyReportDetailDO> reportDetails = reportDetailsPage.getList();
-        if (CollUtil.isNotEmpty(reportDetails)) {
-            // 设置每个日报的生产动态明细
-            reportDetails.forEach(detail -> {
-                if (reportDetailsPair.containsKey(detail.getReportId())) {
-                    List<IotRyDailyReportDetailRespVO> tempDetails = reportDetailsPair.get(detail.getReportId());
-                    tempDetails.add(BeanUtils.toBean(detail, IotRyDailyReportDetailRespVO.class));
-                    reportDetailsPair.put(detail.getReportId(), tempDetails);
-                } else {
-                    List<IotRyDailyReportDetailRespVO> tempDetails = new ArrayList<>();
-                    tempDetails.add(BeanUtils.toBean(detail, IotRyDailyReportDetailRespVO.class));
-                    reportDetailsPair.put(detail.getReportId(), tempDetails);
-                }
-            });
-        }
-        DataPermissionUtils.executeIgnore(() -> {
-            // 查询日报关联的项目信息
-            IotProjectInfoPageReqVO reqVO = new IotProjectInfoPageReqVO();
-            reqVO.setProjectIds(convertList(reports, IotRyDailyReportDO::getProjectId));
-            List<IotProjectInfoDO> projects = iotProjectInfoService.getIotProjectInfos(reqVO);
-            if (CollUtil.isNotEmpty(projects)) {
-                projects.forEach(project -> {
-                    projectPair.put(project.getId(), project.getContractName());
-                });
-            }
-            // 查询日报关联的任务信息
-            IotProjectTaskPageReqVO taskReqVO = new IotProjectTaskPageReqVO();
-            taskReqVO.setTaskIds(convertList(reports, IotRyDailyReportDO::getTaskId));
-            List<IotProjectTaskDO> tasks = iotProjectTaskService.projectTasks(taskReqVO);
-            if (CollUtil.isNotEmpty(tasks)) {
-                tasks.forEach(task -> {
-                    taskPair.put(task.getId(), task.getWellName());
-                    taskLocationPair.put(task.getId(), task.getLocation());
-                    taskTechniquePair.put(task.getId(), task.getTechnique());
-                    if (CollUtil.isNotEmpty(task.getExtProperty())) {
-                        List<IotTaskAttrModelProperty> taskAttrs = task.getExtProperty();
-                        if (CollUtil.isNotEmpty(taskAttrs)) {
-                            // 找到 设计注气量 属性 对应的值
-                            taskAttrs.forEach(attr -> {
-                                if ("设计井深".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
-                                    taskExtPropertyPair.put(task.getId(), new BigDecimal(attr.getActualValue()));
-                                }
-                                if ("设计井身结构".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
-                                    taskWellStructPair.put(task.getId(), attr.getActualValue());
-                                }
-                                if ("额定生产时间".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
-                                    taskRatedProductionTimePair.put(task.getId(), new BigDecimal(attr.getActualValue()));
-                                }
-                                if ("套生段产管尺寸".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
-                                    taskCasingPipeSizePair.put(task.getId(), attr.getActualValue());
-                                }
-                                if ("井控级别".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
-                                    taskWellControlLevelPair.put(task.getId(), attr.getActualValue());
-                                }
-                                if ("井别".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
-                                    taskWellCategoryPair.put(task.getId(), attr.getActualValue());
-                                }
-                            });
-                        }
-                    }
-                });
-            }
-            // 查询当前日报所属施工队伍中包含 井架 的型号
-            equipmentTypePair.set(iotRyDailyReportService.queryEquipmentType(convertList(reports, IotRyDailyReportDO::getDeptId)));
-            // 查询当前日报所属部门的 上井次完井时间 已经绑定任务的情况下
-            List<IotRyDailyReportDO> latestWellDoneTimes = iotRyDailyReportService.latestWellDoneTimes(null);
-            if (CollUtil.isNotEmpty(latestWellDoneTimes)) {
-                latestWellDoneTimes.forEach(time -> {
-                    latestWellDoneTimePair.put(time.getDeptId(), time.getConstructionStartDate());
-                });
-            }
-            // 按施工队伍统计 钻井日报 施工井数 完工井数
-            List<IotRyDailyReportTaskCountVO> deptTasks = iotRyDailyReportService.countTasksByDept();
-            // 修井日报 不存在既填写钻井日报 又填写修井日报的队伍
-            if (ObjUtil.isNotEmpty(pageReqVO.getCreateTime())) {
-                List<IotRyDailyReportTaskCountVO> repairDeptTasks = iotRyDailyReportService.countRepairTasksByDept(pageReqVO);
-                if (CollUtil.isNotEmpty(repairDeptTasks)) {
-                    repairDeptTasks.forEach(task -> {
-                        totalTasksPair.put(task.getDeptId(), task.getTotalTaskCount());
-                        completedTasksPair.put(task.getDeptId(), task.getCompletedTaskCount());
-                    });
-                }
-            }
-            if (CollUtil.isNotEmpty(deptTasks)) {
-                deptTasks.forEach(task -> {
-                    totalTasksPair.put(task.getDeptId(), task.getTotalTaskCount());
-                    completedTasksPair.put(task.getDeptId(), task.getCompletedTaskCount());
-                });
-            }
-        });
-        // 2. 拼接数据
-        return BeanUtils.toBean(reports, IotRyDailyReportRespVO.class, (reportVO) -> {
-            // 油耗 精确到 2位小数
-            if (reportVO.getDailyFuel().compareTo(BigDecimal.ZERO) > 0) {
-                BigDecimal fuel = reportVO.getDailyFuel()
-                        .divide(BigDecimal.valueOf(1), 2, RoundingMode.HALF_UP);
-                reportVO.setDailyFuel(fuel);
-            }
-            // 将 ‘生产配合’ 的值赋值给 ‘其他非生产时间’
-            if (reportVO.getRelocationTime().compareTo(BigDecimal.ZERO) > 0) {
-                reportVO.setOtherProductionTime(reportVO.getRelocationTime());
-            }
-            // 计算非生产时效
-            BigDecimal sumNpt = calculateNonProductTime(reportVO);
-            reportVO.setNonProductionRate(sumNpt.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);
-            }
-
-            // 日报生成日期 格式化时间为 yyyy-MM-dd
-            if (ObjUtil.isNotEmpty(reportVO.getCreateTime())) {
-                reportVO.setCreateTimeStr(LocalDateTimeUtil.format(reportVO.getCreateTime(), DatePattern.NORM_DATE_PATTERN));
-            }
-            // 上井次完井时间 格式化时间为 yyyy-MM-dd
-            if (ObjUtil.isNotEmpty(reportVO.getLatestWellDoneTime())) {
-                reportVO.setLatestWellDoneTimeStr(LocalDateTimeUtil.format(reportVO.getLatestWellDoneTime(), DatePattern.NORM_DATE_PATTERN));
-            }
-            // 2.1 拼接部门信息
-            findAndThen(deptMap, reportVO.getDeptId(), dept -> reportVO.setDeptName(dept.getName()));
-            // 2.2 日报关联的项目信息
-            findAndThen(projectPair, reportVO.getProjectId(), contractName -> reportVO.setContractName(contractName));
-            // 2.3 日报关联的任务信息
-            findAndThen(taskPair, reportVO.getTaskId(), taskName -> reportVO.setTaskName(taskName));
-            // 日报关联的任务施工地点
-            findAndThen(taskLocationPair, reportVO.getTaskId(), location -> reportVO.setLocation(location));
-            // 2.4 设计井深
-            findAndThen(taskExtPropertyPair, reportVO.getTaskId(), wellDepth -> reportVO.setDesignWellDepth(wellDepth));
-            // 生产动态明细
-            findAndThen(reportDetailsPair, reportVO.getId(), details -> {
-                // 每个日报的明细 根据 reportDate+startTime 正序排列
-                details.sort(Comparator.comparing(
-                                // 处理 reportDate 为空的情况
-                                (IotRyDailyReportDetailRespVO detail) -> {
-                                    if (detail.getReportDate() == null) {
-                                        return null; // null 值会排在前面
-                                    }
-                                    return detail.getReportDate().toLocalDate();
-                                },
-                                // 自定义 null 值比较器:null 排在非 null 前面
-                                Comparator.nullsFirst(Comparator.naturalOrder())
-                        )
-                        .thenComparing(
-                                // 处理 startTime 为空的情况
-                                IotRyDailyReportDetailRespVO::getStartTime,
-                                // 自定义 null 值比较器:null 排在非 null 前面
-                                Comparator.nullsFirst(Comparator.naturalOrder())
-                        ));
-                reportVO.setReportDetails(details);
-            });
-            // 施工工艺
-            findAndThen(taskTechniquePair, reportVO.getTaskId(), technique -> reportVO.setTechnique(technique));
-            // 井别
-            findAndThen(taskWellCategoryPair, reportVO.getTaskId(), wellCategory -> reportVO.setWellCategory(wellCategory));
-            // 额定生产时间
-            findAndThen(taskRatedProductionTimePair, reportVO.getTaskId(), ratedProductionTime -> reportVO.setRatedProductionTime(ratedProductionTime));
-            // 套生段产管尺寸
-            findAndThen(taskCasingPipeSizePair, reportVO.getTaskId(), casingPipeSize -> reportVO.setCasingPipeSize(casingPipeSize));
-            // 井控级别
-            findAndThen(taskWellControlLevelPair, reportVO.getTaskId(), wellControlLevel -> reportVO.setWellControlLevel(wellControlLevel));
-            // 2.5 设计井身结构
-            findAndThen(taskWellStructPair, reportVO.getTaskId(), wellStruct -> reportVO.setDesignWellStruct(wellStruct));
-            // 2.6 设备型号
-            findAndThen(equipmentTypePair.get(), reportVO.getDeptId(), equipmentType -> reportVO.setEquipmentType(equipmentType));
-            // 2.7 上井次完井时间
-            findAndThen(latestWellDoneTimePair, reportVO.getDeptId(), latestWellDoneTime -> reportVO.setLatestWellDoneTime(latestWellDoneTime));
-            // 2.8 总施工井数 完工井数
-            findAndThen(totalTasksPair, reportVO.getDeptId(), totalTaskCount -> reportVO.setTotalConstructionWells(totalTaskCount));
-            findAndThen(completedTasksPair, reportVO.getDeptId(), completedTaskCount -> reportVO.setCompletedWells(completedTaskCount));
-            // 钻井 施工状态 数据字典
-            findAndThen(constructStatusPair, reportVO.getRigStatus(), statusLabel -> {
-                if ("1".equals(pageReqVO.getProjectClassification())) {
-                    reportVO.setConstructionStatusName(statusLabel);
-                }
-            });
-            // 修井 施工状态 数据字典
-            findAndThen(constructStatusPair, reportVO.getRepairStatus(), statusLabel -> {
-                if ("2".equals(pageReqVO.getProjectClassification())) {
-                    reportVO.setConstructionStatusName(statusLabel);
-                }
-            });
-            // 非生产时间原因 数据字典
-            findAndThen(nptReasonPair, reportVO.getRyNptReason(), statusLabel -> reportVO.setRyNptReason(statusLabel));
-        });
-    }
-
     /**
      * 瑞恒日报分页 设置关联查询信息
      * @param reports
@@ -1325,7 +1055,7 @@ public class IotRyDailyReportController {
                 reportVO.setOtherProductionTime(reportVO.getRelocationTime());
             }
             // 计算非生产时效
-            BigDecimal sumNpt = calDetailNonProductTime(reportVO);
+            BigDecimal sumNpt = iotRyDailyReportService.calDetailNonProductTime(reportVO);
             reportVO.setNonProductionRate(sumNpt.divide(BigDecimal.valueOf(24), 4, RoundingMode.HALF_UP ));
 
             // 非生产时效 百分比格式 导出使用
@@ -1399,55 +1129,7 @@ public class IotRyDailyReportController {
         });
     }
 
-    /**
-     *  计算非生产时间累加值 计算非生产时效
-     *  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(IotRyDailyReportRespVO 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
-     */
-    private BigDecimal calDetailNonProductTime(IotRyDailyReportDetailComboRespVO 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);
-    }
 
     /**
      * 瑞鹰日报分页 设置关联查询信息

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/vo/IotRyDailyReportRespVO.java

@@ -369,4 +369,7 @@ public class IotRyDailyReportRespVO {
 
     @Schema(description = "是否填写井深进尺标识 true false")
     private boolean footageReadOnly = false;
+
+    @Schema(description = "施工队伍所属上级项目部id", example = "32587")
+    private Long projectDeptId;
 }

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

@@ -68,7 +68,7 @@ public class CreateRdDailyReportStatisticsJob implements JobHandler {
         LocalDateTime yesterday = currentDate.minusDays(1);
 
         LocalDateTime createTimeStart = yesterday.toLocalDate().atStartOfDay();
-        LocalDateTime createTimeEnd = yesterday.toLocalDate().atTime(LocalTime.MAX);
+        LocalDateTime createTimeEnd = yesterday.toLocalDate().atTime(23, 59, 59);
 
         // 查询瑞都昨天的日报列表
         IotRdDailyReportPageReqVO reqVO = new IotRdDailyReportPageReqVO();

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

@@ -68,7 +68,7 @@ public class CreateRhDailyReportStatisticsJob implements JobHandler {
         LocalDateTime yesterday = currentDate.minusDays(1);
 
         LocalDateTime createTimeStart = yesterday.toLocalDate().atStartOfDay();
-        LocalDateTime createTimeEnd = yesterday.toLocalDate().atTime(LocalTime.MAX);
+        LocalDateTime createTimeEnd = yesterday.toLocalDate().atTime(23, 59, 59);
 
         // 查询瑞恒昨天的日报列表
         IotRhDailyReportPageReqVO reqVO = new IotRhDailyReportPageReqVO();

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

@@ -0,0 +1,203 @@
+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.iotrydailyreport.vo.IotRyDailyReportPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotrydailyreport.vo.IotRyDailyReportRespVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotrydailyreport.IotRyDailyReportDO;
+import cn.iocoder.yudao.module.pms.message.PmsMessage;
+import cn.iocoder.yudao.module.pms.service.iotrydailyreport.IotRyDailyReportService;
+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 CreateRyDailyReportStatisticsJob implements JobHandler {
+
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private IotRyDailyReportService iotRyDailyReportService;
+    @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 yesterday = LocalDateTime.of(2026, 8, 29, 0, 0);
+
+        LocalDateTime createTimeStart = yesterday.toLocalDate().atStartOfDay();
+        LocalDateTime createTimeEnd = yesterday.toLocalDate().atTime(23, 59, 59);
+
+        // 查询瑞都昨天的日报列表
+        IotRyDailyReportPageReqVO reqVO = new IotRyDailyReportPageReqVO();
+        reqVO.setCreateTime(new LocalDateTime[]{createTimeStart, createTimeEnd});
+        reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        PageResult<IotRyDailyReportDO> pageReports = iotRyDailyReportService.getIotRyDailyReportPage(reqVO);
+        List<IotRyDailyReportDO> dailyReports = pageReports.getList();
+        if (CollUtil.isEmpty(dailyReports)) {
+            return "没有符合条件的数据";
+        }
+
+        List<IotRyDailyReportRespVO> reports = iotRyDailyReportService.getIotRyDailyReports(dailyReports, reqVO);
+        // 先 按照 项目部projectSort 排序 再按照 队伍teamSort 排序
+        /* reports = reports.stream()
+                .filter(r -> Objects.equals(r.getStatus(), 1))
+                .collect(Collectors.toList()); */
+        if (CollUtil.isEmpty(dailyReports)) {
+            return "没有符合条件的数据";
+        }
+        // 1. 排序:先按项目部 sort,再按队伍 sort(null 排最后)
+        reports.sort(Comparator
+                .comparing(IotRyDailyReportRespVO::getProjectSort,
+                        Comparator.nullsLast(Comparator.naturalOrder()))
+                .thenComparing(IotRyDailyReportRespVO::getTeamSort,
+                        Comparator.nullsLast(Comparator.naturalOrder())));
+
+        // 2. 按项目名称分组(保持顺序)
+        Map<String, List<IotRyDailyReportRespVO>> groupedByProject = reports.stream()
+                .collect(Collectors.groupingBy(
+                        IotRyDailyReportRespVO::getProjectName,
+                        LinkedHashMap::new,
+                        Collectors.toList()
+                ));
+
+        // 构建包含所有项目部的消息(每个项目部一块)
+        StringBuilder finalMsgBuilder = new StringBuilder();
+
+        int projectIndex = 0;
+        int totalProjects = groupedByProject.size();
+        for (Map.Entry<String, List<IotRyDailyReportRespVO>> entry : groupedByProject.entrySet()) {
+            String projectName = entry.getKey();
+            List<IotRyDailyReportRespVO> projectReports = entry.getValue();
+
+            // 项目部标题
+            finalMsgBuilder.append("**").append(projectName).append(":**  \n");
+
+            // 项目部内部循环日报条目
+            for (int i = 0; i < projectReports.size(); i++) {
+                IotRyDailyReportRespVO 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.getConstructionStatusName(), "无")).append(";  \n");
+                // finalMsgBuilder.append("工艺:").append(StrUtil.blankToDefault(report.getTechniqueNames(), "无")).append(";  \n");
+                finalMsgBuilder.append("施工简报:").append(StrUtil.blankToDefault(report.getConstructionBrief(), "无")).append(";  \n");
+                finalMsgBuilder.append("下步施工计划:").append(StrUtil.blankToDefault(report.getNextPlan(), "无")).append("。  \n");
+
+                // 同项目内,条目之间添加虚线(最后一条不加)
+                if (i < projectReports.size() - 1) {
+                    finalMsgBuilder.append("……………………  \n");
+                }
+            }
+
+            // 不同项目之间添加实线(最后一个项目后不加)
+            if (projectIndex < totalProjects - 1) {
+                finalMsgBuilder.append("——————————————  \n");
+            }
+            projectIndex++;
+        }
+
+        String finalMsgTitle = finalMsgBuilder.toString();
+
+        Set<Long> deptIds = new HashSet<>();
+        Set<Long> userIds = new HashSet<>();
+
+        // 查询瑞鹰 158l 下具有 瑞鹰日报汇总推送 角色的人员
+        deptIds.add(158l);
+        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.RY_PRODUCTION_STATUS,
+                                                userId, mobile);
+                                    }
+                                } finally {
+                                    latch.countDown();
+                                }
+                            });
+                        });
+                    }
+                }
+            }
+        }
+        return "创建成功";
+    }
+}

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

@@ -134,6 +134,14 @@ public class PmsMessage {
                 msg = "**四川瑞都 "+yesterdayPattern+" 施工简报**  \n" + name + "["+ DateUtil.format(new Date(),"yyyy-MM-dd hh:mm:ss")+"]";
             }
 
+            // 瑞鹰日报汇总生产动态提醒
+            if (PmsConstants.RY_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.YF_PRODUCTION_STATUS.equals(businessType)) {
                 // businessId 时间戳 转换成 yyyy-MM-dd
@@ -190,7 +198,7 @@ public class PmsMessage {
                 //通知设备状态变更为观察使用
                 dingtalkSendApi.send(mobile, msg, "text",url,  title);
             } else if (PmsConstants.PRODUCTION_EXCEPTION.equals(businessType) || PmsConstants.RH_PRODUCTION_STATUS.equals(businessType)
-                    || PmsConstants.RD_PRODUCTION_STATUS.equals(businessType) || PmsConstants.YF_PRODUCTION_STATUS.equals(businessType)) {
+                    || PmsConstants.RD_PRODUCTION_STATUS.equals(businessType) || PmsConstants.YF_PRODUCTION_STATUS.equals(businessType) || PmsConstants.RY_PRODUCTION_STATUS.equals(businessType)) {
                 // markdown 类型消息 支持 文字加粗
                 dingtalkSendApi.send(mobile, msg, "markdown",url,  title);
             } else {

+ 13 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrydailyreport/IotRyDailyReportService.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.pms.service.iotrydailyreport;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pms.controller.admin.iotrydailyreport.vo.*;
+import cn.iocoder.yudao.module.pms.controller.admin.iotrydailyreportdetail.vo.IotRyDailyReportDetailComboRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotrydailyreportdetail.vo.IotRyDailyReportDetailRespVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotrydailyreport.IotRyDailyReportDO;
 
@@ -231,4 +232,16 @@ public interface IotRyDailyReportService {
      * @return 日报列表
      */
     Map<String, BigDecimal> totalWorkload(IotRyDailyReportPageReqVO reqVO);
+
+    /**
+     * 瑞鹰 生产动态钉钉推送 日报格式转换
+     *
+     * @param dailyReports
+     * @return 日报列表
+     */
+    List<IotRyDailyReportRespVO> getIotRyDailyReports(List<IotRyDailyReportDO> dailyReports, IotRyDailyReportPageReqVO pageReqVO);
+
+    BigDecimal calculateNonProductTime(IotRyDailyReportRespVO dailyReport);
+
+    BigDecimal calDetailNonProductTime(IotRyDailyReportDetailComboRespVO dailyReport);
 }

+ 397 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrydailyreport/IotRyDailyReportServiceImpl.java

@@ -15,8 +15,10 @@ import cn.iocoder.yudao.module.pms.controller.admin.depttype.vo.IotDeptTypePageR
 import cn.iocoder.yudao.module.pms.controller.admin.iotdevicecategorytemplateattrs.vo.IotDeviceProperty;
 import cn.iocoder.yudao.module.pms.controller.admin.iotopeationfill.vo.IotOpeationFillPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotproductionexception.vo.IotProductionExceptionPageReqVO;
+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.iotrydailyreport.vo.*;
+import cn.iocoder.yudao.module.pms.controller.admin.iotrydailyreportdetail.vo.IotRyDailyReportDetailComboRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotrydailyreportdetail.vo.IotRyDailyReportDetailPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotrydailyreportdetail.vo.IotRyDailyReportDetailRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotrydailyreportdetail.vo.IotRyDailyReportDetailSaveReqVO;
@@ -25,6 +27,7 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.depttype.IotDeptTypeDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotopeationfill.IotOpeationFillDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotproductionexception.IotProductionExceptionDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotprojectinfo.IotProjectInfoDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotprojecttask.IotProjectTaskDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotprojecttaskattrs.IotTaskAttrModelProperty;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotrydailyreport.IotRyDailyReportDO;
@@ -33,6 +36,7 @@ import cn.iocoder.yudao.module.pms.dal.mysql.IotDeviceMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.depttype.IotDeptTypeMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotopeationfill.IotOpeationFillMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotproductionexception.IotProductionExceptionMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.iotprojectinfo.IotProjectInfoMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotprojecttask.IotProjectTaskMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotrydailyreport.IotRyDailyReportMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotrydailyreportdetail.IotRyDailyReportDetailMapper;
@@ -43,12 +47,14 @@ 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.controller.admin.dept.vo.dept.DeptListReqVO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
 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.dict.DictDataService;
 import cn.iocoder.yudao.module.system.service.dict.DictTypeService;
 import cn.iocoder.yudao.module.system.service.permission.RoleService;
 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
@@ -65,6 +71,7 @@ import javax.annotation.Resource;
 import java.lang.reflect.Type;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.text.DecimalFormat;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
@@ -78,6 +85,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;
 
@@ -94,7 +102,10 @@ public class IotRyDailyReportServiceImpl implements IotRyDailyReportService {
     @Resource
     private IotRyDailyReportMapper iotRyDailyReportMapper;
     @Resource
+    private IotProjectInfoMapper iotProjectInfoMapper;
+    @Resource
     private IotProjectTaskMapper iotProjectTaskMapper;
+
     @Resource
     private DictTypeService dictTypeService;
     @Resource
@@ -121,6 +132,8 @@ public class IotRyDailyReportServiceImpl implements IotRyDailyReportService {
     private IotRyDailyReportDetailMapper iotRyDailyReportDetailMapper;
     @Resource
     private IotProductionExceptionMapper iotProductionExceptionMapper;
+    @Resource
+    private DictDataService dictDataService;
 
 
     @Override
@@ -1518,6 +1531,338 @@ public class IotRyDailyReportServiceImpl implements IotRyDailyReportService {
         return result;
     }
 
+    @Override
+    public List<IotRyDailyReportRespVO> getIotRyDailyReports(List<IotRyDailyReportDO> reports, IotRyDailyReportPageReqVO pageReqVO) {
+        if (CollUtil.isEmpty(reports)) {
+            return Collections.emptyList();
+        }
+        // 设备部门信息
+        Map<Long, DeptDO> deptMap = deptService.getDeptMap(convertList(reports, IotRyDailyReportDO::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, BigDecimal> taskExtPropertyPair = new HashMap<>();
+        // key任务id   value额定生产时间
+        Map<Long, BigDecimal> taskRatedProductionTimePair = new HashMap<>();
+        // key任务id   value套生段产管尺寸
+        Map<Long, String> taskCasingPipeSizePair = new HashMap<>();
+        // key任务id   value井控级别
+        Map<Long, String> taskWellControlLevelPair = new HashMap<>();
+        // key任务id   value施工工艺
+        Map<Long, String> taskTechniquePair = new HashMap<>();
+        // key任务id   value井别
+        Map<Long, String> taskWellCategoryPair = new HashMap<>();
+        // key任务id   value设计井身结构
+        Map<Long, String> taskWellStructPair = new HashMap<>();
+        // key部门id  value设备型号编号
+        AtomicReference<Map<Long, String>> equipmentTypePair = new AtomicReference<>(new HashMap<>());
+        // key施工队伍id    value最近完工日期
+        Map<Long, LocalDateTime> latestWellDoneTimePair = new HashMap<>();
+        // key施工队伍id    value施工井数量
+        Map<Long, Integer> totalTasksPair = new HashMap<>();
+        // key施工队伍id    value完工井数量
+        Map<Long, Integer> completedTasksPair = new HashMap<>();
+        // key非生产时间原因数据字典value   value非生产时间原因数据字典label
+        Map<String, String> nptReasonPair = new HashMap<>();
+
+        // key施工状态数据字典value   value施工状态数据字典label
+        Map<String, String> constructStatusPair = new HashMap<>();
+
+        // key钻井施工状态数据字典value   value钻井施工状态数据字典label
+        Map<String, String> rigConstructStatusPair = new HashMap<>();
+        // key修井施工状态数据字典value   value修井施工状态数据字典label
+        Map<String, String> repairConstructStatusPair = new HashMap<>();
+
+        // key日报id   value生产动态明细
+        Map<Long, List<IotRyDailyReportDetailRespVO>> reportDetailsPair = new HashMap<>();
+        // key项目部id  value项目部排序值
+        Map<Long, Integer> projectSortPair = new HashMap<>();
+        // key施工队伍id  value队伍所属项目部
+        Map<Long, Long> projectIdPair = new HashMap<>();
+        // key项目部id  value项目部名称
+        Map<Long, String> projectNamePair = new HashMap<>();
+
+        // 钻井 施工状态 字典数据
+        List<DictDataDO> rigStatusDictData = dictDataService.getDictDataListByDictType("rigStatus");
+        // 修井 施工状态 字典数据
+        List<DictDataDO> repairStatusDictData = dictDataService.getDictDataListByDictType("repairStatus");
+        // 非生产时间原因 字典数据
+        List<DictDataDO> nptReasonDictData = dictDataService.getDictDataListByDictType("ryNptReason");
+
+        if (CollUtil.isNotEmpty(nptReasonDictData)) {
+            nptReasonDictData.forEach(data -> {
+                nptReasonPair.put(data.getValue(), data.getLabel());
+            });
+        }
+        // 钻井施工状态
+        if ("1".equals(pageReqVO.getProjectClassification()) && CollUtil.isNotEmpty(rigStatusDictData)) {
+            rigStatusDictData.forEach(data -> {
+                constructStatusPair.put(data.getValue(), data.getLabel());
+            });
+        }
+        // 修井施工状态
+        if ("2".equals(pageReqVO.getProjectClassification()) && CollUtil.isNotEmpty(repairStatusDictData)) {
+            repairStatusDictData.forEach(data -> {
+                constructStatusPair.put(data.getValue(), data.getLabel());
+            });
+        }
+
+        if (StrUtil.isEmpty(pageReqVO.getProjectClassification())) {
+            // 钻井 修井标识没有传值 定时任务生成生产动态列表使用
+            if (CollUtil.isNotEmpty(rigStatusDictData)) {
+                rigStatusDictData.forEach(data -> {
+                    rigConstructStatusPair.put(data.getValue(), data.getLabel());
+                });
+            }
+            if (CollUtil.isNotEmpty(repairStatusDictData)) {
+                rigStatusDictData.forEach(data -> {
+                    repairConstructStatusPair.put(data.getValue(), data.getLabel());
+                });
+            }
+        }
+
+        // 查询每个日报的生产动态明细
+        IotRyDailyReportDetailPageReqVO detailReqVO = new IotRyDailyReportDetailPageReqVO();
+        detailReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        detailReqVO.setReportIds(convertList(reports, IotRyDailyReportDO::getId));
+        PageResult<IotRyDailyReportDetailDO> reportDetailsPage = iotRyDailyReportDetailMapper.selectPage(detailReqVO);
+        List<IotRyDailyReportDetailDO> reportDetails = reportDetailsPage.getList();
+        if (CollUtil.isNotEmpty(reportDetails)) {
+            // 设置每个日报的生产动态明细
+            reportDetails.forEach(detail -> {
+                if (reportDetailsPair.containsKey(detail.getReportId())) {
+                    List<IotRyDailyReportDetailRespVO> tempDetails = reportDetailsPair.get(detail.getReportId());
+                    tempDetails.add(BeanUtils.toBean(detail, IotRyDailyReportDetailRespVO.class));
+                    reportDetailsPair.put(detail.getReportId(), tempDetails);
+                } else {
+                    List<IotRyDailyReportDetailRespVO> tempDetails = new ArrayList<>();
+                    tempDetails.add(BeanUtils.toBean(detail, IotRyDailyReportDetailRespVO.class));
+                    reportDetailsPair.put(detail.getReportId(), tempDetails);
+                }
+            });
+        }
+        DataPermissionUtils.executeIgnore(() -> {
+            // 查询日报关联的项目信息
+            IotProjectInfoPageReqVO reqVO = new IotProjectInfoPageReqVO();
+            reqVO.setProjectIds(convertList(reports, IotRyDailyReportDO::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, IotRyDailyReportDO::getTaskId));
+            List<IotProjectTaskDO> tasks = iotProjectTaskMapper.selectList(taskReqVO);
+            if (CollUtil.isNotEmpty(tasks)) {
+                tasks.forEach(task -> {
+                    taskPair.put(task.getId(), task.getWellName());
+                    taskLocationPair.put(task.getId(), task.getLocation());
+                    taskTechniquePair.put(task.getId(), task.getTechnique());
+                    if (CollUtil.isNotEmpty(task.getExtProperty())) {
+                        List<IotTaskAttrModelProperty> taskAttrs = task.getExtProperty();
+                        if (CollUtil.isNotEmpty(taskAttrs)) {
+                            // 找到 设计注气量 属性 对应的值
+                            taskAttrs.forEach(attr -> {
+                                if ("设计井深".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
+                                    taskExtPropertyPair.put(task.getId(), new BigDecimal(attr.getActualValue()));
+                                }
+                                if ("设计井身结构".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
+                                    taskWellStructPair.put(task.getId(), attr.getActualValue());
+                                }
+                                if ("额定生产时间".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
+                                    taskRatedProductionTimePair.put(task.getId(), new BigDecimal(attr.getActualValue()));
+                                }
+                                if ("套生段产管尺寸".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
+                                    taskCasingPipeSizePair.put(task.getId(), attr.getActualValue());
+                                }
+                                if ("井控级别".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
+                                    taskWellControlLevelPair.put(task.getId(), attr.getActualValue());
+                                }
+                                if ("井别".equals(attr.getName()) && StrUtil.isNotBlank(attr.getActualValue())) {
+                                    taskWellCategoryPair.put(task.getId(), attr.getActualValue());
+                                }
+                            });
+                        }
+                    }
+                });
+            }
+            // 查询当前日报所属施工队伍中包含 井架 的型号
+            equipmentTypePair.set(queryEquipmentType(convertList(reports, IotRyDailyReportDO::getDeptId)));
+            // 查询当前日报所属部门的 上井次完井时间 已经绑定任务的情况下
+            List<IotRyDailyReportDO> latestWellDoneTimes = latestWellDoneTimes(null);
+            if (CollUtil.isNotEmpty(latestWellDoneTimes)) {
+                latestWellDoneTimes.forEach(time -> {
+                    latestWellDoneTimePair.put(time.getDeptId(), time.getConstructionStartDate());
+                });
+            }
+            // 按施工队伍统计 钻井日报 施工井数 完工井数
+            List<IotRyDailyReportTaskCountVO> deptTasks = countTasksByDept();
+            // 修井日报 不存在既填写钻井日报 又填写修井日报的队伍
+            if (ObjUtil.isNotEmpty(pageReqVO.getCreateTime())) {
+                List<IotRyDailyReportTaskCountVO> repairDeptTasks = countRepairTasksByDept(pageReqVO);
+                if (CollUtil.isNotEmpty(repairDeptTasks)) {
+                    repairDeptTasks.forEach(task -> {
+                        totalTasksPair.put(task.getDeptId(), task.getTotalTaskCount());
+                        completedTasksPair.put(task.getDeptId(), task.getCompletedTaskCount());
+                    });
+                }
+            }
+            if (CollUtil.isNotEmpty(deptTasks)) {
+                deptTasks.forEach(task -> {
+                    totalTasksPair.put(task.getDeptId(), task.getTotalTaskCount());
+                    completedTasksPair.put(task.getDeptId(), task.getCompletedTaskCount());
+                });
+            }
+
+            // 查询所有队伍对应的上级部门 sort
+            Set<Long> parentDeptIds = new HashSet<>();
+            if (CollUtil.isNotEmpty(deptMap)) {
+                deptMap.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());
+                    });
+                }
+            }
+        });
+        // 2. 拼接数据
+        return BeanUtils.toBean(reports, IotRyDailyReportRespVO.class, (reportVO) -> {
+            // 油耗 精确到 2位小数
+            if (reportVO.getDailyFuel().compareTo(BigDecimal.ZERO) > 0) {
+                BigDecimal fuel = reportVO.getDailyFuel()
+                        .divide(BigDecimal.valueOf(1), 2, RoundingMode.HALF_UP);
+                reportVO.setDailyFuel(fuel);
+            }
+            // 将 ‘生产配合’ 的值赋值给 ‘其他非生产时间’
+            if (reportVO.getRelocationTime().compareTo(BigDecimal.ZERO) > 0) {
+                reportVO.setOtherProductionTime(reportVO.getRelocationTime());
+            }
+            // 计算非生产时效
+            BigDecimal sumNpt = calculateNonProductTime(reportVO);
+            reportVO.setNonProductionRate(sumNpt.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);
+            }
+
+            // 日报生成日期 格式化时间为 yyyy-MM-dd
+            if (ObjUtil.isNotEmpty(reportVO.getCreateTime())) {
+                reportVO.setCreateTimeStr(LocalDateTimeUtil.format(reportVO.getCreateTime(), DatePattern.NORM_DATE_PATTERN));
+            }
+            // 上井次完井时间 格式化时间为 yyyy-MM-dd
+            if (ObjUtil.isNotEmpty(reportVO.getLatestWellDoneTime())) {
+                reportVO.setLatestWellDoneTimeStr(LocalDateTimeUtil.format(reportVO.getLatestWellDoneTime(), DatePattern.NORM_DATE_PATTERN));
+            }
+            // 2.1 拼接部门信息
+            findAndThen(deptMap, reportVO.getDeptId(), dept -> reportVO.setDeptName(dept.getName()));
+            // 2.2 日报关联的项目信息
+            findAndThen(projectPair, reportVO.getProjectId(), contractName -> reportVO.setContractName(contractName));
+            // 2.3 日报关联的任务信息
+            findAndThen(taskPair, reportVO.getTaskId(), taskName -> reportVO.setTaskName(taskName));
+            // 日报关联的任务施工地点
+            findAndThen(taskLocationPair, reportVO.getTaskId(), location -> reportVO.setLocation(location));
+            // 2.4 设计井深
+            findAndThen(taskExtPropertyPair, reportVO.getTaskId(), wellDepth -> reportVO.setDesignWellDepth(wellDepth));
+            // 生产动态明细
+            findAndThen(reportDetailsPair, reportVO.getId(), details -> {
+                // 每个日报的明细 根据 reportDate+startTime 正序排列
+                details.sort(Comparator.comparing(
+                                // 处理 reportDate 为空的情况
+                                (IotRyDailyReportDetailRespVO detail) -> {
+                                    if (detail.getReportDate() == null) {
+                                        return null; // null 值会排在前面
+                                    }
+                                    return detail.getReportDate().toLocalDate();
+                                },
+                                // 自定义 null 值比较器:null 排在非 null 前面
+                                Comparator.nullsFirst(Comparator.naturalOrder())
+                        )
+                        .thenComparing(
+                                // 处理 startTime 为空的情况
+                                IotRyDailyReportDetailRespVO::getStartTime,
+                                // 自定义 null 值比较器:null 排在非 null 前面
+                                Comparator.nullsFirst(Comparator.naturalOrder())
+                        ));
+                reportVO.setReportDetails(details);
+            });
+            // 施工工艺
+            findAndThen(taskTechniquePair, reportVO.getTaskId(), technique -> reportVO.setTechnique(technique));
+            // 井别
+            findAndThen(taskWellCategoryPair, reportVO.getTaskId(), wellCategory -> reportVO.setWellCategory(wellCategory));
+            // 额定生产时间
+            findAndThen(taskRatedProductionTimePair, reportVO.getTaskId(), ratedProductionTime -> reportVO.setRatedProductionTime(ratedProductionTime));
+            // 套生段产管尺寸
+            findAndThen(taskCasingPipeSizePair, reportVO.getTaskId(), casingPipeSize -> reportVO.setCasingPipeSize(casingPipeSize));
+            // 井控级别
+            findAndThen(taskWellControlLevelPair, reportVO.getTaskId(), wellControlLevel -> reportVO.setWellControlLevel(wellControlLevel));
+            // 2.5 设计井身结构
+            findAndThen(taskWellStructPair, reportVO.getTaskId(), wellStruct -> reportVO.setDesignWellStruct(wellStruct));
+            // 2.6 设备型号
+            findAndThen(equipmentTypePair.get(), reportVO.getDeptId(), equipmentType -> reportVO.setEquipmentType(equipmentType));
+            // 2.7 上井次完井时间
+            findAndThen(latestWellDoneTimePair, reportVO.getDeptId(), latestWellDoneTime -> reportVO.setLatestWellDoneTime(latestWellDoneTime));
+            // 2.8 总施工井数 完工井数
+            findAndThen(totalTasksPair, reportVO.getDeptId(), totalTaskCount -> reportVO.setTotalConstructionWells(totalTaskCount));
+            findAndThen(completedTasksPair, reportVO.getDeptId(), completedTaskCount -> reportVO.setCompletedWells(completedTaskCount));
+            // 钻井 施工状态 数据字典
+            findAndThen(constructStatusPair, reportVO.getRigStatus(), statusLabel -> {
+                if ("1".equals(pageReqVO.getProjectClassification())) {
+                    reportVO.setConstructionStatusName(statusLabel);
+                }
+            });
+            // 钻井 施工状态 数据字典 定时任务生成 生产动态列表使用
+            findAndThen(rigConstructStatusPair, reportVO.getRigStatus(), statusLabel -> {
+                if ("1".equals(reportVO.getProjectClassification())) {
+                    reportVO.setConstructionStatusName(statusLabel);
+                }
+            });
+            // 修井 施工状态 数据字典
+            findAndThen(constructStatusPair, reportVO.getRepairStatus(), statusLabel -> {
+                if ("2".equals(pageReqVO.getProjectClassification())) {
+                    reportVO.setConstructionStatusName(statusLabel);
+                }
+            });
+            // 修井 施工状态 数据字典 定时任务生成 生产动态列表使用
+            findAndThen(repairConstructStatusPair, reportVO.getRigStatus(), statusLabel -> {
+                if ("2".equals(reportVO.getProjectClassification())) {
+                    reportVO.setConstructionStatusName(statusLabel);
+                }
+            });
+            // 非生产时间原因 数据字典
+            findAndThen(nptReasonPair, reportVO.getRyNptReason(), statusLabel -> reportVO.setRyNptReason(statusLabel));
+
+            // 日报 所属 上级项目部
+            findAndThen(projectIdPair, reportVO.getDeptId(), projectId -> reportVO.setProjectDeptId(projectId));
+            // 所有施工队伍的排序值
+            findAndThen(deptMap, reportVO.getDeptId(), dept -> reportVO.setTeamSort(dept.getSort()));
+            // 所有施工队伍所属项目部的排序值
+            findAndThen(projectSortPair, reportVO.getProjectDeptId(), sort -> reportVO.setProjectSort(sort));
+            // 所有施工队伍所属项目部的名称
+            findAndThen(projectNamePair, reportVO.getProjectDeptId(), name -> reportVO.setProjectName(name));
+        });
+    }
+
     private List<IotDeviceDO> devices() {
         // 查询 瑞鹰158l 所有存在设备的队伍
         // 查询 瑞鹰 下所有部门
@@ -1530,6 +1875,58 @@ public class IotRyDailyReportServiceImpl implements IotRyDailyReportService {
         return devices;
     }
 
+    /**
+     *  计算非生产时间累加值 计算非生产时效
+     *  NPT 归类 设备故障repair_time 工程质量accident_time 技术受限complexity_time 生产组织rectification_time 不可抗力waiting_stop_time
+     *      甲方设计partya_design 甲方准备partya_prepare 甲方资源partya_resource 其他非生产时间other_npt_time
+     * @param dailyReport
+     * @return
+     */
+    @Override
+    public BigDecimal calculateNonProductTime(IotRyDailyReportRespVO 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
+     */
+    @Override
+    public BigDecimal calDetailNonProductTime(IotRyDailyReportDetailComboRespVO 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);
+    }
+
     /**
      * 按项目部维度统计数据
      * @param pageReqVO 日报查询对象

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptRespVO.java

@@ -22,6 +22,9 @@ public class DeptRespVO {
     @Schema(description = "类型(公司级1 项目部2 队伍3)", example = "1")
     private String type;
 
+    @Schema(description = "部门设备属性(自有1 租赁2 合作3)", example = "1")
+    private String deviceAttributes;
+
     @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     private Integer sort;
 

+ 3 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java

@@ -29,6 +29,9 @@ public class DeptSaveReqVO {
     @Schema(description = "类型(公司级1 项目部2 队伍3)", example = "1")
     private String type;
 
+    @Schema(description = "部门设备属性(自有1 租赁2 合作3)", example = "1")
+    private String deviceAttributes;
+
     @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
     @NotNull(message = "显示顺序不能为空")
     private Integer sort;

+ 4 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/dept/DeptDO.java

@@ -46,6 +46,10 @@ public class DeptDO extends TenantBaseDO {
      * 类型(公司级1 项目部2 队伍3)
      */
     private String type;
+    /**
+     * 部门设备属性(自有1 租赁2 合作3)
+     */
+    private String deviceAttributes;
     /**
      * 显示顺序
      */