From b871e526e794c09708de431899ef6de75886bbc4 Mon Sep 17 00:00:00 2001
From: gongchunyi <deslre0381@gmail.com>
Date: 星期二, 20 十月 2026 15:22:32 +0800
Subject: [PATCH] feat: 设备巡检优化,巡检项目拆分

---
 src/main/java/com/ruoyi/inspectiontask/pojo/InspectionItem.java                    |   48 +++
 src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java |  101 ++++-
 src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java                    |    8 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java     |  211 +++++++++++-
 src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskItemMapper.java        |    7 
 src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskItemMapper.java            |    7 
 src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java                        |   22 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java             |   80 +++-
 src/main/java/com/ruoyi/inspectiontask/mapper/InspectionItemMapper.java            |    7 
 doc/20261016_设备巡检项目完整脚本.sql                                                        |   71 ++++
 src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTaskItem.java                |   48 +++
 src/main/java/com/ruoyi/inspectiontask/service/InspectionItemService.java          |   19 +
 src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionItemServiceImpl.java |  177 +++++++++++
 src/main/java/com/ruoyi/inspectiontask/controller/InspectionItemController.java    |   70 ++++
 src/main/java/com/ruoyi/inspectiontask/pojo/TimingTaskItem.java                    |   42 ++
 15 files changed, 846 insertions(+), 72 deletions(-)

diff --git "a/doc/20261016_\350\256\276\345\244\207\345\267\241\346\243\200\351\241\271\347\233\256\345\256\214\346\225\264\350\204\232\346\234\254.sql" "b/doc/20261016_\350\256\276\345\244\207\345\267\241\346\243\200\351\241\271\347\233\256\345\256\214\346\225\264\350\204\232\346\234\254.sql"
new file mode 100644
index 0000000..f3dc5d9
--- /dev/null
+++ "b/doc/20261016_\350\256\276\345\244\207\345\267\241\346\243\200\351\241\271\347\233\256\345\256\214\346\225\264\350\204\232\346\234\254.sql"
@@ -0,0 +1,71 @@
+ALTER TABLE `timing_task`
+DROP
+COLUMN `inspection_project`;
+
+ALTER TABLE `inspection_task`
+DROP
+COLUMN `inspection_project`;
+
+CREATE TABLE `inspection_item`
+(
+    `id`               BIGINT        NOT NULL AUTO_INCREMENT COMMENT '宸℃鍐呭涓婚敭',
+    `item_project`     VARCHAR(200)  NOT NULL COMMENT '宸℃椤圭洰',
+    `item_content`     VARCHAR(2000) NOT NULL COMMENT '宸℃鍐呭',
+    `device_ledger_id` BIGINT                 DEFAULT NULL COMMENT '鎵�灞炶澶囷紝NULL琛ㄧず閫氱敤鍐呭',
+    `frequency_type`   VARCHAR(20)   NOT NULL COMMENT '棰戠巼锛欴AILY/WEEKLY/MONTHLY/QUARTERLY/ALL',
+    `sort_num`         INT           NOT NULL DEFAULT 0 COMMENT '鎺掑簭鍙�',
+    `is_active`        TINYINT       NOT NULL DEFAULT 1 COMMENT '鍚敤鐘舵�侊細0鍋滅敤锛�1鍚敤',
+    `remarks`          VARCHAR(500)           DEFAULT NULL COMMENT '澶囨敞',
+    `deleted`          TINYINT       NOT NULL DEFAULT 0 COMMENT '杞垹闄わ細0鏈垹闄わ紝1宸插垹闄�',
+    `create_user`      INT                    DEFAULT NULL COMMENT '鍒涘缓浜�',
+    `update_user`      INT                    DEFAULT NULL COMMENT '淇敼浜�',
+    `create_time`      DATETIME               DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    `update_time`      DATETIME               DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+    `tenant_id`        BIGINT                 DEFAULT NULL COMMENT '绉熸埛ID',
+    `dept_id`          BIGINT                 DEFAULT NULL COMMENT '閮ㄩ棬ID',
+    PRIMARY KEY (`id`),
+    KEY                `idx_inspection_item_device_frequency` (`device_ledger_id`, `frequency_type`, `deleted`, `is_active`),
+    KEY                `idx_inspection_item_project` (`item_project`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='璁惧宸℃鍐呭搴�';
+
+CREATE TABLE `timing_task_item`
+(
+    `id`                 BIGINT        NOT NULL AUTO_INCREMENT COMMENT '浠诲姟鍐呭鍏宠仈涓婚敭',
+    `timing_task_id`     BIGINT        NOT NULL COMMENT '瀹氭椂宸℃浠诲姟ID',
+    `inspection_item_id` BIGINT                 DEFAULT NULL COMMENT '宸℃鍐呭ID锛涗负绌烘椂琛ㄧず鏈换鍔¤嚜瀹氫箟鍐呭',
+    `item_project`       VARCHAR(200)  NOT NULL COMMENT '宸℃椤圭洰蹇収',
+    `item_content`       VARCHAR(2000) NOT NULL COMMENT '宸℃鍐呭蹇収',
+    `sort_num`           INT           NOT NULL DEFAULT 0 COMMENT '浠诲姟鍐呮帓搴忓彿',
+    `create_user`        INT                    DEFAULT NULL COMMENT '鍒涘缓浜�',
+    `update_user`        INT                    DEFAULT NULL COMMENT '淇敼浜�',
+    `create_time`        DATETIME               DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    `update_time`        DATETIME               DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+    `tenant_id`          BIGINT                 DEFAULT NULL COMMENT '绉熸埛ID',
+    `dept_id`            BIGINT                 DEFAULT NULL COMMENT '閮ㄩ棬ID',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_timing_task_inspection_item` (`timing_task_id`, `inspection_item_id`),
+    KEY                  `idx_timing_task_item_content` (`inspection_item_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='瀹氭椂宸℃浠诲姟鍐呭鍏宠仈';
+
+CREATE TABLE `inspection_task_item`
+(
+    `id`                 BIGINT        NOT NULL AUTO_INCREMENT COMMENT '宸℃璁板綍鍐呭涓婚敭',
+    `inspection_task_id` BIGINT        NOT NULL COMMENT '宸℃璁板綍ID',
+    `inspection_item_id` BIGINT                 DEFAULT NULL COMMENT '鏉ユ簮宸℃鍐呭ID',
+    `item_project`       VARCHAR(200)  NOT NULL COMMENT '宸℃椤圭洰蹇収',
+    `item_content`       VARCHAR(2000) NOT NULL COMMENT '宸℃鍐呭蹇収',
+    `item_status`        TINYINT       NOT NULL DEFAULT 0 COMMENT '瀹屾垚鐘舵�侊細0鏈畬鎴愶紝1宸插畬鎴�',
+    `remarks`            VARCHAR(500)           DEFAULT NULL COMMENT '宸℃澶囨敞',
+    `sort_num`           INT           NOT NULL DEFAULT 0 COMMENT '璁板綍鍐呮帓搴忓彿',
+    `completed_by`       BIGINT                 DEFAULT NULL COMMENT '瀹屾垚宸℃鐨勭敤鎴稩D',
+    `completed_time`     DATETIME               DEFAULT NULL COMMENT '瀹屾垚鏃堕棿',
+    `create_user`        INT                    DEFAULT NULL COMMENT '鍒涘缓浜�',
+    `update_user`        INT                    DEFAULT NULL COMMENT '淇敼浜�',
+    `create_time`        DATETIME               DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    `update_time`        DATETIME               DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '淇敼鏃堕棿',
+    `tenant_id`          BIGINT                 DEFAULT NULL COMMENT '绉熸埛ID',
+    `dept_id`            BIGINT                 DEFAULT NULL COMMENT '閮ㄩ棬ID',
+    PRIMARY KEY (`id`),
+    KEY                  `idx_inspection_record_item` (`inspection_task_id`, `sort_num`),
+    KEY                  `idx_inspection_record_item_source` (`inspection_item_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='宸℃璁板綍鍐呭鍙婂畬鎴愭儏鍐�';
diff --git a/src/main/java/com/ruoyi/inspectiontask/controller/InspectionItemController.java b/src/main/java/com/ruoyi/inspectiontask/controller/InspectionItemController.java
new file mode 100644
index 0000000..43c3e43
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/controller/InspectionItemController.java
@@ -0,0 +1,70 @@
+package com.ruoyi.inspectiontask.controller;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.inspectiontask.pojo.InspectionItem;
+import com.ruoyi.inspectiontask.service.InspectionItemService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+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.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@Tag(name = "璁惧宸℃鍐呭")
+@RestController
+@RequestMapping("/inspectionItem")
+@RequiredArgsConstructor
+public class InspectionItemController {
+
+    private final InspectionItemService inspectionItemService;
+
+    @GetMapping("/list")
+    @Operation(summary = "宸℃鍐呭鍒楄〃")
+    public R<List<InspectionItem>> list(
+            InspectionItem query,
+            @RequestParam(defaultValue = "false") boolean includeCommon
+    ) {
+        return R.ok(inspectionItemService.list(query, includeCommon));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鍒嗛〉鏌ヨ宸℃鍐呭")
+    public R<Page<InspectionItem>> page(
+            @RequestParam(defaultValue = "1") long pageNum,
+            @RequestParam(defaultValue = "20") long pageSize,
+            @RequestParam(defaultValue = "false") boolean includeCommon,
+            InspectionItem query
+    ) {
+        return R.ok(inspectionItemService.page(pageNum, pageSize, includeCommon, query));
+    }
+
+    @PostMapping
+    @Operation(summary = "鏂板宸℃鍐呭")
+    public R<Void> create(@RequestBody InspectionItem item) {
+        inspectionItemService.create(item);
+        return R.ok();
+    }
+
+    @PutMapping
+    @Operation(summary = "淇敼宸℃鍐呭")
+    public R<Void> update(@RequestBody InspectionItem item) {
+        inspectionItemService.update(item);
+        return R.ok();
+    }
+
+    @DeleteMapping("/{id}")
+    @Operation(summary = "鍒犻櫎宸℃鍐呭")
+    public R<Void> delete(@PathVariable Long id) {
+        inspectionItemService.delete(id);
+        return R.ok();
+    }
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionItemMapper.java b/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionItemMapper.java
new file mode 100644
index 0000000..8e2c9f7
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionItemMapper.java
@@ -0,0 +1,7 @@
+package com.ruoyi.inspectiontask.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.inspectiontask.pojo.InspectionItem;
+
+public interface InspectionItemMapper extends BaseMapper<InspectionItem> {
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskItemMapper.java b/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskItemMapper.java
new file mode 100644
index 0000000..a563af9
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskItemMapper.java
@@ -0,0 +1,7 @@
+package com.ruoyi.inspectiontask.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.inspectiontask.pojo.InspectionTaskItem;
+
+public interface InspectionTaskItemMapper extends BaseMapper<InspectionTaskItem> {
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskItemMapper.java b/src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskItemMapper.java
new file mode 100644
index 0000000..d649a19
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskItemMapper.java
@@ -0,0 +1,7 @@
+package com.ruoyi.inspectiontask.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.inspectiontask.pojo.TimingTaskItem;
+
+public interface TimingTaskItemMapper extends BaseMapper<TimingTaskItem> {
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionItem.java b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionItem.java
new file mode 100644
index 0000000..367425b
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionItem.java
@@ -0,0 +1,48 @@
+package com.ruoyi.inspectiontask.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("inspection_item")
+public class InspectionItem {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private String itemProject;
+    private String itemContent;
+    private Long deviceLedgerId;
+    private String frequencyType;
+    private Integer sortNum;
+    private Integer isActive;
+    private String remarks;
+    private Integer deleted;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+
+    @TableField(exist = false)
+    private String itemKeyword;
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
index 4e7efad..763b9a4 100644
--- a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
@@ -8,6 +8,7 @@
 import org.springframework.format.annotation.DateTimeFormat;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * @author :yys
@@ -30,13 +31,12 @@
     @Excel(name = "宸℃浠诲姟鍚嶇О")
     private String taskName;
 
-    @Schema(description = "宸℃椤圭洰")
-    @Excel(name = "宸℃椤圭洰")
-    private String inspectionProject;
-
     @Schema(description = "璁惧id")
     private Integer taskId;
 
+    @TableField(exist = false)
+    private List<InspectionTaskItem> inspectionItems;
+
     @Schema(description = "宸℃浜篒D")
     private String inspectorId;
 
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTaskItem.java b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTaskItem.java
new file mode 100644
index 0000000..05e2a32
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTaskItem.java
@@ -0,0 +1,48 @@
+package com.ruoyi.inspectiontask.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("inspection_task_item")
+public class InspectionTaskItem {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long inspectionTaskId;
+    private Long inspectionItemId;
+
+    private String itemProject;
+    private String itemContent;
+    private Integer itemStatus;
+    private String remarks;
+    private Integer sortNum;
+
+    private Long completedBy;
+    private LocalDateTime completedTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
index 43a3edc..6c0f312 100644
--- a/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
@@ -11,6 +11,7 @@
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.util.List;
 
 /**
  * @author :yys
@@ -33,12 +34,17 @@
     @Excel(name = "宸℃浠诲姟鍚嶇О")
     private String taskName;
 
-    @Schema(description = "宸℃椤圭洰")
-    @Excel(name = "宸℃椤圭洰")
-    private String inspectionProject;
-
     @Schema(description = "璁惧id")
     private Integer taskId;
+
+    @TableField(exist = false)
+    private List<Long> inspectionItemIds;
+
+    @TableField(exist = false)
+    private List<TimingTaskItem> inspectionItems;
+
+    @TableField(exist = false)
+    private List<TimingTaskItem> customItems;
 
     @Schema(description = "宸℃浜�")
     @Excel(name = "鎵ц宸℃浜�")
@@ -93,17 +99,17 @@
     private String dateStr;
 
     @Schema(description = "鍒涘缓璇ヨ褰曠殑鐢ㄦ埛")
-    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    @TableField(fill = FieldFill.INSERT)
     private Integer createUser;
 
     @Schema(description = "璁板綍鍒涘缓鏃堕棿")
-    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    @TableField(fill = FieldFill.INSERT)
 //    @JsonFormat(pattern = "yyyy-MM-dd")
 //    @DateTimeFormat(pattern = "yyyy-MM-dd")
     private LocalDateTime createTime;
 
     @Schema(description = "鏈�鍚庝慨鏀硅璁板綍鐨勭敤鎴�")
-    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    @TableField(fill = FieldFill.INSERT_UPDATE)
     private Integer updateUser;
 
     @Schema(description = "璁板綍鏈�鍚庢洿鏂版椂闂�")
@@ -111,7 +117,7 @@
     private LocalDateTime updateTime;
 
     @Schema(description = "绉熸埛ID")
-    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    @TableField(fill = FieldFill.INSERT)
     private Long tenantId;
 
 
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTaskItem.java b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTaskItem.java
new file mode 100644
index 0000000..51094d5
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTaskItem.java
@@ -0,0 +1,42 @@
+package com.ruoyi.inspectiontask.pojo;
+
+import com.baomidou.mybatisplus.annotation.FieldFill;
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("timing_task_item")
+public class TimingTaskItem {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long timingTaskId;
+    private Long inspectionItemId;
+    private String itemProject;
+    private String itemContent;
+    private Integer sortNum;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+    @TableField(fill = FieldFill.INSERT)
+    private Long deptId;
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/InspectionItemService.java b/src/main/java/com/ruoyi/inspectiontask/service/InspectionItemService.java
new file mode 100644
index 0000000..475dfe5
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/InspectionItemService.java
@@ -0,0 +1,19 @@
+package com.ruoyi.inspectiontask.service;
+
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.inspectiontask.pojo.InspectionItem;
+
+import java.util.List;
+
+public interface InspectionItemService {
+
+    List<InspectionItem> list(InspectionItem query, boolean includeCommon);
+
+    Page<InspectionItem> page(long pageNum, long pageSize, boolean includeCommon, InspectionItem query);
+
+    void create(InspectionItem item);
+
+    void update(InspectionItem item);
+
+    void delete(Long id);
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionItemServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionItemServiceImpl.java
new file mode 100644
index 0000000..04002c4
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionItemServiceImpl.java
@@ -0,0 +1,177 @@
+package com.ruoyi.inspectiontask.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.common.exception.ServiceException;
+import com.ruoyi.inspectiontask.mapper.InspectionItemMapper;
+import com.ruoyi.inspectiontask.pojo.InspectionItem;
+import com.ruoyi.inspectiontask.service.InspectionItemService;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+@Service
+@RequiredArgsConstructor
+public class InspectionItemServiceImpl implements InspectionItemService {
+
+    private static final List<String> VALID_FREQUENCIES = List.of(
+            "DAILY", "WEEKLY", "MONTHLY", "QUARTERLY", "ALL"
+    );
+
+    private final InspectionItemMapper inspectionItemMapper;
+
+    @Override
+    public List<InspectionItem> list(InspectionItem query, boolean includeCommon) {
+        return inspectionItemMapper.selectList(buildQuery(query, includeCommon)
+                .orderByAsc(InspectionItem::getSortNum)
+                .orderByAsc(InspectionItem::getId));
+    }
+
+    @Override
+    public Page<InspectionItem> page(long pageNum, long pageSize, boolean includeCommon, InspectionItem query) {
+        long safePageNum = Math.max(pageNum, 1);
+        long safePageSize = Math.min(Math.max(pageSize, 1), 100);
+        return inspectionItemMapper.selectPage(new Page<>(safePageNum, safePageSize),
+                buildQuery(query, includeCommon)
+                        .orderByAsc(InspectionItem::getSortNum)
+                        .orderByAsc(InspectionItem::getId));
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void create(InspectionItem item) {
+        validate(item);
+        normalize(item);
+        item.setDeleted(0);
+        if (existsDuplicate(item, null)) {
+            throw new ServiceException("璇ヨ澶囧拰棰戠巼涓嬪凡瀛樺湪鍚屽悕宸℃鍐呭");
+        }
+        if (inspectionItemMapper.insert(item) <= 0) {
+            throw new ServiceException("鏂板宸℃鍐呭澶辫触");
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void update(InspectionItem item) {
+        if (item == null || item.getId() == null) {
+            throw new ServiceException("宸℃鍐呭缂栧彿涓嶈兘涓虹┖");
+        }
+        validate(item);
+        InspectionItem existing = inspectionItemMapper.selectById(item.getId());
+        if (existing == null || Integer.valueOf(1).equals(existing.getDeleted())) {
+            throw new ServiceException("宸℃鍐呭涓嶅瓨鍦�");
+        }
+        normalize(item);
+        if (existsDuplicate(item, item.getId())) {
+            throw new ServiceException("璇ヨ澶囧拰棰戠巼涓嬪凡瀛樺湪鍚屽悕宸℃鍐呭");
+        }
+
+        existing.setItemProject(item.getItemProject());
+        existing.setItemContent(item.getItemContent());
+        existing.setDeviceLedgerId(item.getDeviceLedgerId());
+        existing.setFrequencyType(item.getFrequencyType());
+        existing.setSortNum(item.getSortNum());
+        existing.setIsActive(item.getIsActive());
+        existing.setRemarks(item.getRemarks());
+        if (inspectionItemMapper.updateById(existing) <= 0) {
+            throw new ServiceException("淇敼宸℃鍐呭澶辫触");
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void delete(Long id) {
+        if (id == null) {
+            throw new ServiceException("宸℃鍐呭缂栧彿涓嶈兘涓虹┖");
+        }
+        InspectionItem item = inspectionItemMapper.selectById(id);
+        if (item == null || Integer.valueOf(1).equals(item.getDeleted())) {
+            throw new ServiceException("宸℃鍐呭涓嶅瓨鍦�");
+        }
+        item.setDeleted(1);
+        item.setIsActive(0);
+        if (inspectionItemMapper.updateById(item) <= 0) {
+            throw new ServiceException("鍒犻櫎宸℃鍐呭澶辫触");
+        }
+    }
+
+    private LambdaQueryWrapper<InspectionItem> buildQuery(InspectionItem query, boolean includeCommon) {
+        InspectionItem filter = query == null ? new InspectionItem() : query;
+        LambdaQueryWrapper<InspectionItem> wrapper = new LambdaQueryWrapper<InspectionItem>()
+                .eq(InspectionItem::getDeleted, 0);
+        if (StringUtils.isNotBlank(filter.getItemProject())) {
+            wrapper.like(InspectionItem::getItemProject, filter.getItemProject().trim());
+        }
+        if (StringUtils.isNotBlank(filter.getItemContent())) {
+            wrapper.like(InspectionItem::getItemContent, filter.getItemContent().trim());
+        }
+        if (StringUtils.isNotBlank(filter.getItemKeyword())) {
+            String keyword = filter.getItemKeyword().trim();
+            wrapper.and(condition -> condition
+                    .like(InspectionItem::getItemProject, keyword)
+                    .or()
+                    .like(InspectionItem::getItemContent, keyword));
+        }
+        if (filter.getIsActive() != null) {
+            wrapper.eq(InspectionItem::getIsActive, filter.getIsActive());
+        }
+        if (filter.getDeviceLedgerId() != null) {
+            if (includeCommon) {
+                wrapper.and(condition -> condition
+                        .eq(InspectionItem::getDeviceLedgerId, filter.getDeviceLedgerId())
+                        .or()
+                        .isNull(InspectionItem::getDeviceLedgerId));
+            } else {
+                wrapper.eq(InspectionItem::getDeviceLedgerId, filter.getDeviceLedgerId());
+            }
+        }
+        if (StringUtils.isNotBlank(filter.getFrequencyType())) {
+            if (includeCommon) {
+                wrapper.and(condition -> condition
+                        .eq(InspectionItem::getFrequencyType, filter.getFrequencyType())
+                        .or()
+                        .eq(InspectionItem::getFrequencyType, "ALL"));
+            } else {
+                wrapper.eq(InspectionItem::getFrequencyType, filter.getFrequencyType());
+            }
+        }
+        return wrapper;
+    }
+
+    private void validate(InspectionItem item) {
+        if (item == null || StringUtils.isBlank(item.getItemProject())
+                || StringUtils.isBlank(item.getItemContent())
+                || StringUtils.isBlank(item.getFrequencyType())
+                || !VALID_FREQUENCIES.contains(item.getFrequencyType())) {
+            throw new ServiceException("宸℃椤圭洰銆佸贰妫�鍐呭鍜岄鐜囦笉鑳戒负绌烘垨鏃犳晥");
+        }
+    }
+
+    private void normalize(InspectionItem item) {
+        item.setItemProject(item.getItemProject().trim());
+        item.setItemContent(item.getItemContent().trim());
+        item.setSortNum(item.getSortNum() == null ? 0 : item.getSortNum());
+        item.setIsActive(item.getIsActive() == null ? 1 : item.getIsActive());
+    }
+
+    private boolean existsDuplicate(InspectionItem item, Long excludedId) {
+        LambdaQueryWrapper<InspectionItem> wrapper = new LambdaQueryWrapper<InspectionItem>()
+                .eq(InspectionItem::getDeleted, 0)
+                .eq(InspectionItem::getItemProject, item.getItemProject())
+                .eq(InspectionItem::getItemContent, item.getItemContent())
+                .eq(InspectionItem::getFrequencyType, item.getFrequencyType());
+        if (item.getDeviceLedgerId() == null) {
+            wrapper.isNull(InspectionItem::getDeviceLedgerId);
+        } else {
+            wrapper.eq(InspectionItem::getDeviceLedgerId, item.getDeviceLedgerId());
+        }
+        if (excludedId != null) {
+            wrapper.ne(InspectionItem::getId, excludedId);
+        }
+        return inspectionItemMapper.selectCount(wrapper) > 0;
+    }
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
index 5826fa7..03bcbc2 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -17,8 +17,10 @@
 import com.ruoyi.device.pojo.DeviceLedger;
 import com.ruoyi.device.pojo.DeviceRepair;
 import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
+import com.ruoyi.inspectiontask.mapper.InspectionTaskItemMapper;
 import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
 import com.ruoyi.inspectiontask.pojo.InspectionTask;
+import com.ruoyi.inspectiontask.pojo.InspectionTaskItem;
 import com.ruoyi.inspectiontask.service.InspectionTaskService;
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.mapper.SysUserMapper;
@@ -27,9 +29,18 @@
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
-import java.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 
@@ -40,10 +51,12 @@
 @Service
 @Slf4j
 @RequiredArgsConstructor
-public class InspectionTaskServiceImpl extends ServiceImpl<InspectionTaskMapper, InspectionTask> implements InspectionTaskService {
-
+public class InspectionTaskServiceImpl
+        extends ServiceImpl<InspectionTaskMapper, InspectionTask>
+        implements InspectionTaskService {
 
     private final InspectionTaskMapper inspectionTaskMapper;
+    private final InspectionTaskItemMapper inspectionTaskItemMapper;
 
     private final SysUserMapper sysUserMapper;
 
@@ -64,17 +77,22 @@
         if (StringUtils.isNotBlank(inspectionTaskDto.getTaskName())) {
             queryWrapper.like(InspectionTask::getTaskName, inspectionTaskDto.getTaskName());
         }
-        if (StringUtils.isNotBlank(inspectionTaskDto.getInspectionProject())) {
-            queryWrapper.like(InspectionTask::getInspectionProject, inspectionTaskDto.getInspectionProject());
-        }
         IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
 
         //  鏃犳暟鎹彁鍓嶈繑鍥�
         if (CollectionUtils.isEmpty(entityPage.getRecords())) {
             return new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
         }
+        List<Long> inspectionTaskIds = entityPage.getRecords().stream().map(InspectionTask::getId).toList();
+        Map<Long, List<InspectionTaskItem>> recordItemsByTask = inspectionTaskItemMapper.selectList(
+                        new LambdaQueryWrapper<InspectionTaskItem>()
+                                .in(InspectionTaskItem::getInspectionTaskId, inspectionTaskIds)
+                                .orderByAsc(InspectionTaskItem::getSortNum))
+                .stream().collect(Collectors.groupingBy(InspectionTaskItem::getInspectionTaskId));
         //鐧昏浜篿ds
-        List<Long> registrantIds = entityPage.getRecords().stream().map(InspectionTask::getRegistrantId).collect(Collectors.toList());
+        List<Long> registrantIds = entityPage.getRecords().stream()
+                .map(InspectionTask::getRegistrantId)
+                .collect(Collectors.toList());
         // 鎵归噺鏌ヨ鐧昏浜�
         Map<Long, SysUser> sysUserMap;
         if (!registrantIds.isEmpty()) {
@@ -101,15 +119,15 @@
         // 浣跨敤SQL鎵归噺鏌ヨ鐢ㄦ埛淇℃伅
         Map<Long, String> userIdToNameMap = allUserIds.isEmpty()
                 ? Collections.emptyMap()
-                : sysUserMapper.selectUsersByIds(new ArrayList<>(allUserIds))
-                .stream()
-                .collect(Collectors.toMap(
-                        SysUser::getUserId,
-                        SysUser::getNickName,
-                        (existing, replacement) -> existing));
+                : sysUserMapper.selectUsersByIds(new ArrayList<>(allUserIds)).stream()
+                        .collect(Collectors.toMap(
+                                SysUser::getUserId,
+                                SysUser::getNickName,
+                                (existing, replacement) -> existing));
         List<InspectionTaskDto> dtoList = entityPage.getRecords().stream().map(inspectionTask -> {
             InspectionTaskDto dto = new InspectionTaskDto();
             BeanUtils.copyProperties(inspectionTask, dto);  // 澶嶅埗涓诲璞″睘鎬�
+            dto.setInspectionItems(recordItemsByTask.getOrDefault(inspectionTask.getId(), Collections.emptyList()));
 
             // 璁剧疆鐧昏浜�
             SysUser sysUser = sysUserMap.get(inspectionTask.getRegistrantId());
@@ -134,10 +152,12 @@
 
             dto.setDateStr(inspectionTask.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
             // 鍒濆鍖栦笁涓檮浠跺垪琛�
-            dto.setCommonFileListVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
-            dto.setCommonFileListAfterVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.AFTER_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
-            dto.setCommonFileListBeforeVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(ApplicationTypeEnum.BEFORE_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
-
+            dto.setCommonFileListVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(
+                    ApplicationTypeEnum.FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
+            dto.setCommonFileListAfterVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(
+                    ApplicationTypeEnum.AFTER_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
+            dto.setCommonFileListBeforeVO(fileUtil.getStorageBlobVOsByApplicationAndRecordTypeAndRecordId(
+                    ApplicationTypeEnum.BEFORE_FILE, RecordTypeEnum.INSPECTION_TASK, inspectionTask.getId()));
 
             return dto;
         }).collect(Collectors.toList());
@@ -175,6 +195,9 @@
         if (i <= 0) {
             return i;
         }
+        if (inspectionTaskDto.getInspectionItems() != null) {
+            updateInspectionItemStatuses(inspectionTask.getId(), inspectionTaskDto);
+        }
         Long linkedRepairId = syncRepairOrderIfAbnormal(inspectionTask, oldInspectionTask);
         if (linkedRepairId != null && !Objects.equals(linkedRepairId, inspectionTask.getDeviceRepairId())) {
             InspectionTask relationUpdate = new InspectionTask();
@@ -187,6 +210,33 @@
         saveInspectionAttachments(inspectionTask.getId(), inspectionTaskDto);
 
         return i;
+    }
+
+    private void updateInspectionItemStatuses(Long inspectionTaskId, InspectionTaskDto request) {
+        List<InspectionTaskItem> requestedItems = request.getInspectionItems();
+        List<InspectionTaskItem> currentItems = inspectionTaskItemMapper.selectList(
+                new LambdaQueryWrapper<InspectionTaskItem>()
+                        .eq(InspectionTaskItem::getInspectionTaskId, inspectionTaskId));
+        Map<Long, InspectionTaskItem> currentById = currentItems.stream()
+                .collect(Collectors.toMap(InspectionTaskItem::getId, Function.identity()));
+        Set<Long> seenIds = new HashSet<>();
+        for (InspectionTaskItem requested : requestedItems) {
+            if (requested == null || requested.getId() == null
+                    || requested.getItemStatus() == null
+                    || (requested.getItemStatus() != 0 && requested.getItemStatus() != 1)
+                    || !seenIds.add(requested.getId())) {
+                throw new IllegalArgumentException("宸℃鍐呭鐘舵�佹棤鏁�");
+            }
+            InspectionTaskItem current = currentById.get(requested.getId());
+            if (current == null) {
+                throw new IllegalArgumentException("宸℃鍐呭娓呭崟宸插彉鍖栵紝璇峰埛鏂板悗閲嶈瘯");
+            }
+            current.setItemStatus(requested.getItemStatus());
+            current.setRemarks(requested.getRemarks());
+            current.setCompletedBy(requested.getItemStatus() == 1 ? SecurityUtils.getUserId() : null);
+            current.setCompletedTime(requested.getItemStatus() == 1 ? LocalDateTime.now() : null);
+            inspectionTaskItemMapper.updateById(current);
+        }
     }
 
     private void validateInspectionInput(InspectionTaskDto inspectionTaskDto, InspectionTask oldInspectionTask) {
@@ -222,19 +272,24 @@
 
     private boolean hasAnyInspectionPhotoAfterSave(InspectionTaskDto inspectionTaskDto, InspectionTask oldInspectionTask) {
         Long recordId = oldInspectionTask == null ? null : oldInspectionTask.getId();
-        return hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListDTO(), ApplicationTypeEnum.FILE, recordId)
-                || hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListAfterDTO(), ApplicationTypeEnum.AFTER_FILE, recordId)
-                || hasApplicationPhotoAfterSave(inspectionTaskDto.getCommonFileListBeforeDTO(), ApplicationTypeEnum.BEFORE_FILE, recordId);
+        return hasApplicationPhotoAfterSave(
+                inspectionTaskDto.getCommonFileListDTO(), ApplicationTypeEnum.FILE, recordId)
+                || hasApplicationPhotoAfterSave(
+                        inspectionTaskDto.getCommonFileListAfterDTO(), ApplicationTypeEnum.AFTER_FILE, recordId)
+                || hasApplicationPhotoAfterSave(
+                        inspectionTaskDto.getCommonFileListBeforeDTO(), ApplicationTypeEnum.BEFORE_FILE, recordId);
     }
 
-    private boolean hasApplicationPhotoAfterSave(List<StorageBlobDTO> requestPhotos, ApplicationTypeEnum applicationType, Long recordId) {
+    private boolean hasApplicationPhotoAfterSave(
+            List<StorageBlobDTO> requestPhotos, ApplicationTypeEnum applicationType, Long recordId) {
         if (requestPhotos != null) {
             return !requestPhotos.isEmpty();
         }
         if (recordId == null) {
             return false;
         }
-        return CollectionUtils.isNotEmpty(fileUtil.getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(applicationType, RecordTypeEnum.INSPECTION_TASK, recordId));
+        return CollectionUtils.isNotEmpty(fileUtil.getStorageAttachmentsByApplicationAndRecordTypeAndRecordId(
+                applicationType, RecordTypeEnum.INSPECTION_TASK, recordId));
     }
 
     private void fillAcceptanceInfo(InspectionTask inspectionTask, InspectionTask oldInspectionTask) {
@@ -383,6 +438,8 @@
         if (ids == null || ids.length == 0) {
             return 0;
         }
+        inspectionTaskItemMapper.delete(new LambdaQueryWrapper<InspectionTaskItem>()
+                .in(InspectionTaskItem::getInspectionTaskId, Arrays.asList(ids)));
         // 鍒犻櫎鏂囦欢
         fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.FILE, RecordTypeEnum.INSPECTION_TASK, Arrays.asList(ids));
         fileUtil.deleteStorageAttachmentsByApplicationAndRecordTypeAndRecordIds(ApplicationTypeEnum.AFTER_FILE, RecordTypeEnum.INSPECTION_TASK, Arrays.asList(ids));
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
index adc2416..c439b6e 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
@@ -1,10 +1,19 @@
 package com.ruoyi.inspectiontask.service.impl;
 
-import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
-import com.ruoyi.inspectiontask.pojo.InspectionTask;
-import com.ruoyi.inspectiontask.pojo.TimingTask;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.ruoyi.common.utils.StringUtils;
-import org.quartz.*;
+import com.ruoyi.inspectiontask.mapper.InspectionTaskItemMapper;
+import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
+import com.ruoyi.inspectiontask.mapper.TimingTaskItemMapper;
+import com.ruoyi.inspectiontask.pojo.InspectionTask;
+import com.ruoyi.inspectiontask.pojo.InspectionTaskItem;
+import com.ruoyi.inspectiontask.pojo.TimingTask;
+import com.ruoyi.inspectiontask.pojo.TimingTaskItem;
+import org.quartz.DisallowConcurrentExecution;
+import org.quartz.Job;
+import org.quartz.JobDataMap;
+import org.quartz.JobExecutionContext;
+import org.quartz.JobExecutionException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.jdbc.core.BeanPropertyRowMapper;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -22,10 +31,14 @@
 @Component
 @DisallowConcurrentExecution // 绂佹骞跺彂鎵ц鍚屼竴涓狫ob
 public class TimingTaskJob implements Job, Serializable {
-    private static final long serialVersionUID = 1L; // 蹇呴』瀹氫箟搴忓垪鍖朓D
+    private static final long serialVersionUID = 1L;
 
     @Autowired
     private InspectionTaskMapper inspectionTaskMapper;
+    @Autowired
+    private TimingTaskItemMapper timingTaskItemMapper;
+    @Autowired
+    private InspectionTaskItemMapper inspectionTaskItemMapper;
     @Autowired
     private JdbcTemplate jdbcTemplate;
 
@@ -38,7 +51,7 @@
         try {
             // 3. 灏濊瘯鏌ヨ浣犵殑涓氬姟鏁版嵁
             // 閫氳繃JDBC妯℃澘鏌ヨ瀹氭椂浠诲姟淇℃伅锛屼娇鐢ㄥ弬鏁板寲鏌ヨ闃叉SQL娉ㄥ叆
-            String yourSql = "SELECT * FROM timing_task where id = ?";
+            String yourSql = "SELECT * FROM timing_task WHERE id = ?";
             List<TimingTask> tasks = jdbcTemplate.query(
                     yourSql,
                     new BeanPropertyRowMapper<>(TimingTask.class),
@@ -59,6 +72,7 @@
             // 2. 鍒涘缓骞朵繚瀛樺贰妫�浠诲姟璁板綍 - 杩欏氨鏄偍鎻愪緵鐨勪唬鐮佸簲璇ユ斁鐨勪綅缃�
             InspectionTask inspectionTask = createInspectionTask(timingTask);
             inspectionTaskMapper.insert(inspectionTask);
+            copyTaskItemsToRecord(timingTask.getId(), inspectionTask.getId());
 
             // 3. 鏇存柊瀹氭椂浠诲姟鐨勬墽琛屾椂闂�
             if (!tasks.isEmpty()) {
@@ -75,9 +89,9 @@
                 );
 
                 // 鎵ц鏇存柊鎿嶄綔
-                String updateSql = "UPDATE timing_task " +
-                        "SET last_execution_time = ?, next_execution_time = ? " +
-                        "WHERE id = ?";
+                String updateSql = "UPDATE timing_task "
+                        + "SET last_execution_time = ?, next_execution_time = ? "
+                        + "WHERE id = ?";
 
                 jdbcTemplate.update(
                         updateSql,
@@ -103,7 +117,6 @@
 
         // 澶嶅埗鍩烘湰灞炴��
         inspectionTask.setTaskName(timingTask.getTaskName());
-        inspectionTask.setInspectionProject(timingTask.getInspectionProject());
         inspectionTask.setTaskId(timingTask.getTaskId());
         inspectionTask.setInspectorId(timingTask.getInspectorIds());
         inspectionTask.setInspectionLocation(timingTask.getInspectionLocation());
@@ -118,6 +131,25 @@
         inspectionTask.setTenantId(timingTask.getTenantId());
 
         return inspectionTask;
+    }
+
+    private void copyTaskItemsToRecord(Long timingTaskId, Long inspectionTaskId) {
+        List<TimingTaskItem> taskItems = timingTaskItemMapper.selectList(
+                new LambdaQueryWrapper<TimingTaskItem>()
+                        .eq(TimingTaskItem::getTimingTaskId, timingTaskId)
+                        .orderByAsc(TimingTaskItem::getSortNum));
+        for (TimingTaskItem source : taskItems) {
+            InspectionTaskItem recordItem = new InspectionTaskItem();
+            recordItem.setInspectionTaskId(inspectionTaskId);
+            recordItem.setInspectionItemId(source.getInspectionItemId());
+            recordItem.setItemProject(source.getItemProject());
+            recordItem.setItemContent(source.getItemContent());
+            recordItem.setItemStatus(0);
+            recordItem.setSortNum(source.getSortNum());
+            recordItem.setTenantId(source.getTenantId());
+            recordItem.setDeptId(source.getDeptId());
+            inspectionTaskItemMapper.insert(recordItem);
+        }
     }
 
 
@@ -243,13 +275,27 @@
 
         for (String dayStr : dayStrs) {
             switch (dayStr) {
-                case "MON": days.add(DayOfWeek.MONDAY); break;
-                case "TUE": days.add(DayOfWeek.TUESDAY); break;
-                case "WED": days.add(DayOfWeek.WEDNESDAY); break;
-                case "THU": days.add(DayOfWeek.THURSDAY); break;
-                case "FRI": days.add(DayOfWeek.FRIDAY); break;
-                case "SAT": days.add(DayOfWeek.SATURDAY); break;
-                case "SUN": days.add(DayOfWeek.SUNDAY); break;
+                case "MON":
+                    days.add(DayOfWeek.MONDAY);
+                    break;
+                case "TUE":
+                    days.add(DayOfWeek.TUESDAY);
+                    break;
+                case "WED":
+                    days.add(DayOfWeek.WEDNESDAY);
+                    break;
+                case "THU":
+                    days.add(DayOfWeek.THURSDAY);
+                    break;
+                case "FRI":
+                    days.add(DayOfWeek.FRIDAY);
+                    break;
+                case "SAT":
+                    days.add(DayOfWeek.SATURDAY);
+                    break;
+                case "SUN":
+                    days.add(DayOfWeek.SUNDAY);
+                    break;
                 default: throw new IllegalArgumentException("鏃犳晥鐨勬槦鏈熷嚑: " + dayStr);
             }
         }
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
index 09491ab..e4156bd 100644
--- a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -4,11 +4,16 @@
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.common.utils.bean.BeanUtils;
 import com.ruoyi.inspectiontask.dto.TimingTaskDto;
+import com.ruoyi.inspectiontask.mapper.InspectionItemMapper;
+import com.ruoyi.inspectiontask.mapper.TimingTaskItemMapper;
 import com.ruoyi.inspectiontask.mapper.TimingTaskMapper;
+import com.ruoyi.inspectiontask.pojo.InspectionItem;
 import com.ruoyi.inspectiontask.pojo.TimingTask;
+import com.ruoyi.inspectiontask.pojo.TimingTaskItem;
 import com.ruoyi.inspectiontask.service.TimingTaskService;
 import com.ruoyi.project.system.domain.SysUser;
 import com.ruoyi.project.system.mapper.SysUserMapper;
@@ -18,9 +23,24 @@
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.time.*;
+import java.time.DayOfWeek;
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Year;
+import java.time.YearMonth;
 import java.time.format.DateTimeFormatter;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 /**
@@ -35,9 +55,11 @@
     private final TimingTaskMapper timingTaskMapper;
     private final TimingTaskScheduler timingTaskScheduler;
     private final SysUserMapper sysUserMapper;
+    private final InspectionItemMapper inspectionItemMapper;
+    private final TimingTaskItemMapper timingTaskItemMapper;
+
     private static final int ENABLED = 1;
     private static final int DISABLED = 0;
-
 
     @Override
     public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) {
@@ -47,9 +69,7 @@
         if (StringUtils.isNotBlank(timingTask.getTaskName())) {
             queryWrapper.like(TimingTask::getTaskName, timingTask.getTaskName());
         }
-        if (StringUtils.isNotBlank(timingTask.getInspectionProject())) {
-            queryWrapper.like(TimingTask::getInspectionProject, timingTask.getInspectionProject());
-        }
+        queryWrapper.eq(TimingTask::getDeleted, 0);
         if (timingTask.getIsEnabled() != null) {
             queryWrapper.eq(TimingTask::getIsEnabled, timingTask.getIsEnabled());
         }
@@ -113,6 +133,16 @@
                 dto.setInspector(inspectorNickNames);
             }
 
+            List<TimingTaskItem> taskItems = timingTaskItemMapper.selectList(
+                    new LambdaQueryWrapper<TimingTaskItem>()
+                            .eq(TimingTaskItem::getTimingTaskId, task.getId())
+                            .orderByAsc(TimingTaskItem::getSortNum));
+            dto.setInspectionItems(taskItems);
+            dto.setInspectionItemIds(taskItems.stream()
+                    .map(TimingTaskItem::getInspectionItemId)
+                    .filter(Objects::nonNull)
+                    .collect(Collectors.toList()));
+
             return dto;
         }).collect(Collectors.toList());
 
@@ -132,6 +162,11 @@
                 throw new IllegalArgumentException("瀹氭椂浠诲姟涓嶅瓨鍦�");
             }
         }
+        List<InspectionItem> selectedItems = resolveSelectedItems(timingTaskDto, oldTimingTask);
+        List<TimingTaskItem> customItems = normalizeCustomItems(timingTaskDto.getCustomItems());
+        if (selectedItems.isEmpty() && customItems.isEmpty()) {
+            throw new ServiceException("璇烽�夋嫨鎴栨坊鍔犺嚦灏戜竴涓贰妫�鍐呭");
+        }
         TimingTask timingTask = new TimingTask();
         BeanUtils.copyProperties(timingTaskDto, timingTask);
         timingTask.setIsEnabled(resolveEnabledValue(timingTask.getIsEnabled(), oldTimingTask));
@@ -139,7 +174,7 @@
         // 1. 瑙f瀽瀛楃涓蹭负 LocalDate锛堝彧鍖呭惈骞存湀鏃ワ級
         DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
         LocalDate localDate = LocalDate.now();
-        if(StringUtils.isNotEmpty(timingTaskDto.getDateStr())){
+        if (StringUtils.isNotEmpty(timingTaskDto.getDateStr())) {
             localDate = LocalDate.parse(timingTaskDto.getDateStr(), formatter);
         }
         // 2. 鑾峰彇褰撳墠绯荤粺鐨� LocalTime锛堝寘鍚椂鍒嗙锛�
@@ -156,17 +191,23 @@
             LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask);
             timingTask.setNextExecutionTime(firstExecutionTime);
             int result = timingTaskMapper.insert(timingTask);
+            if (result > 0) {
+                saveTaskItems(timingTask, selectedItems, customItems);
+            }
             if (result > 0 && isEnabled(timingTask.getIsEnabled(), timingTask.isActive())) {
                 // 鏂板鎴愬姛鍚庢坊鍔犲埌璋冨害鍣�
                 timingTaskScheduler.scheduleTimingTask(timingTask);
             }
             return result;
         } else {
-
-
             int result = timingTaskMapper.updateById(timingTask);
             if (result > 0) {
-                boolean oldEnabled = isEnabled(oldTimingTask == null ? null : oldTimingTask.getIsEnabled(), oldTimingTask != null && oldTimingTask.isActive());
+                timingTaskItemMapper.delete(new LambdaQueryWrapper<TimingTaskItem>()
+                        .eq(TimingTaskItem::getTimingTaskId, timingTask.getId()));
+                saveTaskItems(timingTask, selectedItems, customItems);
+                boolean oldEnabled = isEnabled(
+                        oldTimingTask == null ? null : oldTimingTask.getIsEnabled(),
+                        oldTimingTask != null && oldTimingTask.isActive());
                 boolean newEnabled = isEnabled(timingTask.getIsEnabled(), timingTask.isActive());
                 if (!newEnabled) {
                     timingTaskScheduler.unscheduleTimingTask(timingTask.getId());
@@ -182,8 +223,94 @@
         }
     }
 
+    private List<InspectionItem> resolveSelectedItems(TimingTask request, TimingTask oldTask) {
+        List<Long> itemIds = request.getInspectionItemIds();
+        if (itemIds == null && oldTask != null) {
+            itemIds = timingTaskItemMapper.selectList(new LambdaQueryWrapper<TimingTaskItem>()
+                            .eq(TimingTaskItem::getTimingTaskId, oldTask.getId()))
+                    .stream().map(TimingTaskItem::getInspectionItemId).filter(Objects::nonNull).toList();
+        }
+        if (request.getTaskId() == null || StringUtils.isBlank(request.getFrequencyType())) {
+            throw new ServiceException("璁惧鍜屽贰妫�棰戠巼涓嶈兘涓虹┖");
+        }
+        if (itemIds == null || itemIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+        if (new HashSet<>(itemIds).size() != itemIds.size()) {
+            throw new ServiceException("宸℃鍐呭涓嶈兘閲嶅閫夋嫨");
+        }
+        List<InspectionItem> items = inspectionItemMapper.selectBatchIds(itemIds);
+        if (items.size() != itemIds.size()) {
+            throw new ServiceException("鎵�閫夊贰妫�鍐呭涓嶅瓨鍦�");
+        }
+        Long deviceId = request.getTaskId().longValue();
+        String frequency = request.getFrequencyType();
+        Set<Long> oldItemIds = oldTask == null ? Collections.emptySet()
+                : new HashSet<>(timingTaskItemMapper.selectList(new LambdaQueryWrapper<TimingTaskItem>()
+                        .eq(TimingTaskItem::getTimingTaskId, oldTask.getId()))
+                        .stream().map(TimingTaskItem::getInspectionItemId).filter(Objects::nonNull).toList());
+        boolean invalid = items.stream().anyMatch(item ->
+                (item.getDeviceLedgerId() != null && !deviceId.equals(item.getDeviceLedgerId()))
+                        || !(frequency.equals(item.getFrequencyType()) || "ALL".equals(item.getFrequencyType()))
+                        || (item.getDeleted() != null && item.getDeleted() == 1
+                        || item.getIsActive() == null || item.getIsActive() != 1) && !oldItemIds.contains(item.getId()));
+        if (invalid) {
+            throw new ServiceException("鎵�閫夊贰妫�鍐呭涓庤澶囨垨棰戠巼涓嶅尮閰�");
+        }
+        Map<Long, InspectionItem> itemMap = items.stream()
+                .collect(Collectors.toMap(InspectionItem::getId, Function.identity()));
+        return itemIds.stream().map(itemMap::get).toList();
+    }
+
+    private List<TimingTaskItem> normalizeCustomItems(List<TimingTaskItem> customItems) {
+        if (customItems == null) {
+            return new ArrayList<>();
+        }
+        return customItems.stream()
+                .filter(Objects::nonNull)
+                .filter(item -> StringUtils.isNotBlank(item.getItemProject())
+                        && StringUtils.isNotBlank(item.getItemContent()))
+                .peek(item -> {
+                    item.setItemProject(item.getItemProject().trim());
+                    item.setItemContent(item.getItemContent().trim());
+                })
+                .toList();
+    }
+
+    private void saveTaskItems(
+            TimingTask task,
+            List<InspectionItem> selectedItems,
+            List<TimingTaskItem> customItems) {
+        for (int index = 0; index < selectedItems.size(); index++) {
+            InspectionItem source = selectedItems.get(index);
+            TimingTaskItem taskItem = new TimingTaskItem();
+            taskItem.setTimingTaskId(task.getId());
+            taskItem.setInspectionItemId(source.getId());
+            taskItem.setItemProject(source.getItemProject());
+            taskItem.setItemContent(source.getItemContent());
+            taskItem.setSortNum(index);
+            taskItem.setTenantId(task.getTenantId());
+            taskItem.setDeptId(task.getDeptId());
+            timingTaskItemMapper.insert(taskItem);
+        }
+        for (int index = 0; index < customItems.size(); index++) {
+            TimingTaskItem source = customItems.get(index);
+            TimingTaskItem taskItem = new TimingTaskItem();
+            taskItem.setTimingTaskId(task.getId());
+            taskItem.setItemProject(source.getItemProject());
+            taskItem.setItemContent(source.getItemContent());
+            taskItem.setSortNum(selectedItems.size() + index);
+            taskItem.setTenantId(task.getTenantId());
+            taskItem.setDeptId(task.getDeptId());
+            timingTaskItemMapper.insert(taskItem);
+        }
+    }
+
     public LocalDateTime calculateFirstExecutionTime(TimingTask task) {
         // 鏍规嵁棰戠巼绫诲瀷鍜岃鎯呰绠楅娆℃墽琛屾椂闂�
+        if (task == null || StringUtils.isBlank(task.getFrequencyDetail())) {
+            throw new IllegalArgumentException("璇峰~鍐欎换鍔℃墽琛屾棩鏈熷拰鏃堕棿");
+        }
         String frequencyType = task.getFrequencyType();
         if ("DAILY".equals(frequencyType)) {
             // 濡傛灉鏄瘡澶╂墽琛岋紝璁$畻浠婂ぉ鎴栨槑澶╃殑鍏蜂綋鏃堕棿
@@ -214,6 +341,7 @@
 
     // 鏄犲皠鏄熸湡绠�鍐欎笌DayOfWeek
     private static final Map<String, DayOfWeek> WEEK_DAY_MAP = new HashMap<>();
+
     static {
         WEEK_DAY_MAP.put("MON", DayOfWeek.MONDAY);
         WEEK_DAY_MAP.put("TUE", DayOfWeek.TUESDAY);
@@ -316,7 +444,31 @@
     }
 
     private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) {
-        return null;
+        String[] parts = frequencyDetail.split(",");
+        if (parts.length != 3) {
+            throw new IllegalArgumentException("瀛e害棰戠巼鏍煎紡閿欒锛屽簲涓�'瀛e害鍐呮湀浠�,鏃ユ湡,鏃堕棿'");
+        }
+        int monthInQuarter = Integer.parseInt(parts[0]);
+        int dayOfMonth = Integer.parseInt(parts[1]);
+        LocalTime time = LocalTime.parse(parts[2], DateTimeFormatter.ofPattern("HH:mm"));
+        if (monthInQuarter < 1 || monthInQuarter > 3 || dayOfMonth < 1 || dayOfMonth > 31) {
+            throw new IllegalArgumentException("瀛e害棰戠巼鏃ユ湡鏃犳晥");
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        int currentMonthInQuarter = (now.getMonthValue() - 1) % 3 + 1;
+        int monthsUntilTarget = monthInQuarter - currentMonthInQuarter;
+        if (monthsUntilTarget < 0) {
+            monthsUntilTarget += 3;
+        }
+        YearMonth targetMonth = YearMonth.from(now).plusMonths(monthsUntilTarget);
+        int targetDay = Math.min(dayOfMonth, targetMonth.lengthOfMonth());
+        LocalDateTime target = targetMonth.atDay(targetDay).atTime(time);
+        if (!target.isAfter(now)) {
+            targetMonth = targetMonth.plusMonths(3);
+            target = targetMonth.atDay(Math.min(dayOfMonth, targetMonth.lengthOfMonth())).atTime(time);
+        }
+        return target;
     }
 
     @Override
@@ -464,13 +616,27 @@
 
         for (String dayStr : dayStrs) {
             switch (dayStr) {
-                case "MON": days.add(DayOfWeek.MONDAY); break;
-                case "TUE": days.add(DayOfWeek.TUESDAY); break;
-                case "WED": days.add(DayOfWeek.WEDNESDAY); break;
-                case "THU": days.add(DayOfWeek.THURSDAY); break;
-                case "FRI": days.add(DayOfWeek.FRIDAY); break;
-                case "SAT": days.add(DayOfWeek.SATURDAY); break;
-                case "SUN": days.add(DayOfWeek.SUNDAY); break;
+                case "MON":
+                    days.add(DayOfWeek.MONDAY);
+                    break;
+                case "TUE":
+                    days.add(DayOfWeek.TUESDAY);
+                    break;
+                case "WED":
+                    days.add(DayOfWeek.WEDNESDAY);
+                    break;
+                case "THU":
+                    days.add(DayOfWeek.THURSDAY);
+                    break;
+                case "FRI":
+                    days.add(DayOfWeek.FRIDAY);
+                    break;
+                case "SAT":
+                    days.add(DayOfWeek.SATURDAY);
+                    break;
+                case "SUN":
+                    days.add(DayOfWeek.SUNDAY);
+                    break;
                 default: throw new IllegalArgumentException("鏃犳晥鐨勬槦鏈熷嚑: " + dayStr);
             }
         }
@@ -498,12 +664,15 @@
         return activeFallback;
     }
 
-
-
     @Override
     public int delByIds(Long[] ids) {
+        if (ids == null || ids.length == 0) {
+            return 0;
+        }
         int i = timingTaskMapper.deleteBatchIds(Arrays.asList(ids));
-        if(i > 0){
+        if (i > 0) {
+            timingTaskItemMapper.delete(new LambdaQueryWrapper<TimingTaskItem>()
+                    .in(TimingTaskItem::getTimingTaskId, Arrays.asList(ids)));
             for (Long id : ids) {
                 timingTaskScheduler.unscheduleTimingTask(id);
             }

--
Gitblit v1.9.3