gongchunyi
4 天以前 b7080587bc00e5451aa8435f093f0d8cc6f80320
feat: 设备巡检优化,巡检项目拆分
已添加10个文件
已修改5个文件
918 ■■■■■ 文件已修改
doc/20261016_设备巡检项目完整脚本.sql 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/InspectionItemController.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/mapper/InspectionItemMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskItemMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskItemMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionItem.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTaskItem.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTaskItem.java 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/InspectionItemService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionItemServiceImpl.java 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java 101 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20261016_É豸Ѳ¼ìÏîÄ¿ÍêÕû½Å±¾.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 '频率:DAILY/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 '完成巡检的用户ID',
    `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='巡检记录内容及完成情况';
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();
    }
}
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> {
}
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> {
}
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> {
}
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;
}
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 = "巡检人ID")
    private String inspectorId;
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;
}
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;
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;
}
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);
}
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;
    }
}
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));
        //登记人ids
        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));
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 // ç¦æ­¢å¹¶å‘执行同一个Job
public class TimingTaskJob implements Job, Serializable {
    private static final long serialVersionUID = 1L; // å¿…须定义序列化ID
    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);
            }
        }
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. è§£æžå­—符串为 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("季度频率格式错误,应为'季度内月份,日期,时间'");
        }
        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("季度频率日期无效");
        }
        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);
            }