learshaw пре 2 месеци
родитељ
комит
42adc08b3a
37 измењених фајлова са 3200 додато и 1192 уклоњено
  1. 0 106
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/AdmOpInspectionReportController.java
  2. 248 0
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/InspectionPlanController.java
  3. 128 0
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/InspectionReportController.java
  4. 152 0
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/InspectionSchedulerController.java
  5. 0 104
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/OpInspectionPlanController.java
  6. 359 0
      ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/task/InspectionScheduler.java
  7. 0 135
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/AdmOpInspectionReport.java
  8. 26 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/CheckItemResult.java
  9. 82 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/InspectionPlan.java
  10. 68 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/InspectionReport.java
  11. 35 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/InspectionReportDetail.java
  12. 28 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/InspectionRule.java
  13. 49 0
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/ManualReportSubmit.java
  14. 0 195
      ems/ems-core/src/main/java/com/ruoyi/ems/domain/OpInspectionTask.java
  15. 0 61
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AdmOpInspectionReportMapper.java
  16. 54 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/InspectionPlanMapper.java
  17. 18 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/InspectionReportDetailMapper.java
  18. 24 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/InspectionReportMapper.java
  19. 27 0
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/InspectionRuleMapper.java
  20. 0 61
      ems/ems-core/src/main/java/com/ruoyi/ems/mapper/OpInspectionPlanMapper.java
  21. 0 61
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IAdmOpInspectionReportService.java
  22. 53 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IInspectionPlanService.java
  23. 69 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IInspectionReportService.java
  24. 0 61
      ems/ems-core/src/main/java/com/ruoyi/ems/service/IOpInspectionPlanService.java
  25. 0 87
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AdmOpInspectionReportServiceImpl.java
  26. 732 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/InspectionPlanServiceImpl.java
  27. 184 0
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/InspectionReportServiceImpl.java
  28. 0 87
      ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/OpInspectionPlanServiceImpl.java
  29. 0 78
      ems/ems-core/src/main/resources/mapper/ems/AdmOpInspectionReportMapper.xml
  30. 146 0
      ems/ems-core/src/main/resources/mapper/ems/InspectionPlanMapper.xml
  31. 53 0
      ems/ems-core/src/main/resources/mapper/ems/InspectionReportDetailMapper.xml
  32. 109 0
      ems/ems-core/src/main/resources/mapper/ems/InspectionReportMapper.xml
  33. 97 0
      ems/ems-core/src/main/resources/mapper/ems/InspectionRuleMapper.xml
  34. 0 111
      ems/ems-core/src/main/resources/mapper/ems/OpInspectionPlanMapper.xml
  35. 354 6
      ems/sql/ems_init_data_ctfwq.sql
  36. 103 37
      ems/sql/ems_server.sql
  37. 2 2
      ems/sql/ems_sys_data.sql

+ 0 - 106
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/AdmOpInspectionReportController.java

@@ -1,106 +0,0 @@
-package com.ruoyi.ems.controller;
-
-import com.huashe.common.domain.AjaxResult;
-import com.huashe.common.utils.DateUtils;
-import com.ruoyi.common.core.utils.poi.ExcelUtil;
-import com.ruoyi.common.core.web.controller.BaseController;
-import com.ruoyi.common.core.web.page.TableDataInfo;
-import com.ruoyi.common.log.annotation.Log;
-import com.ruoyi.common.log.enums.BusinessType;
-import com.ruoyi.common.security.annotation.RequiresPermissions;
-import com.ruoyi.common.security.utils.SecurityUtils;
-import com.ruoyi.ems.domain.AdmOpInspectionReport;
-import com.ruoyi.ems.service.IAdmOpInspectionReportService;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.servlet.http.HttpServletResponse;
-import java.util.List;
-
-/**
- * 巡检报告Controller
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-@RestController
-@RequestMapping("/inspectionReport")
-@Api(value = "AdmOpInspectionReportController", description = "巡检报告")
-public class AdmOpInspectionReportController extends BaseController {
-    @Autowired
-    private IAdmOpInspectionReportService inspectionReportService;
-
-    /**
-     * 查询巡检报告列表
-     */
-    @RequiresPermissions("ems:inspection-report:list")
-    @GetMapping("/list")
-    public TableDataInfo list(AdmOpInspectionReport inspectionReport) {
-        startPage();
-        List<AdmOpInspectionReport> list = inspectionReportService.selectAdmOpInspectionReportList(inspectionReport);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出巡检报告列表
-     */
-    @RequiresPermissions("ems:inspection-report:export")
-    @Log(title = "巡检报告", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, AdmOpInspectionReport inspectionReport) {
-        List<AdmOpInspectionReport> list = inspectionReportService.selectAdmOpInspectionReportList(inspectionReport);
-        ExcelUtil<AdmOpInspectionReport> util = new ExcelUtil<AdmOpInspectionReport>(AdmOpInspectionReport.class);
-        util.exportExcel(response, list, "巡检报告数据");
-    }
-
-    /**
-     * 获取巡检报告详细信息
-     */
-    @RequiresPermissions("ems:inspection-report:query")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id) {
-        return success(inspectionReportService.selectAdmOpInspectionReportById(id));
-    }
-
-    /**
-     * 新增巡检报告
-     */
-    @RequiresPermissions("ems:inspection-report:add")
-    @Log(title = "巡检报告", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody AdmOpInspectionReport inspectionReport) {
-        inspectionReport.setSubTime(DateUtils.getNowDate());
-        inspectionReport.setSubmitter(SecurityUtils.getUsername());
-        return toAjax(inspectionReportService.insertAdmOpInspectionReport(inspectionReport));
-    }
-
-    /**
-     * 修改巡检报告
-     */
-    @RequiresPermissions("ems:inspection-report:edit")
-    @Log(title = "巡检报告", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody AdmOpInspectionReport inspectionReport) {
-        inspectionReport.setSubTime(DateUtils.getNowDate());
-        inspectionReport.setSubmitter(SecurityUtils.getUsername());
-        return toAjax(inspectionReportService.updateAdmOpInspectionReport(inspectionReport));
-    }
-
-    /**
-     * 删除巡检报告
-     */
-    @RequiresPermissions("ems:inspection-report:remove")
-    @Log(title = "巡检报告", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids) {
-        return toAjax(inspectionReportService.deleteAdmOpInspectionReportByIds(ids));
-    }
-}

+ 248 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/InspectionPlanController.java

@@ -0,0 +1,248 @@
+package com.ruoyi.ems.controller;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.common.security.utils.SecurityUtils;
+
+import com.ruoyi.ems.domain.InspectionPlan;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.ManualReportSubmit;
+import com.ruoyi.ems.service.IInspectionPlanService;
+
+import com.ruoyi.ems.task.InspectionScheduler;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 巡检计划Controller
+ *
+ * 【重要改动】
+ * 在增删改操作后同步调度器状态
+ * 调度器仅在 EMS 主模块加载,Service 层保持纯净
+ */
+@RestController
+@RequestMapping("/inspection/plan")
+@Api(value = "InspectionPlanController", description = "巡检计划管理")
+public class InspectionPlanController extends BaseController {
+
+    private static final Logger log = LoggerFactory.getLogger(InspectionPlanController.class);
+
+    /** 计划类型:自动巡检 */
+    private static final int PLAN_TYPE_AUTO = 2;
+
+    @Autowired
+    private IInspectionPlanService planService;
+
+    /**
+     * 注入调度器(仅在EMS主模块存在)
+     */
+    @Autowired
+    private InspectionScheduler inspectionScheduler;
+
+    /**
+     * 查询巡检计划列表
+     */
+    @RequiresPermissions("ems:inspection:plan:list")
+    @GetMapping("/list")
+    @ApiOperation("查询巡检计划列表")
+    public TableDataInfo list(InspectionPlan plan) {
+        startPage();
+        List<InspectionPlan> list = planService.selectList(plan);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出巡检计划列表
+     */
+    @RequiresPermissions("ems:inspection:plan:export")
+    @Log(title = "巡检计划", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ApiOperation("导出巡检计划")
+    public void export(HttpServletResponse response, InspectionPlan plan) {
+        List<InspectionPlan> list = planService.selectList(plan);
+        ExcelUtil<InspectionPlan> util = new ExcelUtil<>(InspectionPlan.class);
+        util.exportExcel(response, list, "巡检计划数据");
+    }
+
+    /**
+     * 获取巡检计划详细信息
+     */
+    @RequiresPermissions("ems:inspection:plan:query")
+    @GetMapping("/{id}")
+    @ApiOperation("获取巡检计划详情")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(planService.selectById(id));
+    }
+
+    /**
+     * 根据计划代码获取详情
+     */
+    @RequiresPermissions("ems:inspection:plan:query")
+    @GetMapping("/code/{planCode}")
+    @ApiOperation("根据计划代码获取详情")
+    public AjaxResult getByCode(@PathVariable("planCode") String planCode) {
+        return success(planService.selectByPlanCode(planCode));
+    }
+
+    /**
+     * 新增巡检计划
+     *
+     * 【改动】新增后同步注册到调度器
+     */
+    @RequiresPermissions("ems:inspection:plan:add")
+    @Log(title = "巡检计划", businessType = BusinessType.INSERT)
+    @PostMapping
+    @ApiOperation("新增巡检计划")
+    public AjaxResult add(@RequestBody InspectionPlan plan) {
+        plan.setCreateBy(SecurityUtils.getUsername());
+        int result = planService.insert(plan);
+
+        // 如果是自动巡检且配置了cron,注册到调度器
+        if (result > 0 && isSchedulablePlan(plan)) {
+            syncSchedulerRegister(plan);
+        }
+
+        return toAjax(result);
+    }
+
+    /**
+     * 修改巡检计划
+     *
+     * 【改动】修改后刷新调度器配置
+     */
+    @RequiresPermissions("ems:inspection:plan:edit")
+    @Log(title = "巡检计划", businessType = BusinessType.UPDATE)
+    @PutMapping
+    @ApiOperation("修改巡检计划")
+    public AjaxResult edit(@RequestBody InspectionPlan plan) {
+        plan.setUpdateBy(SecurityUtils.getUsername());
+        int result = planService.update(plan);
+
+        // 刷新调度器配置
+        if (result > 0) {
+            syncSchedulerRefresh(plan.getPlanCode());
+        }
+
+        return toAjax(result);
+    }
+
+    /**
+     * 删除巡检计划
+     *
+     * 【改动】删除前从调度器注销
+     */
+    @RequiresPermissions("ems:inspection:plan:remove")
+    @Log(title = "巡检计划", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除巡检计划")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        // 先从调度器注销
+        for (Long id : ids) {
+            InspectionPlan plan = planService.selectById(id);
+            if (plan != null) {
+                syncSchedulerUnregister(plan.getPlanCode());
+            }
+        }
+
+        return toAjax(planService.deleteByIds(ids));
+    }
+
+    /**
+     * 执行自动巡检
+     * 仅用于自动巡检计划(planType=2)
+     */
+    @RequiresPermissions("ems:inspection:plan:execute")
+    @Log(title = "执行自动巡检", businessType = BusinessType.OTHER)
+    @PostMapping("/execute/{planCode}")
+    @ApiOperation("执行自动巡检")
+    public AjaxResult execute(@PathVariable("planCode") String planCode) {
+        try {
+            String executor = SecurityUtils.getUsername();
+            InspectionReport report = planService.executeAutoInspection(planCode, executor);
+            return success(report);
+        } catch (Exception e) {
+            return error(e.getMessage());
+        }
+    }
+
+    /**
+     * 提交手动巡检报告
+     * 用于手动巡检计划(planType=1),工作人员现场巡检后提交报告
+     */
+    @RequiresPermissions("ems:inspection:plan:execute")
+    @Log(title = "提交手动巡检报告", businessType = BusinessType.INSERT)
+    @PostMapping("/submitManual")
+    @ApiOperation("提交手动巡检报告")
+    public AjaxResult submitManualReport(@RequestBody ManualReportSubmit submit) {
+        try {
+            String submitter = SecurityUtils.getUsername();
+            submit.setSubmitter(submitter);
+            InspectionReport report = planService.submitManualReport(submit);
+            return success(report);
+        } catch (Exception e) {
+            return error(e.getMessage());
+        }
+    }
+
+    // ==================== 调度器同步方法 ====================
+
+    /**
+     * 判断计划是否可调度
+     */
+    private boolean isSchedulablePlan(InspectionPlan plan) {
+        return plan != null
+            && plan.getPlanType() != null
+            && plan.getPlanType() == PLAN_TYPE_AUTO
+            && StringUtils.isNotBlank(plan.getCronExpression());
+    }
+
+    /**
+     * 同步注册到调度器
+     */
+    private void syncSchedulerRegister(InspectionPlan plan) {
+        try {
+            inspectionScheduler.registerPlan(plan);
+            log.info("巡检计划[{}]已注册到调度器", plan.getPlanCode());
+        } catch (Exception e) {
+            // 注册失败不影响业务,仅记录日志
+            log.warn("同步注册调度器失败: planCode={}, error={}", plan.getPlanCode(), e.getMessage());
+        }
+    }
+
+    /**
+     * 同步刷新调度器
+     */
+    private void syncSchedulerRefresh(String planCode) {
+        try {
+            inspectionScheduler.refreshPlan(planCode);
+            log.info("巡检计划[{}]调度配置已刷新", planCode);
+        } catch (Exception e) {
+            log.warn("同步刷新调度器失败: planCode={}, error={}", planCode, e.getMessage());
+        }
+    }
+
+    /**
+     * 同步注销调度器
+     */
+    private void syncSchedulerUnregister(String planCode) {
+        try {
+            inspectionScheduler.unregisterPlan(planCode);
+            log.info("巡检计划[{}]已从调度器注销", planCode);
+        } catch (Exception e) {
+            log.warn("同步注销调度器失败: planCode={}, error={}", planCode, e.getMessage());
+        }
+    }
+}

+ 128 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/InspectionReportController.java

@@ -0,0 +1,128 @@
+package com.ruoyi.ems.controller;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.utils.poi.ExcelUtil;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.core.web.page.TableDataInfo;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.ManualReportSubmit;
+import com.ruoyi.ems.service.IInspectionReportService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 巡检报告Controller
+ */
+@RestController
+@RequestMapping("/inspection/report")
+@Api(value = "InspectionReportController", description = "巡检报告管理")
+public class InspectionReportController extends BaseController {
+
+    @Autowired
+    private IInspectionReportService reportService;
+
+    /**
+     * 查询巡检报告列表
+     */
+    @RequiresPermissions("ems:inspection:report:list")
+    @GetMapping("/list")
+    @ApiOperation("查询巡检报告列表")
+    public TableDataInfo list(InspectionReport report) {
+        startPage();
+        List<InspectionReport> list = reportService.selectList(report);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出巡检报告列表
+     */
+    @RequiresPermissions("ems:inspection:report:export")
+    @Log(title = "巡检报告", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    @ApiOperation("导出巡检报告")
+    public void export(HttpServletResponse response, InspectionReport report) {
+        List<InspectionReport> list = reportService.selectList(report);
+        ExcelUtil<InspectionReport> util = new ExcelUtil<>(InspectionReport.class);
+        util.exportExcel(response, list, "巡检报告数据");
+    }
+
+    /**
+     * 获取巡检报告详细信息
+     */
+    @RequiresPermissions("ems:inspection:report:query")
+    @GetMapping("/{id}")
+    @ApiOperation("获取巡检报告详情")
+    public AjaxResult getInfo(@PathVariable("id") Long id) {
+        return success(reportService.selectById(id));
+    }
+
+    /**
+     * 根据报告代码获取详情
+     */
+    @RequiresPermissions("ems:inspection:report:query")
+    @GetMapping("/code/{reportCode}")
+    @ApiOperation("根据报告代码获取详情")
+    public AjaxResult getByCode(@PathVariable("reportCode") String reportCode) {
+        return success(reportService.selectByReportCode(reportCode));
+    }
+
+    /**
+     * 修改巡检报告(手动备注)
+     */
+    @RequiresPermissions("ems:inspection:report:edit")
+    @Log(title = "巡检报告", businessType = BusinessType.UPDATE)
+    @PutMapping
+    @ApiOperation("修改巡检报告")
+    public AjaxResult edit(@RequestBody InspectionReport report) {
+        return toAjax(reportService.update(report));
+    }
+
+    /**
+     * 更新手动巡检报告
+     * 用于编辑已提交的手动巡检报告,支持更新巡检结果、报告内容、图片等
+     */
+    @RequiresPermissions("ems:inspection:report:edit")
+    @Log(title = "更新手动巡检报告", businessType = BusinessType.UPDATE)
+    @PutMapping("/updateManual")
+    @ApiOperation("更新手动巡检报告")
+    public AjaxResult updateManualReport(@RequestBody ManualReportSubmit submit) {
+        try {
+            submit.setSubmitter(SecurityUtils.getUsername());
+            int result = reportService.updateManualReport(submit);
+            return toAjax(result);
+        } catch (Exception e) {
+            return error(e.getMessage());
+        }
+    }
+
+    /**
+     * 删除巡检报告
+     */
+    @RequiresPermissions("ems:inspection:report:remove")
+    @Log(title = "巡检报告", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    @ApiOperation("删除巡检报告")
+    public AjaxResult remove(@PathVariable Long[] ids) {
+        return toAjax(reportService.deleteByIds(ids));
+    }
+
+    /**
+     * 提交手动巡检结果
+     */
+    @RequiresPermissions("ems:inspection:report:edit")
+    @Log(title = "提交手动巡检", businessType = BusinessType.UPDATE)
+    @PostMapping("/submit")
+    @ApiOperation("提交手动巡检结果")
+    public AjaxResult submitManual(@RequestBody InspectionReport report) {
+        return toAjax(reportService.submitManualReport(report));
+    }
+}

+ 152 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/InspectionSchedulerController.java

@@ -0,0 +1,152 @@
+package com.ruoyi.ems.controller;
+
+import com.huashe.common.domain.AjaxResult;
+import com.ruoyi.common.core.web.controller.BaseController;
+import com.ruoyi.common.log.annotation.Log;
+import com.ruoyi.common.log.enums.BusinessType;
+import com.ruoyi.common.security.annotation.RequiresPermissions;
+
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.task.InspectionScheduler;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+
+/**
+ * 巡检调度器管理Controller
+ *
+ * 提供调度器状态查询、手动触发、刷新配置等功能
+ * 仅在 EMS 主模块可用
+ */
+@RestController
+@RequestMapping("/inspection/scheduler")
+@Api(value = "InspectionSchedulerController", description = "巡检调度器管理")
+public class InspectionSchedulerController extends BaseController {
+
+    @Autowired
+    private InspectionScheduler inspectionScheduler;
+
+    /**
+     * 获取调度器状态
+     */
+    @RequiresPermissions("ems:inspection:plan:query")
+    @GetMapping("/status")
+    @ApiOperation("获取调度器状态")
+    public AjaxResult getStatus() {
+        Map<String, Object> status = inspectionScheduler.getStatus();
+        return success(status);
+    }
+
+    /**
+     * 重新加载所有巡检计划
+     */
+    @RequiresPermissions("ems:inspection:plan:edit")
+    @Log(title = "重载巡检计划", businessType = BusinessType.OTHER)
+    @PostMapping("/reload")
+    @ApiOperation("重新加载所有巡检计划")
+    public AjaxResult reload() {
+        try {
+            inspectionScheduler.loadEnabledPlans();
+            return success("巡检计划重新加载完成,当前注册数量: " + inspectionScheduler.getRegisteredCount());
+        } catch (Exception e) {
+            return error("重新加载失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 刷新指定巡检计划
+     */
+    @RequiresPermissions("ems:inspection:plan:edit")
+    @Log(title = "刷新巡检计划", businessType = BusinessType.OTHER)
+    @PostMapping("/refresh/{planCode}")
+    @ApiOperation("刷新指定巡检计划")
+    public AjaxResult refresh(@PathVariable("planCode") String planCode) {
+        try {
+            inspectionScheduler.refreshPlan(planCode);
+            boolean isRegistered = inspectionScheduler.isRegistered(planCode);
+            return success(isRegistered
+                ? "计划刷新成功,已重新注册"
+                : "计划已注销(可能未配置cron或非自动巡检类型)");
+        } catch (Exception e) {
+            return error("刷新失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 注册指定巡检计划
+     */
+    @RequiresPermissions("ems:inspection:plan:edit")
+    @Log(title = "注册巡检计划", businessType = BusinessType.OTHER)
+    @PostMapping("/register/{planCode}")
+    @ApiOperation("注册指定巡检计划到调度器")
+    public AjaxResult register(@PathVariable("planCode") String planCode) {
+        try {
+            inspectionScheduler.registerPlan(planCode);
+            return success("计划已注册到调度器");
+        } catch (Exception e) {
+            return error("注册失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 注销指定巡检计划
+     */
+    @RequiresPermissions("ems:inspection:plan:edit")
+    @Log(title = "注销巡检计划", businessType = BusinessType.OTHER)
+    @PostMapping("/unregister/{planCode}")
+    @ApiOperation("注销指定巡检计划的定时任务")
+    public AjaxResult unregister(@PathVariable("planCode") String planCode) {
+        try {
+            inspectionScheduler.unregisterPlan(planCode);
+            return success("计划已从调度器注销");
+        } catch (Exception e) {
+            return error("注销失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 手动触发一次巡检(用于测试)
+     */
+    @RequiresPermissions("ems:inspection:plan:execute")
+    @Log(title = "手动触发巡检", businessType = BusinessType.OTHER)
+    @PostMapping("/trigger/{planCode}")
+    @ApiOperation("手动触发一次巡检执行")
+    public AjaxResult trigger(@PathVariable("planCode") String planCode) {
+        try {
+            // 检查是否正在执行
+            if (inspectionScheduler.isExecuting(planCode)) {
+                return error("该计划正在执行中,请稍后再试");
+            }
+
+            InspectionReport report = inspectionScheduler.triggerInspection(planCode);
+            return success(report);
+        } catch (Exception e) {
+            return error("触发执行失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 检查计划是否已注册
+     */
+    @RequiresPermissions("ems:inspection:plan:query")
+    @GetMapping("/registered/{planCode}")
+    @ApiOperation("检查计划是否已注册")
+    public AjaxResult isRegistered(@PathVariable("planCode") String planCode) {
+        boolean registered = inspectionScheduler.isRegistered(planCode);
+        return success(registered);
+    }
+
+    /**
+     * 检查计划是否正在执行
+     */
+    @RequiresPermissions("ems:inspection:plan:query")
+    @GetMapping("/executing/{planCode}")
+    @ApiOperation("检查计划是否正在执行")
+    public AjaxResult isExecuting(@PathVariable("planCode") String planCode) {
+        boolean executing = inspectionScheduler.isExecuting(planCode);
+        return success(executing);
+    }
+}

+ 0 - 104
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/controller/OpInspectionPlanController.java

@@ -1,104 +0,0 @@
-package com.ruoyi.ems.controller;
-
-import java.util.List;
-
-import javax.servlet.http.HttpServletResponse;
-
-import com.huashe.common.utils.uuid.Seq;
-import io.swagger.annotations.Api;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.DeleteMapping;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.PutMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import com.ruoyi.common.core.utils.poi.ExcelUtil;
-import com.ruoyi.common.core.web.controller.BaseController;
-import com.huashe.common.domain.AjaxResult;
-import com.ruoyi.common.core.web.page.TableDataInfo;
-import com.ruoyi.common.log.annotation.Log;
-import com.ruoyi.common.log.enums.BusinessType;
-import com.ruoyi.common.security.annotation.RequiresPermissions;
-import com.ruoyi.ems.domain.OpInspectionTask;
-import com.ruoyi.ems.service.IOpInspectionPlanService;
-
-/**
- * 巡检计划Controller
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-@RestController
-@RequestMapping("/inspectionPlan")
-@Api(value = "OpInspectionPlanController", description = "巡检计划")
-public class OpInspectionPlanController extends BaseController {
-    @Autowired
-    private IOpInspectionPlanService inspectionTaskService;
-
-    /**
-     * 查询巡检任务列表
-     */
-    @RequiresPermissions("ems:inspection-task:list")
-    @GetMapping("/list")
-    public TableDataInfo list(OpInspectionTask inspectionTask) {
-        startPage();
-        List<OpInspectionTask> list = inspectionTaskService.selectOpInspectionPlanList(inspectionTask);
-        return getDataTable(list);
-    }
-
-    /**
-     * 导出巡检任务列表
-     */
-    @RequiresPermissions("ems:inspection-task:export")
-    @Log(title = "巡检任务", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(HttpServletResponse response, OpInspectionTask inspectionTask) {
-        List<OpInspectionTask> list = inspectionTaskService.selectOpInspectionPlanList(inspectionTask);
-        ExcelUtil<OpInspectionTask> util = new ExcelUtil<OpInspectionTask>(OpInspectionTask.class);
-        util.exportExcel(response, list, "巡检任务数据");
-    }
-
-    /**
-     * 获取巡检任务详细信息
-     */
-    @RequiresPermissions("ems:inspection-task:query")
-    @GetMapping(value = "/{id}")
-    public AjaxResult getInfo(@PathVariable("id") Long id) {
-        return success(inspectionTaskService.selectOpInspectionPlanById(id));
-    }
-
-    /**
-     * 新增巡检任务
-     */
-    @RequiresPermissions("ems:inspection-task:add")
-    @Log(title = "巡检任务", businessType = BusinessType.INSERT)
-    @PostMapping
-    public AjaxResult add(@RequestBody OpInspectionTask inspectionTask) {
-        inspectionTask.setTaskCode(Seq.getId());
-        return toAjax(inspectionTaskService.insertOpInspectionPlan(inspectionTask));
-    }
-
-    /**
-     * 修改巡检任务
-     */
-    @RequiresPermissions("ems:inspection-task:edit")
-    @Log(title = "巡检任务", businessType = BusinessType.UPDATE)
-    @PutMapping
-    public AjaxResult edit(@RequestBody OpInspectionTask inspectionTask) {
-        return toAjax(inspectionTaskService.updateOpInspectionPlan(inspectionTask));
-    }
-
-    /**
-     * 删除巡检任务
-     */
-    @RequiresPermissions("ems:inspection-task:remove")
-    @Log(title = "巡检任务", businessType = BusinessType.DELETE)
-    @DeleteMapping("/{ids}")
-    public AjaxResult remove(@PathVariable Long[] ids) {
-        return toAjax(inspectionTaskService.deleteOpInspectionPlanByIds(ids));
-    }
-}

+ 359 - 0
ems/ems-cloud/ems-server/src/main/java/com/ruoyi/ems/task/InspectionScheduler.java

@@ -0,0 +1,359 @@
+package com.ruoyi.ems.task;
+
+import com.ruoyi.ems.domain.InspectionPlan;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.mapper.InspectionPlanMapper;
+import com.ruoyi.ems.service.IInspectionPlanService;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
+import org.springframework.scheduling.support.CronExpression;
+import org.springframework.scheduling.support.CronTrigger;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+
+/**
+ * 巡检调度器
+ *
+ * 【重要】此类需要根据实际项目情况进行调整
+ * 主要功能:
+ * 1. 应用启动时加载所有启用调度的巡检计划
+ * 2. 根据Cron表达式定时执行巡检任务
+ * 3. 提供动态注册/注销计划的能力
+ * 4. 提供调度器状态查询
+ */
+@Component
+public class InspectionScheduler {
+
+    private static final Logger log = LoggerFactory.getLogger(InspectionScheduler.class);
+
+    private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+
+    @Autowired
+    private InspectionPlanMapper planMapper;
+
+    @Autowired
+    private IInspectionPlanService planService;
+
+    /** 任务调度器 */
+    private ThreadPoolTaskScheduler taskScheduler;
+
+    /** 已注册的任务 Map<planCode, ScheduledFuture> */
+    private final ConcurrentHashMap<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
+
+    /** 已注册的计划信息 Map<planCode, InspectionPlan> */
+    private final ConcurrentHashMap<String, InspectionPlan> registeredPlans = new ConcurrentHashMap<>();
+
+    /** 正在执行的计划 Set<planCode> */
+    private final Set<String> executingPlans = ConcurrentHashMap.newKeySet();
+
+    /** 启动时间 */
+    private LocalDateTime startTime;
+
+    /**
+     * 初始化调度器
+     */
+    @PostConstruct
+    public void init() {
+        log.info("========== 初始化巡检调度器 ==========");
+
+        // 创建任务调度器
+        taskScheduler = new ThreadPoolTaskScheduler();
+        taskScheduler.setPoolSize(5);
+        taskScheduler.setThreadNamePrefix("inspection-scheduler-");
+        taskScheduler.setWaitForTasksToCompleteOnShutdown(true);
+        taskScheduler.setAwaitTerminationSeconds(60);
+        taskScheduler.initialize();
+
+        startTime = LocalDateTime.now();
+
+        // 加载已启用的计划
+        loadEnabledPlans();
+
+        log.info("========== 巡检调度器初始化完成,已注册 {} 个计划 ==========", scheduledTasks.size());
+    }
+
+    /**
+     * 销毁调度器
+     */
+    @PreDestroy
+    public void destroy() {
+        log.info("========== 销毁巡检调度器 ==========");
+
+        // 取消所有任务
+        scheduledTasks.values().forEach(future -> future.cancel(false));
+        scheduledTasks.clear();
+        registeredPlans.clear();
+
+        // 关闭调度器
+        if (taskScheduler != null) {
+            taskScheduler.shutdown();
+        }
+
+        log.info("========== 巡检调度器已销毁 ==========");
+    }
+
+    /**
+     * 加载所有已启用调度的计划
+     */
+    public void loadEnabledPlans() {
+        // 先清空现有任务
+        scheduledTasks.values().forEach(future -> future.cancel(false));
+        scheduledTasks.clear();
+        registeredPlans.clear();
+
+        // 查询已启用调度的计划
+        List<InspectionPlan> plans = planMapper.selectEnabledSchedulePlans();
+
+        if (plans == null || plans.isEmpty()) {
+            log.info("没有需要加载的巡检计划");
+            return;
+        }
+
+        for (InspectionPlan plan : plans) {
+            try {
+                doRegisterPlan(plan);
+            } catch (Exception e) {
+                log.error("注册巡检计划失败: planCode={}, error={}", plan.getPlanCode(), e.getMessage());
+            }
+        }
+    }
+
+    /**
+     * 注册计划到调度器
+     */
+    public void registerPlan(String planCode) {
+        InspectionPlan plan = planMapper.selectByPlanCode(planCode);
+        if (plan == null) {
+            throw new RuntimeException("计划不存在: " + planCode);
+        }
+        registerPlan(plan);
+    }
+
+    /**
+     * 注册计划到调度器
+     */
+    public void registerPlan(InspectionPlan plan) {
+        if (plan == null || StringUtils.isBlank(plan.getPlanCode())) {
+            throw new RuntimeException("计划信息不完整");
+        }
+
+        // 先注销旧的任务
+        unregisterPlan(plan.getPlanCode());
+
+        // 检查是否可调度
+        if (plan.getPlanType() != 2) {
+            log.warn("计划[{}]不是自动巡检类型,跳过注册", plan.getPlanCode());
+            return;
+        }
+
+        if (StringUtils.isBlank(plan.getCronExpression())) {
+            log.warn("计划[{}]未配置Cron表达式,跳过注册", plan.getPlanCode());
+            return;
+        }
+
+        doRegisterPlan(plan);
+
+        // 更新数据库调度状态
+        planMapper.updateScheduleEnabled(plan.getPlanCode(), 1);
+    }
+
+    /**
+     * 实际注册任务
+     */
+    private void doRegisterPlan(InspectionPlan plan) {
+        String planCode = plan.getPlanCode();
+        String cron = plan.getCronExpression();
+
+        // 验证Cron表达式
+        if (!CronExpression.isValidExpression(cron)) {
+            throw new RuntimeException("无效的Cron表达式: " + cron);
+        }
+
+        // 创建定时任务
+        Runnable task = () -> executeInspection(planCode);
+
+        ScheduledFuture<?> future = taskScheduler.schedule(task, new CronTrigger(cron));
+
+        scheduledTasks.put(planCode, future);
+        registeredPlans.put(planCode, plan);
+
+        // 计算并更新下次执行时间
+        Date nextExecTime = calculateNextExecTime(cron);
+        planMapper.updateExecInfo(planCode, null, nextExecTime);
+
+        log.info("巡检计划[{}]已注册到调度器,Cron: {}, 下次执行: {}",
+            planCode, cron, nextExecTime != null ? DATETIME_FORMATTER.format(
+                LocalDateTime.ofInstant(nextExecTime.toInstant(), ZoneId.systemDefault())) : "未知");
+    }
+
+    /**
+     * 从调度器注销计划
+     */
+    public void unregisterPlan(String planCode) {
+        ScheduledFuture<?> future = scheduledTasks.remove(planCode);
+        if (future != null) {
+            future.cancel(false);
+            log.info("巡检计划[{}]已从调度器注销", planCode);
+        }
+        registeredPlans.remove(planCode);
+
+        // 更新数据库调度状态
+        planMapper.updateScheduleEnabled(planCode, 0);
+    }
+
+    /**
+     * 刷新计划配置
+     */
+    public void refreshPlan(String planCode) {
+        InspectionPlan plan = planMapper.selectByPlanCode(planCode);
+        if (plan == null) {
+            unregisterPlan(planCode);
+            return;
+        }
+
+        // 检查是否应该注册
+        if (plan.getPlanType() == 2
+            && plan.getScheduleEnabled() != null
+            && plan.getScheduleEnabled() == 1
+            && StringUtils.isNotBlank(plan.getCronExpression())) {
+            registerPlan(plan);
+        } else {
+            unregisterPlan(planCode);
+        }
+    }
+
+    /**
+     * 手动触发一次巡检
+     */
+    public InspectionReport triggerInspection(String planCode) {
+        return executeInspection(planCode);
+    }
+
+    /**
+     * 执行巡检任务
+     */
+    private InspectionReport executeInspection(String planCode) {
+        if (executingPlans.contains(planCode)) {
+            log.warn("计划[{}]正在执行中,跳过本次触发", planCode);
+            return null;
+        }
+
+        executingPlans.add(planCode);
+        log.info("========== 开始执行巡检任务: {} ==========", planCode);
+
+        try {
+            // 调用Service执行巡检
+            InspectionReport report = planService.executeAutoInspection(planCode, "system");
+
+            // 更新执行信息
+            InspectionPlan plan = registeredPlans.get(planCode);
+            if (plan != null) {
+                Date nextExecTime = calculateNextExecTime(plan.getCronExpression());
+                planMapper.updateExecInfo(planCode, new Date(), nextExecTime);
+            }
+
+            log.info("========== 巡检任务执行完成: {}, 报告: {} ==========",
+                planCode, report != null ? report.getReportCode() : "null");
+
+            return report;
+        } catch (Exception e) {
+            log.error("巡检任务执行异常: planCode={}, error={}", planCode, e.getMessage(), e);
+            throw new RuntimeException("巡检执行失败: " + e.getMessage(), e);
+        } finally {
+            executingPlans.remove(planCode);
+        }
+    }
+
+    /**
+     * 计算下次执行时间
+     */
+    private Date calculateNextExecTime(String cron) {
+        if (StringUtils.isBlank(cron)) {
+            return null;
+        }
+        try {
+            CronExpression expression = CronExpression.parse(cron);
+            LocalDateTime next = expression.next(LocalDateTime.now());
+            return next != null ? Date.from(next.atZone(ZoneId.systemDefault()).toInstant()) : null;
+        } catch (Exception e) {
+            log.warn("计算下次执行时间失败: cron={}, error={}", cron, e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * 获取调度器状态
+     *
+     * 【重要】前端调度器状态组件依赖此方法返回的数据结构
+     */
+    public Map<String, Object> getStatus() {
+        Map<String, Object> status = new HashMap<>();
+
+        // 基本状态
+        status.put("running", taskScheduler != null && !taskScheduler.getScheduledThreadPoolExecutor().isShutdown());
+        status.put("registeredCount", scheduledTasks.size());
+        status.put("executingCount", executingPlans.size());
+        status.put("startTime", startTime != null ? DATETIME_FORMATTER.format(startTime) : null);
+
+        // 线程池状态
+        if (taskScheduler != null) {
+            ScheduledThreadPoolExecutor executor = taskScheduler.getScheduledThreadPoolExecutor();
+            status.put("threadPoolStatus", String.format("活跃线程: %d, 池大小: %d, 队列任务: %d",
+                executor.getActiveCount(), executor.getPoolSize(), executor.getQueue().size()));
+        }
+
+        // 已注册的计划列表
+        List<Map<String, Object>> planList = new ArrayList<>();
+        for (Map.Entry<String, InspectionPlan> entry : registeredPlans.entrySet()) {
+            InspectionPlan plan = entry.getValue();
+            Map<String, Object> planInfo = new HashMap<>();
+            planInfo.put("planCode", plan.getPlanCode());
+            planInfo.put("planName", plan.getPlanName());
+            planInfo.put("cronExpression", plan.getCronExpression());
+            planInfo.put("executing", executingPlans.contains(plan.getPlanCode()));
+
+            // 计算下次执行时间
+            Date nextExec = calculateNextExecTime(plan.getCronExpression());
+            planInfo.put("nextExecTime", nextExec != null ?
+                DATETIME_FORMATTER.format(LocalDateTime.ofInstant(nextExec.toInstant(), ZoneId.systemDefault())) : null);
+
+            planList.add(planInfo);
+        }
+        status.put("registeredPlans", planList);
+
+        return status;
+    }
+
+    /**
+     * 获取已注册计划数量
+     */
+    public int getRegisteredCount() {
+        return scheduledTasks.size();
+    }
+
+    /**
+     * 检查计划是否已注册
+     */
+    public boolean isRegistered(String planCode) {
+        return scheduledTasks.containsKey(planCode);
+    }
+
+    /**
+     * 检查计划是否正在执行
+     */
+    public boolean isExecuting(String planCode) {
+        return executingPlans.contains(planCode);
+    }
+}

+ 0 - 135
ems/ems-core/src/main/java/com/ruoyi/ems/domain/AdmOpInspectionReport.java

@@ -1,135 +0,0 @@
-package com.ruoyi.ems.domain;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.huashe.common.annotation.Excel;
-import com.huashe.common.domain.BaseEntity;
-import com.ruoyi.ems.handler.json.CustomBaseSerializer;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
-import java.util.Date;
-
-/**
- * 巡检报告对象 adm_op_inspection_report
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-public class AdmOpInspectionReport extends BaseEntity
-{
-    private static final long serialVersionUID = 1L;
-
-    /** 序号 */
-    private Long id;
-
-    /** 任务代码 */
-    @Excel(name = "任务代码")
-    private String taskCode;
-
-    /** 结果状态 */
-    @Excel(name = "结果状态")
-    private Integer resultStatus;
-
-    /** 结果描述 */
-    @Excel(name = "结果描述")
-    @JsonDeserialize(using = CustomBaseSerializer.class)
-    private String resultMsg;
-
-    /** 完成时间 */
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "完成时间", width = 30, dateFormat = "yyyy-MM-dd")
-    private Date finishTime;
-
-    /** 提交时间 */
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "提交时间", width = 30, dateFormat = "yyyy-MM-dd")
-    private Date subTime;
-
-    /** 提交人 */
-    @Excel(name = "提交人")
-    private String submitter;
-
-    public void setId(Long id)
-    {
-        this.id = id;
-    }
-
-    public Long getId()
-    {
-        return id;
-    }
-
-    public void setTaskCode(String taskCode)
-    {
-        this.taskCode = taskCode;
-    }
-
-    public String getTaskCode()
-    {
-        return taskCode;
-    }
-
-    public void setResultStatus(Integer resultStatus)
-    {
-        this.resultStatus = resultStatus;
-    }
-
-    public Integer getResultStatus()
-    {
-        return resultStatus;
-    }
-
-    public void setResultMsg(String resultMsg)
-    {
-        this.resultMsg = resultMsg;
-    }
-
-    public String getResultMsg()
-    {
-        return resultMsg;
-    }
-
-    public void setFinishTime(Date finishTime)
-    {
-        this.finishTime = finishTime;
-    }
-
-    public Date getFinishTime()
-    {
-        return finishTime;
-    }
-
-    public void setSubTime(Date subTime)
-    {
-        this.subTime = subTime;
-    }
-
-    public Date getSubTime()
-    {
-        return subTime;
-    }
-
-    public void setSubmitter(String submitter)
-    {
-        this.submitter = submitter;
-    }
-
-    public String getSubmitter()
-    {
-        return submitter;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
-            .append("id", getId())
-            .append("taskCode", getTaskCode())
-            .append("resultStatus", getResultStatus())
-            .append("resultMsg", getResultMsg())
-            .append("finishTime", getFinishTime())
-            .append("subTime", getSubTime())
-            .append("submitter", getSubmitter())
-            .toString();
-    }
-}

+ 26 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/CheckItemResult.java

@@ -0,0 +1,26 @@
+// CheckItemResult.java - 检查项结果DTO
+package com.ruoyi.ems.domain;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 检查项结果
+ */
+@Data
+public class CheckItemResult implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    private String ruleCode;
+    private String ruleName;
+    private String attrKey;
+    private String attrName;
+    private String attrUnit;
+    private Integer checkType;
+    private String expectRange;
+    private String actualValue;
+    /** 0:正常 1:异常 */
+    private Integer status;
+    private String message;
+}

+ 82 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/InspectionPlan.java

@@ -0,0 +1,82 @@
+package com.ruoyi.ems.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.huashe.common.annotation.Excel;
+import com.huashe.common.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 巡检计划对象 adm_op_inspection_plan
+ *
+ * 【修改说明】新增调度相关字段:scheduleEnabled, lastExecTime, nextExecTime, execCount
+ */
+@Data
+public class InspectionPlan extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    @Excel(name = "归属区域代码")
+    private String areaCode;
+
+    private String areaName;
+
+    @Excel(name = "计划代码")
+    private String planCode;
+
+    @Excel(name = "计划名称")
+    private String planName;
+
+    @Excel(name = "计划类型", readConverterExp = "1=手动巡检,2=自动巡检")
+    private Integer planType;
+
+    @Excel(name = "计划状态", readConverterExp = "0=待执行,1=执行中,2=已完成,3=已取消")
+    private Integer planStatus;
+
+    @Excel(name = "巡检目标类型", readConverterExp = "0=区域,1=设施,2=设备")
+    private Integer targetType;
+
+    private String targetCodes;
+
+    @Excel(name = "巡检目标")
+    private String targetNames;
+
+    /** Cron表达式(自动巡检用) */
+    private String cronExpression;
+
+    /** 是否启用调度 0:禁用 1:启用 */
+    @Excel(name = "调度状态", readConverterExp = "0=禁用,1=启用")
+    private Integer scheduleEnabled;
+
+    /** 上次执行时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "上次执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date lastExecTime;
+
+    /** 下次执行时间 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "下次执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date nextExecTime;
+
+    /** 累计执行次数 */
+    @Excel(name = "执行次数")
+    private Integer execCount;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "计划执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date planTime;
+
+    @Excel(name = "执行人")
+    private String executor;
+
+    private String description;
+
+    /** 巡检规则列表 */
+    private List<InspectionRule> rules;
+
+    /** 解析后的目标代码列表 */
+    private List<String> targetCodeList;
+}

+ 68 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/InspectionReport.java

@@ -0,0 +1,68 @@
+package com.ruoyi.ems.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.huashe.common.annotation.Excel;
+import com.huashe.common.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 巡检报告对象 adm_op_inspection_report
+ */
+@Data
+public class InspectionReport extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+
+    @Excel(name = "报告代码")
+    private String reportCode;
+
+    @Excel(name = "计划代码")
+    private String planCode;
+
+    @Excel(name = "计划名称")
+    private String planName;
+
+    @Excel(name = "计划类型", readConverterExp = "1=手动巡检,2=自动巡检")
+    private Integer planType;
+
+    private String areaCode;
+
+    @Excel(name = "归属区域")
+    private String areaName;
+
+    private Integer targetType;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "执行时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date executeTime;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "完成时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private Date finishTime;
+
+    @Excel(name = "执行人")
+    private String executor;
+
+    @Excel(name = "结果状态", readConverterExp = "0=正常,1=异常,2=部分异常")
+    private Integer resultStatus;
+
+    @Excel(name = "检查总数")
+    private Integer totalCount;
+
+    @Excel(name = "正常数量")
+    private Integer normalCount;
+
+    @Excel(name = "异常数量")
+    private Integer abnormalCount;
+
+    private String resultSummary;
+
+    private String manualRemark;
+
+    /** 报告明细列表 */
+    private List<InspectionReportDetail> details;
+}

+ 35 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/InspectionReportDetail.java

@@ -0,0 +1,35 @@
+package com.ruoyi.ems.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.huashe.common.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 巡检报告明细对象 adm_op_inspection_report_detail
+ */
+@Data
+public class InspectionReportDetail extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String reportCode;
+    private String deviceCode;
+    private String deviceName;
+    private String deviceModel;
+    private String deviceModelName;
+    private String location;
+    private String areaPath;
+    /** 结果状态 0:正常 1:异常 */
+    private Integer resultStatus;
+    /** 检查项结果JSON */
+    private String checkItems;
+
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date checkTime;
+
+    /** 解析后的检查项列表 */
+    private List<CheckItemResult> checkItemList;
+}

+ 28 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/InspectionRule.java

@@ -0,0 +1,28 @@
+package com.ruoyi.ems.domain;
+
+import com.huashe.common.domain.BaseEntity;
+import lombok.Data;
+
+/**
+ * 巡检规则对象 adm_op_inspection_rule
+ */
+@Data
+public class InspectionRule extends BaseEntity {
+    private static final long serialVersionUID = 1L;
+
+    private Long id;
+    private String planCode;
+    private String ruleCode;
+    private String ruleName;
+    private String deviceModel;
+    private String deviceModelName;
+    private String attrKey;
+    private String attrName;
+    /** 检查类型 1:范围检查 2:等值检查 3:非空检查 4:在线状态 */
+    private Integer checkType;
+    private String minValue;
+    private String maxValue;
+    private String expectValue;
+    private Integer ruleOrder;
+    private Integer enabled;
+}

+ 49 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/domain/ManualReportSubmit.java

@@ -0,0 +1,49 @@
+package com.ruoyi.ems.domain;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 手动巡检报告提交/更新对象
+ * 用于新增提交和编辑已提交的手动巡检报告
+ */
+@Data
+public class ManualReportSubmit implements Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 报告ID(编辑时使用)
+     */
+    private Long id;
+
+    /**
+     * 报告代码(编辑时使用)
+     */
+    private String reportCode;
+
+    /**
+     * 计划代码
+     */
+    private String planCode;
+
+    /**
+     * 结果状态 0:正常 1:异常 2:部分异常
+     */
+    private Integer resultStatus;
+
+    /**
+     * 手动备注(富文本,包含文字和base64图片)
+     */
+    private String manualRemark;
+
+    /**
+     * 附加备注说明
+     */
+    private String remark;
+
+    /**
+     * 提交人/更新人(由Controller自动设置)
+     */
+    private String submitter;
+}

+ 0 - 195
ems/ems-core/src/main/java/com/ruoyi/ems/domain/OpInspectionTask.java

@@ -1,195 +0,0 @@
-package com.ruoyi.ems.domain;
-
-import com.fasterxml.jackson.annotation.JsonFormat;
-import com.huashe.common.annotation.Excel;
-import com.huashe.common.domain.BaseEntity;
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
-
-import java.util.Date;
-
-/**
- * 巡检任务对象 adm_op_inspection_task
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-public class OpInspectionTask extends BaseEntity {
-    private static final long serialVersionUID = 1L;
-
-    /**
-     * 序号
-     */
-    private Long id;
-
-    /**
-     * 区域代码
-     */
-    private String areaCode;
-
-    /**
-     * 任务代码
-     */
-    @Excel(name = "任务代码")
-    private String taskCode;
-
-    /**
-     * 任务名称
-     */
-    @Excel(name = "任务名称")
-    private String taskName;
-
-    /**
-     * 任务类型
-     */
-    @Excel(name = "任务类型")
-    private Integer taskType;
-
-    /**
-     * 任务状态
-     */
-    @Excel(name = "任务状态")
-    private Integer taskStatus;
-
-    /**
-     * 开始时间
-     */
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "开始时间", width = 30, dateFormat = "yyyy-MM-dd")
-    private Date startTime;
-
-    /**
-     * 结束时间
-     */
-    @JsonFormat(pattern = "yyyy-MM-dd")
-    @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd")
-    private Date endTime;
-
-    /**
-     * 执行人
-     */
-    @Excel(name = "执行人")
-    private String executor;
-
-    /**
-     * 巡检对象
-     */
-    @Excel(name = "巡检对象")
-    private Integer objType;
-
-    /**
-     * 选择巡检对象
-     */
-    private String objCode;
-
-    /**
-     * 对象名称
-     */
-    @Excel(name = "对象名称")
-    private String objName;
-
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    public Long getId() {
-        return id;
-    }
-
-    public String getAreaCode() {
-        return areaCode;
-    }
-
-    public void setAreaCode(String areaCode) {
-        this.areaCode = areaCode;
-    }
-
-    public void setTaskCode(String taskCode) {
-        this.taskCode = taskCode;
-    }
-
-    public String getTaskCode() {
-        return taskCode;
-    }
-
-    public void setTaskName(String taskName) {
-        this.taskName = taskName;
-    }
-
-    public String getTaskName() {
-        return taskName;
-    }
-
-    public void setTaskType(Integer taskType) {
-        this.taskType = taskType;
-    }
-
-    public Integer getTaskType() {
-        return taskType;
-    }
-
-    public void setTaskStatus(Integer taskStatus) {
-        this.taskStatus = taskStatus;
-    }
-
-    public Integer getTaskStatus() {
-        return taskStatus;
-    }
-
-    public void setStartTime(Date startTime) {
-        this.startTime = startTime;
-    }
-
-    public Date getStartTime() {
-        return startTime;
-    }
-
-    public void setEndTime(Date endTime) {
-        this.endTime = endTime;
-    }
-
-    public Date getEndTime() {
-        return endTime;
-    }
-
-    public void setExecutor(String executor) {
-        this.executor = executor;
-    }
-
-    public String getExecutor() {
-        return executor;
-    }
-
-    public void setObjType(Integer objType) {
-        this.objType = objType;
-    }
-
-    public Integer getObjType() {
-        return objType;
-    }
-
-    public void setObjCode(String objCode) {
-        this.objCode = objCode;
-    }
-
-    public String getObjCode() {
-        return objCode;
-    }
-
-    public void setObjName(String objName) {
-        this.objName = objName;
-    }
-
-    public String getObjName() {
-        return objName;
-    }
-
-    @Override
-    public String toString() {
-        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE).append("id", getId())
-            .append("taskCode", getTaskCode()).append("taskName", getTaskName()).append("taskType", getTaskType())
-            .append("taskStatus", getTaskStatus()).append("startTime", getStartTime()).append("endTime", getEndTime())
-            .append("executor", getExecutor()).append("objType", getObjType()).append("objCode", getObjCode())
-            .append("objName", getObjName()).toString();
-    }
-}

+ 0 - 61
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/AdmOpInspectionReportMapper.java

@@ -1,61 +0,0 @@
-package com.ruoyi.ems.mapper;
-
-import com.ruoyi.ems.domain.AdmOpInspectionReport;
-
-import java.util.List;
-
-/**
- * 巡检报告Mapper接口
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-public interface AdmOpInspectionReportMapper {
-    /**
-     * 查询巡检报告
-     *
-     * @param id 巡检报告主键
-     * @return 巡检报告
-     */
-    AdmOpInspectionReport selectAdmOpInspectionReportById(Long id);
-
-    /**
-     * 查询巡检报告列表
-     *
-     * @param inspectionReport 巡检报告
-     * @return 巡检报告集合
-     */
-    List<AdmOpInspectionReport> selectAdmOpInspectionReportList(AdmOpInspectionReport inspectionReport);
-
-    /**
-     * 新增巡检报告
-     *
-     * @param inspectionReport 巡检报告
-     * @return 结果
-     */
-    int insertAdmOpInspectionReport(AdmOpInspectionReport inspectionReport);
-
-    /**
-     * 修改巡检报告
-     *
-     * @param inspectionReport 巡检报告
-     * @return 结果
-     */
-    int updateAdmOpInspectionReport(AdmOpInspectionReport inspectionReport);
-
-    /**
-     * 删除巡检报告
-     *
-     * @param id 巡检报告主键
-     * @return 结果
-     */
-    int deleteAdmOpInspectionReportById(Long id);
-
-    /**
-     * 批量删除巡检报告
-     *
-     * @param ids 需要删除的数据主键集合
-     * @return 结果
-     */
-    int deleteAdmOpInspectionReportByIds(Long[] ids);
-}

+ 54 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/InspectionPlanMapper.java

@@ -0,0 +1,54 @@
+package com.ruoyi.ems.mapper;
+
+import com.ruoyi.ems.domain.InspectionPlan;
+import org.apache.ibatis.annotations.Param;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 巡检计划Mapper接口
+ *
+ * 【修改说明】新增调度相关方法
+ */
+public interface InspectionPlanMapper {
+
+    InspectionPlan selectById(Long id);
+
+    InspectionPlan selectByPlanCode(String planCode);
+
+    List<InspectionPlan> selectList(InspectionPlan plan);
+
+    /**
+     * 查询已启用调度的自动巡检计划
+     * 供调度器启动时加载
+     */
+    List<InspectionPlan> selectEnabledSchedulePlans();
+
+    int insert(InspectionPlan plan);
+
+    int update(InspectionPlan plan);
+
+    int updateStatus(@Param("planCode") String planCode, @Param("status") Integer status);
+
+    /**
+     * 更新调度执行信息
+     * @param planCode 计划代码
+     * @param lastExecTime 上次执行时间
+     * @param nextExecTime 下次执行时间
+     */
+    int updateExecInfo(@Param("planCode") String planCode,
+        @Param("lastExecTime") Date lastExecTime,
+        @Param("nextExecTime") Date nextExecTime);
+
+    /**
+     * 更新调度启用状态
+     * @param planCode 计划代码
+     * @param scheduleEnabled 是否启用 0-禁用 1-启用
+     */
+    int updateScheduleEnabled(@Param("planCode") String planCode,
+        @Param("scheduleEnabled") Integer scheduleEnabled);
+
+    int deleteById(Long id);
+
+    int deleteByIds(Long[] ids);
+}

+ 18 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/InspectionReportDetailMapper.java

@@ -0,0 +1,18 @@
+package com.ruoyi.ems.mapper;
+
+import com.ruoyi.ems.domain.InspectionReportDetail;
+import java.util.List;
+
+/**
+ * 巡检报告明细Mapper接口
+ */
+public interface InspectionReportDetailMapper {
+
+    List<InspectionReportDetail> selectByReportCode(String reportCode);
+
+    int insert(InspectionReportDetail detail);
+
+    int insertBatch(List<InspectionReportDetail> details);
+
+    int deleteByReportCode(String reportCode);
+}

+ 24 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/InspectionReportMapper.java

@@ -0,0 +1,24 @@
+package com.ruoyi.ems.mapper;
+
+import com.ruoyi.ems.domain.InspectionReport;
+import java.util.List;
+
+/**
+ * 巡检报告Mapper接口
+ */
+public interface InspectionReportMapper {
+
+    InspectionReport selectById(Long id);
+
+    InspectionReport selectByReportCode(String reportCode);
+
+    List<InspectionReport> selectList(InspectionReport report);
+
+    int insert(InspectionReport report);
+
+    int update(InspectionReport report);
+
+    int deleteById(Long id);
+
+    int deleteByIds(Long[] ids);
+}

+ 27 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/InspectionRuleMapper.java

@@ -0,0 +1,27 @@
+package com.ruoyi.ems.mapper;
+
+import com.ruoyi.ems.domain.InspectionRule;
+
+import java.util.List;
+
+/**
+ * 巡检规则Mapper接口
+ */
+public interface InspectionRuleMapper {
+
+    InspectionRule selectById(Long id);
+
+    List<InspectionRule> selectByPlanCode(String planCode);
+
+    List<InspectionRule> selectEnabledByPlanCode(String planCode);
+
+    int insert(InspectionRule rule);
+
+    int insertBatch(List<InspectionRule> rules);
+
+    int update(InspectionRule rule);
+
+    int deleteById(Long id);
+
+    int deleteByPlanCode(String planCode);
+}

+ 0 - 61
ems/ems-core/src/main/java/com/ruoyi/ems/mapper/OpInspectionPlanMapper.java

@@ -1,61 +0,0 @@
-package com.ruoyi.ems.mapper;
-
-import com.ruoyi.ems.domain.OpInspectionTask;
-
-import java.util.List;
-
-/**
- * 巡检任务Mapper接口
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-public interface OpInspectionPlanMapper {
-    /**
-     * 查询巡检任务
-     *
-     * @param id 巡检任务主键
-     * @return 巡检任务
-     */
-    OpInspectionTask selectOpInspectionPlanById(Long id);
-
-    /**
-     * 查询巡检任务列表
-     *
-     * @param inspectionTask 巡检任务
-     * @return 巡检任务集合
-     */
-    List<OpInspectionTask> selectOpInspectionPlanList(OpInspectionTask inspectionTask);
-
-    /**
-     * 新增巡检任务
-     *
-     * @param inspectionTask 巡检任务
-     * @return 结果
-     */
-    int insertOpInspectionPlan(OpInspectionTask inspectionTask);
-
-    /**
-     * 修改巡检任务
-     *
-     * @param inspectionTask 巡检任务
-     * @return 结果
-     */
-    int updateOpInspectionPlan(OpInspectionTask inspectionTask);
-
-    /**
-     * 删除巡检任务
-     *
-     * @param id 巡检任务主键
-     * @return 结果
-     */
-    int deleteOpInspectionPlanById(Long id);
-
-    /**
-     * 批量删除巡检任务
-     *
-     * @param ids 需要删除的数据主键集合
-     * @return 结果
-     */
-    int deleteOpInspectionPlanByIds(Long[] ids);
-}

+ 0 - 61
ems/ems-core/src/main/java/com/ruoyi/ems/service/IAdmOpInspectionReportService.java

@@ -1,61 +0,0 @@
-package com.ruoyi.ems.service;
-
-import com.ruoyi.ems.domain.AdmOpInspectionReport;
-
-import java.util.List;
-
-/**
- * 巡检报告Service接口
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-public interface IAdmOpInspectionReportService {
-    /**
-     * 查询巡检报告
-     *
-     * @param id 巡检报告主键
-     * @return 巡检报告
-     */
-    AdmOpInspectionReport selectAdmOpInspectionReportById(Long id);
-
-    /**
-     * 查询巡检报告列表
-     *
-     * @param admOpInspectionReport 巡检报告
-     * @return 巡检报告集合
-     */
-    List<AdmOpInspectionReport> selectAdmOpInspectionReportList(AdmOpInspectionReport admOpInspectionReport);
-
-    /**
-     * 新增巡检报告
-     *
-     * @param admOpInspectionReport 巡检报告
-     * @return 结果
-     */
-    int insertAdmOpInspectionReport(AdmOpInspectionReport admOpInspectionReport);
-
-    /**
-     * 修改巡检报告
-     *
-     * @param admOpInspectionReport 巡检报告
-     * @return 结果
-     */
-    int updateAdmOpInspectionReport(AdmOpInspectionReport admOpInspectionReport);
-
-    /**
-     * 批量删除巡检报告
-     *
-     * @param ids 需要删除的巡检报告主键集合
-     * @return 结果
-     */
-    int deleteAdmOpInspectionReportByIds(Long[] ids);
-
-    /**
-     * 删除巡检报告信息
-     *
-     * @param id 巡检报告主键
-     * @return 结果
-     */
-    int deleteAdmOpInspectionReportById(Long id);
-}

+ 53 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/IInspectionPlanService.java

@@ -0,0 +1,53 @@
+package com.ruoyi.ems.service;
+
+import com.ruoyi.ems.domain.InspectionPlan;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.ManualReportSubmit;
+
+import java.util.List;
+
+/**
+ * 巡检计划Service接口
+ */
+public interface IInspectionPlanService {
+
+    InspectionPlan selectById(Long id);
+
+    InspectionPlan selectByPlanCode(String planCode);
+
+    List<InspectionPlan> selectList(InspectionPlan plan);
+
+    int insert(InspectionPlan plan);
+
+    int update(InspectionPlan plan);
+
+    int deleteByIds(Long[] ids);
+
+    /**
+     * 执行自动巡检任务
+     * 根据配置的规则自动检查设备状态
+     *
+     * @param planCode 计划代码
+     * @param executor 执行人
+     * @return 巡检报告
+     */
+    InspectionReport executeAutoInspection(String planCode, String executor);
+
+    /**
+     * 提交手动巡检报告
+     * 工作人员现场巡检后,手动填写并提交报告
+     *
+     * @param submit 手动提交的报告数据
+     * @return 巡检报告
+     */
+    InspectionReport submitManualReport(ManualReportSubmit submit);
+
+    /**
+     * 执行巡检任务(兼容旧接口,内部会根据计划类型区分处理)
+     * @deprecated 请使用 executeAutoInspection 或 submitManualReport
+     */
+    @Deprecated
+    default InspectionReport executeInspection(String planCode, String executor) {
+        return executeAutoInspection(planCode, executor);
+    }
+}

+ 69 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/IInspectionReportService.java

@@ -0,0 +1,69 @@
+package com.ruoyi.ems.service;
+
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.ManualReportSubmit;
+
+import java.util.List;
+
+/**
+ * 巡检报告Service接口
+ */
+public interface IInspectionReportService {
+
+    /**
+     * 根据ID查询巡检报告
+     *
+     * @param id 报告ID
+     * @return 巡检报告
+     */
+    InspectionReport selectById(Long id);
+
+    /**
+     * 根据报告代码查询巡检报告
+     *
+     * @param reportCode 报告代码
+     * @return 巡检报告
+     */
+    InspectionReport selectByReportCode(String reportCode);
+
+    /**
+     * 查询巡检报告列表
+     *
+     * @param report 查询条件
+     * @return 巡检报告列表
+     */
+    List<InspectionReport> selectList(InspectionReport report);
+
+    /**
+     * 提交手动巡检结果(旧接口,用于更新已存在的报告)
+     *
+     * @param report 报告数据
+     * @return 影响行数
+     */
+    int submitManualReport(InspectionReport report);
+
+    /**
+     * 更新手动巡检报告
+     * 用于编辑已提交的手动巡检报告,支持更新巡检结果、报告内容、图片等
+     *
+     * @param submit 更新数据
+     * @return 影响行数
+     */
+    int updateManualReport(ManualReportSubmit submit);
+
+    /**
+     * 更新巡检报告
+     *
+     * @param report 报告数据
+     * @return 影响行数
+     */
+    int update(InspectionReport report);
+
+    /**
+     * 批量删除巡检报告
+     *
+     * @param ids 报告ID数组
+     * @return 影响行数
+     */
+    int deleteByIds(Long[] ids);
+}

+ 0 - 61
ems/ems-core/src/main/java/com/ruoyi/ems/service/IOpInspectionPlanService.java

@@ -1,61 +0,0 @@
-package com.ruoyi.ems.service;
-
-import com.ruoyi.ems.domain.OpInspectionTask;
-
-import java.util.List;
-
-/**
- * 巡检任务Service接口
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-public interface IOpInspectionPlanService {
-    /**
-     * 查询巡检任务
-     *
-     * @param id 巡检任务主键
-     * @return 巡检任务
-     */
-    OpInspectionTask selectOpInspectionPlanById(Long id);
-
-    /**
-     * 查询巡检任务列表
-     *
-     * @param opInspectionTask 巡检任务
-     * @return 巡检任务集合
-     */
-    List<OpInspectionTask> selectOpInspectionPlanList(OpInspectionTask opInspectionTask);
-
-    /**
-     * 新增巡检任务
-     *
-     * @param opInspectionTask 巡检任务
-     * @return 结果
-     */
-    int insertOpInspectionPlan(OpInspectionTask opInspectionTask);
-
-    /**
-     * 修改巡检任务
-     *
-     * @param opInspectionTask 巡检任务
-     * @return 结果
-     */
-    int updateOpInspectionPlan(OpInspectionTask opInspectionTask);
-
-    /**
-     * 批量删除巡检任务
-     *
-     * @param ids 需要删除的巡检任务主键集合
-     * @return 结果
-     */
-    int deleteOpInspectionPlanByIds(Long[] ids);
-
-    /**
-     * 删除巡检任务信息
-     *
-     * @param id 巡检任务主键
-     * @return 结果
-     */
-    int deleteOpInspectionPlanById(Long id);
-}

+ 0 - 87
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/AdmOpInspectionReportServiceImpl.java

@@ -1,87 +0,0 @@
-package com.ruoyi.ems.service.impl;
-
-import com.ruoyi.ems.domain.AdmOpInspectionReport;
-import com.ruoyi.ems.mapper.AdmOpInspectionReportMapper;
-import com.ruoyi.ems.service.IAdmOpInspectionReportService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-/**
- * 巡检报告Service业务层处理
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-@Service
-public class AdmOpInspectionReportServiceImpl implements IAdmOpInspectionReportService {
-    @Autowired
-    private AdmOpInspectionReportMapper admOpInspectionReportMapper;
-
-    /**
-     * 查询巡检报告
-     *
-     * @param id 巡检报告主键
-     * @return 巡检报告
-     */
-    @Override
-    public AdmOpInspectionReport selectAdmOpInspectionReportById(Long id) {
-        return admOpInspectionReportMapper.selectAdmOpInspectionReportById(id);
-    }
-
-    /**
-     * 查询巡检报告列表
-     *
-     * @param admOpInspectionReport 巡检报告
-     * @return 巡检报告
-     */
-    @Override
-    public List<AdmOpInspectionReport> selectAdmOpInspectionReportList(AdmOpInspectionReport admOpInspectionReport) {
-        return admOpInspectionReportMapper.selectAdmOpInspectionReportList(admOpInspectionReport);
-    }
-
-    /**
-     * 新增巡检报告
-     *
-     * @param admOpInspectionReport 巡检报告
-     * @return 结果
-     */
-    @Override
-    public int insertAdmOpInspectionReport(AdmOpInspectionReport admOpInspectionReport) {
-        return admOpInspectionReportMapper.insertAdmOpInspectionReport(admOpInspectionReport);
-    }
-
-    /**
-     * 修改巡检报告
-     *
-     * @param admOpInspectionReport 巡检报告
-     * @return 结果
-     */
-    @Override
-    public int updateAdmOpInspectionReport(AdmOpInspectionReport admOpInspectionReport) {
-        return admOpInspectionReportMapper.updateAdmOpInspectionReport(admOpInspectionReport);
-    }
-
-    /**
-     * 批量删除巡检报告
-     *
-     * @param ids 需要删除的巡检报告主键
-     * @return 结果
-     */
-    @Override
-    public int deleteAdmOpInspectionReportByIds(Long[] ids) {
-        return admOpInspectionReportMapper.deleteAdmOpInspectionReportByIds(ids);
-    }
-
-    /**
-     * 删除巡检报告信息
-     *
-     * @param id 巡检报告主键
-     * @return 结果
-     */
-    @Override
-    public int deleteAdmOpInspectionReportById(Long id) {
-        return admOpInspectionReportMapper.deleteAdmOpInspectionReportById(id);
-    }
-}

+ 732 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/InspectionPlanServiceImpl.java

@@ -0,0 +1,732 @@
+package com.ruoyi.ems.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.alibaba.fastjson2.JSONArray;
+import com.huashe.common.utils.DateUtils;
+import com.huashe.common.utils.uuid.Seq;
+import com.ruoyi.ems.domain.CheckItemResult;
+import com.ruoyi.ems.domain.EmsDevice;
+import com.ruoyi.ems.domain.EmsObjAttrValue;
+import com.ruoyi.ems.domain.InspectionPlan;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.InspectionReportDetail;
+import com.ruoyi.ems.domain.InspectionRule;
+import com.ruoyi.ems.domain.ManualReportSubmit;
+import com.ruoyi.ems.mapper.InspectionPlanMapper;
+import com.ruoyi.ems.mapper.InspectionReportDetailMapper;
+import com.ruoyi.ems.mapper.InspectionReportMapper;
+import com.ruoyi.ems.mapper.InspectionRuleMapper;
+import com.ruoyi.ems.model.QueryDevice;
+import com.ruoyi.ems.service.IEmsDeviceService;
+import com.ruoyi.ems.service.IEmsObjAttrValueService;
+import com.ruoyi.ems.service.IInspectionPlanService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+
+/**
+ * 巡检计划Service实现
+ */
+@Service
+public class InspectionPlanServiceImpl implements IInspectionPlanService {
+
+    private static final Logger log = LoggerFactory.getLogger(InspectionPlanServiceImpl.class);
+
+    /** 计划类型常量 */
+    private static final int PLAN_TYPE_MANUAL = 1;  // 手动巡检
+    private static final int PLAN_TYPE_AUTO = 2;    // 自动巡检
+
+    /** 目标类型常量 */
+    private static final int TARGET_TYPE_AREA = 0;
+    private static final int TARGET_TYPE_FACS = 1;
+    private static final int TARGET_TYPE_DEVICE = 2;
+
+    /** 检查类型常量 */
+    private static final int CHECK_TYPE_RANGE = 1;
+    private static final int CHECK_TYPE_EQUAL = 2;
+    private static final int CHECK_TYPE_NOT_NULL = 3;
+    private static final int CHECK_TYPE_ONLINE = 4;
+
+    /** 计划状态常量 */
+    private static final int PLAN_STATUS_PENDING = 0;
+    private static final int PLAN_STATUS_RUNNING = 1;
+    private static final int PLAN_STATUS_COMPLETED = 2;
+
+    /** 结果状态常量 */
+    private static final int RESULT_NORMAL = 0;
+    private static final int RESULT_ABNORMAL = 1;
+    private static final int RESULT_PARTIAL = 2;
+
+    @Autowired
+    private InspectionPlanMapper planMapper;
+
+    @Autowired
+    private InspectionRuleMapper ruleMapper;
+
+    @Autowired
+    private InspectionReportMapper reportMapper;
+
+    @Autowired
+    private InspectionReportDetailMapper detailMapper;
+
+    @Autowired
+    private IEmsDeviceService deviceService;
+
+    @Autowired
+    private IEmsObjAttrValueService attrValueService;
+
+    @Override
+    public InspectionPlan selectById(Long id) {
+        InspectionPlan plan = planMapper.selectById(id);
+        if (plan != null) {
+            plan.setRules(ruleMapper.selectByPlanCode(plan.getPlanCode()));
+            parseTargetCodes(plan);
+        }
+        return plan;
+    }
+
+    @Override
+    public InspectionPlan selectByPlanCode(String planCode) {
+        InspectionPlan plan = planMapper.selectByPlanCode(planCode);
+        if (plan != null) {
+            plan.setRules(ruleMapper.selectByPlanCode(planCode));
+            parseTargetCodes(plan);
+        }
+        return plan;
+    }
+
+    @Override
+    public List<InspectionPlan> selectList(InspectionPlan plan) {
+        return planMapper.selectList(plan);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int insert(InspectionPlan plan) {
+        // 生成计划代码
+        plan.setPlanCode("IP" + DateUtils.dateTimeNow() + Seq.getId(Seq.uploadSeqType));
+        plan.setPlanStatus(PLAN_STATUS_PENDING);
+
+        int cnt = planMapper.insert(plan);
+
+        // 保存规则(仅自动巡检需要)
+        if (plan.getPlanType() == PLAN_TYPE_AUTO && CollectionUtils.isNotEmpty(plan.getRules())) {
+            for (int i = 0; i < plan.getRules().size(); i++) {
+                InspectionRule rule = plan.getRules().get(i);
+                rule.setPlanCode(plan.getPlanCode());
+                rule.setRuleCode("IR" + Seq.getId());
+                rule.setRuleOrder(i + 1);
+                if (rule.getEnabled() == null) {
+                    rule.setEnabled(1);
+                }
+            }
+            ruleMapper.insertBatch(plan.getRules());
+        }
+
+        return cnt;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int update(InspectionPlan plan) {
+        int cnt = planMapper.update(plan);
+
+        // 更新规则(仅自动巡检且传递了规则数据时才处理)
+        if (plan.getPlanType() != null
+            && plan.getPlanType() == PLAN_TYPE_AUTO
+            && plan.getRules() != null) {
+
+            ruleMapper.deleteByPlanCode(plan.getPlanCode());
+            if (CollectionUtils.isNotEmpty(plan.getRules())) {
+                for (int i = 0; i < plan.getRules().size(); i++) {
+                    InspectionRule rule = plan.getRules().get(i);
+                    rule.setPlanCode(plan.getPlanCode());
+                    if (StringUtils.isBlank(rule.getRuleCode())) {
+                        rule.setRuleCode("IR" + Seq.getId());
+                    }
+                    rule.setRuleOrder(i + 1);
+                    if (rule.getEnabled() == null) {
+                        rule.setEnabled(1);
+                    }
+                }
+                ruleMapper.insertBatch(plan.getRules());
+            }
+        }
+
+        return cnt;
+    }
+
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteByIds(Long[] ids) {
+        for (Long id : ids) {
+            InspectionPlan plan = planMapper.selectById(id);
+            if (plan != null) {
+                ruleMapper.deleteByPlanCode(plan.getPlanCode());
+            }
+        }
+        return planMapper.deleteByIds(ids);
+    }
+
+    /**
+     * 执行自动巡检
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public InspectionReport executeAutoInspection(String planCode, String executor) {
+        log.info("开始执行自动巡检任务: planCode={}, executor={}", planCode, executor);
+
+        InspectionPlan plan = selectByPlanCode(planCode);
+        if (plan == null) {
+            log.error("巡检计划不存在: {}", planCode);
+            throw new RuntimeException("巡检计划不存在");
+        }
+
+        // 校验计划类型
+        if (plan.getPlanType() != PLAN_TYPE_AUTO) {
+            throw new RuntimeException("该计划不是自动巡检类型,请使用提交报告功能");
+        }
+
+        // 更新计划状态为执行中
+        planMapper.updateStatus(planCode, PLAN_STATUS_RUNNING);
+
+        InspectionReport report = null;
+        try {
+            // 获取需要巡检的设备列表
+            List<EmsDevice> devices = getInspectionDevices(plan);
+            log.info("获取到巡检设备数量: {}", devices != null ? devices.size() : 0);
+
+            if (CollectionUtils.isEmpty(devices)) {
+                planMapper.updateStatus(planCode, PLAN_STATUS_COMPLETED);
+                throw new RuntimeException("未找到需要巡检的设备");
+            }
+
+            // 获取启用的巡检规则
+            List<InspectionRule> rules = ruleMapper.selectEnabledByPlanCode(planCode);
+            log.info("获取到启用的巡检规则数量: {}", rules != null ? rules.size() : 0);
+
+            // 如果没有规则,创建默认规则(检查设备在线状态)
+            if (CollectionUtils.isEmpty(rules)) {
+                log.warn("没有配置巡检规则,将使用默认规则(设备在线状态检查)");
+                rules = createDefaultRules();
+            }
+
+            // 创建巡检报告
+            report = createReport(plan, executor);
+            log.info("创建巡检报告: {}", report.getReportCode());
+
+            // 执行巡检
+            List<InspectionReportDetail> details = new ArrayList<>();
+            int normalCount = 0;
+            int abnormalCount = 0;
+
+            for (EmsDevice device : devices) {
+                try {
+                    InspectionReportDetail detail = checkDevice(device, rules, report.getReportCode());
+                    details.add(detail);
+
+                    if (detail.getResultStatus() == RESULT_NORMAL) {
+                        normalCount++;
+                    } else {
+                        abnormalCount++;
+                    }
+                } catch (Exception e) {
+                    log.error("检查设备异常: deviceCode={}, error={}", device.getDeviceCode(), e.getMessage(), e);
+                    InspectionReportDetail errorDetail = createErrorDetail(device, report.getReportCode(), e.getMessage());
+                    details.add(errorDetail);
+                    abnormalCount++;
+                }
+            }
+
+            // 保存报告明细
+            if (CollectionUtils.isNotEmpty(details)) {
+                log.info("保存报告明细数量: {}", details.size());
+                detailMapper.insertBatch(details);
+            }
+
+            // 更新报告统计
+            report.setTotalCount(devices.size());
+            report.setNormalCount(normalCount);
+            report.setAbnormalCount(abnormalCount);
+            report.setFinishTime(new Date());
+
+            if (abnormalCount == 0) {
+                report.setResultStatus(RESULT_NORMAL);
+            } else if (normalCount == 0) {
+                report.setResultStatus(RESULT_ABNORMAL);
+            } else {
+                report.setResultStatus(RESULT_PARTIAL);
+            }
+
+            // 生成结果摘要
+            Map<String, Object> summary = new HashMap<>();
+            summary.put("totalDevices", devices.size());
+            summary.put("normalDevices", normalCount);
+            summary.put("abnormalDevices", abnormalCount);
+            summary.put("checkRules", rules.size());
+            report.setResultSummary(JSON.toJSONString(summary));
+
+            reportMapper.update(report);
+            log.info("自动巡检执行完成: reportCode={}, total={}, normal={}, abnormal={}",
+                report.getReportCode(), devices.size(), normalCount, abnormalCount);
+
+            // 更新计划状态
+            planMapper.updateStatus(planCode, PLAN_STATUS_COMPLETED);
+
+            // 返回完整报告
+            report.setDetails(details);
+            return report;
+
+        } catch (Exception e) {
+            log.error("自动巡检执行异常: planCode={}, error={}", planCode, e.getMessage(), e);
+            planMapper.updateStatus(planCode, PLAN_STATUS_PENDING);
+            throw new RuntimeException("巡检执行失败: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 提交手动巡检报告
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public InspectionReport submitManualReport(ManualReportSubmit submit) {
+        log.info("提交手动巡检报告: planCode={}, submitter={}", submit.getPlanCode(), submit.getSubmitter());
+
+        // 校验参数
+        if (StringUtils.isBlank(submit.getPlanCode())) {
+            throw new RuntimeException("计划代码不能为空");
+        }
+        if (StringUtils.isBlank(submit.getManualRemark())) {
+            throw new RuntimeException("巡检报告内容不能为空");
+        }
+
+        InspectionPlan plan = selectByPlanCode(submit.getPlanCode());
+        if (plan == null) {
+            throw new RuntimeException("巡检计划不存在");
+        }
+
+        // 校验计划类型
+        if (plan.getPlanType() != PLAN_TYPE_MANUAL) {
+            throw new RuntimeException("该计划不是手动巡检类型");
+        }
+
+        // 校验计划状态(只有待执行状态可以提交)
+        if (plan.getPlanStatus() != PLAN_STATUS_PENDING) {
+            throw new RuntimeException("该计划已完成或已取消,无法提交报告");
+        }
+
+        // 创建巡检报告
+        InspectionReport report = new InspectionReport();
+        report.setReportCode("IR" + DateUtils.dateTimeNow() + Seq.getId(Seq.uploadSeqType));
+        report.setPlanCode(plan.getPlanCode());
+        report.setPlanName(plan.getPlanName());
+        report.setPlanType(plan.getPlanType());
+        report.setAreaCode(plan.getAreaCode());
+        report.setAreaName(plan.getAreaName());
+        report.setTargetType(plan.getTargetType());
+        report.setExecuteTime(new Date());
+        report.setFinishTime(new Date());
+        report.setExecutor(StringUtils.isNotBlank(submit.getSubmitter()) ? submit.getSubmitter() : plan.getExecutor());
+        report.setResultStatus(submit.getResultStatus() != null ? submit.getResultStatus() : RESULT_NORMAL);
+        report.setManualRemark(submit.getManualRemark());
+
+        // 手动巡检不统计设备数量(由人工填写报告)
+        report.setTotalCount(0);
+        report.setNormalCount(0);
+        report.setAbnormalCount(0);
+
+        // 生成结果摘要
+        Map<String, Object> summary = new HashMap<>();
+        summary.put("type", "manual");
+        summary.put("targetNames", plan.getTargetNames());
+        summary.put("submitter", submit.getSubmitter());
+        summary.put("submitTime", DateUtils.dateTimeNow());
+
+        if (StringUtils.isNotBlank(submit.getRemark())) {
+            summary.put("remark", submit.getRemark());
+        }
+        report.setResultSummary(JSON.toJSONString(summary));
+
+        // 保存报告
+        reportMapper.insert(report);
+
+        // 更新计划状态为已完成
+        planMapper.updateStatus(submit.getPlanCode(), PLAN_STATUS_COMPLETED);
+
+        log.info("手动巡检报告提交成功: reportCode={}, planCode={}", report.getReportCode(), submit.getPlanCode());
+
+        return report;
+    }
+
+    // ===================== 以下为私有辅助方法 =====================
+
+    /**
+     * 创建默认巡检规则
+     */
+    private List<InspectionRule> createDefaultRules() {
+        List<InspectionRule> rules = new ArrayList<>();
+        InspectionRule rule = new InspectionRule();
+        rule.setRuleCode("DEFAULT_ONLINE");
+        rule.setRuleName("设备在线状态检查");
+        rule.setDeviceModel(null);
+        rule.setAttrKey("deviceStatus");
+        rule.setAttrName("设备状态");
+        rule.setCheckType(CHECK_TYPE_ONLINE);
+        rule.setExpectValue("1");
+        rule.setEnabled(1);
+        rules.add(rule);
+        return rules;
+    }
+
+    /**
+     * 创建异常报告明细
+     */
+    private InspectionReportDetail createErrorDetail(EmsDevice device, String reportCode, String errorMsg) {
+        InspectionReportDetail detail = new InspectionReportDetail();
+        detail.setReportCode(reportCode);
+        detail.setDeviceCode(device.getDeviceCode());
+        detail.setDeviceName(device.getDeviceName() != null ? device.getDeviceName() : device.getDeviceCode());
+        detail.setDeviceModel(device.getDeviceModel());
+        detail.setDeviceModelName(device.getDeviceModelName());
+        detail.setLocation(device.getLocation() != null ? device.getLocation() : "");
+        detail.setAreaPath(device.getAreaPath() != null ? device.getAreaPath() : "");
+        detail.setCheckTime(new Date());
+        detail.setResultStatus(RESULT_ABNORMAL);
+
+        CheckItemResult errorResult = new CheckItemResult();
+        errorResult.setRuleCode("ERROR");
+        errorResult.setRuleName("系统检查");
+        errorResult.setStatus(RESULT_ABNORMAL);
+        errorResult.setMessage("检查异常: " + errorMsg);
+
+        List<CheckItemResult> checkResults = new ArrayList<>();
+        checkResults.add(errorResult);
+        detail.setCheckItems(JSON.toJSONString(checkResults));
+        detail.setCheckItemList(checkResults);
+
+        return detail;
+    }
+
+    /**
+     * 根据计划获取需要巡检的设备列表
+     */
+    private List<EmsDevice> getInspectionDevices(InspectionPlan plan) {
+        List<EmsDevice> devices = new ArrayList<>();
+        List<String> targetCodes = plan.getTargetCodeList();
+
+        log.info("获取巡检设备, targetType={}, targetCodes={}", plan.getTargetType(), targetCodes);
+
+        if (CollectionUtils.isEmpty(targetCodes)) {
+            log.warn("目标代码列表为空");
+            return devices;
+        }
+
+        switch (plan.getTargetType()) {
+            case TARGET_TYPE_AREA:
+                for (String areaCode : targetCodes) {
+                    try {
+                        QueryDevice query = new QueryDevice();
+                        query.setAreaCode(areaCode);
+                        List<EmsDevice> areaDevices = deviceService.selectByAreaTree(query);
+                        log.info("区域{}下设备数量: {}", areaCode, areaDevices != null ? areaDevices.size() : 0);
+                        if (CollectionUtils.isNotEmpty(areaDevices)) {
+                            devices.addAll(areaDevices);
+                        }
+                    } catch (Exception e) {
+                        log.error("获取区域设备异常: areaCode={}, error={}", areaCode, e.getMessage());
+                    }
+                }
+                break;
+
+            case TARGET_TYPE_FACS:
+                for (String facsCode : targetCodes) {
+                    try {
+                        QueryDevice query = new QueryDevice();
+                        query.setRefFacs(facsCode);
+                        List<EmsDevice> facsDevices = deviceService.selectDetailList(query);
+                        log.info("设施{}下设备数量: {}", facsCode, facsDevices != null ? facsDevices.size() : 0);
+                        if (CollectionUtils.isNotEmpty(facsDevices)) {
+                            devices.addAll(facsDevices);
+                        }
+                    } catch (Exception e) {
+                        log.error("获取设施设备异常: facsCode={}, error={}", facsCode, e.getMessage());
+                    }
+                }
+                break;
+
+            case TARGET_TYPE_DEVICE:
+                for (String deviceCode : targetCodes) {
+                    try {
+                        EmsDevice device = deviceService.selectDetailByCode(deviceCode);
+                        if (device != null) {
+                            devices.add(device);
+                        } else {
+                            log.warn("设备不存在: {}", deviceCode);
+                        }
+                    } catch (Exception e) {
+                        log.error("获取设备异常: deviceCode={}, error={}", deviceCode, e.getMessage());
+                    }
+                }
+                break;
+
+            default:
+                log.warn("未知的目标类型: {}", plan.getTargetType());
+        }
+
+        // 去重
+        if (CollectionUtils.isNotEmpty(devices)) {
+            devices = devices.stream()
+                .filter(d -> d != null && StringUtils.isNotBlank(d.getDeviceCode()))
+                .collect(Collectors.collectingAndThen(
+                    Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(EmsDevice::getDeviceCode))),
+                    ArrayList::new
+                ));
+        }
+
+        log.info("最终获取到设备数量: {}", devices.size());
+        return devices;
+    }
+
+    /**
+     * 检查单个设备
+     */
+    private InspectionReportDetail checkDevice(EmsDevice device, List<InspectionRule> rules, String reportCode) {
+        InspectionReportDetail detail = new InspectionReportDetail();
+        detail.setReportCode(reportCode);
+        detail.setDeviceCode(device.getDeviceCode());
+        detail.setDeviceName(device.getDeviceName() != null ? device.getDeviceName() : device.getDeviceCode());
+        detail.setDeviceModel(device.getDeviceModel());
+        detail.setDeviceModelName(device.getDeviceModelName());
+        detail.setLocation(device.getLocation() != null ? device.getLocation() : "");
+        detail.setAreaPath(device.getAreaPath() != null ? device.getAreaPath() : "");
+        detail.setCheckTime(new Date());
+
+        List<CheckItemResult> checkResults = new ArrayList<>();
+        boolean hasAbnormal = false;
+
+        // 获取设备属性值
+        Map<String, String> attrValueMap = new HashMap<>();
+        if (StringUtils.isNotBlank(device.getDeviceModel())) {
+            try {
+                List<EmsObjAttrValue> attrValues = attrValueService.selectByObjCode(device.getDeviceModel(), device.getDeviceCode());
+                if (CollectionUtils.isNotEmpty(attrValues)) {
+                    attrValueMap = attrValues.stream()
+                        .filter(v -> v.getAttrKey() != null && v.getAttrValue() != null)
+                        .collect(Collectors.toMap(EmsObjAttrValue::getAttrKey, EmsObjAttrValue::getAttrValue, (v1, v2) -> v2));
+                }
+            } catch (Exception e) {
+                log.warn("获取设备属性值异常: deviceCode={}, error={}", device.getDeviceCode(), e.getMessage());
+            }
+        }
+
+        // 应用每个规则
+        for (InspectionRule rule : rules) {
+            if (StringUtils.isNotBlank(rule.getDeviceModel()) &&
+                !StringUtils.equals(rule.getDeviceModel(), device.getDeviceModel())) {
+                continue;
+            }
+
+            try {
+                CheckItemResult result = checkRule(rule, device, attrValueMap);
+                checkResults.add(result);
+
+                if (result.getStatus() == RESULT_ABNORMAL) {
+                    hasAbnormal = true;
+                }
+            } catch (Exception e) {
+                log.warn("检查规则异常: ruleCode={}, deviceCode={}, error={}",
+                    rule.getRuleCode(), device.getDeviceCode(), e.getMessage());
+                CheckItemResult errorResult = new CheckItemResult();
+                errorResult.setRuleCode(rule.getRuleCode());
+                errorResult.setRuleName(rule.getRuleName());
+                errorResult.setStatus(RESULT_ABNORMAL);
+                errorResult.setMessage("检查异常: " + e.getMessage());
+                checkResults.add(errorResult);
+                hasAbnormal = true;
+            }
+        }
+
+        if (checkResults.isEmpty()) {
+            CheckItemResult defaultResult = new CheckItemResult();
+            defaultResult.setRuleCode("NO_RULE");
+            defaultResult.setRuleName("无适用规则");
+            defaultResult.setAttrKey("deviceStatus");
+            defaultResult.setAttrName("设备状态");
+            defaultResult.setActualValue(device.getDeviceStatus() != null ? device.getDeviceStatus().toString() : "未知");
+            defaultResult.setStatus(RESULT_NORMAL);
+            defaultResult.setMessage("无适用检查规则");
+            checkResults.add(defaultResult);
+        }
+
+        detail.setResultStatus(hasAbnormal ? RESULT_ABNORMAL : RESULT_NORMAL);
+        detail.setCheckItems(JSON.toJSONString(checkResults));
+        detail.setCheckItemList(checkResults);
+
+        return detail;
+    }
+
+    /**
+     * 检查单个规则
+     */
+    private CheckItemResult checkRule(InspectionRule rule, EmsDevice device, Map<String, String> attrValueMap) {
+        CheckItemResult result = new CheckItemResult();
+        result.setRuleCode(rule.getRuleCode());
+        result.setRuleName(rule.getRuleName() != null ? rule.getRuleName() : "未命名规则");
+        result.setAttrKey(rule.getAttrKey());
+        result.setAttrName(rule.getAttrName() != null ? rule.getAttrName() : rule.getAttrKey());
+        result.setCheckType(rule.getCheckType());
+
+        String actualValue = null;
+
+        if ("deviceStatus".equals(rule.getAttrKey())) {
+            actualValue = device.getDeviceStatus() != null ? device.getDeviceStatus().toString() : null;
+        } else {
+            actualValue = attrValueMap.get(rule.getAttrKey());
+        }
+
+        result.setActualValue(actualValue != null ? actualValue : "无数据");
+
+        Integer checkType = rule.getCheckType();
+        if (checkType == null) {
+            checkType = CHECK_TYPE_NOT_NULL;
+        }
+
+        switch (checkType) {
+            case CHECK_TYPE_RANGE:
+                result.setExpectRange(formatRange(rule.getMinValue(), rule.getMaxValue()));
+                checkRange(result, actualValue, rule.getMinValue(), rule.getMaxValue());
+                break;
+            case CHECK_TYPE_EQUAL:
+                result.setExpectRange("= " + (rule.getExpectValue() != null ? rule.getExpectValue() : ""));
+                checkEqual(result, actualValue, rule.getExpectValue());
+                break;
+            case CHECK_TYPE_NOT_NULL:
+                result.setExpectRange("非空");
+                checkNotNull(result, actualValue);
+                break;
+            case CHECK_TYPE_ONLINE:
+                result.setExpectRange("在线(1)");
+                checkOnline(result, actualValue, rule.getExpectValue());
+                break;
+            default:
+                result.setStatus(RESULT_NORMAL);
+                result.setMessage("未知检查类型: " + checkType);
+        }
+
+        return result;
+    }
+
+    private String formatRange(String minValue, String maxValue) {
+        String min = StringUtils.isNotBlank(minValue) ? minValue : "-∞";
+        String max = StringUtils.isNotBlank(maxValue) ? maxValue : "+∞";
+        return min + " ~ " + max;
+    }
+
+    private void checkRange(CheckItemResult result, String actualValue, String minValue, String maxValue) {
+        if (StringUtils.isBlank(actualValue)) {
+            result.setStatus(RESULT_ABNORMAL);
+            result.setMessage("属性值为空");
+            return;
+        }
+
+        try {
+            BigDecimal actual = new BigDecimal(actualValue);
+            BigDecimal min = StringUtils.isNotBlank(minValue) ? new BigDecimal(minValue) : null;
+            BigDecimal max = StringUtils.isNotBlank(maxValue) ? new BigDecimal(maxValue) : null;
+
+            boolean inRange = true;
+            if (min != null && actual.compareTo(min) < 0) {
+                inRange = false;
+            }
+            if (max != null && actual.compareTo(max) > 0) {
+                inRange = false;
+            }
+
+            result.setStatus(inRange ? RESULT_NORMAL : RESULT_ABNORMAL);
+            result.setMessage(inRange ? "正常" : "超出范围");
+        } catch (NumberFormatException e) {
+            result.setStatus(RESULT_ABNORMAL);
+            result.setMessage("数值格式错误: " + actualValue);
+        }
+    }
+
+    private void checkEqual(CheckItemResult result, String actualValue, String expectValue) {
+        if (StringUtils.equals(actualValue, expectValue)) {
+            result.setStatus(RESULT_NORMAL);
+            result.setMessage("正常");
+        } else {
+            result.setStatus(RESULT_ABNORMAL);
+            result.setMessage("值不匹配,期望: " + expectValue);
+        }
+    }
+
+    private void checkNotNull(CheckItemResult result, String actualValue) {
+        if (StringUtils.isNotBlank(actualValue)) {
+            result.setStatus(RESULT_NORMAL);
+            result.setMessage("正常");
+        } else {
+            result.setStatus(RESULT_ABNORMAL);
+            result.setMessage("属性值为空");
+        }
+    }
+
+    private void checkOnline(CheckItemResult result, String actualValue, String expectValue) {
+        String expect = StringUtils.isNotBlank(expectValue) ? expectValue : "1";
+        if (StringUtils.equals(actualValue, expect)) {
+            result.setStatus(RESULT_NORMAL);
+            result.setMessage("在线");
+        } else {
+            result.setStatus(RESULT_ABNORMAL);
+            result.setMessage("离线或异常");
+        }
+    }
+
+    private InspectionReport createReport(InspectionPlan plan, String executor) {
+        InspectionReport report = new InspectionReport();
+        report.setReportCode("IR" + DateUtils.dateTimeNow() + Seq.getId(Seq.uploadSeqType));
+        report.setPlanCode(plan.getPlanCode());
+        report.setPlanName(plan.getPlanName() != null ? plan.getPlanName() : "");
+        report.setPlanType(plan.getPlanType());
+        report.setAreaCode(plan.getAreaCode());
+        report.setAreaName(plan.getAreaName() != null ? plan.getAreaName() : "");
+        report.setTargetType(plan.getTargetType());
+        report.setExecuteTime(new Date());
+        report.setExecutor(StringUtils.isNotBlank(executor) ? executor :
+            (StringUtils.isNotBlank(plan.getExecutor()) ? plan.getExecutor() : "system"));
+        report.setResultStatus(RESULT_NORMAL);
+        report.setTotalCount(0);
+        report.setNormalCount(0);
+        report.setAbnormalCount(0);
+
+        reportMapper.insert(report);
+        return report;
+    }
+
+    private void parseTargetCodes(InspectionPlan plan) {
+        if (StringUtils.isNotBlank(plan.getTargetCodes())) {
+            try {
+                plan.setTargetCodeList(JSONArray.parseArray(plan.getTargetCodes(), String.class));
+            } catch (Exception e) {
+                log.warn("解析目标代码失败: {}", plan.getTargetCodes());
+                plan.setTargetCodeList(new ArrayList<>());
+            }
+        } else {
+            plan.setTargetCodeList(new ArrayList<>());
+        }
+    }
+}

+ 184 - 0
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/InspectionReportServiceImpl.java

@@ -0,0 +1,184 @@
+package com.ruoyi.ems.service.impl;
+
+import com.alibaba.fastjson2.JSON;
+import com.huashe.common.utils.DateUtils;
+import com.ruoyi.common.security.utils.SecurityUtils;
+import com.ruoyi.ems.domain.CheckItemResult;
+import com.ruoyi.ems.domain.InspectionReport;
+import com.ruoyi.ems.domain.InspectionReportDetail;
+import com.ruoyi.ems.domain.ManualReportSubmit;
+import com.ruoyi.ems.mapper.InspectionReportDetailMapper;
+import com.ruoyi.ems.mapper.InspectionReportMapper;
+import com.ruoyi.ems.service.IInspectionReportService;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 巡检报告Service实现
+ */
+@Service
+public class InspectionReportServiceImpl implements IInspectionReportService {
+
+    private static final Logger log = LoggerFactory.getLogger(InspectionReportServiceImpl.class);
+
+    @Autowired
+    private InspectionReportMapper reportMapper;
+
+    @Autowired
+    private InspectionReportDetailMapper detailMapper;
+
+    @Override
+    public InspectionReport selectById(Long id) {
+        InspectionReport report = reportMapper.selectById(id);
+        if (report != null) {
+            fillDetails(report);
+        }
+        return report;
+    }
+
+    @Override
+    public InspectionReport selectByReportCode(String reportCode) {
+        InspectionReport report = reportMapper.selectByReportCode(reportCode);
+        if (report != null) {
+            fillDetails(report);
+        }
+        return report;
+    }
+
+    @Override
+    public List<InspectionReport> selectList(InspectionReport report) {
+        return reportMapper.selectList(report);
+    }
+
+    @Override
+    public int update(InspectionReport report) {
+        report.setUpdateBy(SecurityUtils.getUsername());
+        return reportMapper.update(report);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int deleteByIds(Long[] ids) {
+        for (Long id : ids) {
+            InspectionReport report = reportMapper.selectById(id);
+            if (report != null) {
+                detailMapper.deleteByReportCode(report.getReportCode());
+            }
+        }
+        return reportMapper.deleteByIds(ids);
+    }
+
+    @Override
+    public int submitManualReport(InspectionReport report) {
+        InspectionReport dbReport = reportMapper.selectById(report.getId());
+        if (dbReport == null) {
+            throw new RuntimeException("报告不存在");
+        }
+        // 更新内容
+        dbReport.setManualRemark(report.getManualRemark());
+        dbReport.setResultStatus(report.getResultStatus());
+        dbReport.setFinishTime(new Date());
+        dbReport.setUpdateBy(SecurityUtils.getUsername());
+
+        return reportMapper.update(dbReport);
+    }
+
+    /**
+     * 更新手动巡检报告
+     * 用于编辑已提交的手动巡检报告
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int updateManualReport(ManualReportSubmit submit) {
+        log.info("更新手动巡检报告: id={}, reportCode={}", submit.getId(), submit.getReportCode());
+
+        // 参数校验
+        if (submit.getId() == null && StringUtils.isBlank(submit.getReportCode())) {
+            throw new RuntimeException("报告ID或报告代码不能为空");
+        }
+
+        // 查询原报告
+        InspectionReport dbReport;
+        if (submit.getId() != null) {
+            dbReport = reportMapper.selectById(submit.getId());
+        } else {
+            dbReport = reportMapper.selectByReportCode(submit.getReportCode());
+        }
+
+        if (dbReport == null) {
+            throw new RuntimeException("巡检报告不存在");
+        }
+
+        // 只允许编辑手动巡检报告(planType=1)
+        if (dbReport.getPlanType() != null && dbReport.getPlanType() != 1) {
+            throw new RuntimeException("只能编辑手动巡检报告");
+        }
+
+        // 更新报告内容
+        dbReport.setResultStatus(submit.getResultStatus());
+        dbReport.setManualRemark(submit.getManualRemark());
+        dbReport.setUpdateBy(submit.getSubmitter());
+
+        // 更新结果摘要(包含图片和备注)
+        Map<String, Object> summary = new HashMap<>();
+
+        // 尝试保留原有的摘要信息
+        if (StringUtils.isNotBlank(dbReport.getResultSummary())) {
+            try {
+                Map<String, Object> oldSummary = JSON.parseObject(dbReport.getResultSummary(), Map.class);
+                if (oldSummary != null) {
+                    summary.putAll(oldSummary);
+                }
+            } catch (Exception e) {
+                log.warn("解析原报告摘要失败: {}", e.getMessage());
+            }
+        }
+
+        // 更新摘要字段
+        summary.put("type", "manual");
+        summary.put("updateBy", submit.getSubmitter());
+        summary.put("updateTime", DateUtils.dateTimeNow());
+
+        if (StringUtils.isNotBlank(submit.getRemark())) {
+            summary.put("remark", submit.getRemark());
+        } else {
+            summary.remove("remark");
+        }
+
+        dbReport.setResultSummary(JSON.toJSONString(summary));
+
+        int result = reportMapper.update(dbReport);
+        log.info("手动巡检报告更新成功: reportCode={}", dbReport.getReportCode());
+
+        return result;
+    }
+
+    /**
+     * 填充报告明细
+     */
+    private void fillDetails(InspectionReport report) {
+        List<InspectionReportDetail> details = detailMapper.selectByReportCode(report.getReportCode());
+        if (CollectionUtils.isNotEmpty(details)) {
+            for (InspectionReportDetail detail : details) {
+                if (StringUtils.isNotBlank(detail.getCheckItems())) {
+                    try {
+                        detail.setCheckItemList(JSON.parseArray(detail.getCheckItems(), CheckItemResult.class));
+                    } catch (Exception e) {
+                        log.warn("解析检查项失败: {}", e.getMessage());
+                    }
+                }
+            }
+        }
+        report.setDetails(details);
+    }
+}

+ 0 - 87
ems/ems-core/src/main/java/com/ruoyi/ems/service/impl/OpInspectionPlanServiceImpl.java

@@ -1,87 +0,0 @@
-package com.ruoyi.ems.service.impl;
-
-import com.ruoyi.ems.domain.OpInspectionTask;
-import com.ruoyi.ems.mapper.OpInspectionPlanMapper;
-import com.ruoyi.ems.service.IOpInspectionPlanService;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.stereotype.Service;
-
-import java.util.List;
-
-/**
- * 巡检任务Service业务层处理
- *
- * @author ruoyi
- * @date 2024-08-29
- */
-@Service
-public class OpInspectionPlanServiceImpl implements IOpInspectionPlanService {
-    @Autowired
-    private OpInspectionPlanMapper inspectionPlanMapper;
-
-    /**
-     * 查询巡检任务
-     *
-     * @param id 巡检任务主键
-     * @return 巡检任务
-     */
-    @Override
-    public OpInspectionTask selectOpInspectionPlanById(Long id) {
-        return inspectionPlanMapper.selectOpInspectionPlanById(id);
-    }
-
-    /**
-     * 查询巡检任务列表
-     *
-     * @param opInspectionPlan 巡检任务
-     * @return 巡检任务
-     */
-    @Override
-    public List<OpInspectionTask> selectOpInspectionPlanList(OpInspectionTask opInspectionPlan) {
-        return inspectionPlanMapper.selectOpInspectionPlanList(opInspectionPlan);
-    }
-
-    /**
-     * 新增巡检任务
-     *
-     * @param opInspectionPlan 巡检任务
-     * @return 结果
-     */
-    @Override
-    public int insertOpInspectionPlan(OpInspectionTask opInspectionPlan) {
-        return inspectionPlanMapper.insertOpInspectionPlan(opInspectionPlan);
-    }
-
-    /**
-     * 修改巡检任务
-     *
-     * @param opInspectionPlan 巡检任务
-     * @return 结果
-     */
-    @Override
-    public int updateOpInspectionPlan(OpInspectionTask opInspectionPlan) {
-        return inspectionPlanMapper.updateOpInspectionPlan(opInspectionPlan);
-    }
-
-    /**
-     * 批量删除巡检任务
-     *
-     * @param ids 需要删除的巡检任务主键
-     * @return 结果
-     */
-    @Override
-    public int deleteOpInspectionPlanByIds(Long[] ids) {
-        return inspectionPlanMapper.deleteOpInspectionPlanByIds(ids);
-    }
-
-    /**
-     * 删除巡检任务信息
-     *
-     * @param id 巡检任务主键
-     * @return 结果
-     */
-    @Override
-    public int deleteOpInspectionPlanById(Long id) {
-        return inspectionPlanMapper.deleteOpInspectionPlanById(id);
-    }
-}

+ 0 - 78
ems/ems-core/src/main/resources/mapper/ems/AdmOpInspectionReportMapper.xml

@@ -1,78 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper
-PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.ruoyi.ems.mapper.AdmOpInspectionReportMapper">
-
-    <resultMap type="com.ruoyi.ems.domain.AdmOpInspectionReport" id="AdmOpInspectionReportResult">
-        <result property="id"    column="id"    />
-        <result property="taskCode"    column="task_code"    />
-        <result property="resultStatus"    column="result_status"    />
-        <result property="resultMsg"    column="result_msg"    />
-        <result property="finishTime"    column="finish_time"    />
-        <result property="subTime"    column="sub_time"    />
-        <result property="submitter"    column="submitter"    />
-    </resultMap>
-
-    <sql id="selectAdmOpInspectionReportVo">
-        select id, task_code, result_status, result_msg, finish_time, sub_time, submitter from adm_op_inspection_report
-    </sql>
-
-    <select id="selectAdmOpInspectionReportList" parameterType="com.ruoyi.ems.domain.AdmOpInspectionReport" resultMap="AdmOpInspectionReportResult">
-        <include refid="selectAdmOpInspectionReportVo"/>
-        <where>
-            <if test="taskCode != null  and taskCode != ''"> and task_code = #{taskCode}</if>
-            <if test="resultStatus != null "> and result_status = #{resultStatus}</if>
-            <if test="submitter != null  and submitter != ''"> and submitter like concat('%', #{submitter}, '%')</if>
-        </where>
-    </select>
-
-    <select id="selectAdmOpInspectionReportById" parameterType="Long" resultMap="AdmOpInspectionReportResult">
-        <include refid="selectAdmOpInspectionReportVo"/>
-        where id = #{id}
-    </select>
-
-    <insert id="insertAdmOpInspectionReport" parameterType="com.ruoyi.ems.domain.AdmOpInspectionReport" useGeneratedKeys="true" keyProperty="id">
-        insert into adm_op_inspection_report
-        <trim prefix="(" suffix=")" suffixOverrides=",">
-            <if test="taskCode != null and taskCode != ''">task_code,</if>
-            <if test="resultStatus != null">result_status,</if>
-            <if test="resultMsg != null">result_msg,</if>
-            <if test="finishTime != null">finish_time,</if>
-            <if test="subTime != null">sub_time,</if>
-            <if test="submitter != null">submitter,</if>
-         </trim>
-        <trim prefix="values (" suffix=")" suffixOverrides=",">
-            <if test="taskCode != null and taskCode != ''">#{taskCode},</if>
-            <if test="resultStatus != null">#{resultStatus},</if>
-            <if test="resultMsg != null">#{resultMsg},</if>
-            <if test="finishTime != null">#{finishTime},</if>
-            <if test="subTime != null">#{subTime},</if>
-            <if test="submitter != null">#{submitter},</if>
-         </trim>
-    </insert>
-
-    <update id="updateAdmOpInspectionReport" parameterType="com.ruoyi.ems.domain.AdmOpInspectionReport">
-        update adm_op_inspection_report
-        <trim prefix="SET" suffixOverrides=",">
-            <if test="taskCode != null and taskCode != ''">task_code = #{taskCode},</if>
-            <if test="resultStatus != null">result_status = #{resultStatus},</if>
-            <if test="resultMsg != null">result_msg = #{resultMsg},</if>
-            <if test="finishTime != null">finish_time = #{finishTime},</if>
-            <if test="subTime != null">sub_time = #{subTime},</if>
-            <if test="submitter != null">submitter = #{submitter},</if>
-        </trim>
-        where id = #{id}
-    </update>
-
-    <delete id="deleteAdmOpInspectionReportById" parameterType="Long">
-        delete from adm_op_inspection_report where id = #{id}
-    </delete>
-
-    <delete id="deleteAdmOpInspectionReportByIds" parameterType="String">
-        delete from adm_op_inspection_report where id in
-        <foreach item="id" collection="array" open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </delete>
-</mapper>

+ 146 - 0
ems/ems-core/src/main/resources/mapper/ems/InspectionPlanMapper.xml

@@ -0,0 +1,146 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.ems.mapper.InspectionPlanMapper">
+
+    <resultMap type="com.ruoyi.ems.domain.InspectionPlan" id="InspectionPlanResult">
+        <id property="id" column="id"/>
+        <result property="areaCode" column="area_code"/>
+        <result property="areaName" column="area_name"/>
+        <result property="planCode" column="plan_code"/>
+        <result property="planName" column="plan_name"/>
+        <result property="planType" column="plan_type"/>
+        <result property="planStatus" column="plan_status"/>
+        <result property="targetType" column="target_type"/>
+        <result property="targetCodes" column="target_codes"/>
+        <result property="targetNames" column="target_names"/>
+        <result property="cronExpression" column="cron_expression"/>
+        <!-- 新增调度相关字段映射 -->
+        <result property="scheduleEnabled" column="schedule_enabled"/>
+        <result property="lastExecTime" column="last_exec_time"/>
+        <result property="nextExecTime" column="next_exec_time"/>
+        <result property="execCount" column="exec_count"/>
+        <!-- 结束新增 -->
+        <result property="planTime" column="plan_time"/>
+        <result property="executor" column="executor"/>
+        <result property="description" column="description"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="selectVo">
+        SELECT p.id, p.area_code, a.area_name, p.plan_code, p.plan_name, p.plan_type,
+               p.plan_status, p.target_type, p.target_codes, p.target_names,
+               p.cron_expression,
+               p.schedule_enabled, p.last_exec_time, p.next_exec_time, p.exec_count,
+               p.plan_time, p.executor, p.description,
+               p.create_by, p.create_time, p.update_by, p.update_time
+        FROM adm_op_inspection_plan p
+                 LEFT JOIN adm_area a ON p.area_code = a.area_code
+    </sql>
+
+    <select id="selectById" parameterType="Long" resultMap="InspectionPlanResult">
+        <include refid="selectVo"/>
+        WHERE p.id = #{id}
+    </select>
+
+    <select id="selectByPlanCode" parameterType="String" resultMap="InspectionPlanResult">
+        <include refid="selectVo"/>
+        WHERE p.plan_code = #{planCode}
+    </select>
+
+    <select id="selectList" parameterType="com.ruoyi.ems.domain.InspectionPlan" resultMap="InspectionPlanResult">
+        <include refid="selectVo"/>
+        <where>
+            <if test="areaCode != null and areaCode != ''">AND p.area_code = #{areaCode}</if>
+            <if test="planCode != null and planCode != ''">AND p.plan_code = #{planCode}</if>
+            <if test="planName != null and planName != ''">AND p.plan_name LIKE CONCAT('%', #{planName}, '%')</if>
+            <if test="planType != null">AND p.plan_type = #{planType}</if>
+            <if test="planStatus != null">AND p.plan_status = #{planStatus}</if>
+            <if test="targetType != null">AND p.target_type = #{targetType}</if>
+            <if test="executor != null and executor != ''">AND p.executor LIKE CONCAT('%', #{executor}, '%')</if>
+            <if test="scheduleEnabled != null">AND p.schedule_enabled = #{scheduleEnabled}</if>
+        </where>
+        ORDER BY p.create_time DESC
+    </select>
+
+    <!-- 查询已启用调度的自动巡检计划(供调度器启动时加载) -->
+    <select id="selectEnabledSchedulePlans" resultMap="InspectionPlanResult">
+        <include refid="selectVo"/>
+        WHERE p.plan_type = 2
+        AND p.schedule_enabled = 1
+        AND p.cron_expression IS NOT NULL
+        AND p.cron_expression != ''
+    </select>
+
+    <insert id="insert" parameterType="com.ruoyi.ems.domain.InspectionPlan" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO adm_op_inspection_plan (
+            area_code, plan_code, plan_name, plan_type, plan_status, target_type,
+            target_codes, target_names, cron_expression, schedule_enabled,
+            plan_time, executor, description, create_by, create_time
+        ) VALUES (
+                     #{areaCode}, #{planCode}, #{planName}, #{planType}, #{planStatus}, #{targetType},
+                     #{targetCodes}, #{targetNames}, #{cronExpression}, #{scheduleEnabled},
+                     #{planTime}, #{executor}, #{description}, #{createBy}, NOW()
+                 )
+    </insert>
+
+    <update id="update" parameterType="com.ruoyi.ems.domain.InspectionPlan">
+        UPDATE adm_op_inspection_plan
+        <set>
+            <if test="areaCode != null and areaCode != ''">area_code = #{areaCode},</if>
+            <if test="planName != null and planName != ''">plan_name = #{planName},</if>
+            <if test="planType != null">plan_type = #{planType},</if>
+            <if test="planStatus != null">plan_status = #{planStatus},</if>
+            <if test="targetType != null">target_type = #{targetType},</if>
+            <if test="targetCodes != null">target_codes = #{targetCodes},</if>
+            <if test="targetNames != null">target_names = #{targetNames},</if>
+            <if test="cronExpression != null">cron_expression = #{cronExpression},</if>
+            <if test="scheduleEnabled != null">schedule_enabled = #{scheduleEnabled},</if>
+            <if test="lastExecTime != null">last_exec_time = #{lastExecTime},</if>
+            <if test="nextExecTime != null">next_exec_time = #{nextExecTime},</if>
+            <if test="execCount != null">exec_count = #{execCount},</if>
+            <if test="planTime != null">plan_time = #{planTime},</if>
+            <if test="executor != null">executor = #{executor},</if>
+            <if test="description != null">description = #{description},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            update_time = NOW()
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <update id="updateStatus">
+        UPDATE adm_op_inspection_plan SET plan_status = #{status}, update_time = NOW()
+        WHERE plan_code = #{planCode}
+    </update>
+
+    <!-- 更新调度执行信息 -->
+    <update id="updateExecInfo">
+        UPDATE adm_op_inspection_plan
+        SET last_exec_time = #{lastExecTime},
+            next_exec_time = #{nextExecTime},
+            exec_count = IFNULL(exec_count, 0) + 1,
+            update_time = NOW()
+        WHERE plan_code = #{planCode}
+    </update>
+
+    <!-- 更新调度启用状态 -->
+    <update id="updateScheduleEnabled">
+        UPDATE adm_op_inspection_plan
+        SET schedule_enabled = #{scheduleEnabled},
+            update_time = NOW()
+        WHERE plan_code = #{planCode}
+    </update>
+
+    <delete id="deleteById" parameterType="Long">
+        DELETE FROM adm_op_inspection_plan WHERE id = #{id}
+    </delete>
+
+    <delete id="deleteByIds" parameterType="Long">
+        DELETE FROM adm_op_inspection_plan WHERE id IN
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 53 - 0
ems/ems-core/src/main/resources/mapper/ems/InspectionReportDetailMapper.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.ems.mapper.InspectionReportDetailMapper">
+
+    <resultMap type="com.ruoyi.ems.domain.InspectionReportDetail" id="DetailResult">
+        <id property="id" column="id"/>
+        <result property="reportCode" column="report_code"/>
+        <result property="deviceCode" column="device_code"/>
+        <result property="deviceName" column="device_name"/>
+        <result property="deviceModel" column="device_model"/>
+        <result property="deviceModelName" column="device_model_name"/>
+        <result property="location" column="location"/>
+        <result property="areaPath" column="area_path"/>
+        <result property="resultStatus" column="result_status"/>
+        <result property="checkItems" column="check_items"/>
+        <result property="checkTime" column="check_time"/>
+        <result property="createTime" column="create_time"/>
+    </resultMap>
+
+    <select id="selectByReportCode" parameterType="String" resultMap="DetailResult">
+        SELECT id, report_code, device_code, device_name, device_model, device_model_name,
+               location, area_path, result_status, check_items, check_time, create_time
+        FROM adm_op_inspection_report_detail
+        WHERE report_code = #{reportCode}
+        ORDER BY result_status DESC, id
+    </select>
+
+    <insert id="insert" parameterType="com.ruoyi.ems.domain.InspectionReportDetail" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO adm_op_inspection_report_detail (
+            report_code, device_code, device_name, device_model, device_model_name,
+            location, area_path, result_status, check_items, check_time, create_time
+        ) VALUES (
+                     #{reportCode}, #{deviceCode}, #{deviceName}, #{deviceModel}, #{deviceModelName},
+                     #{location}, #{areaPath}, #{resultStatus}, #{checkItems}, #{checkTime}, NOW()
+                 )
+    </insert>
+
+    <insert id="insertBatch" parameterType="java.util.List">
+        INSERT INTO adm_op_inspection_report_detail (
+        report_code, device_code, device_name, device_model, device_model_name,
+        location, area_path, result_status, check_items, check_time, create_time
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.reportCode}, #{item.deviceCode}, #{item.deviceName}, #{item.deviceModel},
+            #{item.deviceModelName}, #{item.location}, #{item.areaPath}, #{item.resultStatus},
+            #{item.checkItems}, #{item.checkTime}, NOW())
+        </foreach>
+    </insert>
+
+    <delete id="deleteByReportCode" parameterType="String">
+        DELETE FROM adm_op_inspection_report_detail WHERE report_code = #{reportCode}
+    </delete>
+</mapper>

+ 109 - 0
ems/ems-core/src/main/resources/mapper/ems/InspectionReportMapper.xml

@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.ems.mapper.InspectionReportMapper">
+
+    <resultMap type="com.ruoyi.ems.domain.InspectionReport" id="InspectionReportResult">
+        <id property="id" column="id"/>
+        <result property="reportCode" column="report_code"/>
+        <result property="planCode" column="plan_code"/>
+        <result property="planName" column="plan_name"/>
+        <result property="planType" column="plan_type"/>
+        <result property="areaCode" column="area_code"/>
+        <result property="areaName" column="area_name"/>
+        <result property="targetType" column="target_type"/>
+        <result property="executeTime" column="execute_time"/>
+        <result property="finishTime" column="finish_time"/>
+        <result property="executor" column="executor"/>
+        <result property="resultStatus" column="result_status"/>
+        <result property="totalCount" column="total_count"/>
+        <result property="normalCount" column="normal_count"/>
+        <result property="abnormalCount" column="abnormal_count"/>
+        <result property="resultSummary" column="result_summary"/>
+        <result property="manualRemark" column="manual_remark"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="selectVo">
+        SELECT r.id, r.report_code, r.plan_code, r.plan_name, r.plan_type,
+               r.area_code, r.area_name, r.target_type, r.execute_time, r.finish_time,
+               r.executor, r.result_status, r.total_count, r.normal_count, r.abnormal_count,
+               r.result_summary, r.manual_remark, r.create_by, r.create_time, r.update_by, r.update_time
+        FROM adm_op_inspection_report r
+    </sql>
+
+    <select id="selectById" parameterType="Long" resultMap="InspectionReportResult">
+        <include refid="selectVo"/>
+        WHERE r.id = #{id}
+    </select>
+
+    <select id="selectByReportCode" parameterType="String" resultMap="InspectionReportResult">
+        <include refid="selectVo"/>
+        WHERE r.report_code = #{reportCode}
+    </select>
+
+    <select id="selectList" parameterType="com.ruoyi.ems.domain.InspectionReport" resultMap="InspectionReportResult">
+        <include refid="selectVo"/>
+        <where>
+            <if test="areaCode != null and areaCode != ''">AND r.area_code = #{areaCode}</if>
+            <if test="planCode != null and planCode != ''">AND r.plan_code = #{planCode}</if>
+            <if test="planName != null and planName != ''">AND r.plan_name LIKE CONCAT('%', #{planName}, '%')</if>
+            <if test="planType != null">AND r.plan_type = #{planType}</if>
+            <if test="resultStatus != null">AND r.result_status = #{resultStatus}</if>
+            <if test="executor != null and executor != ''">AND r.executor LIKE CONCAT('%', #{executor}, '%')</if>
+            <if test="params != null and params.beginTime != null and params.beginTime != ''">
+                AND r.execute_time &gt;= #{params.beginTime}
+            </if>
+            <if test="params != null and params.endTime != null and params.endTime != ''">
+                AND r.execute_time &lt;= #{params.endTime}
+            </if>
+        </where>
+        ORDER BY r.execute_time DESC
+    </select>
+
+    <insert id="insert" parameterType="com.ruoyi.ems.domain.InspectionReport" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO adm_op_inspection_report (
+            report_code, plan_code, plan_name, plan_type, area_code, area_name, target_type,
+            execute_time, finish_time, executor, result_status, total_count, normal_count,
+            abnormal_count, result_summary, manual_remark, create_by, create_time
+        ) VALUES (
+                     #{reportCode}, #{planCode}, #{planName}, #{planType}, #{areaCode}, #{areaName}, #{targetType},
+                     #{executeTime}, #{finishTime}, #{executor}, #{resultStatus}, #{totalCount}, #{normalCount},
+                     #{abnormalCount}, #{resultSummary}, #{manualRemark}, #{createBy}, NOW()
+                 )
+    </insert>
+
+    <update id="update" parameterType="com.ruoyi.ems.domain.InspectionReport">
+        UPDATE adm_op_inspection_report
+        <set>
+            <if test="planName != null and planName != ''">plan_name = #{planName},</if>
+            <if test="areaCode != null and areaCode != ''">area_code = #{areaCode},</if>
+            <if test="areaName != null and areaName != ''">area_name = #{areaName},</if>
+            <if test="executeTime != null">execute_time = #{executeTime},</if>
+            <if test="finishTime != null">finish_time = #{finishTime},</if>
+            <if test="executor != null and executor != ''">executor = #{executor},</if>
+            <if test="resultStatus != null">result_status = #{resultStatus},</if>
+            <if test="totalCount != null">total_count = #{totalCount},</if>
+            <if test="normalCount != null">normal_count = #{normalCount},</if>
+            <if test="abnormalCount != null">abnormal_count = #{abnormalCount},</if>
+            <if test="resultSummary != null">result_summary = #{resultSummary},</if>
+            <if test="manualRemark != null">manual_remark = #{manualRemark},</if>
+            <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
+            update_time = NOW()
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <delete id="deleteById" parameterType="Long">
+        DELETE FROM adm_op_inspection_report WHERE id = #{id}
+    </delete>
+
+    <delete id="deleteByIds" parameterType="Long">
+        DELETE FROM adm_op_inspection_report WHERE id IN
+        <foreach item="id" collection="array" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+</mapper>

+ 97 - 0
ems/ems-core/src/main/resources/mapper/ems/InspectionRuleMapper.xml

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.ems.mapper.InspectionRuleMapper">
+
+    <resultMap type="com.ruoyi.ems.domain.InspectionRule" id="InspectionRuleResult">
+        <id property="id" column="id"/>
+        <result property="planCode" column="plan_code"/>
+        <result property="ruleCode" column="rule_code"/>
+        <result property="ruleName" column="rule_name"/>
+        <result property="deviceModel" column="device_model"/>
+        <result property="deviceModelName" column="device_model_name"/>
+        <result property="attrKey" column="attr_key"/>
+        <result property="attrName" column="attr_name"/>
+        <result property="checkType" column="check_type"/>
+        <result property="minValue" column="min_value"/>
+        <result property="maxValue" column="max_value"/>
+        <result property="expectValue" column="expect_value"/>
+        <result property="ruleOrder" column="rule_order"/>
+        <result property="enabled" column="enabled"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateTime" column="update_time"/>
+    </resultMap>
+
+    <sql id="selectVo">
+        SELECT r.id, r.plan_code, r.rule_code, r.rule_name, r.device_model,
+               m.model_name as device_model_name, r.attr_key, r.attr_name,
+               r.check_type, r.min_value, r.max_value, r.expect_value,
+               r.rule_order, r.enabled, r.create_time, r.update_time
+        FROM adm_op_inspection_rule r
+                 LEFT JOIN adm_ems_obj_model m ON r.device_model = m.model_code
+    </sql>
+
+    <select id="selectById" parameterType="Long" resultMap="InspectionRuleResult">
+        <include refid="selectVo"/>
+        WHERE r.id = #{id}
+    </select>
+
+    <select id="selectByPlanCode" parameterType="String" resultMap="InspectionRuleResult">
+        <include refid="selectVo"/>
+        WHERE r.plan_code = #{planCode}
+        ORDER BY r.rule_order
+    </select>
+
+    <select id="selectEnabledByPlanCode" parameterType="String" resultMap="InspectionRuleResult">
+        <include refid="selectVo"/>
+        WHERE r.plan_code = #{planCode} AND r.enabled = 1
+        ORDER BY r.rule_order
+    </select>
+
+    <insert id="insert" parameterType="com.ruoyi.ems.domain.InspectionRule" useGeneratedKeys="true" keyProperty="id">
+        INSERT INTO adm_op_inspection_rule (
+            plan_code, rule_code, rule_name, device_model, attr_key, attr_name,
+            check_type, min_value, max_value, expect_value, rule_order, enabled, create_time
+        ) VALUES (
+                     #{planCode}, #{ruleCode}, #{ruleName}, #{deviceModel}, #{attrKey}, #{attrName},
+                     #{checkType}, #{minValue}, #{maxValue}, #{expectValue}, #{ruleOrder}, #{enabled}, NOW()
+                 )
+    </insert>
+
+    <insert id="insertBatch" parameterType="java.util.List">
+        INSERT INTO adm_op_inspection_rule (
+        plan_code, rule_code, rule_name, device_model, attr_key, attr_name,
+        check_type, min_value, max_value, expect_value, rule_order, enabled, create_time
+        ) VALUES
+        <foreach collection="list" item="item" separator=",">
+            (#{item.planCode}, #{item.ruleCode}, #{item.ruleName}, #{item.deviceModel},
+            #{item.attrKey}, #{item.attrName}, #{item.checkType}, #{item.minValue},
+            #{item.maxValue}, #{item.expectValue}, #{item.ruleOrder}, #{item.enabled}, NOW())
+        </foreach>
+    </insert>
+
+    <update id="update" parameterType="com.ruoyi.ems.domain.InspectionRule">
+        UPDATE adm_op_inspection_rule
+        <set>
+            <if test="ruleName != null and ruleName != ''">rule_name = #{ruleName},</if>
+            <if test="deviceModel != null">device_model = #{deviceModel},</if>
+            <if test="attrKey != null and attrKey != ''">attr_key = #{attrKey},</if>
+            <if test="attrName != null">attr_name = #{attrName},</if>
+            <if test="checkType != null">check_type = #{checkType},</if>
+            <if test="minValue != null">min_value = #{minValue},</if>
+            <if test="maxValue != null">max_value = #{maxValue},</if>
+            <if test="expectValue != null">expect_value = #{expectValue},</if>
+            <if test="ruleOrder != null">rule_order = #{ruleOrder},</if>
+            <if test="enabled != null">enabled = #{enabled},</if>
+            update_time = NOW()
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <delete id="deleteById" parameterType="Long">
+        DELETE FROM adm_op_inspection_rule WHERE id = #{id}
+    </delete>
+
+    <delete id="deleteByPlanCode" parameterType="String">
+        DELETE FROM adm_op_inspection_rule WHERE plan_code = #{planCode}
+    </delete>
+</mapper>

+ 0 - 111
ems/ems-core/src/main/resources/mapper/ems/OpInspectionPlanMapper.xml

@@ -1,111 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<!DOCTYPE mapper
-        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
-        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-<mapper namespace="com.ruoyi.ems.mapper.OpInspectionPlanMapper">
-
-    <resultMap type="com.ruoyi.ems.domain.OpInspectionTask" id="OpInspectionPlanResult">
-        <result property="id" column="id"/>
-        <result property="areaCode" column="area_code"/>
-        <result property="taskCode" column="task_code"/>
-        <result property="taskName" column="task_name"/>
-        <result property="taskType" column="task_type"/>
-        <result property="taskStatus" column="task_status"/>
-        <result property="startTime" column="start_time"/>
-        <result property="endTime" column="end_time"/>
-        <result property="executor" column="executor"/>
-        <result property="objType" column="obj_type"/>
-        <result property="objCode" column="obj_code"/>
-        <result property="objName" column="obj_name"/>
-    </resultMap>
-
-    <sql id="selectOpInspectionPlanVo">
-        select id,
-               area_code,
-               task_code,
-               task_name,
-               task_type,
-               start_time,
-               executor,
-               obj_type,
-               obj_code,
-               obj_name
-        from adm_op_inspection_plan
-    </sql>
-
-    <select id="selectOpInspectionPlanList" parameterType="com.ruoyi.ems.domain.OpInspectionTask"
-            resultMap="OpInspectionPlanResult">
-        <include refid="selectOpInspectionPlanVo"/>
-        <where>
-            <if test="areaCode != null and areaCode != ''">and area_code = #{areaCode}</if>
-            <if test="taskCode != null and taskCode != ''">and task_code = #{taskCode}</if>
-            <if test="taskType != null ">and task_type = #{taskType}</if>
-            <if test="executor != null  and executor != ''">and executor = #{executor}</if>
-            <if test="objType != null ">and obj_type = #{objType}</if>
-            <if test="objCode != null and objCode != ''">and obj_code = #{objCode}</if>
-            <if test="taskName != null and taskName != ''">and task_name like concat('%', #{taskName}, '%')</if>
-            <if test="objName != null  and objName != ''">and obj_name like concat('%', #{objName}, '%')</if>
-        </where>
-    </select>
-
-    <select id="selectOpInspectionPlanById" parameterType="Long" resultMap="OpInspectionPlanResult">
-        <include refid="selectOpInspectionPlanVo"/>
-        where id = #{id}
-    </select>
-
-    <insert id="insertOpInspectionPlan" parameterType="com.ruoyi.ems.domain.OpInspectionTask"
-            useGeneratedKeys="true" keyProperty="id">
-        insert into adm_op_inspection_plan
-        <trim prefix="(" suffix=")" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">area_code,</if>
-            <if test="taskCode != null and taskCode != ''">task_code,</if>
-            <if test="taskName != null and taskName != ''">task_name,</if>
-            <if test="taskType != null">task_type,</if>
-            <if test="startTime != null">start_time,</if>
-            <if test="executor != null">executor,</if>
-            <if test="objType != null">obj_type,</if>
-            <if test="objCode != null and objCode != ''">obj_code,</if>
-            <if test="objName != null">obj_name,</if>
-        </trim>
-        <trim prefix="values (" suffix=")" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">#{areaCode},</if>
-            <if test="taskCode != null and taskCode != ''">#{taskCode},</if>
-            <if test="taskName != null and taskName != ''">#{taskName},</if>
-            <if test="taskType != null">#{taskType},</if>
-            <if test="startTime != null">#{startTime},</if>
-            <if test="executor != null">#{executor},</if>
-            <if test="objType != null">#{objType},</if>
-            <if test="objCode != null and objCode != ''">#{objCode},</if>
-            <if test="objName != null">#{objName},</if>
-        </trim>
-    </insert>
-
-    <update id="updateOpInspectionPlan" parameterType="com.ruoyi.ems.domain.OpInspectionTask">
-        update adm_op_inspection_plan
-        <trim prefix="SET" suffixOverrides=",">
-            <if test="areaCode != null and areaCode != ''">area_code = #{areaCode},</if>
-            <if test="taskCode != null and taskCode != ''">task_code = #{taskCode},</if>
-            <if test="taskName != null and taskName != ''">task_name = #{taskName},</if>
-            <if test="taskType != null">task_type = #{taskType},</if>
-            <if test="startTime != null">start_time = #{startTime},</if>
-            <if test="executor != null">executor = #{executor},</if>
-            <if test="objType != null">obj_type = #{objType},</if>
-            <if test="objCode != null and objCode != ''">obj_code = #{objCode},</if>
-            <if test="objName != null">obj_name = #{objName},</if>
-        </trim>
-        where id = #{id}
-    </update>
-
-    <delete id="deleteOpInspectionPlanById" parameterType="Long">
-        delete
-        from adm_op_inspection_plan
-        where id = #{id}
-    </delete>
-
-    <delete id="deleteOpInspectionPlanByIds" parameterType="String">
-        delete from adm_op_inspection_plan where id in
-        <foreach item="id" collection="array" open="(" separator="," close=")">
-            #{id}
-        </foreach>
-    </delete>
-</mapper>

+ 354 - 6
ems/sql/ems_init_data_ctfwq.sql

@@ -2229,12 +2229,360 @@ INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, a
 INSERT INTO adm_op_alarm(area_code, system_code, obj_type, obj_code, obj_name, alarm_date, alarm_time, alarm_code, alarm_msg, alarm_type, alarm_state)VALUES('321283124S3003', 'SYS_CN', 1, 'B-101', '开水泡面间', '2025-05-14', '2025-05-14 20:15:23', '00002', '北区开水间配电告警', 1, 1);
 
 -- 巡检计划
-INSERT INTO `adm_op_inspection_plan` (`area_code`, `task_code`, `task_name`, `task_type`, `start_time`, `executor`, `obj_type`, `obj_code`, `obj_name`) VALUES ('321283124S3001', '250702185440A002', '安全巡检', 1, '2025-07-10 00:00:00', '系统', 2, 'W201', '北区-供电网(常泰高速服务区(北区))');
-INSERT INTO `adm_op_inspection_plan` (`area_code`, `task_code`, `task_name`, `task_type`, `start_time`, `executor`, `obj_type`, `obj_code`, `obj_name`) VALUES ('321283124S3002', '250702185514A003', '安全巡检', 1, '2025-07-11 00:00:00', '系统', 2, 'W202', '南区-供电网(常泰高速服务区(南区))');
-
--- 巡检报告
-INSERT INTO `adm_op_inspection_report` (`task_code`, `result_status`, `result_msg`, `finish_time`, `sub_time`, `submitter`) VALUES ('1001', 0, '<p>设施运行正常</p>', '2025-07-12 00:00:00', '2025-07-02 19:08:11', 'admin');
-INSERT INTO `adm_op_inspection_report` (`task_code`, `result_status`, `result_msg`, `finish_time`, `sub_time`, `submitter`) VALUES ('1002', 1, '<p>线材破损</p>', '2025-07-10 00:00:00', '2025-07-02 19:08:53', 'admin');
+-- =====================================================
+-- 巡检任务测试数据 - 常泰高速服务区能源管理平台
+-- 生成时间: 2026-01-31
+-- =====================================================
+
+-- 清理旧数据(可选,谨慎使用)
+-- DELETE FROM adm_op_inspection_rule WHERE plan_code LIKE 'IP_TEST%';
+-- DELETE FROM adm_op_inspection_plan WHERE plan_code LIKE 'IP_TEST%';
+
+-- =====================================================
+-- 一、自动巡检计划 - 按区域
+-- =====================================================
+
+-- 1. 北区全部设备在线状态巡检(每天上午8点)
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3001',
+             'IP_TEST_AREA_001',
+             '北区设备在线状态日检',
+             2,  -- 自动巡检
+             0,  -- 待执行
+             0,  -- 目标类型:区域
+             '["321283124S3001"]',
+             '常泰高速服务区(北区)',
+             '0 0 8 * * ?',  -- 每天上午8点
+             1,  -- 启用调度
+             '每日上午8点自动检查北区所有设备在线状态、运行状态等关键指标',
+             'admin',
+             NOW()
+         );
+
+-- 北区巡检规则
+INSERT INTO `adm_op_inspection_rule` (`plan_code`, `rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `min_value`, `max_value`, `expect_value`, `rule_order`, `enabled`) VALUES
+-- 通用设备状态检查
+('IP_TEST_AREA_001', 'IR_AREA001_001', '设备在线状态', NULL, 'deviceStatus', '设备状态', 4, NULL, NULL, '1', 1, 1),
+
+-- BA新风设备检查
+('IP_TEST_AREA_001', 'IR_AREA001_002', '新风机运行状态', 'M_Z020_DEV_BA_XF', 'xfStatus', '新风机运行状态', 3, NULL, NULL, NULL, 2, 1),
+('IP_TEST_AREA_001', 'IR_AREA001_003', '新风机故障检查', 'M_Z020_DEV_BA_XF', 'xfFault', '新风机故障', 2, NULL, NULL, '0', 3, 1),
+('IP_TEST_AREA_001', 'IR_AREA001_004', '送风温度范围', 'M_Z020_DEV_BA_XF', 'sfTemp', '送风温度', 1, '15', '35', NULL, 4, 1),
+
+-- BA水箱检查
+('IP_TEST_AREA_001', 'IR_AREA001_005', '水箱液位检查', 'M_Z020_DEV_BA_WT', 'tankLevel', '水箱液位', 1, '20', '95', NULL, 5, 1),
+('IP_TEST_AREA_001', 'IR_AREA001_006', '水箱高液位报警', 'M_Z020_DEV_BA_WT', 'highLevelAlarm', '高液位报警', 2, NULL, NULL, '0', 6, 1),
+('IP_TEST_AREA_001', 'IR_AREA001_007', '水箱低液位报警', 'M_Z020_DEV_BA_WT', 'lowLevelAlarm', '低液位报警', 2, NULL, NULL, '0', 7, 1),
+
+-- 光伏采集器检查
+('IP_TEST_AREA_001', 'IR_AREA001_008', '光伏采集器数据更新', 'M_W2_DEV_PHOTOVOLTAIC_COL', 'lastUpdateTime', '数据最后接收时间', 3, NULL, NULL, NULL, 8, 1),
+
+-- 充电桩主机检查
+('IP_TEST_AREA_001', 'IR_AREA001_009', '充电桩在线状态', 'M_W2_DEV_CHARGING_HOST', 'deviceStatus', '设备状态', 4, NULL, NULL, '1', 9, 1);
+
+
+-- 2. 南区设备在线状态巡检(每天上午8点)
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3002',
+             'IP_TEST_AREA_002',
+             '南区设备在线状态日检',
+             2,  -- 自动巡检
+             0,  -- 待执行
+             0,  -- 目标类型:区域
+             '["321283124S3002"]',
+             '常泰高速服务区(南区)',
+             '0 0 8 * * ?',  -- 每天上午8点
+             1,  -- 启用调度
+             '每日上午8点自动检查南区所有设备在线状态及关键运行参数',
+             'admin',
+             NOW()
+         );
+
+-- 南区巡检规则(与北区类似)
+INSERT INTO `adm_op_inspection_rule` (`plan_code`, `rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `min_value`, `max_value`, `expect_value`, `rule_order`, `enabled`) VALUES
+                                                                                                                                                                                                           ('IP_TEST_AREA_002', 'IR_AREA002_001', '设备在线状态', NULL, 'deviceStatus', '设备状态', 4, NULL, NULL, '1', 1, 1),
+                                                                                                                                                                                                           ('IP_TEST_AREA_002', 'IR_AREA002_002', '新风机运行状态', 'M_Z020_DEV_BA_XF', 'xfStatus', '新风机运行状态', 3, NULL, NULL, NULL, 2, 1),
+                                                                                                                                                                                                           ('IP_TEST_AREA_002', 'IR_AREA002_003', '新风机故障检查', 'M_Z020_DEV_BA_XF', 'xfFault', '新风机故障', 2, NULL, NULL, '0', 3, 1),
+                                                                                                                                                                                                           ('IP_TEST_AREA_002', 'IR_AREA002_004', '水箱液位检查', 'M_Z020_DEV_BA_WT', 'tankLevel', '水箱液位', 1, '20', '95', NULL, 4, 1),
+                                                                                                                                                                                                           ('IP_TEST_AREA_002', 'IR_AREA002_005', '水箱高液位报警', 'M_Z020_DEV_BA_WT', 'highLevelAlarm', '高液位报警', 2, NULL, NULL, '0', 5, 1);
+
+
+-- =====================================================
+-- 二、自动巡检计划 - 按设施(针对设备模型)
+-- =====================================================
+
+-- 3. 北区暖通设施巡检(每天上午9点、下午3点)
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3001',
+             'IP_TEST_FACS_001',
+             '北区暖通设施运行巡检',
+             2,  -- 自动巡检
+             0,  -- 待执行
+             1,  -- 目标类型:设施
+             '["Z-KT-01"]',
+             '北区-暖通设施',
+             '0 0 9,15 * * ?',  -- 每天上午9点、下午3点
+             1,
+             '检查北区暖通设施(新风、空调、水箱、水泵)的运行状态和关键参数',
+             'admin',
+             NOW()
+         );
+
+-- 暖通设施巡检规则
+INSERT INTO `adm_op_inspection_rule` (`plan_code`, `rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `min_value`, `max_value`, `expect_value`, `rule_order`, `enabled`) VALUES
+-- 新风设备 M_Z020_DEV_BA_XF
+('IP_TEST_FACS_001', 'IR_FACS001_001', '新风机运行状态', 'M_Z020_DEV_BA_XF', 'xfStatus', '新风机运行状态', 3, NULL, NULL, NULL, 1, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_002', '新风机故障检测', 'M_Z020_DEV_BA_XF', 'xfFault', '新风机故障', 2, NULL, NULL, '0', 2, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_003', '排风机故障检测', 'M_Z020_DEV_BA_XF', 'pfFault', '排风机故障', 2, NULL, NULL, '0', 3, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_004', '送风温度范围', 'M_Z020_DEV_BA_XF', 'sfTemp', '送风温度', 1, '10', '40', NULL, 4, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_005', '回风温度范围', 'M_Z020_DEV_BA_XF', 'hfTemp', '回风温度', 1, '5', '45', NULL, 5, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_006', '滤网压差报警', 'M_Z020_DEV_BA_XF', 'lwDpAlarm', '滤网压差报警', 2, NULL, NULL, '0', 6, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_007', '防冻开关报警', 'M_Z020_DEV_BA_XF', 'afAlarm', '防冻开关报警', 2, NULL, NULL, '0', 7, 1),
+
+-- 空调设备 M_Z020_DEV_BA_AHU
+('IP_TEST_FACS_001', 'IR_FACS001_008', '空调运行状态', 'M_Z020_DEV_BA_AHU', 'xfStatus', '新风机运行状态', 3, NULL, NULL, NULL, 8, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_009', '空调故障检测', 'M_Z020_DEV_BA_AHU', 'xfFault', '新风机故障', 2, NULL, NULL, '0', 9, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_010', '空调送风温度', 'M_Z020_DEV_BA_AHU', 'sfTemp', '送风温度', 1, '10', '40', NULL, 10, 1),
+
+-- 水箱 M_Z020_DEV_BA_WT
+('IP_TEST_FACS_001', 'IR_FACS001_011', '水箱液位', 'M_Z020_DEV_BA_WT', 'tankLevel', '水箱液位', 1, '15', '90', NULL, 11, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_012', '高液位报警', 'M_Z020_DEV_BA_WT', 'highLevelAlarm', '高液位报警', 2, NULL, NULL, '0', 12, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_013', '低液位报警', 'M_Z020_DEV_BA_WT', 'lowLevelAlarm', '低液位报警', 2, NULL, NULL, '0', 13, 1),
+
+-- 水泵 M_Z020_DEV_BA_WP
+('IP_TEST_FACS_001', 'IR_FACS001_014', '水泵运行状态', 'M_Z020_DEV_BA_WP', 'runningState', '运行状态', 3, NULL, NULL, NULL, 14, 1),
+('IP_TEST_FACS_001', 'IR_FACS001_015', '水泵故障检测', 'M_Z020_DEV_BA_WP', 'faultState', '故障状态', 2, NULL, NULL, '0', 15, 1);
+
+
+-- 4. 北区能耗监测设施巡检(每小时执行)
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3001',
+             'IP_TEST_FACS_002',
+             '北区能耗监测设施巡检',
+             2,  -- 自动巡检
+             0,  -- 待执行
+             1,  -- 目标类型:设施
+             '["NH01"]',
+             '北区-能耗监测',
+             '0 0 * * * ?',  -- 每小时整点执行
+             1,
+             '每小时检查北区能耗监测设备的数据采集状态',
+             'admin',
+             NOW()
+         );
+
+-- 能耗监测设施巡检规则
+INSERT INTO `adm_op_inspection_rule` (`plan_code`, `rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `min_value`, `max_value`, `expect_value`, `rule_order`, `enabled`) VALUES
+-- BA采集网关 M_W4_DEV_BA_GA
+('IP_TEST_FACS_002', 'IR_FACS002_001', '采集器在线状态', 'M_W4_DEV_BA_GA', 'deviceStatus', '设备状态', 4, NULL, NULL, '1', 1, 1),
+
+-- 电能采集测点 M_W4_DEV_BA_METER_E
+('IP_TEST_FACS_002', 'IR_FACS002_002', '电表读数非空', 'M_W4_DEV_BA_METER_E', 'value', '表计读数', 3, NULL, NULL, NULL, 2, 1),
+
+-- 用水采集测点 M_W4_DEV_BA_METER_W
+('IP_TEST_FACS_002', 'IR_FACS002_003', '水表读数非空', 'M_W4_DEV_BA_METER_W', 'value', '表计读数', 3, NULL, NULL, NULL, 3, 1);
+
+
+-- 5. 北区充电桩设施巡检(每30分钟)
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3001',
+             'IP_TEST_FACS_003',
+             '北区充电桩运行状态巡检',
+             2,  -- 自动巡检
+             0,  -- 待执行
+             1,  -- 目标类型:设施
+             '["Charge01"]',
+             '北区-充电桩监测',
+             '0 0,30 * * * ?',  -- 每30分钟
+             1,
+             '实时监控北区充电桩的运行状态和关键参数',
+             'admin',
+             NOW()
+         );
+
+-- 充电桩设施巡检规则
+INSERT INTO `adm_op_inspection_rule` (`plan_code`, `rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `min_value`, `max_value`, `expect_value`, `rule_order`, `enabled`) VALUES
+-- 充电主机 M_W2_DEV_CHARGING_HOST
+('IP_TEST_FACS_003', 'IR_FACS003_001', '充电桩主机在线', 'M_W2_DEV_CHARGING_HOST', 'deviceStatus', '设备状态', 4, NULL, NULL, '1', 1, 1),
+('IP_TEST_FACS_003', 'IR_FACS003_002', '充电枪数量检查', 'M_W2_DEV_CHARGING_HOST', 'gunCount', '充电枪数量', 1, '1', '16', NULL, 2, 1),
+
+-- 充电枪 M_W2_DEV_CHARGING_PILE
+('IP_TEST_FACS_003', 'IR_FACS003_003', '充电枪状态检查', 'M_W2_DEV_CHARGING_PILE', 'status', '状态', 3, NULL, NULL, NULL, 3, 1),
+('IP_TEST_FACS_003', 'IR_FACS003_004', '输出电压范围', 'M_W2_DEV_CHARGING_PILE', 'outputVoltage', '输出电压', 1, '0', '1000', NULL, 4, 1),
+('IP_TEST_FACS_003', 'IR_FACS003_005', '输出电流范围', 'M_W2_DEV_CHARGING_PILE', 'outputCurrent', '输出电流', 1, '0', '500', NULL, 5, 1),
+('IP_TEST_FACS_003', 'IR_FACS003_006', '枪线温度范围', 'M_W2_DEV_CHARGING_PILE', 'gunTemperature', '枪线温度', 1, '-20', '80', NULL, 6, 1);
+
+
+-- 6. 南区照明设施巡检(每天晚上7点)
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3002',
+             'IP_TEST_FACS_004',
+             '南区照明设施夜间巡检',
+             2,  -- 自动巡检
+             0,  -- 待执行
+             1,  -- 目标类型:设施
+             '["Z-ZM-02"]',
+             '南区-照明设施',
+             '0 0 19 * * ?',  -- 每天晚上7点
+             1,
+             '每日傍晚检查南区照明设施的运行状态',
+             'admin',
+             NOW()
+         );
+
+-- 照明设施巡检规则
+INSERT INTO `adm_op_inspection_rule` (`plan_code`, `rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `min_value`, `max_value`, `expect_value`, `rule_order`, `enabled`) VALUES
+-- 照明灯组 M_Z010_DEV_SQUARE_LIGHT
+('IP_TEST_FACS_004', 'IR_FACS004_001', '灯组设备状态', 'M_Z010_DEV_SQUARE_LIGHT', 'deviceStatus', '设备状态', 2, NULL, NULL, '1', 1, 1),
+('IP_TEST_FACS_004', 'IR_FACS004_002', '信号强度检查', 'M_Z010_DEV_SQUARE_LIGHT', 'csq', '信号强度', 1, '5', '31', NULL, 2, 1),
+('IP_TEST_FACS_004', 'IR_FACS004_003', '电压范围检查', 'M_Z010_DEV_SQUARE_LIGHT', 'voltage', '电压', 1, '180', '260', NULL, 3, 1),
+('IP_TEST_FACS_004', 'IR_FACS004_004', '电流范围检查', 'M_Z010_DEV_SQUARE_LIGHT', 'current', '电流', 1, '0', '20', NULL, 4, 1),
+('IP_TEST_FACS_004', 'IR_FACS004_005', '设备温度检查', 'M_Z010_DEV_SQUARE_LIGHT', 'temperature', '设备温度', 1, '-30', '70', NULL, 5, 1),
+
+-- 照明集中器 M_Z010_DEV_SQUARE_CONCENTRATOR
+('IP_TEST_FACS_004', 'IR_FACS004_006', '集中器设备状态', 'M_Z010_DEV_SQUARE_CONCENTRATOR', 'deviceStatus', '设备状态', 2, NULL, NULL, '1', 6, 1),
+('IP_TEST_FACS_004', 'IR_FACS004_007', 'A相电压范围', 'M_Z010_DEV_SQUARE_CONCENTRATOR', 'ua', 'A相电压', 1, '180', '260', NULL, 7, 1),
+('IP_TEST_FACS_004', 'IR_FACS004_008', 'B相电压范围', 'M_Z010_DEV_SQUARE_CONCENTRATOR', 'ub', 'B相电压', 1, '180', '260', NULL, 8, 1),
+('IP_TEST_FACS_004', 'IR_FACS004_009', 'C相电压范围', 'M_Z010_DEV_SQUARE_CONCENTRATOR', 'uc', 'C相电压', 1, '180', '260', NULL, 9, 1);
+
+
+-- 7. 电力监控设施巡检(每2小时)
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3001',
+             'IP_TEST_FACS_005',
+             '电力监控设备巡检',
+             2,  -- 自动巡检
+             0,  -- 待执行
+             1,  -- 目标类型:设施
+             '["W201"]',
+             '北区-供电网',
+             '0 0 */2 * * ?',  -- 每2小时
+             1,
+             '定期检查电力监控设备的运行状态和电力参数',
+             'admin',
+             NOW()
+         );
+
+-- 电力监控设施巡检规则
+INSERT INTO `adm_op_inspection_rule` (`plan_code`, `rule_code`, `rule_name`, `device_model`, `attr_key`, `attr_name`, `check_type`, `min_value`, `max_value`, `expect_value`, `rule_order`, `enabled`) VALUES
+-- 电力保护装置 M_W4_DEV_ELEC_MONITOR_BHZZ
+('IP_TEST_FACS_005', 'IR_FACS005_001', '保护装置在线', 'M_W4_DEV_ELEC_MONITOR_BHZZ', 'deviceStatus', '在线状态', 2, NULL, NULL, '1', 1, 1),
+('IP_TEST_FACS_005', 'IR_FACS005_002', '频率范围检查', 'M_W4_DEV_ELEC_MONITOR_BHZZ', 'Fr', '频率', 1, '49', '51', NULL, 2, 1),
+('IP_TEST_FACS_005', 'IR_FACS005_003', 'A相电流范围', 'M_W4_DEV_ELEC_MONITOR_BHZZ', 'Ia', 'A相电流', 1, '0', '1000', NULL, 3, 1),
+('IP_TEST_FACS_005', 'IR_FACS005_004', 'AB线电压范围', 'M_W4_DEV_ELEC_MONITOR_BHZZ', 'Uab', 'AB线电压', 1, '350', '420', NULL, 4, 1),
+
+-- 多功能电表 M_W4_DEV_ELEC_MONITOR_DB
+('IP_TEST_FACS_005', 'IR_FACS005_005', '电表在线状态', 'M_W4_DEV_ELEC_MONITOR_DB', 'deviceStatus', '在线状态', 2, NULL, NULL, '1', 5, 1),
+('IP_TEST_FACS_005', 'IR_FACS005_006', '总功率因数', 'M_W4_DEV_ELEC_MONITOR_DB', 'Pf', '总功率因数', 1, '0.7', '1', NULL, 6, 1),
+('IP_TEST_FACS_005', 'IR_FACS005_007', '电流不平衡度', 'M_W4_DEV_ELEC_MONITOR_DB', 'CUB', '电流不平衡度', 1, '0', '30', NULL, 7, 1),
+('IP_TEST_FACS_005', 'IR_FACS005_008', '电压不平衡度', 'M_W4_DEV_ELEC_MONITOR_DB', 'VUB', '电压不平衡度', 1, '0', '10', NULL, 8, 1),
+('IP_TEST_FACS_005', 'IR_FACS005_009', '环境温度范围', 'M_W4_DEV_ELEC_MONITOR_DB', 'Temp', '环境温度', 1, '-20', '60', NULL, 9, 1);
+
+
+-- =====================================================
+-- 三、手动巡检计划 - 按设备
+-- =====================================================
+
+-- 8. 北区水箱人工巡检
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `plan_time`, `executor`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3001',
+             'IP_TEST_DEV_001',
+             '北区水箱人工巡检',
+             1,  -- 手动巡检
+             0,  -- 待执行
+             2,  -- 目标类型:设备
+             '["Z020-B-WT-1"]',
+             '北区水箱',
+             NULL,  -- 手动巡检无cron
+             0,     -- 不启用调度
+             DATE_ADD(NOW(), INTERVAL 1 DAY),  -- 计划明天执行
+             '张三',
+             '人工现场检查北区水箱的外观、管道连接、阀门状态、液位显示等',
+             'admin',
+             NOW()
+         );
+
+
+-- 9. 北区充电桩群控箱人工巡检
+INSERT INTO `adm_op_inspection_plan` (
+    `area_code`, `plan_code`, `plan_name`, `plan_type`, `plan_status`,
+    `target_type`, `target_codes`, `target_names`, `cron_expression`,
+    `schedule_enabled`, `plan_time`, `executor`, `description`, `create_by`, `create_time`
+) VALUES (
+             '321283124S3001',
+             'IP_TEST_DEV_002',
+             '充电桩群控箱01人工巡检',
+             1,  -- 手动巡检
+             0,  -- 待执行
+             2,  -- 目标类型:设备
+             '["W2-B-CHARGING-HOST-01", "W2-B-CHARGING-HOST-02"]',
+             '充电桩群控箱01, 充电桩群控箱02',
+             NULL,  -- 手动巡检无cron
+             0,     -- 不启用调度
+             DATE_ADD(NOW(), INTERVAL 2 DAY),  -- 计划后天执行
+             '李四',
+             '人工现场检查充电桩群控箱的外观完整性、接线端子、散热风扇、指示灯状态、充电枪座等',
+             'admin',
+             NOW()
+         );
+
+
+-- =====================================================
+-- 四、查询验证
+-- =====================================================
+
+-- 查看已创建的巡检计划
+-- SELECT plan_code, plan_name, plan_type, target_type, target_names, cron_expression, schedule_enabled
+-- FROM adm_op_inspection_plan
+-- WHERE plan_code LIKE 'IP_TEST%'
+-- ORDER BY plan_code;
+
+-- 查看巡检规则统计
+-- SELECT p.plan_code, p.plan_name, COUNT(r.id) as rule_count
+-- FROM adm_op_inspection_plan p
+-- LEFT JOIN adm_op_inspection_rule r ON p.plan_code = r.plan_code
+-- WHERE p.plan_code LIKE 'IP_TEST%'
+-- GROUP BY p.plan_code, p.plan_name
+-- ORDER BY p.plan_code;
+
+-- 查看所有巡检规则详情
+-- SELECT r.plan_code, r.rule_code, r.rule_name, r.device_model, r.attr_key, r.check_type,
+--        r.min_value, r.max_value, r.expect_value
+-- FROM adm_op_inspection_rule r
+-- WHERE r.plan_code LIKE 'IP_TEST%'
+-- ORDER BY r.plan_code, r.rule_order;
 
 
 -- ============================================================================

+ 103 - 37
ems/sql/ems_server.sql

@@ -1259,44 +1259,110 @@ create table adm_op_alarm  (
 ) engine=innodb auto_increment=1 comment = '能源设施告警表';
 
 
--- ----------------------------
--- 巡检计划表
--- ----------------------------
-drop table if exists adm_op_inspection_plan;
-create table adm_op_inspection_plan (
-  `id`              bigint(20)      not null auto_increment      comment '序号',
-  `area_code`       varchar(32)     not null                     comment '园区代码',
-  `task_code`       varchar(64)     not null                     comment '任务代码',
-  `task_name`       varchar(16)     not null                     comment '任务名称',
-  `task_type`       int             default null                 comment '任务类型 1:自动,2:手动',
-  `start_time`      datetime        default null                 comment '开始时间',
-  `executor`        varchar(32)     default null                 comment '执行人',
-  `obj_type`        int             default null                 comment '0:园区,1:区块,2:设施',
-  `obj_code`        varchar(16)     default null                 comment '对象编码',
-  `obj_name`        varchar(64)     default null                 comment '对象名称',
-  primary key (`id`),
-  unique key ux_op_inspection_plan_code(`task_code`),
-  key ux_op_inspection_plan(`area_code`)
-) engine=innodb auto_increment=1 comment = '巡检计划表';
-
-
--- ----------------------------
--- 巡检报告表
--- ----------------------------
-drop table if exists adm_op_inspection_report;
-create table adm_op_inspection_report (
-  `id`              bigint(20)      not null auto_increment      comment '序号',
-  `task_code`       varchar(64)     not null                     comment '任务代码',
-  `result_status`   int             not null                     comment '结果状态 0:正常  1:异常',
-  `result_msg`      text            default null                 comment '结果描述',
-  `finish_time`     datetime        default null                 comment '完成时间',
-  `sub_time`        datetime        default null                 comment '提交时间',
-  `submitter`       varchar(32)     default null                 comment '提交人',
-  primary key (`id`),
-  key inx_op_inspection_report(`task_code`)
-) engine=innodb auto_increment=1 comment = '巡检报告表';
-
+-- 1. 巡检计划表(支持手动/自动巡检)
+DROP TABLE IF EXISTS adm_op_inspection_plan;
+CREATE TABLE adm_op_inspection_plan (
+    `id`                bigint(20)      NOT NULL AUTO_INCREMENT    COMMENT '序号',
+    `area_code`         varchar(32)     NOT NULL                   COMMENT '归属区域代码',
+    `plan_code`         varchar(64)     NOT NULL                   COMMENT '计划代码',
+    `plan_name`         varchar(64)     NOT NULL                   COMMENT '计划名称',
+    `plan_type`         tinyint         NOT NULL DEFAULT 1         COMMENT '计划类型 1:手动巡检 2:自动巡检',
+    `plan_status`       tinyint         NOT NULL DEFAULT 0         COMMENT '计划状态 0:待执行 1:执行中 2:已完成 3:已取消',
+    `target_type`       tinyint         NOT NULL                   COMMENT '巡检目标类型 0:区域 1:设施 2:设备',
+    `target_codes`      text            DEFAULT NULL               COMMENT '巡检目标代码(JSON数组)',
+    `target_names`      varchar(512)    DEFAULT NULL               COMMENT '巡检目标名称(逗号分隔,用于展示)',
+    `cron_expression`   varchar(64)     DEFAULT NULL               COMMENT 'Cron表达式(自动巡检用)',
+    `schedule_enabled`  tinyint         DEFAULT 0                  COMMENT '是否启用调度 0:禁用 1:启用',
+    `last_exec_time`    datetime        DEFAULT NULL               COMMENT '上次执行时间',
+    `next_exec_time`    datetime        DEFAULT NULL               COMMENT '下次执行时间',
+    `exec_count`        int             DEFAULT 0                  COMMENT '累计执行次数',
+    `plan_time`         datetime        DEFAULT NULL               COMMENT '计划执行时间',
+    `executor`          varchar(32)     DEFAULT NULL               COMMENT '执行人(手动巡检)',
+    `description`       varchar(256)    DEFAULT NULL               COMMENT '计划描述',
+    `create_by`         varchar(64)     DEFAULT NULL               COMMENT '创建者',
+    `create_time`       datetime        DEFAULT CURRENT_TIMESTAMP  COMMENT '创建时间',
+    `update_by`         varchar(64)     DEFAULT NULL               COMMENT '更新者',
+    `update_time`       datetime        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `ux_plan_code` (`plan_code`),
+    KEY `idx_area_code` (`area_code`),
+    KEY `idx_plan_status` (`plan_status`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='巡检计划表';
+
+-- 2. 巡检规则表(自动巡检的检查规则配置)
+DROP TABLE IF EXISTS adm_op_inspection_rule;
+CREATE TABLE adm_op_inspection_rule (
+    `id`                bigint(20)      NOT NULL AUTO_INCREMENT    COMMENT '序号',
+    `plan_code`         varchar(64)     NOT NULL                   COMMENT '计划代码',
+    `rule_code`         varchar(64)     NOT NULL                   COMMENT '规则代码',
+    `rule_name`         varchar(64)     NOT NULL                   COMMENT '规则名称',
+    `device_model`      varchar(64)     DEFAULT NULL               COMMENT '设备模型代码(可选,不填则应用于所有设备)',
+    `attr_key`          varchar(128)    NOT NULL                   COMMENT '属性标识',
+    `attr_name`         varchar(256)    DEFAULT NULL               COMMENT '属性名称',
+    `check_type`        tinyint         NOT NULL DEFAULT 1         COMMENT '检查类型 1:范围检查 2:等值检查 3:非空检查 4:在线状态',
+    `min_value`         varchar(64)     DEFAULT NULL               COMMENT '最小值(范围检查用)',
+    `max_value`         varchar(64)     DEFAULT NULL               COMMENT '最大值(范围检查用)',
+    `expect_value`      varchar(64)     DEFAULT NULL               COMMENT '期望值(等值检查用)',
+    `rule_order`        int             DEFAULT 0                  COMMENT '规则顺序',
+    `enabled`           tinyint         DEFAULT 1                  COMMENT '是否启用 0:禁用 1:启用',
+    `create_time`       datetime        DEFAULT CURRENT_TIMESTAMP  COMMENT '创建时间',
+    `update_time`       datetime        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `ux_rule_code` (`rule_code`),
+    KEY `idx_plan_code` (`plan_code`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='巡检规则表';
+
+-- 3. 巡检报告表
+DROP TABLE IF EXISTS adm_op_inspection_report;
+CREATE TABLE adm_op_inspection_report (
+    `id`                bigint(20)      NOT NULL AUTO_INCREMENT    COMMENT '序号',
+    `report_code`       varchar(64)     NOT NULL                   COMMENT '报告代码',
+    `plan_code`         varchar(64)     NOT NULL                   COMMENT '计划代码',
+    `plan_name`         varchar(64)     DEFAULT NULL               COMMENT '计划名称',
+    `plan_type`         tinyint         DEFAULT NULL               COMMENT '计划类型 1:手动巡检 2:自动巡检',
+    `area_code`         varchar(32)     DEFAULT NULL               COMMENT '归属区域代码',
+    `area_name`         varchar(64)     DEFAULT NULL               COMMENT '归属区域名称',
+    `target_type`       tinyint         DEFAULT NULL               COMMENT '巡检目标类型',
+    `execute_time`      datetime        DEFAULT NULL               COMMENT '执行时间',
+    `finish_time`       datetime        DEFAULT NULL               COMMENT '完成时间',
+    `executor`          varchar(32)     DEFAULT NULL               COMMENT '执行人',
+    `result_status`     tinyint         NOT NULL DEFAULT 0         COMMENT '结果状态 0:正常 1:异常 2:部分异常',
+    `total_count`       int             DEFAULT 0                  COMMENT '检查总数',
+    `normal_count`      int             DEFAULT 0                  COMMENT '正常数量',
+    `abnormal_count`    int             DEFAULT 0                  COMMENT '异常数量',
+    `result_summary`    text            DEFAULT NULL               COMMENT '结果摘要(JSON)',
+    `manual_remark`     text            DEFAULT NULL               COMMENT '手动备注(富文本,手动巡检用)',
+    `create_by`         varchar(64)     DEFAULT NULL               COMMENT '创建人',
+    `update_by`         varchar(64)     DEFAULT NULL               COMMENT '修改人',
+    `create_time`       datetime        DEFAULT CURRENT_TIMESTAMP  COMMENT '创建时间',
+    `update_time`       datetime        DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `ux_report_code` (`report_code`),
+    KEY `idx_plan_code` (`plan_code`),
+    KEY `idx_execute_time` (`execute_time`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='巡检报告表';
+
+-- 4. 巡检报告明细表(记录每个设备的检查结果)
+DROP TABLE IF EXISTS adm_op_inspection_report_detail;
+CREATE TABLE adm_op_inspection_report_detail (
+    `id`                bigint(20)      NOT NULL AUTO_INCREMENT    COMMENT '序号',
+    `report_code`       varchar(64)     NOT NULL                   COMMENT '报告代码',
+    `device_code`       varchar(64)     NOT NULL                   COMMENT '设备代码',
+    `device_name`       varchar(64)     DEFAULT NULL               COMMENT '设备名称',
+    `device_model`      varchar(64)     DEFAULT NULL               COMMENT '设备模型',
+    `device_model_name` varchar(128)    DEFAULT NULL               COMMENT '设备模型名称',
+    `location`          varchar(200)    DEFAULT NULL               COMMENT '设备位置',
+    `area_path`         varchar(512)    DEFAULT NULL               COMMENT '区域路径',
+    `result_status`     tinyint         NOT NULL DEFAULT 0         COMMENT '结果状态 0:正常 1:异常',
+    `check_items`       text            DEFAULT NULL               COMMENT '检查项结果(JSON数组)',
+    `check_time`        datetime        DEFAULT NULL               COMMENT '检查时间',
+    `create_time`       datetime        DEFAULT CURRENT_TIMESTAMP  COMMENT '创建时间',
+    PRIMARY KEY (`id`),
+    KEY `idx_report_code` (`report_code`),
+    KEY `idx_device_code` (`device_code`)
+) ENGINE=InnoDB AUTO_INCREMENT=1 COMMENT='巡检报告明细表';
 
+                                                                                                                                                                                                          ('IP202501300002', 'IR004', 'CO2浓度检查', 'M_Z020_DEV_BA_XF', 'ppm', 'CO2浓度', 1, '0', '1000', NULL, 4, 1);
 -- ----------------------------
 -- 设备台账表
 -- ----------------------------

+ 2 - 2
ems/sql/ems_sys_data.sql

@@ -59,8 +59,8 @@ insert into sys_menu values ('144',  '抄表管理',       '4',    '4',  'device
 
 INSERT INTO sys_menu VALUES ('151',  '告警策略',       '5',    '1',  'warn-strategy',       'alarm/index',            '', 1, 0, 'C', '0', '0',    'warn:strategy', 'warnstrategy', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:01:10', '告警策略');
 INSERT INTO sys_menu VALUES ('152',  '告警列表',       '5',    '2',  'warn-list',           'alarm/alarm-info/index', '', 1, 0, 'C', '0', '0',    'warn:list', 'warnmsg', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:01:36', '告警策略');
-INSERT INTO sys_menu VALUES ('153',  '巡检计划',       '5',    '3',  'oper-plan',           'task/index',             '', 1, 0, 'C', '0', '0',    'oper-mgr:task', 'task', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:02:38', '巡检任务');
-insert into sys_menu values ('154',  '巡检报告',       '5',    '4',  'oper-report',         'task/report/index',      '', 1, 0, 'C', '0', '0',    'oper-mgr:report',        'note',           'admin', sysdate(), '', null, '巡检报告');
+INSERT INTO sys_menu VALUES ('153',  '巡检计划',       '5',    '3',  'oper-plan',           'inspection/plan/index',             '', 1, 0, 'C', '0', '0',    'oper-mgr:task', 'task', 'admin', '2024-08-29 15:40:27', 'admin', '2024-08-29 16:02:38', '巡检任务');
+insert into sys_menu values ('154',  '巡检报告',       '5',    '4',  'oper-report',         'inspection/report/index',      '', 1, 0, 'C', '0', '0',    'oper-mgr:report',        'note',           'admin', sysdate(), '', null, '巡检报告');
 insert into sys_menu values ('155',  '系统对接',       '5',    '5',  'adapter',          null, '', 1, 0, 'M', '0', '0', '',  'client',      'admin', sysdate(), '', null, '系统对接');