Procházet zdrojové kódy

Merge remote-tracking branch 'origin/master'

lipenghui před 2 dny
rodič
revize
5e561af256
30 změnil soubory, kde provedl 1378 přidání a 324 odebrání
  1. 18 4
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/IotMainWorkOrderController.java
  2. 18 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/vo/IotDeviceMainTimeoutVO.java
  3. 3 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotopeationfill/IotOpeationFillController.java
  4. 33 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/IotRyDailyReportController.java
  5. 5 2
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/vo/IotRyDailyReportPageReqVO.java
  6. 29 2
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/vo/IotRyDailyReportRespVO.java
  7. 8 2
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/vo/IotRyDailyReportSaveReqVO.java
  8. 10 2
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/iotrydailyreport/IotRyDailyReportDO.java
  9. 5 1
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/IotDeviceMapper.java
  10. 7 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotopeationfill/IotOpeationFillMapper.java
  11. 13 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotrhdailyreport/IotRhDailyReportMapper.java
  12. 111 3
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotrydailyreport/IotRyDailyReportMapper.java
  13. 10 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotsapstocklog/IotSapStockLogMapper.java
  14. 119 45
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/IotOperationPlanJob.java
  15. 162 53
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/sap/SyncSapStockJob.java
  16. 3 151
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/sap/service/IotSapServiceImpl.java
  17. 17 4
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorder/IotMainWorkOrderService.java
  18. 565 4
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorder/IotMainWorkOrderServiceImpl.java
  19. 46 14
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotopeationfill/IotOpeationFillServiceImpl.java
  20. 15 1
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrhdailyreport/IotRhDailyReportServiceImpl.java
  21. 45 29
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrydailyreport/IotRyDailyReportServiceImpl.java
  22. 45 0
      yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotDeviceMapper.xml
  23. 1 1
      yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotModelTemplateMapper.xml
  24. 12 0
      yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotOpeationFillMapper.xml
  25. 0 3
      yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotOperationPlanMapper.xml
  26. 1 1
      yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/iotprojectinfo/IotProjectInfoMapper.xml
  27. 2 2
      yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/iotprojecttask/IotProjectTaskMapper.xml
  28. 57 0
      yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/iotprojecttask/IotRhDailyReportMapper.xml
  29. 15 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java
  30. 3 0
      yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptListReqVO.java

+ 18 - 4
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/IotMainWorkOrderController.java

@@ -10,10 +10,7 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderPageReqVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderRespVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveReqVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.*;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorder.IotMainWorkOrderDO;
@@ -34,6 +31,7 @@ import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
 import java.io.IOException;
@@ -158,6 +156,22 @@ public class IotMainWorkOrderController {
         return success(new PageResult<>(buildDeviceDistanceList(pageResult.getList()), pageResult.getTotal()));
     }
 
+    @GetMapping("/deviceMainTimeoutList")
+    @Operation(summary = "以公司为维度 统计所有超时保养的设备(保养里程 保养时长 保养自然日期) 统计报表明细")
+    @PermitAll
+    public CommonResult<List<IotDeviceRespVO>> deviceMainTimeoutList(@Valid IotMainWorkOrderPageReqVO pageReqVO) {
+        List<IotDeviceRespVO> timeoutDevices = iotMainWorkOrderService.deviceMainTimeoutList(pageReqVO);
+        return success(buildDeviceDistanceList(timeoutDevices));
+    }
+
+    @GetMapping("/timeoutDeviceMainDistances")
+    @Operation(summary = "以公司为维度 统计所有超时保养的设备(保养里程 保养时长 保养自然日期)")
+    @PermitAll
+    public CommonResult<List<IotDeviceMainTimeoutVO>> timeoutDeviceMainDistances(@Valid IotMainWorkOrderPageReqVO pageReqVO) {
+        List<IotDeviceMainTimeoutVO> deviceMainDistances = iotMainWorkOrderService.timeoutDeviceMainDistances(pageReqVO);
+        return success(deviceMainDistances);
+    }
+
     @GetMapping("/maintenanceSearch")
     @Operation(summary = "以设备为维度统计所有保养计划明细中最近的保养数据 里程/时间/自然日期 可能以设备BOM分组")
     @PreAuthorize("@ss.hasPermission('pms:iot-main-work-order:query')")

+ 18 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/vo/IotDeviceMainTimeoutVO.java

@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - 超时保养设备 Request VO")
+@Data
+public class IotDeviceMainTimeoutVO {
+    @Schema(description = "公司级部门名称")
+    private String deptName;
+
+    @Schema(description = "公司级部门id")
+    private Long deptId;
+
+    @Schema(description = "公司下包含的所有超时保养的设备数量")
+    private Integer count;
+
+}

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotopeationfill/IotOpeationFillController.java

@@ -269,6 +269,9 @@ public class IotOpeationFillController {
                 IotRyDailyReportSaveReqVO saveReqVO1 = BeanUtil.mapToBean(stringRyObjectMap, IotRyDailyReportSaveReqVO.class,false);
                 saveReqVO1.setDeptId(fillDO1.getDeptId());
                 saveReqVO1.setFillOrderCreateTime(fillList.get(0).getCreateTime().atStartOfDay());
+                if(fillDO1.getDeviceCategoryId()==228){
+                    saveReqVO1.setProjectClassification("2");
+                }
                 iotRyDailyReportService.createIotRyDailyReport(saveReqVO1);
             }
 

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

@@ -122,6 +122,16 @@ public class IotRyDailyReportController {
         Map<Long, String> taskPair = 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设备型号编号
@@ -149,6 +159,7 @@ public class IotRyDailyReportController {
             if (CollUtil.isNotEmpty(tasks)) {
                 tasks.forEach(task -> {
                     taskPair.put(task.getId(), StrUtil.join(" - ", task.getWellName(), task.getLocation()));
+                    taskTechniquePair.put(task.getId(), task.getTechnique());
                     if (CollUtil.isNotEmpty(task.getExtProperty())) {
                         List<IotTaskAttrModelProperty> taskAttrs = task.getExtProperty();
                         if (CollUtil.isNotEmpty(taskAttrs)) {
@@ -160,6 +171,18 @@ public class IotRyDailyReportController {
                                 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());
+                                }
                             });
                         }
                     }
@@ -193,6 +216,16 @@ public class IotRyDailyReportController {
             findAndThen(taskPair, reportVO.getTaskId(), taskName -> reportVO.setTaskName(taskName));
             // 2.4 设计井深
             findAndThen(taskExtPropertyPair, reportVO.getTaskId(), wellDepth -> reportVO.setDesignWellDepth(wellDepth));
+            // 施工工艺
+            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 设备型号

+ 5 - 2
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/vo/IotRyDailyReportPageReqVO.java

@@ -66,7 +66,7 @@ public class IotRyDailyReportPageReqVO extends PageParam {
     private BigDecimal[] nonProductionTime;
 
     @Schema(description = "非生产时间原因", example = "不香")
-    private String nptReason;
+    private String ryNptReason;
 
     @Schema(description = "施工开始日期")
     @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@@ -79,7 +79,10 @@ public class IotRyDailyReportPageReqVO extends PageParam {
     @Schema(description = "当日生产情况生产动态", example = "1")
     private String productionStatus;
 
-    @Schema(description = "下步工作计划")
+    @Schema(description = "目前工序")
+    private String currentOperation;
+
+    @Schema(description = "下步工作计划 currentOperation")
     private String nextPlan;
 
     @Schema(description = "施工状态(动迁 准备 施工 完工)", example = "1")

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

@@ -73,13 +73,17 @@ public class IotRyDailyReportRespVO {
     @ExcelProperty("当月油耗(吨)")
     private BigDecimal monthlyFuel;
 
+    @Schema(description = "生产时间(H)")
+    @ExcelProperty("生产时间(H)")
+    private BigDecimal productionTime;
+
     @Schema(description = "非生产时间(H)")
     @ExcelProperty("非生产时间(H)")
     private BigDecimal nonProductionTime;
 
     @Schema(description = "非生产时间原因", example = "不香")
     @ExcelProperty("非生产时间原因")
-    private String nptReason;
+    private String ryNptReason;
 
 
     @Schema(description = "进尺工作时间(H)")
@@ -126,7 +130,11 @@ public class IotRyDailyReportRespVO {
     @ExcelProperty("当日生产情况生产动态")
     private String productionStatus;
 
-    @Schema(description = "下步工作计划")
+    @Schema(description = "目前工序")
+    @ExcelProperty("目前工序")
+    private String currentOperation;
+
+    @Schema(description = "下步工作计划 currentOperation")
     @ExcelProperty("下步工作计划")
     private String nextPlan;
 
@@ -212,4 +220,23 @@ public class IotRyDailyReportRespVO {
 
     @Schema(description = "设备型号/编号", example = "70D")
     private String equipmentType;
+
+    @Schema(description = "运行时效", example = "80%")
+    private BigDecimal transitTime;
+
+    @Schema(description = "额定生产时间(H)", example = "12")
+    private BigDecimal ratedProductionTime = BigDecimal.ZERO;
+
+    @Schema(description = "套生段产管尺寸(mm)", example = "12.88")
+    private String casingPipeSize;
+
+    @Schema(description = "井控级别", example = "一级井控")
+    private String wellControlLevel;
+
+    @Schema(description = "施工工艺", example = "检泵")
+    private String technique;
+
+    @Schema(description = "井别", example = "气井")
+    private String wellCategory;
+
 }

+ 8 - 2
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotrydailyreport/vo/IotRyDailyReportSaveReqVO.java

@@ -55,11 +55,14 @@ public class IotRyDailyReportSaveReqVO {
     @Schema(description = "当月油耗(吨)")
     private BigDecimal monthlyFuel;
 
+    @Schema(description = "生产时间(H)")
+    private BigDecimal productionTime;
+
     @Schema(description = "非生产时间(H)")
     private BigDecimal nonProductionTime;
 
     @Schema(description = "非生产时间原因", example = "不香")
-    private String nptReason;
+    private String ryNptReason;
 
 
     @Schema(description = "进尺工作时间(H)")
@@ -93,7 +96,10 @@ public class IotRyDailyReportSaveReqVO {
     @Schema(description = "当日生产情况生产动态", example = "1")
     private String productionStatus;
 
-    @Schema(description = "下步工作计划")
+    @Schema(description = "目前工序")
+    private String currentOperation;
+
+    @Schema(description = "下步工作计划 下部工序")
     private String nextPlan;
 
     @Schema(description = "施工状态(动迁 准备 施工 完工)", example = "1")

+ 10 - 2
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/iotrydailyreport/IotRyDailyReportDO.java

@@ -85,6 +85,10 @@ public class IotRyDailyReportDO extends BaseDO {
      * 当月油耗(吨)
      */
     private BigDecimal monthlyFuel;
+    /**
+     * 生产时间(H)
+     */
+    private BigDecimal productionTime;
     /**
      * 非生产时间(H)
      */
@@ -92,7 +96,7 @@ public class IotRyDailyReportDO extends BaseDO {
     /**
      * 非生产时间原因
      */
-    private String nptReason;
+    private String ryNptReason;
 
     /**
      * 进尺工作时间(H)
@@ -148,7 +152,11 @@ public class IotRyDailyReportDO extends BaseDO {
      */
     private String productionStatus;
     /**
-     * 下步工作计划
+     * 目前工序
+     */
+    private String currentOperation;
+    /**
+     * 下步工作计划 下部工序
      */
     private String nextPlan;
     /**

+ 5 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/IotDeviceMapper.java

@@ -113,7 +113,7 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
     default List<IotDeviceDO> selectListAlone(IotDevicePageReqVO reqVO) {
         LambdaQueryWrapperX<IotDeviceDO> queryWrapper = new LambdaQueryWrapperX<>();
         queryWrapper
-                .inIfPresent(IotDeviceDO::getDeptId, reqVO.getDeviceIds())
+                .inIfPresent(IotDeviceDO::getId, reqVO.getDeviceIds())
                 .eqIfPresent(IotDeviceDO::getBrand, reqVO.getBrand())
                 .eqIfPresent(IotDeviceDO::getModel, reqVO.getModel())
                 .eqIfPresent(IotDeviceDO::getDeviceStatus, reqVO.getDeviceStatus())
@@ -288,6 +288,10 @@ public interface IotDeviceMapper extends BaseMapperX<IotDeviceDO> {
                                            @Param("alarmDeviceIds") Collection<Long> alarmDeviceIds,
                                            @Param("deptIds") Collection<Long> deptIds);
 
+    List<IotDeviceRespVO> timeoutDeviceMaintenances(@Param("reqVO") IotMainWorkOrderPageReqVO reqVO,
+                                              @Param("alarmDeviceIds") Collection<Long> alarmDeviceIds,
+                                              @Param("deptIds") Collection<Long> deptIds);
+
     List<IotDeviceRespVO> deviceAlarmDistances(@Param("sortedDeviceIds") Collection<Long> sortedDeviceIds,
                                            @Param("deviceIds") Collection<Long> deviceIds,
                                            @Param("mainBomDeviceIds") Collection<Long> mainBomDeviceIds);

+ 7 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotopeationfill/IotOpeationFillMapper.java

@@ -17,6 +17,7 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.iotdevicerunlog.IotDeviceRunLo
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmodeltemplateattrs.IotModelTemplateAttrsDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotopeationfill.IotOpeationFillDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotopeationfill.IotOpeationFillOrderDO;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
@@ -248,4 +249,10 @@ public interface IotOpeationFillMapper extends BaseMapperX<IotOpeationFillDO> {
     @TenantIgnore
     IotDeviceRunLogDO getDesc(IotDeviceRunLogDO runLogDO);
 
+    @TenantIgnore
+    IotDeviceRunLogDO getTeamType(IotDeviceRunLogDO runLogDO);
+
+    @TenantIgnore
+    AdminUserDO getUserInfo(IotDeviceRunLogDO runLogDO);
+
 }

+ 13 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotrhdailyreport/IotRhDailyReportMapper.java

@@ -8,11 +8,13 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.pms.controller.admin.iotrhdailyreport.vo.IotRhDailyReportPageReqVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotrhdailyreport.IotRhDailyReportDO;
+import com.baomidou.mybatisplus.core.metadata.IPage;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
 
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
@@ -60,6 +62,17 @@ public interface IotRhDailyReportMapper extends BaseMapperX<IotRhDailyReportDO>
                 .orderByAsc(IotRhDailyReportDO::getDeptId));
     } */
 
+    /**
+     * 查询 瑞恒日报列表
+     * @param page
+     * @param reqVO
+     * @param projectIds
+     * @return
+     */
+    IPage<IotRhDailyReportDO> rhDailyReports(IPage<IotRhDailyReportDO> page, @Param("reqVO") IotRhDailyReportPageReqVO reqVO,
+                                             @Param("taskIds") Collection<Long> taskIds,
+                                             @Param("projectIds") Collection<Long> projectIds, @Param("deptIds") Collection<Long> deptIds);
+
     default PageResult<IotRhDailyReportDO> selectPage(IotRhDailyReportPageReqVO reqVO) {
         // 检查contractName不为空但projectIds为空的情况
         if (StrUtil.isNotBlank(reqVO.getContractName()) && (CollUtil.isEmpty(reqVO.getProjectIds()))) {

+ 111 - 3
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotrydailyreport/IotRyDailyReportMapper.java

@@ -26,12 +26,14 @@ import java.util.Objects;
 @Mapper
 public interface IotRyDailyReportMapper extends BaseMapperX<IotRyDailyReportDO> {
 
-    default PageResult<IotRyDailyReportDO> selectPage(IotRyDailyReportPageReqVO reqVO) {
+    /* default PageResult<IotRyDailyReportDO> selectPage(IotRyDailyReportPageReqVO reqVO) {
+        // 获取查询参数中的projectClassification值
+        String projectClassification = reqVO.getProjectClassification();
         return selectPage(reqVO, new LambdaQueryWrapperX<IotRyDailyReportDO>()
                 .eqIfPresent(IotRyDailyReportDO::getDeptId, reqVO.getDeptId())
                 .eqIfPresent(IotRyDailyReportDO::getProjectId, reqVO.getProjectId())
                 .eqIfPresent(IotRyDailyReportDO::getTaskId, reqVO.getTaskId())
-                .eqIfPresent(IotRyDailyReportDO::getProjectClassification, reqVO.getProjectClassification())
+                // .eqIfPresent(IotRyDailyReportDO::getProjectClassification, reqVO.getProjectClassification())
                 .eqIfPresent(IotRyDailyReportDO::getRelocationDays, reqVO.getRelocationDays())
                 .betweenIfPresent(IotRyDailyReportDO::getLatestWellDoneTime, reqVO.getLatestWellDoneTime())
                 .eqIfPresent(IotRyDailyReportDO::getCurrentDepth, reqVO.getCurrentDepth())
@@ -43,7 +45,7 @@ public interface IotRyDailyReportMapper extends BaseMapperX<IotRyDailyReportDO>
                 .eqIfPresent(IotRyDailyReportDO::getDailyFuel, reqVO.getDailyFuel())
                 .eqIfPresent(IotRyDailyReportDO::getMonthlyFuel, reqVO.getMonthlyFuel())
                 .betweenIfPresent(IotRyDailyReportDO::getNonProductionTime, reqVO.getNonProductionTime())
-                .eqIfPresent(IotRyDailyReportDO::getNptReason, reqVO.getNptReason())
+                .eqIfPresent(IotRyDailyReportDO::getRyNptReason, reqVO.getRyNptReason())
                 .betweenIfPresent(IotRyDailyReportDO::getConstructionStartDate, reqVO.getConstructionStartDate())
                 .betweenIfPresent(IotRyDailyReportDO::getConstructionEndDate, reqVO.getConstructionEndDate())
                 .eqIfPresent(IotRyDailyReportDO::getProductionStatus, reqVO.getProductionStatus())
@@ -64,6 +66,63 @@ public interface IotRyDailyReportMapper extends BaseMapperX<IotRyDailyReportDO>
                 .betweenIfPresent(IotRyDailyReportDO::getCreateTime, reqVO.getCreateTime())
                 .orderByDesc(IotRyDailyReportDO::getCreateTime)
                 .orderByAsc(IotRyDailyReportDO::getId));
+    } */
+
+    default PageResult<IotRyDailyReportDO> selectPage(IotRyDailyReportPageReqVO reqVO) {
+        // 获取查询参数中的 projectClassification 值
+        String projectClassification = reqVO.getProjectClassification();
+        LambdaQueryWrapperX<IotRyDailyReportDO> queryWrapper = new LambdaQueryWrapperX<IotRyDailyReportDO>();
+        // 创建查询包装器
+        queryWrapper
+                .eqIfPresent(IotRyDailyReportDO::getDeptId, reqVO.getDeptId())
+                .eqIfPresent(IotRyDailyReportDO::getProjectId, reqVO.getProjectId())
+                .eqIfPresent(IotRyDailyReportDO::getTaskId, reqVO.getTaskId())
+                .eqIfPresent(IotRyDailyReportDO::getRelocationDays, reqVO.getRelocationDays())
+                .betweenIfPresent(IotRyDailyReportDO::getLatestWellDoneTime, reqVO.getLatestWellDoneTime())
+                .eqIfPresent(IotRyDailyReportDO::getCurrentDepth, reqVO.getCurrentDepth())
+                .eqIfPresent(IotRyDailyReportDO::getDailyFootage, reqVO.getDailyFootage())
+                .eqIfPresent(IotRyDailyReportDO::getMonthlyFootage, reqVO.getMonthlyFootage())
+                .eqIfPresent(IotRyDailyReportDO::getAnnualFootage, reqVO.getAnnualFootage())
+                .eqIfPresent(IotRyDailyReportDO::getDailyPowerUsage, reqVO.getDailyPowerUsage())
+                .eqIfPresent(IotRyDailyReportDO::getMonthlyPowerUsage, reqVO.getMonthlyPowerUsage())
+                .eqIfPresent(IotRyDailyReportDO::getDailyFuel, reqVO.getDailyFuel())
+                .eqIfPresent(IotRyDailyReportDO::getMonthlyFuel, reqVO.getMonthlyFuel())
+                .betweenIfPresent(IotRyDailyReportDO::getNonProductionTime, reqVO.getNonProductionTime())
+                .eqIfPresent(IotRyDailyReportDO::getRyNptReason, reqVO.getRyNptReason())
+                .betweenIfPresent(IotRyDailyReportDO::getConstructionStartDate, reqVO.getConstructionStartDate())
+                .betweenIfPresent(IotRyDailyReportDO::getConstructionEndDate, reqVO.getConstructionEndDate())
+                .eqIfPresent(IotRyDailyReportDO::getProductionStatus, reqVO.getProductionStatus())
+                .eqIfPresent(IotRyDailyReportDO::getNextPlan, reqVO.getNextPlan())
+                .eqIfPresent(IotRyDailyReportDO::getRigStatus, reqVO.getRigStatus())
+                .eqIfPresent(IotRyDailyReportDO::getPersonnel, reqVO.getPersonnel())
+                .eqIfPresent(IotRyDailyReportDO::getMudDensity, reqVO.getMudDensity())
+                .eqIfPresent(IotRyDailyReportDO::getMudViscosity, reqVO.getMudViscosity())
+                .eqIfPresent(IotRyDailyReportDO::getLateralLength, reqVO.getLateralLength())
+                .eqIfPresent(IotRyDailyReportDO::getWellInclination, reqVO.getWellInclination())
+                .eqIfPresent(IotRyDailyReportDO::getAzimuth, reqVO.getAzimuth())
+                .eqIfPresent(IotRyDailyReportDO::getExtProperty, reqVO.getExtProperty())
+                .eqIfPresent(IotRyDailyReportDO::getSort, reqVO.getSort())
+                .eqIfPresent(IotRyDailyReportDO::getRemark, reqVO.getRemark())
+                .eqIfPresent(IotRyDailyReportDO::getStatus, reqVO.getStatus())
+                .eqIfPresent(IotRyDailyReportDO::getProcessInstanceId, reqVO.getProcessInstanceId())
+                .eqIfPresent(IotRyDailyReportDO::getAuditStatus, reqVO.getAuditStatus())
+                .betweenIfPresent(IotRyDailyReportDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(IotRyDailyReportDO::getCreateTime)
+                .orderByAsc(IotRyDailyReportDO::getId);
+
+        // 单独处理 projectClassification 条件
+        if ("1".equals(projectClassification)) {
+            // 当值为 "1" 时,查询 project_classification 为 "1" 或空字符串的记录
+            queryWrapper.and(wrapper -> wrapper
+                    .eq(IotRyDailyReportDO::getProjectClassification, "1")
+                    .or().eq(IotRyDailyReportDO::getProjectClassification, "")
+            );
+        } else {
+            // 其他情况:有值则精确匹配,无值则不添加条件(保持原逻辑)
+            queryWrapper.eqIfPresent(IotRyDailyReportDO::getProjectClassification, projectClassification);
+        }
+
+        return selectPage(reqVO, queryWrapper);
     }
 
     /**
@@ -145,6 +204,31 @@ public interface IotRyDailyReportMapper extends BaseMapperX<IotRyDailyReportDO>
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
     }
 
+    /**
+     * 查询指定部门、工单生成日期 前一天所在月份的最早的记录天 井深
+     * @param deptId
+     * @param reportDate
+     * @return
+     */
+    default BigDecimal monthStartFootage(Long deptId, LocalDateTime reportDate) {
+        if (reportDate == null) {
+            return BigDecimal.ZERO;
+        }
+        // 获取 reportDate 的前一天日期 查询前一天所在月份的井深
+        LocalDateTime previousDay = reportDate.minusDays(1);
+        int year = previousDay.getYear();
+        int month = previousDay.getMonthValue();
+
+        IotRyDailyReportDO report = selectOne(new LambdaQueryWrapperX<IotRyDailyReportDO>()
+                .eqIfPresent(IotRyDailyReportDO::getDeptId, deptId)
+                .apply("YEAR(construction_start_date) = {0}", year)
+                .apply("MONTH(construction_start_date) = {0}", month)
+                .orderByAsc(IotRyDailyReportDO::getCreateTime)
+                .last("limit 1"));
+
+        return ObjUtil.isEmpty(report) ? BigDecimal.ZERO : report.getCurrentDepth();
+    }
+
     /**
      * 查询每个施工队伍的 历史 年进尺
      * @param deptId 部门ID
@@ -170,6 +254,30 @@ public interface IotRyDailyReportMapper extends BaseMapperX<IotRyDailyReportDO>
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
     }
 
+    /**
+     * 查询指定部门、工单生成日期 前一天所在年份的最早记录天 井深
+     * @param deptId
+     * @param reportDate
+     * @return
+     */
+    default BigDecimal yearStartFootage(Long deptId, LocalDateTime reportDate) {
+        if (reportDate == null) {
+            return BigDecimal.ZERO;
+        }
+        // 获取 reportDate 的前一天日期 查询前一天所在年份每一条记录天的井深
+        LocalDateTime previousDay = reportDate.minusDays(1);
+        int year = previousDay.getYear();
+        int month = previousDay.getMonthValue();
+
+        IotRyDailyReportDO report = selectOne(new LambdaQueryWrapperX<IotRyDailyReportDO>()
+                .eqIfPresent(IotRyDailyReportDO::getDeptId, deptId)
+                .apply("YEAR(construction_start_date) = {0}", year)
+                .orderByAsc(IotRyDailyReportDO::getCreateTime)
+                .last("limit 1"));
+
+        return ObjUtil.isEmpty(report) ? BigDecimal.ZERO : report.getCurrentDepth();
+    }
+
     /**
      * 按部门统计任务数量
      * @return 部门任务统计列表

+ 10 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotsapstocklog/IotSapStockLogMapper.java

@@ -5,7 +5,9 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.pms.controller.admin.iotsapstocklog.vo.IotSapStockLogPageReqVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotsapstocklog.IotSapStockLogDO;
+import org.apache.ibatis.annotations.Delete;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
 
 /**
  * PMS SAP 库存(sap同步的原始数据日志) Mapper
@@ -46,4 +48,12 @@ public interface IotSapStockLogMapper extends BaseMapperX<IotSapStockLogDO> {
                 .orderByDesc(IotSapStockLogDO::getId));
     }
 
+    /**
+     * 物理删除 上次同步保存的SAP库存数据
+     *
+     * @return 删除条数
+     */
+    @Delete("DELETE FROM rq_iot_sap_stock_log WHERE 1=1 AND factory_code = #{factoryCode}")
+    Integer deleteSapStockLogs(@Param("factoryCode") String factoryCode);
+
 }

+ 119 - 45
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/IotOperationPlanJob.java

@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.pms.controller.admin.iotrhdailyreport.vo.IotRhDai
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.inspect.IotInspectPlanDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotdevicerunlog.IotDeviceRunLogDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotopeationfill.IotOpeationFillDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotopeationfill.IotOpeationFillOrderDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotoperationplan.IotOperationPlanDO;
@@ -21,6 +22,7 @@ import cn.iocoder.yudao.module.pms.dal.mysql.iotoperationplandev.IotOperationPla
 import cn.iocoder.yudao.module.pms.message.PmsMessage;
 import cn.iocoder.yudao.module.pms.service.iotopeationfill.IotOpeationFillService;
 import cn.iocoder.yudao.module.pms.service.iotrhdailyreport.IotRhDailyReportService;
+import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
@@ -33,6 +35,7 @@ import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -162,9 +165,31 @@ public class IotOperationPlanJob implements JobHandler {
 
 
     private void extracted(List<Long> devIdList, List<IotOpeationFillOrderDO> orderList,IotOperationPlanDO plan) {
-        LocalDateTime localDateTime = DateUtils.contactTime(plan.getBeginCreateTime());
-        plan.setLastCreateTime(localDateTime);
+        //瑞鹰日报计划为12小时执行一次
+        //判断计划周期是否为小时,如果是,则保存上次执行时间为当前时间最近的整点
+        if ("hour".equals(plan.getPlanUnit())) {
+            // 获取当前时间
+            LocalDateTime now = LocalDateTime.now();
+            // 获取当前分钟数
+            int minute = now.getMinute();
+
+            LocalDateTime localDateTime;
+            if (minute <= 30) {
+                // 如果分钟数小于等于30,取当前小时的整点
+                localDateTime = now.truncatedTo(ChronoUnit.HOURS);
+            } else {
+                // 如果分钟数大于30,取下一小时的整点
+                localDateTime = now.truncatedTo(ChronoUnit.HOURS).plusHours(1);
+            }
+            plan.setLastCreateTime(localDateTime);
+        } else {
+            LocalDateTime localDateTime = DateUtils.contactTime(plan.getBeginCreateTime());
+            plan.setLastCreateTime(localDateTime);
+        }
         planMapper.updateById(plan);
+
+
+
         //4、根据设备ID生成子表数据
         List<IotOpeationFillDO> deviceList = iotOpeationFillMapper.getFillDevices(devIdList);
         List<Integer> idList = deviceList.stream().map(IotOpeationFillDO::getUserId).collect(Collectors.toList());
@@ -173,54 +198,93 @@ public class IotOperationPlanJob implements JobHandler {
                 .collect(Collectors.toList());
         //插入工单主表
         iotOpeationFillOrderMapper.insertBatch(orderList1);
-        Set<Long> rdIdList = new HashSet<>();
-        rdIdList =  deptService.getChildDeptIdListFromCache(163L);
-        rdIdList.add(163L);
-        Set<Long> rhIdList = new HashSet<>();
-        rhIdList =  deptService.getChildDeptIdListFromCache(157L);
-        rhIdList.add(157L);
-        boolean exist = rdIdList.contains(plan.getDeptId());
-        if(!exist){
-            //瑞鹰SCP项目部50010队伍临时创建
-            if(plan.getDeptId()==292){
-                //筛选日报工单
-                List<IotOpeationFillOrderDO> virOrderList = new ArrayList<>();
-                IotOpeationFillOrderDO fillDO = new IotOpeationFillOrderDO();
-                fillDO.setOrderName("40006队"+"/"+LocalDate.now()+"运行记录填报");
-                fillDO.setDeptId(322L);
-                fillDO.setOrderStatus(0);
-                fillDO.setCreateTime(LocalDateTime.now());
-                fillDO.setUserName("李小虎");
-                fillDO.setUserId(486);
-                fillDO.setMobile(String.valueOf(17723897643L));
-                virOrderList.add(fillDO);
-                orderList1.addAll(virOrderList);
-                iotOpeationFillOrderMapper.insertBatch(virOrderList);
-                //创建日报设备
-                List <IotOpeationFillDO> devList = opeationFillService.reportMethod(virOrderList);
-                //插入日报设备
-                iotOpeationFillMapper.insertFill(devList);
+
+
+        Set<Long> ryIdList = new HashSet<>();
+        if(plan.getIsReport()==1){
+
+            ryIdList =  deptService.getChildDeptIdListFromCache(158L);
+            ryIdList.add(158L);
+
+            List<IotOpeationFillOrderDO> zxjOrderList = new ArrayList<>();
+
+            for (IotOpeationFillOrderDO orderDO:orderList1) {
+                IotDeviceRunLogDO runLogDO = new IotDeviceRunLogDO();
+                runLogDO.setDeptId(orderDO.getDeptId());
+                //获取队伍类型
+                IotDeviceRunLogDO teamType = iotOpeationFillMapper.getTeamType(runLogDO);
+
+                //如果是钻井队伍,则日报工单责任人更新为技术员
+                //修井队伍不变
+                if(teamType!=null&&teamType.getPointCode().equals("zj")){
+                    //获取技术员信息
+                    AdminUserDO userInfo = iotOpeationFillMapper.getUserInfo(runLogDO);
+                    if(userInfo!=null){
+                        orderDO.setUserName(userInfo.getUsername());
+                        orderDO.setUserId(new BigDecimal(userInfo.getId()).intValue());
+                        orderDO.setMobile(userInfo.getMobile());
+                    }
+                    zxjOrderList.add(orderDO);
+                }else if(teamType!=null&&teamType.getPointCode().equals("xj")){
+                    zxjOrderList.add(orderDO);
+                }
             }
 
-            boolean rhContain = rhIdList.contains(plan.getDeptId());
-            //瑞恒日报插入
-            rhReportInsert(plan, orderList1, rhContain);
-        }
-        //发送钉钉消息
-        sendDingMessage(orderList1);
-        //正常工单设备
-        for (IotOpeationFillDO device:deviceList) {
-            for (IotOpeationFillOrderDO order:orderList1) {
-                if(device.getUserId().intValue()==order.getUserId().intValue()){
-                    device.setOrderId(order.getId());
+
+            // 按dept_id去重,保留第一个出现的元素
+            List<IotOpeationFillOrderDO> distinctOrderList = zxjOrderList.stream()
+                    .collect(Collectors.toMap(
+                            IotOpeationFillOrderDO::getDeptId,
+                            order -> {
+                                order.setId(null); // 关键:重置ID,避免与已有数据冲突
+                                return order;
+                            },
+                            (existing, replacement) -> existing
+                    ))
+                    .values()
+                    .stream()
+                    .collect(Collectors.toList());
+            //插入工单主表
+            iotOpeationFillOrderMapper.insertBatch(distinctOrderList);
+            //发送钉钉消息
+            sendDingMessage(distinctOrderList);
+
+            boolean ryContain = ryIdList.contains(plan.getDeptId());
+            //瑞鹰日报插入
+            ryReportInsert(plan,distinctOrderList,ryContain);
+
+        }else{
+            ryIdList =  deptService.getChildDeptIdListFromCache(163L);
+            ryIdList.add(163L);
+            Set<Long> rhIdList = new HashSet<>();
+            rhIdList =  deptService.getChildDeptIdListFromCache(157L);
+            rhIdList.add(157L);
+            boolean exist = ryIdList.contains(plan.getDeptId());
+            if(!exist){
+                boolean rhContain = rhIdList.contains(plan.getDeptId());
+                //瑞恒日报插入
+                rhReportInsert(plan, orderList1, rhContain);
+            }
+            //发送钉钉消息
+            sendDingMessage(orderList1);
+            //正常工单设备
+            for (IotOpeationFillDO device:deviceList) {
+                for (IotOpeationFillOrderDO order:orderList1) {
+                    if(device.getUserId().intValue()==order.getUserId().intValue()){
+                        device.setOrderId(order.getId());
+                    }
                 }
             }
+            //插入子表
+            for (IotOpeationFillDO re:deviceList) {
+                re.setDeviceId(re.getId());
+            }
+            iotOpeationFillMapper.insertFill(deviceList);
         }
-        //插入子表
-        for (IotOpeationFillDO re:deviceList) {
-            re.setDeviceId(re.getId());
-        }
-        iotOpeationFillMapper.insertFill(deviceList);
+
+
+
+
 
     }
 
@@ -276,6 +340,16 @@ public class IotOperationPlanJob implements JobHandler {
         }
     }
 
+
+    private void ryReportInsert(IotOperationPlanDO plan, List<IotOpeationFillOrderDO> orderList1, boolean ryContain) {
+        if(ryContain){
+            //创建日报设备
+            List <IotOpeationFillDO> devList = opeationFillService.reportMethod(orderList1);
+            //插入日报设备
+            iotOpeationFillMapper.insertFill(devList);
+        }
+    }
+
     private void rhReportInsert(IotOperationPlanDO plan, List<IotOpeationFillOrderDO> orderList1, boolean rhContain) {
         if(rhContain){
 

+ 162 - 53
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/sap/SyncSapStockJob.java

@@ -2,10 +2,8 @@ package cn.iocoder.yudao.module.pms.job.sap;
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.collection.CollectionUtil;
-import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
 import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
-import cn.iocoder.yudao.module.pms.dal.mysql.iotsappickinglist.IotSapPickingListMapper;
 import cn.iocoder.yudao.module.pms.sap.SapConnector;
 import cn.iocoder.yudao.module.pms.sap.service.IotSapService;
 import cn.iocoder.yudao.module.pms.sap.vo.IotSapStockVO;
@@ -20,7 +18,9 @@ import org.springframework.stereotype.Component;
 import javax.annotation.Resource;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.module.pms.framework.config.MultiThreadConfiguration.PMS_THREAD_POOL_TASK_EXECUTOR;
@@ -35,8 +35,7 @@ public class SyncSapStockJob implements JobHandler {
     private SapConnector sapConnector;
     @Autowired
     private SapOrgApi sapOrgApi;
-    @Autowired
-    private IotSapPickingListMapper sapPickingListMapper;
+
     @Resource(name = PMS_THREAD_POOL_TASK_EXECUTOR)
     private ThreadPoolTaskExecutor pmsThreadPoolTaskExecutor;
     @Autowired
@@ -50,59 +49,169 @@ public class SyncSapStockJob implements JobHandler {
         if (CollUtil.isEmpty(factoryCodes)) {
             return "No SAP Factory";
         }
-        factoryCodes.forEach(factory -> {
-            CountDownLatch latch = new CountDownLatch(factoryCodes.size());;
-            pmsThreadPoolTaskExecutor.execute(() -> {
+        log.info("共找到 {} 个工厂需要同步: {}", factoryCodes.size(), factoryCodes);
+
+        // 提前获取SAP连接,避免每个线程重复创建
+        JCoDestination destination;
+        try {
+            destination = sapConnector.getDestination();
+            // 测试连接是否有效
+            destination.ping();
+            log.info("SAP连接建立成功");
+        } catch (JCoException e) {
+            log.error("SAP连接建立失败", e);
+            return "SAP连接失败: " + e.getMessage();
+        }
+
+        // 使用CountDownLatch等待所有线程完成
+        CountDownLatch latch = new CountDownLatch(factoryCodes.size());
+        List<CompletableFuture<Void>> futures = new ArrayList<>();
+        List<String> failedFactories = new ArrayList<>();
+        List<String> successFactories = new ArrayList<>();
+
+
+        // 为每个工厂创建异步任务
+        for (String factory : factoryCodes) {
+            CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                 try {
-                    JCoDestination destination = sapConnector.getDestination();
-                    JCoFunction function = destination.getRepository().getFunction("ZPMS_002");
-                    if (ObjUtil.isNotEmpty(function)) {
-                        // 设置输入参数
-                        JCoParameterList input = function.getImportParameterList();
-                        input.setValue("IV_MATNR", "");    // 物料编号
-                        input.setValue("IV_WERKS", factory);    // 工厂
-                        input.setValue("IV_LGORT", "");    // 库存地点
-                        // 执行 RFC 调用
-                        function.execute(destination);
-                        // 获取输出参数
-                        JCoParameterList output = function.getTableParameterList();
-                        JCoTable etStockTable = function.getTableParameterList().getTable("ET_STOCK");
-                        System.out.println(factory + "当前工厂对应的SAP库存数量:" + etStockTable.getNumRows());
-                        List<IotSapStockVO> sapStocks = new ArrayList<>();
-                        if (etStockTable != null && etStockTable.getNumRows() > 0) {
-                            for (int i = 0; i < etStockTable.getNumRows(); i++) {
-                                etStockTable.setRow(i);
-                                IotSapStockVO sapStock = new IotSapStockVO();
-                                sapStock.setWERKS(etStockTable.getString("WERKS"));
-                                sapStock.setMATNR(etStockTable.getString("MATNR"));
-                                sapStock.setMAKTX(etStockTable.getString("MAKTX"));
-                                sapStock.setLABST(etStockTable.getBigDecimal("LABST"));
-                                sapStock.setLGORT(etStockTable.getString("LGORT"));
-                                sapStock.setCHARG(etStockTable.getString("CHARG"));
-                                sapStock.setSOBKZ(etStockTable.getString("SOBKZ"));
-                                sapStock.setPSPNR(etStockTable.getString("PSPNR"));
-                                sapStock.setPOSID(etStockTable.getString("POSID"));
-                                sapStock.setMEINS(etStockTable.getString("MEINS"));
-                                sapStock.setINSME(etStockTable.getString("INSME"));
-                                sapStock.setSPEME(etStockTable.getString("SPEME"));
-                                sapStock.setJIAGE(etStockTable.getBigDecimal("JIAGE"));
-                                sapStocks.add(sapStock);
-                            }
-                        }
-                        // 处理头部表信息 和 明细表数据
-                        if (CollUtil.isNotEmpty(sapStocks)) {
-                            iotSapService.processSapStock(factory, sapStocks);
-                        }
-                    }
-                } catch (JCoException e) {
-                    // 记录调用接口异常日志
-                    throw new RuntimeException(e);
+                    syncFactoryStock(destination, factory);
+                    successFactories.add(factory);
+                    log.info("工厂 {} 库存同步完成", factory);
+                } catch (Exception e) {
+                    failedFactories.add(factory);
+                    log.error("工厂 {} 库存同步失败", factory, e);
                 } finally {
                     latch.countDown();
                 }
-            });
-        });
-        return "SAP Stock Info: ET_STOCK";
+            }, pmsThreadPoolTaskExecutor);
+
+            futures.add(future);
+        }
+
+        // 等待所有任务完成,设置超时时间
+        try {
+            boolean completed = latch.await(3, TimeUnit.MINUTES);
+            if (!completed) {
+                log.warn("SAP库存同步任务超时,可能有些工厂数据未完成同步");
+            }
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            log.error("SAP库存同步任务被中断", e);
+            return "任务被中断";
+        }
+
+        // 汇总执行结果
+        String result = buildResultMessage(factoryCodes.size(), successFactories.size(), failedFactories);
+        log.info("SAP库存同步任务完成: {}", result);
+
+        return result;
+    }
+
+    /**
+     * 同步单个工厂的库存数据
+     */
+    private void syncFactoryStock(JCoDestination destination, String factory) {
+        JCoFunction function = null;
+        try {
+            // 每个线程使用独立的JCoFunction实例
+            function = destination.getRepository().getFunction("ZPMS_002");
+            if (function == null) {
+                throw new RuntimeException("未找到SAP函数 ZPMS_002");
+            }
+
+            // 设置输入参数
+            JCoParameterList input = function.getImportParameterList();
+            input.setValue("IV_MATNR", "");    // 物料编号
+            input.setValue("IV_WERKS", factory);    // 工厂
+            input.setValue("IV_LGORT", "");    // 库存地点
+
+            // 执行 RFC 调用
+            long startTime = System.currentTimeMillis();
+            function.execute(destination);
+            long endTime = System.currentTimeMillis();
+
+            log.debug("工厂 {} SAP调用耗时: {}ms", factory, (endTime - startTime));
+
+            // 处理返回数据
+            JCoTable etStockTable = function.getTableParameterList().getTable("ET_STOCK");
+            if (etStockTable == null) {
+                log.warn("工厂 {} 未返回ET_STOCK表数据", factory);
+                return;
+            }
+
+            int rowCount = etStockTable.getNumRows();
+            log.info("工厂 {} 获取到 {} 条库存数据", factory, rowCount);
+
+            if (rowCount > 0) {
+                List<IotSapStockVO> sapStocks = parseSapStockData(etStockTable, factory);
+                if (CollUtil.isNotEmpty(sapStocks)) {
+                    iotSapService.processSapStock(factory, sapStocks);
+                    log.info("工厂 {} 成功处理 {} 条库存记录", factory, sapStocks.size());
+                }
+            }
+
+        } catch (JCoException e) {
+            throw new RuntimeException("工厂 " + factory + " SAP调用失败", e);
+        } finally {
+            // 清理JCo资源
+            if (function != null) {
+                try {
+                    // JCoFunction没有close方法,但可以显式清理
+                    function = null;
+                } catch (Exception e) {
+                    log.warn("清理JCoFunction资源时发生警告", e);
+                }
+            }
+        }
+    }
+
+    /**
+     * 解析SAP库存数据
+     */
+    private List<IotSapStockVO> parseSapStockData(JCoTable etStockTable, String factory) {
+        List<IotSapStockVO> sapStocks = new ArrayList<>();
+
+        for (int i = 0; i < etStockTable.getNumRows(); i++) {
+            etStockTable.setRow(i);
+            try {
+                IotSapStockVO sapStock = new IotSapStockVO();
+                sapStock.setWERKS(etStockTable.getString("WERKS"));
+                sapStock.setMATNR(etStockTable.getString("MATNR"));
+                sapStock.setMAKTX(etStockTable.getString("MAKTX"));
+                sapStock.setLABST(etStockTable.getBigDecimal("LABST"));
+                sapStock.setLGORT(etStockTable.getString("LGORT"));
+                sapStock.setCHARG(etStockTable.getString("CHARG"));
+                sapStock.setSOBKZ(etStockTable.getString("SOBKZ"));
+                sapStock.setPSPNR(etStockTable.getString("PSPNR"));
+                sapStock.setPOSID(etStockTable.getString("POSID"));
+                sapStock.setMEINS(etStockTable.getString("MEINS"));
+                sapStock.setINSME(etStockTable.getString("INSME"));
+                sapStock.setSPEME(etStockTable.getString("SPEME"));
+                sapStock.setJIAGE(etStockTable.getBigDecimal("JIAGE"));
+
+                sapStocks.add(sapStock);
+            } catch (Exception e) {
+                log.error("解析工厂 {} 第 {} 行库存数据时发生错误", factory, i, e);
+            }
+        }
+
+        return sapStocks;
+    }
+
+    /**
+     * 构建结果消息
+     */
+    private String buildResultMessage(int total, int success, List<String> failedFactories) {
+        StringBuilder result = new StringBuilder();
+        result.append("SAP库存同步完成。总计: ").append(total)
+                .append(", 成功: ").append(success)
+                .append(", 失败: ").append(total - success);
+
+        if (CollUtil.isNotEmpty(failedFactories)) {
+            result.append("。失败工厂: ").append(failedFactories);
+        }
+
+        return result.toString();
     }
 
     /**

+ 3 - 151
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/sap/service/IotSapServiceImpl.java

@@ -25,7 +25,6 @@ import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.saporg.SapOrgApi;
 import cn.iocoder.yudao.module.system.api.saporg.dto.SapOrgRespDTO;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -582,17 +581,6 @@ public class IotSapServiceImpl implements IotSapService {
         }
         System.out.println(factoryCode + "当前库中已有sap库存数量:" + existStockKeys.size());
 
-        // todo 没有维护SAP工厂 库存地点的组织 已经存在的库存数据
-        /* Set<String> noFactoryExistStockKeys = new HashSet<>();
-        if (CollUtil.isNotEmpty(existStocks)) {
-            noFactoryExistStockKeys = existStocks.stream()
-                    // 过滤非空对象和非空code
-                    .filter(stk -> ObjUtil.isNotEmpty(stk) && StrUtil.isNotBlank(stk.getMaterialCode())
-                            && StrUtil.isNotBlank(stk.getFactoryCode()) && StrUtil.isNotBlank(stk.getStorageLocationCode()))
-                    .map(stk -> StrUtil.join("-", stk.getFactoryCode(), stk.getStorageLocationCode(), stk.getMaterialCode()))
-                    .collect(Collectors.toSet());
-        } */
-
         // 将已经存在的库存数据 设置成 Map<String, IotSapStockDO> 的形式 便于后续更新
         // key工厂code-库存地点code-物料编码       value库存对象   从此集合中筛选将要被更新的库存对象
         Map<String, IotSapStockDO> tobeUpdatedStockPair = new HashMap<>();
@@ -605,19 +593,6 @@ public class IotSapServiceImpl implements IotSapService {
                         (existing, replacement) -> existing // 处理键冲突: 保留先出现的元素
                 ));
 
-        // todo 没有配置SAP 工厂 库存地点 的组织库存更新
-        // 将已经存在的库存数据 设置成 Map<String, IotSapStockDO> 的形式 便于后续更新
-        // key工厂code-库存地点code-物料编码       value库存对象   从此集合中筛选将要被更新的库存对象
-        /* Map<String, IotSapStockDO> noFactoryTobeUpdatedStockPair = new HashMap<>();
-        noFactoryTobeUpdatedStockPair = existStocks.stream()
-                .filter(stk -> StrUtil.isNotBlank(stk.getMaterialCode()) && StrUtil.isNotBlank(stk.getFactoryCode())
-                        && StrUtil.isNotBlank(stk.getStorageLocationCode()))
-                .collect(Collectors.toMap(
-                        stk -> StrUtil.join("-", stk.getFactoryCode(), stk.getStorageLocationCode(), stk.getMaterialCode()),
-                        stk -> stk, // 值为对象本身
-                        (existing, replacement) -> existing // 处理键冲突: 保留先出现的元素
-                )); */
-
         // 找出需要新增的库存(SAP物料编码去掉前导零后,不在现有物料集合中的记录)
         Set<String> finalExistStockKeys = existStockKeys;
         List<IotSapStockVO> newStocks = sapStocks.stream()
@@ -635,19 +610,6 @@ public class IotSapServiceImpl implements IotSapService {
 
         System.out.println(factoryCode + "需要新增的sap库存数量:" + newStocks.size());
 
-        // todo 没有维护SAP工厂 库存地点的组织 新增的库存数据
-        // 找出需要新增的库存(SAP物料编码去掉前导零后,不在现有物料集合中的记录)
-        /* Set<String> noFactoryFinalExistStockKeys = noFactoryExistStockKeys;
-        List<IotSapStockVO> noFactoryNewStocks = sapStocks.stream()
-                .filter(stk -> StrUtil.isNotBlank(stk.getMATNR()) && StrUtil.isNotBlank(stk.getLGORT()) && StrUtil.isNotBlank(stk.getWERKS()))
-                .filter(stk -> {
-                    // 处理前导零:移除MATNR前的 00000000
-                    String processedCode = stk.getMATNR().replaceFirst("^0+", "");
-                    // 本地已经配置过库存地点 包含SAP库存接口返回的库存地点
-                    return !noFactoryFinalExistStockKeys.contains(StrUtil.join("-", stk.getWERKS(), stk.getLGORT(), processedCode));
-                })
-                .collect(Collectors.toList()); */
-
         // 新增SAP库存数据
         List<IotSapStockDO> tobeAddedStocks = new ArrayList<>();
         if (CollUtil.isNotEmpty(newStocks)) {
@@ -698,50 +660,6 @@ public class IotSapServiceImpl implements IotSapService {
             });
         }
 
-        // todo 新增 没有配置组织部门的 SAP库存数据
-        /** List<IotSapStockDO> noFactoryTobeAddedStocks = new ArrayList<>();
-        if (CollUtil.isNotEmpty(noFactoryNewStocks)) {
-            noFactoryNewStocks.forEach(stock -> {
-                // 只保存pms维护过库存地点的数据
-                String uniqueKey = StrUtil.join("-", stock.getWERKS(), stock.getLGORT());
-                IotSapStockDO sapStock = new IotSapStockDO();
-                // 部门统一设置为 科瑞石油技术
-                sapStock.setDeptId(156l); **/
-                // 工厂id
-                /* if (factoryIdPair.containsKey(stock.getWERKS())) {
-                    sapStock.setFactoryId(factoryIdPair.get(stock.getWERKS()));
-                } */
-                /** sapStock.setFactoryCode(stock.getWERKS());
-                // 工厂名称
-                if (factoryNamePair.containsKey(stock.getWERKS())) {
-                    sapStock.setFactory(factoryNamePair.get(stock.getWERKS()));
-                }
-                // 库存地点 code
-                sapStock.setStorageLocationCode(stock.getLGORT()); **/
-                // 库存地点id
-                /* if (storageLocationIdPair.containsKey(uniqueKey)) {
-                    sapStock.setStorageLocationId(storageLocationIdPair.get(uniqueKey));
-                } */
-                // 库存地点名称
-                /** if (stockLocationNamePair.containsKey(uniqueKey)) {
-                    sapStock.setProjectDepartment(stockLocationNamePair.get(uniqueKey));
-                }
-                // 物料编码 需要去掉前缀 00000000
-                sapStock.setMaterialCode(stock.getMATNR().replaceFirst("^0+", ""));
-                // 物料描述
-                sapStock.setMaterialName(stock.getMAKTX());
-                // 库存数量
-                sapStock.setQuantity(stock.getLABST());
-                // 单价
-                sapStock.setUnitPrice(stock.getJIAGE());
-                // 基本单位
-                sapStock.setUnit(stock.getMEINS());
-                // 同步时间
-                sapStock.setSyncTime(LocalDateTime.now());
-                noFactoryTobeAddedStocks.add(sapStock);
-            });
-        } **/
-
         // 使用返回的SAP库存数据更新已有的 库存数据
         // pms中已有 SAP返回数据中也存在 则更新 再次判断更新deptId 因为有可能又将SAP库存地点关联到了 组织部门
         List<IotSapStockDO> actualUpdatedStocks = new ArrayList<>();
@@ -787,69 +705,10 @@ public class IotSapServiceImpl implements IotSapService {
             });
         }
 
-        // todo sap 工厂 库存地点 未关联组织部门
-        // 使用返回的SAP库存数据更新已有的 库存数据 pms中已有 SAP返回数据中也存在 则更新
-        /* List<IotSapStockDO> noFactoryActualUpdatedStocks = new ArrayList<>();
-        // key工厂code-库存地点code-物料编码      value库存对象
-        Map<String, IotSapStockVO> noFactoryExistStockPair = createNoFactoryExistStockMap(sapStocks, noFactoryExistStockKeys, storageLocationIdPair);
-        if (CollUtil.isNotEmpty(noFactoryExistStockPair)) {
-            Map<String, IotSapStockDO> finalTobeUpdatedStockPair = noFactoryTobeUpdatedStockPair;
-            noFactoryExistStockPair.forEach((k, v) -> {
-                if (finalTobeUpdatedStockPair.containsKey(k)) {
-                    IotSapStockDO sapStock = finalTobeUpdatedStockPair.get(k);
-                    sapStock.setUnitPrice(v.getJIAGE());    // 更新库存价格
-                    sapStock.setQuantity(v.getLABST());     // 更新库存数量
-                    sapStock.setUnit(v.getMEINS());         // 更新基本单位
-                    noFactoryActualUpdatedStocks.add(sapStock);
-                }
-            });
-        }
-        System.out.println(factoryCode + "SAP工厂库存地点未关联组织部门-需要更新的SAP库存数量:" + noFactoryActualUpdatedStocks.size()); */
-
         // pms中已有 但是 SAP返回数据不存在 如果设置了安全库存,则库存为0,若没有设置安全库存,则可以删除
 
-        // 遍历明细数据 新增 修改 SAP库存数据
-        /* List<IotSapStockDO> tobeSapStocks = new ArrayList<>();
-        sapStocks.forEach(stock -> {
-            // 只保存pms维护过库存地点的数据
-            if (locationCodeDeptIdPair.containsKey(stock.getLGORT())) {
-                IotSapStockDO sapStock = new IotSapStockDO();
-                // 部门id
-                sapStock.setDeptId(locationCodeDeptIdPair.get(stock.getLGORT()));
-                // 工厂id
-                if (factoryIdPair.containsKey(stock.getWERKS())) {
-                    sapStock.setFactoryId(factoryIdPair.get(stock.getWERKS()));
-                }
-                // 工厂名称
-                if (factoryNamePair.containsKey(stock.getWERKS())) {
-                    sapStock.setFactory(factoryNamePair.get(stock.getWERKS()));
-                }
-                // 库存地点id
-                if (storageLocationIdPair.containsKey(stock.getLGORT())) {
-                    sapStock.setStorageLocationId(storageLocationIdPair.get(stock.getLGORT()));
-                }
-                // 库存地点名称
-                if (stockLocationNamePair.containsKey(stock.getLGORT())) {
-                    sapStock.setProjectDepartment(stockLocationNamePair.get(stock.getLGORT()));
-                }
-                // 物料编码 需要去掉前缀 00000000
-                sapStock.setMaterialCode(stock.getMATNR());
-                // 物料描述
-                sapStock.setMaterialName(stock.getMAKTX());
-                // 库存数量
-                sapStock.setQuantity(stock.getLABST());
-                // 单价
-                sapStock.setUnitPrice(stock.getJIAGE());
-                // 基本单位
-                sapStock.setUnit(stock.getMEINS());
-                // 同步时间
-                sapStock.setSyncTime(LocalDateTime.now());
-                tobeSapStocks.add(sapStock);
-            }
-        }); */
-        // 保存SAP库存原始数据 先删除 再新增
-        QueryWrapper<IotSapStockLogDO> queryWrapper = new QueryWrapper<>();
-        TenantUtils.execute(1L, () -> iotSapStockLogMapper.delete(queryWrapper));
+        // 保存SAP库存原始数据 先删除当前工厂下的数据 再新增
+        TenantUtils.execute(1L, () -> iotSapStockLogMapper.deleteSapStockLogs(factoryCode));
         if (CollUtil.isNotEmpty(tobeAddedSapStockLogs)) {
             TenantUtils.execute(1L, () -> iotSapStockLogMapper.insertBatch(tobeAddedSapStockLogs));
         }
@@ -857,18 +716,11 @@ public class IotSapServiceImpl implements IotSapService {
         if (CollUtil.isNotEmpty(tobeAddedStocks)) {
             TenantUtils.execute(1L, () -> iotSapStockMapper.insertBatch(tobeAddedStocks));
         }
-        // 本地库存 初始化 批量插入本地库存 记录 工厂 库存地点 未关联了组织架构部门
-        /* if (CollUtil.isNotEmpty(noFactoryTobeAddedStocks)) {
-            TenantUtils.execute(1L, () -> iotSapStockMapper.insertBatch(noFactoryTobeAddedStocks));
-        } */
+
         // pms中存在而且SAP接口也返回的库存数据:更新
         if (CollUtil.isNotEmpty(actualUpdatedStocks)) {
             TenantUtils.execute(1L, () -> iotSapStockMapper.updateBatch(actualUpdatedStocks));
         }
-        // pms中存在而且SAP接口也返回的库存数据:更新 工厂 库存地点 未关联了组织架构部门
-        /* if (CollUtil.isNotEmpty(noFactoryActualUpdatedStocks)) {
-            TenantUtils.execute(1L, () -> iotSapStockMapper.updateBatch(noFactoryActualUpdatedStocks));
-        } */
     }
 
     /**

+ 17 - 4
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorder/IotMainWorkOrderService.java

@@ -1,10 +1,7 @@
 package cn.iocoder.yudao.module.pms.service.iotmainworkorder;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderPageReqVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderRespVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveReqVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.*;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorder.IotMainWorkOrderDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbom.IotMainWorkOrderBomDO;
@@ -83,6 +80,22 @@ public interface IotMainWorkOrderService {
      */
     PageResult<IotDeviceRespVO> deviceMainDistances(IotMainWorkOrderPageReqVO pageReqVO);
 
+    /**
+     * 以公司为维度 统计所有超时保养的设备(保养里程 保养时长 保养自然日期) 统计报表明细
+     *
+     * @param pageReqVO 列表查询
+     * @return 超时保养设备列表
+     */
+    List<IotDeviceRespVO> deviceMainTimeoutList(IotMainWorkOrderPageReqVO pageReqVO);
+
+    /**
+     * 以公司为维度 统计所有超时保养的设备(保养里程 保养时长 保养自然日期)
+     *
+     * @param pageReqVO 分页查询
+     * @return 保养工单分页
+     */
+    List<IotDeviceMainTimeoutVO> timeoutDeviceMainDistances(IotMainWorkOrderPageReqVO pageReqVO);
+
     /**
      * 以设备为维度统计所有保养计划明细中最近的保养数据 里程/时间/自然日期 可能以设备BOM分组
      *

+ 565 - 4
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorder/IotMainWorkOrderServiceImpl.java

@@ -9,14 +9,12 @@ import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
 import cn.iocoder.yudao.module.pms.controller.admin.iotdevicerunlog.vo.IotDeviceRunLogRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotlockstock.vo.IotLockStockPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmaintenancebom.vo.IotMaintenanceBomPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmaintenancebom.vo.IotMaintenanceBomRespVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderPageReqVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderRespVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveReqVO;
-import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.*;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomSaveReqVO;
@@ -42,6 +40,8 @@ import cn.iocoder.yudao.module.pms.dal.redis.BizNoRedisDAO;
 import cn.iocoder.yudao.module.pms.service.IotDeviceService;
 import cn.iocoder.yudao.module.pms.service.iotdevicerunlog.IotDeviceRunLogService;
 import cn.iocoder.yudao.module.pms.service.iotmaintenancebom.IotMaintenanceBomService;
+import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
+import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
 import cn.iocoder.yudao.module.system.service.dept.DeptService;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -98,6 +98,8 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
     private DeptService deptService;
     @Resource
     private IotOutboundMapper iotOutboundMapper;
+    @Resource
+    private DeptMapper deptMapper;
 
     @Override
     public Long createIotMainWorkOrder(IotMainWorkOrderSaveReqVO createReqVO) {
@@ -660,6 +662,530 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
         }
     }
 
+    @Override
+    public List<IotDeviceRespVO> deviceMainTimeoutList(IotMainWorkOrderPageReqVO pageReqVO) {
+        // 所有保养计划 + 保养工单 明细中待保养的最近距离 里程/时间/自然日
+        List<IotMainWorkOrderBomDO> workOrderBomS = new ArrayList<>();
+        Set<Long> deviceIds = CollUtil.isEmpty(workOrderBomS)
+                ? new HashSet<>()
+                : workOrderBomS.stream()
+                .map(IotMainWorkOrderBomDO::getDeviceId) // 获取 deviceId
+                .filter(Objects::nonNull)      // 过滤非空值
+                .collect(Collectors.toSet());  // 收集到 Set 中
+        Set<Long> orderDeviceIds = new HashSet<>();
+        orderDeviceIds.addAll(deviceIds);
+        // 查询保养计划明细中不包含 deviceIds 的设备id集合
+        List<IotMaintenanceBomDO> mainBomS = new ArrayList<>();
+        Set<Long> mainBomDeviceIds = new HashSet<>();
+        IotMaintenanceBomPageReqVO mainBomReqVO = new IotMaintenanceBomPageReqVO();
+        if (CollUtil.isNotEmpty(deviceIds)) {
+            mainBomReqVO.setDeviceIds(new ArrayList<>(deviceIds));
+            mainBomS = iotMaintenanceBomMapper.selectAlarmList(mainBomReqVO);
+        } else {
+            mainBomS = iotMaintenanceBomMapper.selectList(mainBomReqVO);
+        }
+        Set<String> boundedMultiAttrNames = new HashSet<>();
+        Set<Long> deviceCategoryIds = new HashSet<>();
+        Map<Long, Long> deviceCategoryPair = new HashMap<>();
+        if (CollUtil.isNotEmpty(mainBomS)) {
+            mainBomDeviceIds = CollUtil.isEmpty(mainBomS)
+                    ? Collections.emptySet()
+                    : mainBomS.stream()
+                    .map(IotMaintenanceBomDO::getDeviceId) // 获取 deviceId
+                    .filter(Objects::nonNull)      // 过滤非空值
+                    .collect(Collectors.toSet());  // 收集到 Set 中
+            // 查询所有保养计划 保养项 中已经绑定的 多个累计时长 公里数 属性名称值
+            mainBomS.forEach(bom -> {
+                if (StrUtil.isNotBlank(bom.getType())) {
+                    // 累计公里数属性
+                    boundedMultiAttrNames.add(bom.getType());
+                }
+                if (StrUtil.isNotBlank(bom.getCode())) {
+                    // 累计时长属性
+                    boundedMultiAttrNames.add(bom.getCode());
+                }
+            });
+            // 查询所有设备类别id
+            // 组装bom关联的设备信息
+            Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(mainBomS,
+                    bom -> Stream.of(bom.getDeviceId())));
+            if (CollUtil.isNotEmpty(deviceMap)) {
+                deviceMap.forEach((k,v) -> {
+                    deviceCategoryIds.add(v.getAssetClass());
+                    deviceCategoryPair.put(k, v.getAssetClass());
+                });
+            }
+        }
+        deviceIds.addAll(mainBomDeviceIds);
+        if (CollUtil.isEmpty(deviceIds)){
+            // 没有设备信息,返回空列表
+            return new ArrayList<>();
+        }
+        // 查询 运行记录模板中包含多个累计 时长 公里数 属性的集合
+        List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
+        if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
+            multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames);
+        }
+        // key(设备id-累计时长属性名称)    value时长属性累计时长数值
+        Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
+        if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
+            multipleAccumulatedData.forEach(data -> {
+                String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
+                tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
+            });
+        }
+
+        // 查询 运行记录模板中 正常的累计时长 公里数集合
+        // Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMap(new ArrayList<>(deviceIds));
+        Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap =
+                iotDeviceRunLogService.getDeviceRunLogMapAlone(new ArrayList<>(deviceIds), new ArrayList<>(deviceCategoryIds), deviceCategoryPair);
+        // 以设备为维度统计每个设备相关的保养项的最近保养距离 key设备id    value设备下每个保养项的的最小保养距离集合
+        Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
+        // 设备保养明细 key设备id  value设备保养工单明细下所有保养规则数据最小值
+        Map<Long, String> resultMap = new HashMap<>();
+        // 记录保养项的 提前量 key设备id-保养项id-距离保养值   value提前量
+        Map<String, BigDecimal> bomLeadPair = new HashMap<>();
+        if (CollUtil.isNotEmpty(mainBomS)) {
+            // 遍历保养计划明细 计算 保养计划中的设备 的最近的保养时间
+            for (IotMaintenanceBomDO bom : mainBomS) {
+                BigDecimal runningTimeDistance = null;
+                BigDecimal runningKiloDistance = null;
+                BigDecimal naturalDateDistance = null;
+                // 计算每个保养项 每个保养规则下的 距离保养时间 单位 小时
+                if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule()) {
+                    // 运行时间保养规则
+                    BigDecimal totalRunTime = BigDecimal.ZERO;
+                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                        totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
+                    } else {
+                        // 运行记录模板中包含多个累计时长 公里数类型的属性
+                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
+                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
+                            totalRunTime = tempTotalRunDataPair.get(uniqueKey);
+                        }
+                    }
+                    BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
+                    BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
+                    BigDecimal timePeriodLead = bom.getTimePeriodLead();    // 运行时间周期提前量
+                    runningTimeDistance = runningTimePeriod.subtract(totalRunTime.subtract(lastRunningTime));
+                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningTimeDistance);
+                    bomLeadPair.put(uniqueKey, timePeriodLead);
+                }
+                if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule()) {
+                    // 运行里程保养规则 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
+                    BigDecimal totalMileage = BigDecimal.ZERO;
+                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                        totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
+                    } else {
+                        // 运行记录模板中包含多个累计时长 公里数类型的属性
+                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getType());
+                        if (tempTotalRunDataPair.containsKey(uniqueKey)) {
+                            totalMileage = tempTotalRunDataPair.get(uniqueKey);
+                        }
+                    }
+                    BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
+                    BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
+                    BigDecimal kiloCycleLead = bom.getKiloCycleLead();    // 运行里程周期提前量
+                    runningKiloDistance = runningKiloPeriod.subtract(totalMileage.subtract(lastRunningKilo));
+                    String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningKiloDistance);
+                    bomLeadPair.put(uniqueKey, kiloCycleLead);
+                }
+                if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 0 == bom.getNaturalDateRule()) {
+                    // 自然日期保养规则
+                    LocalDateTime lastNaturalDate = bom.getLastNaturalDate();      // 上次保养自然日期
+                    BigDecimal naturalDatePeriod = bom.getNextNaturalDate();        // 自然日周期
+                    BigDecimal natualDateLead = bom.getNaturalDatePeriodLead();    // 自然日周期提前量
+                    if (ObjUtil.isNotEmpty(lastNaturalDate) && ObjUtil.isNotEmpty(naturalDatePeriod) && ObjUtil.isNotEmpty(natualDateLead)) {
+                        // 计算有效天数 = 自然日周期 - 提前量
+                        long days = naturalDatePeriod.longValue(); // 转为长整型天数
+                        // 计算目标日期:上次保养日期 + 有效天数(注意:LocalDateTime加天数会自动处理日期进位)
+                        LocalDateTime targetDate = lastNaturalDate.plusDays(days);
+                        // 获取当前日期时间
+                        LocalDateTime now = LocalDateTime.now();
+                        // 计算日期差值(以天为单位)
+                        naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(now, targetDate));
+                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), naturalDateDistance);
+                        bomLeadPair.put(uniqueKey, natualDateLead);
+                    }
+                }
+                // 找出距离0最近的数,作为工单的最近保养距离数据
+                Object closet = chooseNearestDistance(runningTimeDistance, runningKiloDistance, naturalDateDistance);
+                Map<String, Object> tempDistance = new HashMap<>();
+                if (closet == runningTimeDistance) {
+                    tempDistance.put("H", closet);
+                } else if (closet == runningKiloDistance) {
+                    tempDistance.put("KM", closet);
+                } else if (closet == naturalDateDistance) {
+                    tempDistance.put("D", closet);
+                }
+                if (orderDistancePair.containsKey(bom.getDeviceId())) {
+                    List<Map<String, Object>> tempDistances = orderDistancePair.get(bom.getDeviceId());
+                    tempDistances.add(tempDistance);
+                    orderDistancePair.put(bom.getDeviceId(), tempDistances);
+                } else {
+                    List<Map<String, Object>> tempDistances = new ArrayList<>();
+                    tempDistances.add(tempDistance);
+                    orderDistancePair.put(bom.getDeviceId(), tempDistances);
+                }
+            }
+        }
+        // 以设备id 为维度 统计每个保养工单明细中 距离最近的保养数据
+        resultMap = findClosestToZero(orderDistancePair);
+        // 根据保养项规则应该生成保养工单的设备id集合
+        Set<Long> shouldWorkOrderFlags = new HashSet<>();
+        // 找出设备保养项距离保养最小值对应的 提前量
+        if (CollUtil.isNotEmpty(resultMap)) {
+            resultMap.forEach((k,v) -> {
+                // k设备id    v距离保养最小值(带单位)
+                // 去掉v中带的单位 310 D   -61.00 H
+                String[] distanceStrs = v.split(" ");
+                BigDecimal tempDistance = new BigDecimal(distanceStrs[0]);
+                if (tempDistance.compareTo(BigDecimal.ZERO) < 0) {
+                    shouldWorkOrderFlags.add(k);
+                }
+                if (CollUtil.isNotEmpty(bomLeadPair)) {
+                    bomLeadPair.forEach((key,value) -> {
+                        // key设备id-保养项id-距离保养值   value提前量
+                        String[] uniqueKeyStrs = key.split("-");
+                        if (uniqueKeyStrs.length == 3) {
+                            if (String.valueOf(k).equals(uniqueKeyStrs[0]) && distanceStrs[0].equals(uniqueKeyStrs[2])
+                                    && tempDistance.compareTo(BigDecimal.ZERO)>=0 && tempDistance.compareTo(value)<=0 )  {
+                                shouldWorkOrderFlags.add(k);
+                            }
+                        }
+                    });
+                }
+            });
+        }
+        // 对集合 resultMap 中所有数据进行排序 按照 map 的value值 去除后面的 字符后 升序排列
+        // 排序后输出一个 List<Long> 类型的集合,排序对应上面的排序规则
+        List<Long> sortedDeviceIds = sortByNumericTimeoutValue(resultMap);
+        try {
+            // 左侧组织树 组织层级查询
+            Set<Long> ids = new HashSet<>();
+            if (Objects.nonNull(pageReqVO.getDeptId())) {
+                ids = deptService.getChildDeptIdListFromCache(pageReqVO.getDeptId());
+                ids.add(pageReqVO.getDeptId());
+            }
+            // 不查询保养订单表 暂时传一个不会出现的设备id
+            orderDeviceIds.add(Long.MIN_VALUE);
+            List<IotDeviceRespVO> alarmDevices = iotDeviceMapper.deviceAlarmDistances(sortedDeviceIds, orderDeviceIds, sortedDeviceIds);
+            // 处理当前分页数据 拼接上已经排序的 筛选出的设备保养项 最小保养距离
+            List<Long> alarmDeviceIds = new ArrayList<>();
+            Map<Long, IotDeviceRespVO> alarmDevicePair = new HashMap<>();
+            if (CollUtil.isNotEmpty(alarmDevices)) {
+                for (IotDeviceRespVO device : alarmDevices) {
+                    if (resultMap.containsKey(device.getId())) {
+                        device.setMainDistance(resultMap.get(device.getId()));
+                        alarmDeviceIds.add(device.getId());
+                    }
+                    alarmDevicePair.put(device.getId(), device);
+                }
+            }
+            // 查询所有设备列表 通过SQL形式 使用 FIELD 字段
+            List<IotDeviceRespVO> page = iotDeviceMapper.timeoutDeviceMaintenances(pageReqVO, alarmDeviceIds, ids);
+            if (CollUtil.isNotEmpty(page)) {
+                page.forEach(device -> {
+                    if (alarmDevicePair.containsKey(device.getId())) {
+                        device.setMainDistance(alarmDevicePair.get(device.getId()).getMainDistance());
+                        device.setPlanId(alarmDevicePair.get(device.getId()).getPlanId());
+                        device.setWorkOrderId(alarmDevicePair.get(device.getId()).getWorkOrderId());
+                    }
+                    if (shouldWorkOrderFlags.contains(device.getId())) {
+                        device.setShouldWorkOrder(true);
+                    }
+                });
+            }
+            return page;
+        } catch (Exception exception) {
+            if (exception.getMessage().contains("Table does not exist")) {
+                return new ArrayList<>();
+            }
+            throw exception;
+        }
+    }
+
+
+    @Override
+    public List<IotDeviceMainTimeoutVO> timeoutDeviceMainDistances(IotMainWorkOrderPageReqVO pageReqVO) {
+        // key公司级部门名称     value公司下所有保养超时的设备数量
+        Map<String, Integer> companyNameTimeoutDevices = new HashMap<>();
+        List<IotDeviceMainTimeoutVO> companyMainTimeoutDevices = new ArrayList<>();
+        // 所有保养计划 明细中待保养的最近距离 里程/时间/自然日
+        DataPermissionUtils.executeIgnore(() -> {
+            List<IotMainWorkOrderBomDO> workOrderBomS = new ArrayList<>();
+            Set<Long> deviceIds = CollUtil.isEmpty(workOrderBomS)
+                    ? new HashSet<>()
+                    : workOrderBomS.stream()
+                    .map(IotMainWorkOrderBomDO::getDeviceId) // 获取 deviceId
+                    .filter(Objects::nonNull)      // 过滤非空值
+                    .collect(Collectors.toSet());  // 收集到 Set 中
+            Set<Long> orderDeviceIds = new HashSet<>();
+            orderDeviceIds.addAll(deviceIds);
+            // 查询保养计划明细中不包含 deviceIds 的设备id集合
+            List<IotMaintenanceBomDO> mainBomS = new ArrayList<>();
+            Set<Long> mainBomDeviceIds = new HashSet<>();
+            IotMaintenanceBomPageReqVO mainBomReqVO = new IotMaintenanceBomPageReqVO();
+            if (CollUtil.isNotEmpty(deviceIds)) {
+                mainBomReqVO.setDeviceIds(new ArrayList<>(deviceIds));
+                mainBomS = iotMaintenanceBomMapper.selectAlarmList(mainBomReqVO);
+            } else {
+                mainBomS = iotMaintenanceBomMapper.selectList(mainBomReqVO);
+            }
+            Set<String> boundedMultiAttrNames = new HashSet<>();
+            Set<Long> deviceCategoryIds = new HashSet<>();
+            Map<Long, Long> deviceCategoryPair = new HashMap<>();
+            if (CollUtil.isNotEmpty(mainBomS)) {
+                mainBomDeviceIds = CollUtil.isEmpty(mainBomS)
+                        ? Collections.emptySet()
+                        : mainBomS.stream()
+                        .map(IotMaintenanceBomDO::getDeviceId) // 获取 deviceId
+                        .filter(Objects::nonNull)      // 过滤非空值
+                        .collect(Collectors.toSet());  // 收集到 Set 中
+                // 查询所有保养计划 保养项 中已经绑定的 多个累计时长 公里数 属性名称值
+                mainBomS.forEach(bom -> {
+                    if (StrUtil.isNotBlank(bom.getType())) {
+                        // 累计公里数属性
+                        boundedMultiAttrNames.add(bom.getType());
+                    }
+                    if (StrUtil.isNotBlank(bom.getCode())) {
+                        // 累计时长属性
+                        boundedMultiAttrNames.add(bom.getCode());
+                    }
+                });
+                // 查询所有设备类别id
+                // 组装bom关联的设备信息
+                Map<Long, IotDeviceRespVO> deviceMap = iotDeviceService.getDeviceMap(convertListByFlatMap(mainBomS,
+                        bom -> Stream.of(bom.getDeviceId())));
+                if (CollUtil.isNotEmpty(deviceMap)) {
+                    deviceMap.forEach((k,v) -> {
+                        deviceCategoryIds.add(v.getAssetClass());
+                        deviceCategoryPair.put(k, v.getAssetClass());
+                    });
+                }
+            }
+            deviceIds.addAll(mainBomDeviceIds);
+            if (CollUtil.isEmpty(deviceIds)){
+                // 没有设备信息,返回空列表
+                return companyMainTimeoutDevices;
+            }
+            // 查询 运行记录模板中包含多个累计 时长 公里数 属性的集合
+            List<IotDeviceRunLogRespVO> multipleAccumulatedData = new ArrayList<>();
+            if (CollUtil.isNotEmpty(deviceIds) && CollUtil.isNotEmpty(boundedMultiAttrNames)) {
+                multipleAccumulatedData = iotDeviceRunLogService.multipleAccumulatedData(deviceIds, boundedMultiAttrNames);
+            }
+            // key(设备id-累计时长属性名称)    value时长属性累计时长数值
+            Map<String, BigDecimal> tempTotalRunDataPair = new HashMap<>();
+            if (CollUtil.isNotEmpty(multipleAccumulatedData)) {
+                multipleAccumulatedData.forEach(data -> {
+                    String uniqueKey = StrUtil.join("-", data.getDeviceId(), data.getPointName());
+                    tempTotalRunDataPair.put(uniqueKey, data.getTotalRunTime());
+                });
+            }
+
+            // 查询 运行记录模板中 正常的累计时长 公里数集合
+            // Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMap(new ArrayList<>(deviceIds));
+            Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap =
+                    iotDeviceRunLogService.getDeviceRunLogMapAlone(new ArrayList<>(deviceIds), new ArrayList<>(deviceCategoryIds), deviceCategoryPair);
+            // 以设备为维度统计每个设备相关的保养项的最近保养距离 key设备id    value设备下每个保养项的的最小保养距离集合
+            Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
+            // 设备保养明细 key设备id  value设备保养工单明细下所有保养规则数据最小值
+            Map<Long, String> resultMap = new HashMap<>();
+            // 记录保养项的 提前量 key设备id-保养项id-距离保养值   value提前量
+            Map<String, BigDecimal> bomLeadPair = new HashMap<>();
+            if (CollUtil.isNotEmpty(mainBomS)) {
+                // 遍历保养计划明细 计算 保养计划中的设备 的最近的保养时间
+                for (IotMaintenanceBomDO bom : mainBomS) {
+                    BigDecimal runningTimeDistance = null;
+                    BigDecimal runningKiloDistance = null;
+                    BigDecimal naturalDateDistance = null;
+                    // 计算每个保养项 每个保养规则下的 距离保养时间 单位 小时
+                    if (ObjUtil.isNotEmpty(bom.getRunningTimeRule()) && 0 == bom.getRunningTimeRule()) {
+                        // 运行时间保养规则
+                        BigDecimal totalRunTime = BigDecimal.ZERO;
+                        if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                            totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
+                        } else {
+                            // 运行记录模板中包含多个累计时长 公里数类型的属性
+                            String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getCode());
+                            if (tempTotalRunDataPair.containsKey(uniqueKey)) {
+                                totalRunTime = tempTotalRunDataPair.get(uniqueKey);
+                            }
+                        }
+                        BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
+                        BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
+                        BigDecimal timePeriodLead = bom.getTimePeriodLead();    // 运行时间周期提前量
+                        runningTimeDistance = runningTimePeriod.subtract(totalRunTime.subtract(lastRunningTime));
+                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningTimeDistance);
+                        bomLeadPair.put(uniqueKey, timePeriodLead);
+                    }
+                    if (ObjUtil.isNotEmpty(bom.getMileageRule()) && 0 == bom.getMileageRule()) {
+                        // 运行里程保养规则 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
+                        BigDecimal totalMileage = BigDecimal.ZERO;
+                        if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                            totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
+                        } else {
+                            // 运行记录模板中包含多个累计时长 公里数类型的属性
+                            String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getType());
+                            if (tempTotalRunDataPair.containsKey(uniqueKey)) {
+                                totalMileage = tempTotalRunDataPair.get(uniqueKey);
+                            }
+                        }
+                        BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
+                        BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
+                        BigDecimal kiloCycleLead = bom.getKiloCycleLead();    // 运行里程周期提前量
+                        runningKiloDistance = runningKiloPeriod.subtract(totalMileage.subtract(lastRunningKilo));
+                        String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), runningKiloDistance);
+                        bomLeadPair.put(uniqueKey, kiloCycleLead);
+                    }
+                    if (ObjUtil.isNotEmpty(bom.getNaturalDateRule()) && 0 == bom.getNaturalDateRule()) {
+                        // 自然日期保养规则
+                        LocalDateTime lastNaturalDate = bom.getLastNaturalDate();      // 上次保养自然日期
+                        BigDecimal naturalDatePeriod = bom.getNextNaturalDate();        // 自然日周期
+                        BigDecimal natualDateLead = bom.getNaturalDatePeriodLead();    // 自然日周期提前量
+                        if (ObjUtil.isNotEmpty(lastNaturalDate) && ObjUtil.isNotEmpty(naturalDatePeriod) && ObjUtil.isNotEmpty(natualDateLead)) {
+                            // 计算有效天数 = 自然日周期 - 提前量
+                            long days = naturalDatePeriod.longValue(); // 转为长整型天数
+                            // 计算目标日期:上次保养日期 + 有效天数(注意:LocalDateTime加天数会自动处理日期进位)
+                            LocalDateTime targetDate = lastNaturalDate.plusDays(days);
+                            // 获取当前日期时间
+                            LocalDateTime now = LocalDateTime.now();
+                            // 计算日期差值(以天为单位)
+                            naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(now, targetDate));
+                            String uniqueKey = StrUtil.join("-", bom.getDeviceId(), bom.getBomNodeId(), naturalDateDistance);
+                            bomLeadPair.put(uniqueKey, natualDateLead);
+                        }
+                    }
+                    // 找出距离0最近的数,作为工单的最近保养距离数据
+                    Object closet = chooseNearestDistance(runningTimeDistance, runningKiloDistance, naturalDateDistance);
+                    Map<String, Object> tempDistance = new HashMap<>();
+                    if (closet == runningTimeDistance) {
+                        tempDistance.put("H", closet);
+                    } else if (closet == runningKiloDistance) {
+                        tempDistance.put("KM", closet);
+                    } else if (closet == naturalDateDistance) {
+                        tempDistance.put("D", closet);
+                    }
+                    if (orderDistancePair.containsKey(bom.getDeviceId())) {
+                        List<Map<String, Object>> tempDistances = orderDistancePair.get(bom.getDeviceId());
+                        tempDistances.add(tempDistance);
+                        orderDistancePair.put(bom.getDeviceId(), tempDistances);
+                    } else {
+                        List<Map<String, Object>> tempDistances = new ArrayList<>();
+                        tempDistances.add(tempDistance);
+                        orderDistancePair.put(bom.getDeviceId(), tempDistances);
+                    }
+                }
+            }
+            // 以设备id 为维度 统计每个保养工单明细中 距离最近的保养数据
+            resultMap = findClosestToZero(orderDistancePair);
+            // 根据保养项规则应该生成保养工单的设备id集合
+            Set<Long> shouldWorkOrderFlags = new HashSet<>();
+            // 找出设备保养项距离保养最小值对应的 提前量
+            if (CollUtil.isNotEmpty(resultMap)) {
+                resultMap.forEach((k,v) -> {
+                    // k设备id    v距离保养最小值(带单位)
+                    // 去掉v中带的单位 310 D   -61.00 H
+                    String[] distanceStrs = v.split(" ");
+                    BigDecimal tempDistance = new BigDecimal(distanceStrs[0]);
+                    if (tempDistance.compareTo(BigDecimal.ZERO) < 0) {
+                        shouldWorkOrderFlags.add(k);
+                    }
+                    if (CollUtil.isNotEmpty(bomLeadPair)) {
+                        bomLeadPair.forEach((key,value) -> {
+                            // key设备id-保养项id-距离保养值   value提前量
+                            String[] uniqueKeyStrs = key.split("-");
+                            if (uniqueKeyStrs.length == 3) {
+                                if (String.valueOf(k).equals(uniqueKeyStrs[0]) && distanceStrs[0].equals(uniqueKeyStrs[2])
+                                        && tempDistance.compareTo(BigDecimal.ZERO)>=0 && tempDistance.compareTo(value)<=0 )  {
+                                    shouldWorkOrderFlags.add(k);
+                                }
+                            }
+                        });
+                    }
+                });
+            }
+            // 对集合 resultMap 中所有数据进行排序 按照 map 的value值 去除后面的 字符后 升序排列
+            // 排序后输出一个 List<Long> 类型的集合,排序对应上面的排序规则
+            // 筛选出所有 保养距离 小于0 的设备
+            List<Long> sortedDeviceIds = sortByNumericTimeoutValue(resultMap);
+            try {
+                // 查询所有公司级别的组织部门
+                Set<Long> parentIds = new HashSet<>();
+                parentIds.add(DeptDO.PARENT_ID_ROOT);
+                // 公司级部门
+                List<DeptDO> theFirstLevelDepts = deptMapper.selectListByParentId(parentIds);
+                AtomicReference<List<DeptDO>> theSecondLevelDepts = new AtomicReference<>(new ArrayList<>());
+                if (CollUtil.isNotEmpty(theFirstLevelDepts)) {
+                    theFirstLevelDepts.forEach(dept -> {
+                        parentIds.clear();
+                        parentIds.add(dept.getId());
+                        theSecondLevelDepts.set(deptMapper.selectListByParentId(parentIds));
+                    });
+                }
+                Map<Long, String> firstLevelDeptNamePair = new HashMap<>();
+                // key子部门id     value公司级部门id
+                Map<Long, Long> deptCompanyPair = new HashMap<>();
+                if (CollUtil.isNotEmpty(theSecondLevelDepts.get())) {
+                    AtomicReference<Set<Long>> ids = new AtomicReference<>(new HashSet<>());
+                    theSecondLevelDepts.get().forEach(dept -> {
+                        // 对部门名称进行 处理 去掉多语言部分
+                        String deptName = dept.getName();
+                        deptName = StrUtil.subBefore(deptName, "~~", false);
+                        firstLevelDeptNamePair.put(dept.getId(), deptName);
+                        // 查询每个公司下所有子部门 将子部门与公司建立对应关系
+                        ids.set(deptService.getChildDeptIdListFromCache(dept.getId()));
+                        ids.get().add(dept.getId());
+                        if (CollUtil.isNotEmpty(ids.get())) {
+                            ids.get().forEach(deptId -> {
+                                deptCompanyPair.put(deptId, dept.getId());
+                            });
+                        }
+                        ids.get().clear();;
+                    });
+                    // 查询 排序后的保养距离超时的 sortedDeviceIds 集合中 每个设备 对应的部门
+                    IotDevicePageReqVO deviceReqVO = new IotDevicePageReqVO();
+                    deviceReqVO.setDeviceIds(sortedDeviceIds);
+                    List<IotDeviceDO> devices = iotDeviceMapper.selectListAlone(deviceReqVO);
+                    // key公司级部门id     value公司下所有保养超时的设备数量
+                    Map<Long, Integer> companyTimeoutDevices = new HashMap<>();
+
+                    if (CollUtil.isNotEmpty(devices)) {
+                        devices.forEach(device -> {
+                            if (deptCompanyPair.containsKey(device.getDeptId())) {
+                                Long tempCompanyDeptId = deptCompanyPair.get(device.getDeptId());
+                                if (companyTimeoutDevices.containsKey(tempCompanyDeptId)) {
+                                    Integer tempCount = companyTimeoutDevices.get(tempCompanyDeptId);
+                                    companyTimeoutDevices.put(tempCompanyDeptId, ++tempCount);
+                                } else {
+                                    companyTimeoutDevices.put(tempCompanyDeptId, 1);
+                                }
+                            }
+                        });
+                        if (CollUtil.isNotEmpty(companyTimeoutDevices)) {
+                            companyTimeoutDevices.forEach((k,v) -> {
+                                // key公司级部门id     value公司下所有保养超时的设备数量
+                                if (firstLevelDeptNamePair.containsKey(k)) {
+                                    String companyName = firstLevelDeptNamePair.get(k);
+                                    companyNameTimeoutDevices.put(companyName, v);
+                                    IotDeviceMainTimeoutVO timeoutDevice = new IotDeviceMainTimeoutVO();
+                                    timeoutDevice.setDeptName(companyName);
+                                    timeoutDevice.setDeptId(k);
+                                    timeoutDevice.setCount(v);
+                                    companyMainTimeoutDevices.add(timeoutDevice);
+                                }
+                            });
+                        }
+                    }
+                }
+                return companyMainTimeoutDevices;
+            } catch (Exception exception) {
+                return new HashMap<>();
+            }
+        });
+        return companyMainTimeoutDevices;
+    }
+
     @Override
     public PageResult<IotDeviceRespVO> maintenanceSearch(IotMainWorkOrderPageReqVO pageReqVO) {
         // 所有保养计划 明细中待保养的最近距离 里程/时间/自然日
@@ -1064,6 +1590,41 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
                 .collect(Collectors.toList());
     }
 
+    /**
+     * 筛选出 保养距离 超时的所有设备
+     * 排序 Map<Long, String> 中的元素 删除 类似 D、 H、 KM 后进行升序排列
+     * @param map
+     * @return
+     */
+    private List<Long> sortByNumericTimeoutValue(Map<Long, String> map) {
+        return map.entrySet().stream()
+                .map(entry -> {
+                    String[] parts = entry.getValue().split("\\s+");
+                    if (parts.length < 2) {
+                        return new AbstractMap.SimpleEntry<>(entry.getKey(), Double.MAX_VALUE);
+                    }
+
+                    try {
+                        BigDecimal value = new BigDecimal(parts[0]);
+                        // 单位转换:H→天,KM/D保持不变
+                        if ("H".equals(parts[1])) {
+                            value = value.divide(BigDecimal.valueOf(24), 2, BigDecimal.ROUND_HALF_UP);
+                        }
+                        return new AbstractMap.SimpleEntry<>(entry.getKey(), value.doubleValue());
+                    } catch (NumberFormatException e) {
+                        return new AbstractMap.SimpleEntry<>(entry.getKey(), Double.MAX_VALUE);
+                    }
+                })
+                // 2. 过滤出转换后数值小于0的数据
+                .filter(entry -> entry.getValue() < 0)
+                .sorted(Comparator
+                        .<AbstractMap.SimpleEntry<Long, Double>>comparingDouble(AbstractMap.SimpleEntry::getValue) // 按转换值升序
+                        .thenComparingLong(AbstractMap.SimpleEntry::getKey) // 值相同时按键升序
+                )
+                .map(AbstractMap.SimpleEntry::getKey)
+                .collect(Collectors.toList());
+    }
+
     /**
      * 找出每个设备对应的bom保养项集合中 距离保养最短的 数据
      * @param orderDistancePair

+ 46 - 14
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotopeationfill/IotOpeationFillServiceImpl.java

@@ -56,7 +56,9 @@ public class IotOpeationFillServiceImpl implements IotOpeationFillService {
 
     private static Long RH_CLASS_ID = 226L;
 
-    private static Long RY_CLASS_ID = 227L;
+    private static Long RY_ZJ_CLASS_ID = 227L;
+
+    private static Long RY_XJ_CLASS_ID = 228L;
 
     @Override
     public Long createIotOpeationFill(IotOpeationFillSaveReqVO createReqVO) {
@@ -342,6 +344,12 @@ public class IotOpeationFillServiceImpl implements IotOpeationFillService {
         if(orderList.size()>0){
             //遍历工单主数据,根据dept_id查询是否为根节点
             for (IotOpeationFillOrderDO orderDO:orderList) {
+
+                IotDeviceRunLogDO runLogDO = new IotDeviceRunLogDO();
+                runLogDO.setDeptId(orderDO.getDeptId());
+                //获取队伍类型
+                IotDeviceRunLogDO teamType = iotOpeationFillMapper.getTeamType(runLogDO);
+
                 List<IotOpeationFillOrderDO> childList = iotOpeationFillMapper.childList(orderDO);
 
                 int randomNum = generateUniqueNumber();
@@ -354,8 +362,13 @@ public class IotOpeationFillServiceImpl implements IotOpeationFillService {
 
                     if(rhIdList.contains(orderDO.getDeptId())){
                         devOrder.setDeviceCategoryId(RH_CLASS_ID);
-                    }else if(ryIdList.contains(orderDO.getDeptId())){
-                        devOrder.setDeviceCategoryId(RY_CLASS_ID);
+                    }else if(ryIdList.contains(orderDO.getDeptId())&&teamType!=null){
+                        //队伍类型为zj则日报设备分类为钻井,否则为修井
+                        if(teamType.getPointCode().equals("zj")){
+                            devOrder.setDeviceCategoryId(RY_ZJ_CLASS_ID);
+                        }else{
+                            devOrder.setDeviceCategoryId(RY_XJ_CLASS_ID);
+                        }
                     }
 
                     devOrder.setDeptId(orderDO.getDeptId());
@@ -376,7 +389,14 @@ public class IotOpeationFillServiceImpl implements IotOpeationFillService {
     // 日期格式化器,用于生成每天的唯一标识
     private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
 
-    public static synchronized int generateUniqueNumber() {
+    /**
+     * 生成每天唯一的6位数字序列号
+     * 格式:两位日期码 + 四位序列号
+     *
+     * @return 唯一的6位数字
+     * @throws RuntimeException 当超过每日最大生成量时抛出
+     */
+    public static int generateUniqueNumber() {
         // 获取当前日期字符串,如20231005
         String today = LocalDate.now().format(DATE_FORMATTER);
 
@@ -385,18 +405,30 @@ public class IotOpeationFillServiceImpl implements IotOpeationFillService {
                 Integer.parseInt(today.substring(4, 6)) +
                 Integer.parseInt(today.substring(6, 8))) % 99 + 1;
 
-        // 获取并递增当天的序列号,不存在则初始化为1
-        int seq = dailySequence.merge(today, 1, Integer::sum);
+        // 使用循环确保获取到正确的序列号,处理并发情况
+        while (true) {
+            // 获取当前序列号,不存在则为0
+            Integer current = dailySequence.get(today);
+            int nextSeq = (current == null) ? 1 : current + 1;
 
-        // 检查是否超过每天的最大生成量
-        if (seq > 9999) {
-            // 回退计数器,因为我们已经递增了它
-            dailySequence.put(today, 9999);
-            throw new RuntimeException("今日已超过最大生成数量(9999个)");
-        }
+            // 检查是否超过每天的最大生成量
+            if (nextSeq > 9999) {
+                throw new RuntimeException("今日已超过最大生成数量(9999个)");
+            }
 
-        // 组合日期标识和序列号,生成6位唯一数字
-        return dateCode * 10000 + seq;
+            // 使用putIfAbsent确保原子性操作,如果不存在则设置为1
+            if (current == null) {
+                if (dailySequence.putIfAbsent(today, 1) == null) {
+                    return dateCode * 10000 + 1;
+                }
+            }
+            // 使用replace确保只有当前值匹配时才更新,避免覆盖其他线程的更新
+            else if (dailySequence.replace(today, current, nextSeq)) {
+                return dateCode * 10000 + nextSeq;
+            }
+
+            // 如果上述操作失败,循环重试
+        }
     }
 
 

+ 15 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotrhdailyreport/IotRhDailyReportServiceImpl.java

@@ -25,6 +25,8 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
 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 com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
 import org.springframework.stereotype.Service;
@@ -344,7 +346,19 @@ public class IotRhDailyReportServiceImpl implements IotRhDailyReportService {
             ids.add(pageReqVO.getDeptId());
             pageReqVO.setDeptIds(ids);
         }
-        return iotRhDailyReportMapper.selectPage(pageReqVO);
+        // 检查contractName不为空但projectIds为空的情况
+        if (StrUtil.isNotBlank(pageReqVO.getContractName()) && (CollUtil.isEmpty(pageReqVO.getProjectIds()))) {
+            return new PageResult<>(Collections.emptyList(), 0L);
+        }
+        // 检查taskName不为空但taskIds为空的情况
+        if (StrUtil.isNotBlank(pageReqVO.getTaskName()) && (CollUtil.isEmpty(pageReqVO.getTaskIds()))) {
+            return new PageResult<>(Collections.emptyList(), 0L);
+        }
+        IPage<IotRhDailyReportDO> page = iotRhDailyReportMapper.rhDailyReports(
+                new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize()), pageReqVO,
+                pageReqVO.getTaskIds(), pageReqVO.getProjectIds(), ids);
+
+        return new PageResult<>(page.getRecords(), page.getTotal());
     }
 
     @Override

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

@@ -64,7 +64,9 @@ public class IotRyDailyReportServiceImpl implements IotRyDailyReportService {
         if (ObjUtil.isEmpty(createReqVO.getDeptId())) {
             throw exception(IOT_RH_DAILY_REPORT_NO_DEPT);
         }
-        if (ObjUtil.isEmpty(createReqVO.getCurrentDepth())) {
+        if ((StrUtil.isBlank(createReqVO.getProjectClassification())||"1".equals(createReqVO.getProjectClassification()))
+                && ObjUtil.isEmpty(createReqVO.getCurrentDepth())) {
+            // 钻井类型日报必须 填写当前井深
             throw exception(IOT_RY_DAILY_REPORT_CURRENT_DEPTH_NOT_EXISTS);
         }
 
@@ -96,34 +98,48 @@ public class IotRyDailyReportServiceImpl implements IotRyDailyReportService {
             // throw exception(IOT_PROJECT_TASK_NOT_RELATED);
         }
 
-        // 计算月进尺 年累计进尺 reportDate对应的前一天的 年-月份 对应的日报记录日进尺的累加值
-        // 查询指定部门、指定时间段 填写的日报记录的 ’月进尺数据‘(逻辑细则:reportDate 所在的月份与数据表的字段 construction_start_date
-        // 所在的月份相匹配 且 create_time < reportDate 的记录的 ’daily_footage‘ 字段值累加计算得出
-        BigDecimal historyMonthlyFootage = iotRyDailyReportMapper.monthlyFootages(createReqVO.getDeptId(), reportDate);
-
-        // 查询指定部门、指定时间段 填写的日报记录的 ’年进尺数据‘(逻辑细则:reportDate 所在的年份与数据表的字段 construction_start_date
-        // 所在的年份相匹配 且 create_time < reportDate 的记录的 ’daily_footage‘ 字段值累加计算得出
-        BigDecimal historyAnnualFootage = iotRyDailyReportMapper.annualFootages(createReqVO.getDeptId(), reportDate);
-
-        // 当前井深 计算 日进尺 月进尺 年累计进尺
-        // (当前井深 - 前一天日报中填写的 ‘当前井深’)= 日进尺
-        // 将 reportDate 减去1天 得到日期 date(yyyy-MM-dd) 查询date对应的日报记录
-        IotRyDailyReportDO lastReport = iotRyDailyReportMapper.selectLatestReportBeforeDate(
-                createReqVO.getDeptId(), null, reportDate);
-        if (ObjUtil.isNotEmpty(lastReport) && ObjUtil.isNotEmpty(lastReport.getCurrentDepth())) {
-            // 当前井深-前一天日报的当前井深 = 日进尺
-            BigDecimal dailyFootage = createReqVO.getCurrentDepth().subtract(lastReport.getCurrentDepth());
-            iotRyDailyReport.setDailyFootage(dailyFootage);
-            // 日进尺+历史的当前月进尺 = 总的月进尺
-            iotRyDailyReport.setMonthlyFootage(dailyFootage.add(historyMonthlyFootage));
-            // 日进尺+历史的当前年进尺 = 总的年进尺
-            iotRyDailyReport.setAnnualFootage(dailyFootage.add(historyAnnualFootage));
-        } else {
-            // 如果没有查询到数据 则当前井深 就是日进尺
-            iotRyDailyReport.setDailyFootage(createReqVO.getCurrentDepth());
-            iotRyDailyReport.setMonthlyFootage(createReqVO.getCurrentDepth().add(historyMonthlyFootage));
-            // 日进尺+历史的当前年进尺 = 总的年进尺
-            iotRyDailyReport.setAnnualFootage(createReqVO.getCurrentDepth().add(historyAnnualFootage));
+        if (StrUtil.isBlank(createReqVO.getProjectClassification()) || "1".equals(createReqVO.getProjectClassification())) {
+            // 钻井类型日报 需要根据当前井深 计算 日进尺 月进尺 年累计进尺
+
+            // 计算月进尺 年累计进尺 reportDate对应的前一天的 年-月份 对应的日报记录日进尺的累加值
+            // 查询指定部门、指定时间段 填写的日报记录的 ’月进尺数据‘(逻辑细则:reportDate 所在的月份与数据表的字段 construction_start_date
+            // 所在的月份相匹配 且 create_time < reportDate 的记录的 ’daily_footage‘ 字段值累加计算得出
+            BigDecimal monthStartFootage = iotRyDailyReportMapper.monthStartFootage(createReqVO.getDeptId(), reportDate);
+
+            // 查询指定部门、指定时间段 填写的日报记录的 ’年进尺数据‘(逻辑细则:reportDate 所在的年份与数据表的字段 construction_start_date
+            // 所在的年份相匹配 且 create_time < reportDate 的记录的 ’daily_footage‘ 字段值累加计算得出
+            BigDecimal yearStartFootage = iotRyDailyReportMapper.yearStartFootage(createReqVO.getDeptId(), reportDate);
+
+            // 当前井深 计算 日进尺 月进尺 年累计进尺
+            // (当前井深 - 前一天日报中填写的 ‘当前井深’)= 日进尺
+            // 将 reportDate 减去1天 得到日期 date(yyyy-MM-dd) 查询date对应的日报记录
+            IotRyDailyReportDO lastReport = iotRyDailyReportMapper.selectLatestReportBeforeDate(
+                    createReqVO.getDeptId(), null, reportDate);
+            if (ObjUtil.isNotEmpty(lastReport) && ObjUtil.isNotEmpty(lastReport.getCurrentDepth())) {
+                // 当前井深-前一天日报的当前井深 = 日进尺
+                BigDecimal dailyFootage = createReqVO.getCurrentDepth().subtract(lastReport.getCurrentDepth());
+                iotRyDailyReport.setDailyFootage(dailyFootage);
+                // 月进尺 = 当前井深 - 当前月份的最早记录天的井深
+                BigDecimal monthlyFootage = (monthStartFootage.compareTo(BigDecimal.ZERO) == 0)
+                        ? BigDecimal.ZERO : createReqVO.getCurrentDepth().subtract(monthStartFootage);
+                iotRyDailyReport.setMonthlyFootage(monthlyFootage);
+                // 年累计进尺 = 当前井深 - 当前年最早记录的井深
+                BigDecimal annualFootage = (yearStartFootage.compareTo(BigDecimal.ZERO) == 0)
+                        ? BigDecimal.ZERO : createReqVO.getCurrentDepth().subtract(yearStartFootage);
+                iotRyDailyReport.setAnnualFootage(annualFootage);
+            } else {
+                // 如果没有查询到数据 则设置 日进尺 为0 后期可修复数据
+                iotRyDailyReport.setDailyFootage(BigDecimal.ZERO);
+                // 月进尺 = 当前井深 - 当前月份的最早记录天的井深
+                iotRyDailyReport.setMonthlyFootage(BigDecimal.ZERO);
+                // 年累计进尺 = 当前井深 - 当前年最早记录的井深
+                iotRyDailyReport.setAnnualFootage(BigDecimal.ZERO);
+            }
+        }
+
+        // 设置瑞鹰日报类型 钻井 修井
+        if (StrUtil.isBlank(createReqVO.getProjectClassification())) {
+            iotRyDailyReport.setProjectClassification("1");
         }
 
         // 当天如果已经有此小队的记录 新增 当天如果没有此小队的日报记录 修改

+ 45 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotDeviceMapper.xml

@@ -86,6 +86,51 @@
         rid.id DESC;
     </select>
 
+    <select id="timeoutDeviceMaintenances"
+            resultType="cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO">
+        SELECT
+        rid.id,
+        rid.dept_id AS deptId,
+        rid.device_code AS deviceCode,
+        rid.device_name AS deviceName,
+        rid.device_status AS deviceStatus,
+        rid.model AS model,
+        rid.asset_property AS assetProperty
+        FROM rq_iot_device rid
+        WHERE rid.deleted = 0
+        <if test="deptIds != null and deptIds.size &gt; 0">
+            AND rid.dept_id IN
+            <foreach collection="deptIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="reqVO.deviceName!=null and reqVO.deviceName!=''">
+            AND rid.device_name LIKE concat(concat("%",#{reqVO.deviceName}),"%")
+        </if>
+        <if test="reqVO.deviceCode!=null and reqVO.deviceCode!=''">
+            AND rid.device_code LIKE concat(concat("%",#{reqVO.deviceCode}),"%")
+        </if>
+        <if test="alarmDeviceIds != null and alarmDeviceIds.size &gt; 0">
+            AND rid.id IN
+            <foreach collection="alarmDeviceIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        ORDER BY
+        <if test="alarmDeviceIds != null and alarmDeviceIds.size &gt; 0">
+            CASE WHEN rid.id IN
+            <foreach collection="alarmDeviceIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+            THEN 0 ELSE 1 END,
+            FIELD(rid.id,
+            <foreach collection="alarmDeviceIds" index="index" item="key" separator=",">
+                #{key}
+            </foreach>),
+        </if>
+        rid.id DESC;
+    </select>
+
     <select id="deviceDistances"
             resultType="cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO">
         SELECT *

+ 1 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotModelTemplateMapper.xml

@@ -11,7 +11,7 @@
 
     <select id="isRelated" parameterType="cn.iocoder.yudao.module.pms.controller.admin.iotmodeltemplate.vo.IotModelTemplatePageReqVO"
             resultType="cn.iocoder.yudao.module.pms.dal.dataobject.iotmodeltemplate.IotModelTemplateDO">
-        select distinct a.* from rqiot.rq_iot_opeation_fill_order a,
+        select distinct a.* from rq_iot_opeation_fill_order a,
                                  rq_iot_opeation_fill b,
                                  rq_iot_model_template c
         where a.id =b.order_id

+ 12 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotOpeationFillMapper.xml

@@ -1177,5 +1177,17 @@
     </select>
 
 
+    <select id="getTeamType" parameterType="cn.iocoder.yudao.module.pms.dal.dataobject.iotdevicerunlog.IotDeviceRunLogDO"
+            resultType="cn.iocoder.yudao.module.pms.dal.dataobject.iotdevicerunlog.IotDeviceRunLogDO">
+        select type as point_code from system_dept_type where dept_id = #{deptId}
+    </select>
+
+
+    <select id="getUserInfo" parameterType="cn.iocoder.yudao.module.pms.dal.dataobject.iotdevicerunlog.IotDeviceRunLogDO"
+            resultType="cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO">
+        select * from system_users where dept_id = #{deptId} and nickname = '技术员'
+    </select>
+
+
 
 </mapper>

+ 0 - 3
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/IotOperationPlanMapper.xml

@@ -17,9 +17,6 @@
             status = 0
         and
             deleted = 0
-        and
-            is_report is null
-        or  is_report != 1
     </select>
 
     <select id="getReportPlan" resultType="cn.iocoder.yudao.module.pms.dal.dataobject.iotoperationplan.IotOperationPlanDO">

+ 1 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/iotprojectinfo/IotProjectInfoMapper.xml

@@ -11,6 +11,6 @@
 
     <select id="projectList" parameterType="cn.iocoder.yudao.module.pms.controller.admin.iotprojectinfo.vo.IotProjectInfoSaveReqVO"
     resultType="cn.iocoder.yudao.module.pms.dal.dataobject.iotprojectinfo.IotProjectInfoDO">
-        select * from rqiot.rq_iot_project_info where deleted = 0
+        select * from rq_iot_project_info where deleted = 0
     </select>
 </mapper>

+ 2 - 2
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/iotprojecttask/IotProjectTaskMapper.xml

@@ -35,8 +35,8 @@
         b.contract_name,
         b.contract_code
         from
-        rqiot.rq_iot_project_task a,
-        rqiot.rq_iot_project_info b
+        rq_iot_project_task a,
+        rq_iot_project_info b
         where 1=1
         and a.project_id = b.id
         and a.deleted = 0

+ 57 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/resources/mapper/static/iotprojecttask/IotRhDailyReportMapper.xml

@@ -15,6 +15,29 @@
         <result column="construction_end_date" property="constructionEndDate" />
         <result column="relocation_days" property="relocationDays" />
         <!-- 其他字段映射 -->
+        <result column="project_classification" property="projectClassification" />
+        <result column="transit_time" property="transitTime" />
+        <result column="daily_gas_injection" property="dailyGasInjection" />
+        <result column="daily_water_injection" property="dailyWaterInjection" />
+        <result column="daily_inject_gas_time" property="dailyInjectGasTime" />
+        <result column="daily_inject_water_time" property="dailyInjectWaterTime" />
+        <result column="daily_power_usage" property="dailyPowerUsage" />
+        <!-- 其他字段映射 -->
+        <result column="non_production_time" property="nonProductionTime" />
+        <result column="npt_reason" property="nptReason" />
+        <result column="production_status" property="productionStatus" />
+        <result column="next_plan" property="nextPlan" />
+        <result column="total_water_injection" property="totalWaterInjection" />
+        <result column="personnel" property="personnel" />
+        <result column="total_gas_injection" property="totalGasInjection" />
+        <!-- 其他字段映射 -->
+        <result column="cumulative_completion" property="cumulativeCompletion" />
+        <result column="remark" property="remark" />
+        <result column="status" property="status" />
+        <result column="creator" property="creator" />
+        <result column="create_time" property="createTime" />
+        <result column="updater" property="updater" />
+        <result column="update_time" property="updateTime" />
     </resultMap>
 
     <!-- 使用窗口函数实现分组查询 -->
@@ -112,4 +135,38 @@
         GROUP BY dept_id;
     </select>
 
+    <select id="rhDailyReports" resultMap="BaseResultMap"
+            parameterType="cn.iocoder.yudao.module.pms.controller.admin.iotrhdailyreport.vo.IotRhDailyReportPageReqVO">
+        SELECT
+            *
+        FROM
+            rq_iot_rh_daily_report rdr
+        INNER JOIN system_dept d ON d.id = rdr.dept_id
+        WHERE
+            rdr.deleted = 0
+        AND rdr.dept_id IS NOT NULL
+        <if test="deptIds != null and deptIds.size &gt; 0">
+            AND rdr.dept_id IN
+            <foreach collection="deptIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="projectIds != null and projectIds.size &gt; 0">
+            AND rdr.project_id IN
+            <foreach collection="projectIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        <if test="taskIds != null and taskIds.size &gt; 0">
+            AND rdr.task_id IN
+            <foreach collection="taskIds" index="index" item="key" open="(" separator="," close=")">
+                #{key}
+            </foreach>
+        </if>
+        ORDER BY
+            rdr.create_time DESC,
+            REGEXP_REPLACE ( d.`name`, '[0-9]+', '' ) ASC,
+            CAST( REGEXP_REPLACE ( d.`name`, '[^0-9]+', '' ) AS UNSIGNED ) ASC;
+    </select>
+
 </mapper>

+ 15 - 0
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/DeptController.java

@@ -92,6 +92,21 @@ public class DeptController {
         return success(BeanUtils.toBean(list, DeptSimpleRespVO.class));
     }
 
+    @GetMapping("/specifiedSimpleDepts")
+    @Operation(summary = "获取指定部门精简信息列表", description = "只包含被开启的部门,主要用于前端的下拉选项")
+    @PermitAll
+    @Parameter(name = "deptId", description = "部门id", required = true, example = "1024")
+    public CommonResult<List<DeptSimpleRespVO>> specifiedSimpleDepts(@RequestParam("deptId") Long deptId) {
+        DeptListReqVO reqVO = new DeptListReqVO();
+        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
+        reqVO.setDeptId(deptId);
+        List<DeptDO> list = deptService.getChildDeptList(deptId);
+        // 查询 deptId 对应的部门 加入 最终返回的集合
+        DeptDO currentDept = deptService.getDept(deptId);
+        list.add(currentDept);
+        return success(BeanUtils.toBean(list, DeptSimpleRespVO.class));
+    }
+
     @GetMapping("/get")
     @Operation(summary = "获得部门信息")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")

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

@@ -18,4 +18,7 @@ public class DeptListReqVO {
     @Schema(description = "部门id集合", example = "[12,14]")
     private Collection<Long> deptIds;
 
+    @Schema(description = "指定的部门id", example = "123")
+    private Long deptId;
+
 }