From 2cfd0045a109b01ac890f0f2e968cbb44415d0ac Mon Sep 17 00:00:00 2001
From: maven <2163098428@qq.com>
Date: 星期五, 19 九月 2025 11:45:43 +0800
Subject: [PATCH] yys  博达商贸-巡检管理

---
 src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java           |   16 
 src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java     |  257 ++++++
 src/main/java/com/ruoyi/common/utils/MinioUtils.java                                   |   88 ++
 src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeScanRecordMapper.java              |   11 
 src/main/java/com/ruoyi/inspectiontask/dto/QrCodeDto.java                              |    8 
 src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPlanServiceImpl.java |   41 +
 src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java                            |   97 ++
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java                 |   65 +
 src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeServiceImpl.java             |   54 +
 src/main/java/com/ruoyi/basic/pojo/StorageBlob.java                                    |    6 
 pom.xml                                                                                |    8 
 src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java                      |   20 
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java           |  234 ++++++
 src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementPlanMapper.java            |   22 
 src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPlan.java                    |  105 ++
 src/main/java/com/ruoyi/inspectiontask/controller/QrCodeScanRecordController.java      |   56 +
 src/main/java/com/ruoyi/project/system/mapper/SysUserMapper.java                       |    7 
 src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java   |  194 ++++
 src/main/java/com/ruoyi/inspectiontask/pojo/QrCodeScanRecord.java                      |   68 +
 src/main/java/com/ruoyi/inspectiontask/controller/QrCodeController.java                |   56 +
 src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java                  |   23 
 src/main/java/com/ruoyi/inspectiontask/StorageAttachmentRecordType.java                |   23 
 src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java                          |   12 
 src/main/java/com/ruoyi/inspectiontask/service/QrCodeService.java                      |   19 
 src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java                        |   81 ++
 src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java         |  348 ++++++++
 src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeMapper.java                        |   11 
 src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java              |   20 
 src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java        |   59 +
 src/main/resources/mapper/procurementrecord/ProcurementPlanMapper.xml                  |   16 
 src/main/java/com/ruoyi/inspectiontask/dto/QrCodeScanRecordDto.java                    |   20 
 src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java                  |    5 
 src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskMapper.java                    |   11 
 src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java                                  |    2 
 src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java                    |    3 
 src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java    |   67 +
 src/main/java/com/ruoyi/procurementrecord/service/ProcurementPlanService.java          |   25 
 src/main/resources/mapper/system/SysUserMapper.xml                                     |   22 
 src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java            |   58 +
 src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java                |   11 
 src/main/java/com/ruoyi/inspectiontask/pojo/QrCode.java                                |   63 +
 src/main/java/com/ruoyi/inspectiontask/service/QrCodeScanRecordService.java            |   20 
 42 files changed, 2,327 insertions(+), 5 deletions(-)

diff --git a/pom.xml b/pom.xml
index 17973d1..316c132 100644
--- a/pom.xml
+++ b/pom.xml
@@ -307,6 +307,14 @@
 	                <fork>true</fork> <!-- 濡傛灉娌℃湁璇ラ厤缃紝devtools涓嶄細鐢熸晥 -->
 	            </configuration>
 	        </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>16</source>
+                    <target>16</target>
+                </configuration>
+            </plugin>
         </plugins>
 	</build>
 
diff --git a/src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java b/src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
index b868048..17a7d71 100644
--- a/src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
+++ b/src/main/java/com/ruoyi/basic/dto/StorageBlobDTO.java
@@ -6,4 +6,6 @@
 @Data
 public class StorageBlobDTO extends StorageBlob {
     private String url;
+
+    private String downloadUrl;
 }
diff --git a/src/main/java/com/ruoyi/basic/pojo/StorageBlob.java b/src/main/java/com/ruoyi/basic/pojo/StorageBlob.java
index 8e92865..188d9c5 100644
--- a/src/main/java/com/ruoyi/basic/pojo/StorageBlob.java
+++ b/src/main/java/com/ruoyi/basic/pojo/StorageBlob.java
@@ -64,4 +64,10 @@
      */
     @TableField(value = "byte_size")
     private Long byteSize;
+
+    /**
+     * 0鐢熶骇鍓� 1鐢熶骇鍚� 2鐢熶骇闂
+     */
+    @TableField(value = "type")
+    private Long type;
 }
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java b/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
index e285063..de2c096 100644
--- a/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
+++ b/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
@@ -34,6 +34,9 @@
      */
     public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
 
+    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType);
+
+
     /**
      * 鍒犻櫎閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
      * @param storageAttachment 鏂囦欢淇℃伅
diff --git a/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java b/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
index 78c8647..d0e4b70 100644
--- a/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
+++ b/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
@@ -78,6 +78,22 @@
     }
 
     @Override
+    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, String fileType) {
+        // 鍒犻櫎鏃у浘
+        deleteStorageAttachment(new StorageAttachment(fileType.toString(), (long) recordType.ordinal(), recordId));
+        for (StorageAttachment attachment : attachments) {
+            // 鑾峰彇鍏宠仈璁板綍
+            StorageBlob storageBlob = attachment.getStorageBlobDTO();
+            attachment.setName(fileType.toString());
+            attachment.setRecordType((long) recordType.ordinal());
+            attachment.setRecordId(recordId);
+            attachment.setStorageBlobId(storageBlob.getId());
+            storageAttachmentMapper.insert(attachment);
+        }
+
+    }
+
+    @Override
     public int deleteStorageAttachment(StorageAttachment storageAttachment) {
         // 鍏堝垹闄ゆ槑缁嗚〃
         storageBlobService.deleteStorageBlobs(storageAttachment);
diff --git a/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java b/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
index 8b0597e..a4b889d 100644
--- a/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
+++ b/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
@@ -9,7 +9,10 @@
 @AllArgsConstructor
 public enum StorageAttachmentRecordType {
     // 渚嬪瓙 瀹為檯寮�鍙戣鍒犻櫎
-    Template("Template","鑼冧緥");
+    Template("Template","鑼冧緥"),
+    Archives("Archives","鏂囨。绠$悊"),
+    InspectionTasks("InspectionTasks","鐢熶骇宸℃"),
+    QrCodeScanRecords("QrCodeScanRecords","浜岀淮鐮佹壂鐮佽褰曟枃浠�");
 
 
     private final String code;
diff --git a/src/main/java/com/ruoyi/common/utils/MinioUtils.java b/src/main/java/com/ruoyi/common/utils/MinioUtils.java
index 55af1dd..30e479a 100644
--- a/src/main/java/com/ruoyi/common/utils/MinioUtils.java
+++ b/src/main/java/com/ruoyi/common/utils/MinioUtils.java
@@ -21,12 +21,10 @@
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
 import java.io.InputStream;
+import java.net.URLEncoder;
 import java.nio.charset.StandardCharsets;
 import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
@@ -306,4 +304,86 @@
         return null;
     }
 
+    /**
+     * 鐢熸垚棰勮URL
+     * @param bucketFilename 鏂囦欢鍦∕inIO涓殑鍞竴鏍囪瘑
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param useDefaultExpiry 鏄惁浣跨敤榛樿杩囨湡鏃堕棿锛坱rue=浣跨敤榛樿杩囨湡鏃堕棿锛宖alse=姘镐箙鏈夋晥锛�
+     * @return 棰勮URL
+     */
+    public String getPreviewUrls(String bucketFilename, String bucketName, boolean useDefaultExpiry) {
+        if (StringUtils.isBlank(bucketFilename)) {
+            return null;
+        }
+
+        try {
+            // 楠岃瘉鏂囦欢瀛樺湪鎬�
+            minioClient.statObject(StatObjectArgs.builder()
+                    .bucket(bucketName)
+                    .object(bucketFilename)
+                    .build());
+
+            GetPresignedObjectUrlArgs.Builder builder = GetPresignedObjectUrlArgs.builder()
+                    .method(Method.GET)
+                    .bucket(bucketName)
+                    .object(bucketFilename);
+
+            // 璁剧疆杩囨湡鏃堕棿锛歶seDefaultExpiry=true 浣跨敤閰嶇疆鐨勮繃鏈熸椂闂�
+            if (useDefaultExpiry) {
+                builder.expiry(previewExpiry, TimeUnit.HOURS);
+            }
+
+            return minioClient.getPresignedObjectUrl(builder.build());
+        } catch (Exception e) {
+            throw new UtilException("鐢熸垚棰勮URL澶辫触: " + e.getMessage(), e);
+        }
+    }
+
+
+    /**
+     * 鐢熸垚涓嬭浇URL锛堝己鍒舵祻瑙堝櫒涓嬭浇锛�
+     * @param bucketFilename 鏂囦欢鍦∕inIO涓殑鍞竴鏍囪瘑
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param originalFileName 鍘熷鏂囦欢鍚嶏紙鐢ㄤ簬涓嬭浇鏃舵樉绀猴級
+     * @param useDefaultExpiry 鏄惁浣跨敤榛樿杩囨湡鏃堕棿锛坱rue=浣跨敤榛樿锛宖alse=鏃犺繃鏈熸椂闂达級
+     * @return 涓嬭浇URL
+     */
+    public String getDownloadUrls(String bucketFilename, String bucketName, String originalFileName, boolean useDefaultExpiry) {
+        if (StringUtils.isBlank(bucketFilename)) {
+            return null;
+        }
+
+        try {
+            // 楠岃瘉鏂囦欢瀛樺湪鎬�
+            minioClient.statObject(StatObjectArgs.builder()
+                    .bucket(bucketName)
+                    .object(bucketFilename)
+                    .build());
+
+            // 姝g‘缂栫爜鏂囦欢鍚嶏細鏇挎崲 + 涓� %20
+            String encodedFileName = URLEncoder.encode(originalFileName, StandardCharsets.UTF_8)
+                    .replace("+", "%20");
+
+            Map<String, String> reqParams = new HashMap<>();
+            reqParams.put("response-content-disposition",
+                    "attachment; filename=\"" + encodedFileName + "\"");
+
+            GetPresignedObjectUrlArgs.Builder builder = GetPresignedObjectUrlArgs.builder()
+                    .method(Method.GET)
+                    .bucket(bucketName)
+                    .object(bucketFilename)
+                    .extraQueryParams(reqParams);
+
+            // 鏍规嵁鍙傛暟鍐冲畾鏄惁璁剧疆杩囨湡鏃堕棿
+            if (useDefaultExpiry) {
+                // 浣跨敤榛樿杩囨湡鏃堕棿锛堜粠閰嶇疆璇诲彇锛�
+                builder.expiry(previewExpiry, TimeUnit.HOURS);
+            } else {
+                // 涓嶈缃繃鏈熸椂闂达紙MinIO 榛樿7澶╋級
+            }
+            return minioClient.getPresignedObjectUrl(builder.build());
+        } catch (Exception e) {
+            throw new UtilException("鐢熸垚涓嬭浇URL澶辫触: " + e.getMessage(), e);
+        }
+    }
 }
diff --git a/src/main/java/com/ruoyi/inspectiontask/StorageAttachmentRecordType.java b/src/main/java/com/ruoyi/inspectiontask/StorageAttachmentRecordType.java
new file mode 100644
index 0000000..e15b741
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/StorageAttachmentRecordType.java
@@ -0,0 +1,23 @@
+package com.ruoyi.inspectiontask;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 闄勪欢璁板綍绫诲瀷鏋氫妇
+ *
+ */
+@Getter
+@AllArgsConstructor
+public enum StorageAttachmentRecordType {
+    // 渚嬪瓙 瀹為檯寮�鍙戣鍒犻櫎
+    Template("Template","鑼冧緥"),
+    Archives("Archives","鏂囨。绠$悊"),
+    InspectionTasks("InspectionTasks","鐢熶骇宸℃"),
+    QrCodeScanRecords("QrCodeScanRecords","浜岀淮鐮佹壂鐮佽褰曟枃浠�");
+
+
+    private final String code;
+    private final String info;
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java b/src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java
new file mode 100644
index 0000000..a32dd4b
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java
@@ -0,0 +1,59 @@
+package com.ruoyi.inspectiontask.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
+import com.ruoyi.inspectiontask.pojo.InspectionTask;
+import com.ruoyi.inspectiontask.service.InspectionTaskService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:52
+ */
+@RestController
+@Api(tags = "宸℃浠诲姟绠$悊")
+@RequestMapping("/inspectionTask")
+public class InspectionTaskController extends BaseController {
+
+    @Autowired
+    private InspectionTaskService inspectionTaskService;
+
+    /**
+     * 宸℃浠诲姟琛ㄨ〃鏌ヨ
+     */
+    @GetMapping("/list")
+    @ApiOperation("宸℃浠诲姟琛ㄨ〃鏌ヨ")
+    public R<IPage<InspectionTaskDto>> list(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto) {
+        IPage<InspectionTaskDto> list = inspectionTaskService.selectInspectionTaskList(page,inspectionTaskDto);
+        return R.ok(list);
+    }
+
+    /**
+     * 宸℃浠诲姟琛ㄦ柊澧炰慨鏀�
+     */
+    @PostMapping("/addOrEditInspectionTask")
+    @ApiOperation("宸℃浠诲姟琛ㄦ柊澧炰慨鏀�")
+    @Transactional(rollbackFor = Exception.class)
+    public R addOrEditInspectionTask(@RequestBody InspectionTaskDto inspectionTaskDto) {
+        return R.ok(inspectionTaskService.addOrEditInspectionTask(inspectionTaskDto));
+    }
+
+    /**
+     * 宸℃浠诲姟琛ㄥ垹闄�
+     */
+    @DeleteMapping("/delInspectionTask")
+    @ApiOperation("宸℃浠诲姟琛ㄥ垹闄�")
+    @Transactional(rollbackFor = Exception.class)
+    public R remove(@RequestBody Long[] ids) {
+        return R.ok(inspectionTaskService.delByIds(ids));
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/controller/QrCodeController.java b/src/main/java/com/ruoyi/inspectiontask/controller/QrCodeController.java
new file mode 100644
index 0000000..3d35be5
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/controller/QrCodeController.java
@@ -0,0 +1,56 @@
+package com.ruoyi.inspectiontask.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.inspectiontask.dto.QrCodeDto;
+import com.ruoyi.inspectiontask.pojo.QrCode;
+import com.ruoyi.inspectiontask.service.QrCodeService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:52
+ */
+@RestController
+@Api(tags = "浜岀淮鐮佺鐞�")
+@RequestMapping("/qrCode")
+public class QrCodeController extends BaseController {
+
+    @Autowired
+    private QrCodeService qrCodeService;
+
+    /**
+     * 浜岀淮鐮佺鐞嗚〃鏌ヨ
+     */
+    @GetMapping("/list")
+    @ApiOperation("浜岀淮鐮佺鐞嗚〃鏌ヨ")
+    public R<IPage<QrCode>> list(Page page, QrCodeDto qrCodeDto) {
+        IPage<QrCode> list = qrCodeService.selectQrCodeList(page, qrCodeDto);
+        return R.ok(list);
+    }
+
+    /**
+     * 浜岀淮鐮佺鐞嗚〃鏂板淇敼
+     */
+    @PostMapping("/addOrEditQrCode")
+    @ApiOperation("浜岀淮鐮佺鐞嗚〃鏂板淇敼")
+    public R<Long> addOrEditQrCode(@RequestBody QrCodeDto qrCodeDto) {
+        return R.ok(qrCodeService.addOrEditQrCode(qrCodeDto));
+    }
+
+    /**
+     * 浜岀淮鐮佺鐞嗚〃鍒犻櫎
+     */
+    @DeleteMapping("/delQrCode")
+    @ApiOperation("浜岀淮鐮佺鐞嗚〃鍒犻櫎")
+    public R remove(@RequestBody Long[] ids) {
+        return R.ok(qrCodeService.delByIds(ids));
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/controller/QrCodeScanRecordController.java b/src/main/java/com/ruoyi/inspectiontask/controller/QrCodeScanRecordController.java
new file mode 100644
index 0000000..00329b6
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/controller/QrCodeScanRecordController.java
@@ -0,0 +1,56 @@
+package com.ruoyi.inspectiontask.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.inspectiontask.dto.QrCodeScanRecordDto;
+import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
+import com.ruoyi.inspectiontask.service.QrCodeScanRecordService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:53
+ */
+@RestController
+@Api(tags = "浜岀淮鐮佹壂鎻忚褰曠鐞�")
+@RequestMapping("/qrCodeScanRecord")
+public class QrCodeScanRecordController extends BaseController {
+
+    @Autowired
+    private QrCodeScanRecordService qrCodeScanRecordService;
+
+    /**
+     * 浜岀淮鐮佹壂鐮佽褰曡〃鏌ヨ
+     */
+    @GetMapping("/list")
+    @ApiOperation("浜岀淮鐮佹壂鐮佽褰曡〃鏌ヨ")
+    public R<IPage<QrCodeScanRecordDto>> list(Page<QrCodeScanRecord> page, QrCodeScanRecordDto qrCodeScanRecordDto) {
+        IPage<QrCodeScanRecordDto> list = qrCodeScanRecordService.selectQrCodeScanRecordList(page, qrCodeScanRecordDto);
+        return R.ok(list);
+    }
+
+    /**
+     * 浜岀淮鐮佹壂鐮佽褰曡〃鏂板淇敼
+     */
+    @PostMapping("/addOrEditQrCodeRecord")
+    @ApiOperation("浜岀淮鐮佹壂鐮佽褰曡〃鏂板淇敼")
+    public R addOrEditQrCodeRecord(@RequestBody QrCodeScanRecordDto qrCodeScanRecordDto) {
+        return R.ok(qrCodeScanRecordService.addOrEditQrCodeRecord(qrCodeScanRecordDto));
+    }
+
+    /**
+     * 浜岀淮鐮佹壂鐮佽褰曡〃鍒犻櫎
+     */
+    @DeleteMapping("/delSalesRecord")
+    @ApiOperation("浜岀淮鐮佹壂鐮佽褰曡〃鍒犻櫎")
+    public R remove(@RequestBody Long[] ids) {
+        return R.ok(qrCodeScanRecordService.delByIds(ids));
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java b/src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java
new file mode 100644
index 0000000..6b80e1b
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java
@@ -0,0 +1,58 @@
+package com.ruoyi.inspectiontask.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.R;
+import com.ruoyi.inspectiontask.dto.TimingTaskDto;
+import com.ruoyi.inspectiontask.pojo.TimingTask;
+import com.ruoyi.inspectiontask.service.TimingTaskService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.AllArgsConstructor;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:53
+ */
+@RestController
+@Api(tags = "瀹氭椂浠诲姟绠$悊")
+@RequestMapping("/timingTask")
+public class TimingTaskController extends BaseController {
+
+
+    @Autowired
+    private TimingTaskService timingTaskService;
+
+    /**
+     * 瀹氭椂宸℃浠诲姟琛ㄦ煡璇�
+     */
+    @GetMapping("/list")
+    @ApiOperation(value = "瀹氭椂浠诲姟鍒楄〃")
+    public R<IPage<TimingTaskDto>> list(Page<TimingTask> page, TimingTask timingTask) {
+        IPage<TimingTaskDto> list = timingTaskService.selectTimingTaskList(page,timingTask);
+        return R.ok(list);
+    }
+
+    /**
+     * 瀹氭椂宸℃浠诲姟琛ㄦ柊澧炰慨鏀�
+     */
+    @PostMapping("/addOrEditTimingTask")
+    @ApiOperation(value = "鏂板淇敼瀹氭椂浠诲姟")
+    public R addOrEditTimingTask(@RequestBody TimingTaskDto timingTaskDto) throws SchedulerException {
+        return R.ok(timingTaskService.addOrEditTimingTask(timingTaskDto));
+    }
+
+    /**
+     * 瀹氭椂宸℃浠诲姟琛ㄥ垹闄�
+     */
+    @DeleteMapping("/delTimingTask")
+    @ApiOperation(value = "鍒犻櫎瀹氭椂浠诲姟")
+    public R remove(@RequestBody Long[] ids) {
+        return R.ok(timingTaskService.delByIds(ids));
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java b/src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java
new file mode 100644
index 0000000..f92bde5
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/dto/InspectionTaskDto.java
@@ -0,0 +1,20 @@
+package com.ruoyi.inspectiontask.dto;
+
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.pojo.StorageAttachment;
+import com.ruoyi.inspectiontask.pojo.InspectionTask;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class InspectionTaskDto extends InspectionTask {
+
+    private List<StorageBlobDTO> storageBlobDTO;
+    private List<StorageBlobDTO> beforeProduction;
+    private List<StorageBlobDTO> afterProduction;
+    private List<StorageBlobDTO> productionIssues;
+
+    private List<StorageAttachment> attachments;
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/dto/QrCodeDto.java b/src/main/java/com/ruoyi/inspectiontask/dto/QrCodeDto.java
new file mode 100644
index 0000000..2badd86
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/dto/QrCodeDto.java
@@ -0,0 +1,8 @@
+package com.ruoyi.inspectiontask.dto;
+
+import com.ruoyi.inspectiontask.pojo.QrCode;
+import lombok.Data;
+
+@Data
+public class QrCodeDto extends QrCode {
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/dto/QrCodeScanRecordDto.java b/src/main/java/com/ruoyi/inspectiontask/dto/QrCodeScanRecordDto.java
new file mode 100644
index 0000000..4247330
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/dto/QrCodeScanRecordDto.java
@@ -0,0 +1,20 @@
+package com.ruoyi.inspectiontask.dto;
+
+
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.inspectiontask.pojo.QrCode;
+import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class QrCodeScanRecordDto extends QrCodeScanRecord {
+
+    private QrCode qrCode;
+
+    private String scanner;
+
+    private List<StorageBlobDTO> storageBlobDTO;
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java b/src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java
new file mode 100644
index 0000000..ad26f89
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/dto/TimingTaskDto.java
@@ -0,0 +1,12 @@
+package com.ruoyi.inspectiontask.dto;
+
+import com.ruoyi.inspectiontask.pojo.TimingTask;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class TimingTaskDto extends TimingTask {
+
+    private List<String> inspector;
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java b/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java
new file mode 100644
index 0000000..9c79059
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/mapper/InspectionTaskMapper.java
@@ -0,0 +1,11 @@
+package com.ruoyi.inspectiontask.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.inspectiontask.pojo.InspectionTask;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:46
+ */
+public interface InspectionTaskMapper extends BaseMapper<InspectionTask> {
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeMapper.java b/src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeMapper.java
new file mode 100644
index 0000000..e2a9c6b
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeMapper.java
@@ -0,0 +1,11 @@
+package com.ruoyi.inspectiontask.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.inspectiontask.pojo.QrCode;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:46
+ */
+public interface QrCodeMapper extends BaseMapper<QrCode> {
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeScanRecordMapper.java b/src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeScanRecordMapper.java
new file mode 100644
index 0000000..595a1f5
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/mapper/QrCodeScanRecordMapper.java
@@ -0,0 +1,11 @@
+package com.ruoyi.inspectiontask.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:46
+ */
+public interface QrCodeScanRecordMapper extends BaseMapper<QrCodeScanRecord> {
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskMapper.java b/src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskMapper.java
new file mode 100644
index 0000000..94e41e8
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/mapper/TimingTaskMapper.java
@@ -0,0 +1,11 @@
+package com.ruoyi.inspectiontask.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.inspectiontask.pojo.TimingTask;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:47
+ */
+public interface TimingTaskMapper extends BaseMapper<TimingTask> {
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
new file mode 100644
index 0000000..74fd9d2
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
@@ -0,0 +1,81 @@
+package com.ruoyi.inspectiontask.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:25
+ */
+@Data
+@ApiModel
+@TableName("inspection_task")
+public class InspectionTask {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 宸℃浠诲姟鍞竴鏍囪瘑
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "宸℃浠诲姟鍚嶇О")
+    private String taskName;
+
+    @ApiModelProperty(value = "宸℃浜篒D")
+    private String inspectorId;
+
+    @ApiModelProperty(value = "鎵ц宸℃鐨勪汉鍛樺鍚�")
+    private String inspector;
+
+    @ApiModelProperty(value = "浠诲姟闄勫姞璇存槑鎴栫壒娈婃儏鍐佃褰�")
+    private String remarks;
+
+    @ApiModelProperty(value = "浠诲姟鐧昏浜篒D")
+    private Long registrantId;
+
+    @ApiModelProperty(value = "浠诲姟鐧昏浜哄鍚�")
+    private String registrant;
+
+    @ApiModelProperty(value = "棰戞")
+    private String frequencyType;
+
+    @ApiModelProperty(value = "鏃堕棿缁嗚妭")
+    private String frequencyDetail;
+
+    @ApiModelProperty(value = "宸℃鍦扮偣璇︾粏鎻忚堪")
+    private String inspectionLocation;
+
+    @ApiModelProperty(value = "杞垹闄ゆ爣蹇楋紝0=鏈垹闄わ紝1=宸插垹闄�")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "鍒涘缓璇ヨ褰曠殑鐢ㄦ埛")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    private String createUser;
+
+    @ApiModelProperty(value = "璁板綍鍒涘缓鏃堕棿")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty(value = "鏈�鍚庝慨鏀硅璁板綍鐨勭敤鎴�")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    private String updateUser;
+
+    @ApiModelProperty(value = "璁板綍鏈�鍚庢洿鏂版椂闂�")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    @ApiModelProperty(value = "绉熸埛")
+    @TableField(fill = FieldFill.INSERT)
+    private Integer tenantId;
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/QrCode.java b/src/main/java/com/ruoyi/inspectiontask/pojo/QrCode.java
new file mode 100644
index 0000000..be2abbf
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/QrCode.java
@@ -0,0 +1,63 @@
+package com.ruoyi.inspectiontask.pojo;
+
+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 com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:29
+ */
+@Data
+@ApiModel
+@TableName("qr_code")
+public class QrCode {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 浜岀淮鐮佸敮涓�鏍囪瘑
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "璁惧鍚嶇О")
+    private String deviceName;
+
+    @ApiModelProperty(value = "鎵�鍦ㄤ綅缃弿杩�")
+    private String location;
+
+    @ApiModelProperty(value = "绉熸埛ID锛岀敤浜庡绉熸埛闅旂")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    private Integer tenantId;
+
+    @ApiModelProperty(value = "杞垹闄ゆ爣蹇楋紝0=鏈垹闄わ紝1=宸插垹闄�")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "鍒涘缓璇ヨ褰曠殑鐢ㄦ埛")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    private Integer createUser;
+
+    @ApiModelProperty(value = "璁板綍鍒涘缓鏃堕棿")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty(value = "鏈�鍚庝慨鏀硅璁板綍鐨勭敤鎴�")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+
+    @ApiModelProperty(value = "璁板綍鏈�鍚庢洿鏂版椂闂�")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/QrCodeScanRecord.java b/src/main/java/com/ruoyi/inspectiontask/pojo/QrCodeScanRecord.java
new file mode 100644
index 0000000..fa00d05
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/QrCodeScanRecord.java
@@ -0,0 +1,68 @@
+package com.ruoyi.inspectiontask.pojo;
+
+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 com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:29
+ */
+@Data
+@TableName("qr_code_scan_record")
+@ApiModel
+public class QrCodeScanRecord {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 鎵爜璁板綍鍞竴鏍囪瘑
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "鍏宠仈鐨勪簩缁寸爜ID")
+    private Long qrCodeId;
+
+    @ApiModelProperty(value = "鎵爜浜虹敤鎴稩D")
+    private Long scannerId;
+
+    @ApiModelProperty(value = "瀹為檯鎵爜鏃堕棿")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime scanTime;
+
+    @ApiModelProperty(value = "绉熸埛ID锛岀敤浜庡绉熸埛闅旂")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    private Integer tenantId;
+
+    @ApiModelProperty(value = "杞垹闄ゆ爣蹇楋紝0=鏈垹闄わ紝1=宸插垹闄�")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "鍒涘缓璇ヨ褰曠殑鐢ㄦ埛")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    private Integer createUser;
+
+    @ApiModelProperty(value = "璁板綍鍒涘缓鏃堕棿")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty(value = "鏈�鍚庝慨鏀硅璁板綍鐨勭敤鎴�")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    private Integer updateBy;
+
+    @ApiModelProperty(value = "璁板綍鏈�鍚庢洿鏂版椂闂�")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
new file mode 100644
index 0000000..41099ea
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
@@ -0,0 +1,97 @@
+package com.ruoyi.inspectiontask.pojo;
+
+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 com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:27
+ */
+@Data
+@ApiModel
+@TableName("timing_task")
+public class TimingTask {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 涓婚敭ID
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "浠诲姟鍚嶇О")
+    private String taskName;
+
+    @ApiModelProperty(value = "宸℃浜�")
+    private String inspectorIds;
+
+    @ApiModelProperty(value = "宸℃鍦扮偣")
+    private String inspectionLocation;
+
+    @ApiModelProperty(value = "棰戞")
+    private String frequencyType;
+
+    @ApiModelProperty(value = "棰戞璇︽儏")
+    private String frequencyDetail;
+
+    @ApiModelProperty(value = "涓嬫鎵ц鏃堕棿")
+    private LocalDateTime nextExecutionTime;
+
+    @ApiModelProperty(value = "鏈�鍚庢墽琛屾椂闂�")
+    private LocalDateTime lastExecutionTime;
+
+    @ApiModelProperty(value = "鏄惁婵�娲�")
+    private boolean isActive;
+
+    @ApiModelProperty(value = "澶囨敞")
+    private String remarks;
+
+    @ApiModelProperty(value = "鐧昏浜篿d")
+    private Long registrantId;
+
+    @ApiModelProperty(value = "鐧昏浜�")
+    private String registrant;
+
+    @ApiModelProperty(value = "鐧昏鏃ユ湡")
+    private LocalDate registrationDate;
+
+    @ApiModelProperty(value = "鐘舵��")
+    private String status;
+
+    @ApiModelProperty(value = "杞垹闄ゆ爣蹇楋紝0=鏈垹闄わ紝1=宸插垹闄�")
+    private Integer deleted;
+
+    @ApiModelProperty(value = "鍒涘缓璇ヨ褰曠殑鐢ㄦ埛")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    private Integer createUser;
+
+    @ApiModelProperty(value = "璁板綍鍒涘缓鏃堕棿")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    @ApiModelProperty(value = "鏈�鍚庝慨鏀硅璁板綍鐨勭敤鎴�")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+
+    @ApiModelProperty(value = "璁板綍鏈�鍚庢洿鏂版椂闂�")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
+    private LocalDateTime updateTime;
+
+    @ApiModelProperty(value = "绉熸埛ID")
+    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
+    private Integer tenantId;
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java b/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
new file mode 100644
index 0000000..de175f2
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/InspectionTaskService.java
@@ -0,0 +1,20 @@
+package com.ruoyi.inspectiontask.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
+import com.ruoyi.inspectiontask.pojo.InspectionTask;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:49
+ */
+public interface InspectionTaskService extends IService<InspectionTask> {
+
+    IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto);
+
+    int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto);
+
+    int delByIds(Long[] ids);
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/QrCodeScanRecordService.java b/src/main/java/com/ruoyi/inspectiontask/service/QrCodeScanRecordService.java
new file mode 100644
index 0000000..8d3cbab
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/QrCodeScanRecordService.java
@@ -0,0 +1,20 @@
+package com.ruoyi.inspectiontask.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.inspectiontask.dto.QrCodeScanRecordDto;
+import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:50
+ */
+public interface QrCodeScanRecordService extends IService<QrCodeScanRecord> {
+
+    IPage<QrCodeScanRecordDto> selectQrCodeScanRecordList(Page<QrCodeScanRecord> page, QrCodeScanRecordDto qrCodeScanRecordDto);
+
+    int addOrEditQrCodeRecord(QrCodeScanRecordDto qrCodeScanRecordDto);
+
+    int delByIds(Long[] ids);
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/QrCodeService.java b/src/main/java/com/ruoyi/inspectiontask/service/QrCodeService.java
new file mode 100644
index 0000000..0eeecd2
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/QrCodeService.java
@@ -0,0 +1,19 @@
+package com.ruoyi.inspectiontask.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.inspectiontask.dto.QrCodeDto;
+import com.ruoyi.inspectiontask.pojo.QrCode;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:50
+ */
+public interface QrCodeService extends IService<QrCode>{
+    IPage<QrCode> selectQrCodeList(Page page, QrCodeDto qrCodeDto);
+
+    Long addOrEditQrCode(QrCodeDto qrCodeDto);
+
+    int delByIds(Long[] ids);
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java b/src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java
new file mode 100644
index 0000000..8d00032
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/TimingTaskService.java
@@ -0,0 +1,23 @@
+package com.ruoyi.inspectiontask.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.inspectiontask.dto.TimingTaskDto;
+import com.ruoyi.inspectiontask.pojo.TimingTask;
+import org.quartz.SchedulerException;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:51
+ */
+public interface TimingTaskService extends IService<TimingTask> {
+
+    IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask);
+
+    int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException;
+
+    int delByIds(Long[] ids);
+
+    void updateTaskExecutionTime(Long taskId);
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
new file mode 100644
index 0000000..26e3bdd
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -0,0 +1,257 @@
+package com.ruoyi.inspectiontask.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.mapper.StorageAttachmentMapper;
+import com.ruoyi.basic.mapper.StorageBlobMapper;
+import com.ruoyi.basic.pojo.StorageAttachment;
+import com.ruoyi.basic.pojo.StorageBlob;
+import com.ruoyi.basic.service.StorageAttachmentService;
+import com.ruoyi.common.utils.MinioUtils;
+import com.ruoyi.common.utils.SecurityUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
+import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
+import com.ruoyi.inspectiontask.pojo.InspectionTask;
+import com.ruoyi.inspectiontask.service.InspectionTaskService;
+import com.ruoyi.project.system.domain.SysUser;
+import com.ruoyi.project.system.mapper.SysUserMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static com.ruoyi.common.constant.StorageAttachmentConstants.StorageAttachmentFile;
+import static com.ruoyi.common.enums.StorageAttachmentRecordType.InspectionTasks;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:54
+ */
+@Service
+@Slf4j
+public class InspectionTaskServiceImpl extends ServiceImpl<InspectionTaskMapper, InspectionTask> implements InspectionTaskService {
+
+
+    @Autowired
+    private InspectionTaskMapper inspectionTaskMapper;
+
+    @Autowired
+    private StorageAttachmentService storageAttachmentService;
+
+    @Autowired
+    private StorageBlobMapper storageBlobMapper;
+
+    @Autowired
+    private StorageAttachmentMapper storageAttachmentMapper;
+
+    @Autowired
+    private MinioUtils minioUtils;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Override
+    public IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto) {
+        LambdaQueryWrapper<InspectionTask> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.orderByDesc(InspectionTask::getCreateTime);
+        IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
+
+        //  鏃犳暟鎹彁鍓嶈繑鍥�
+        if (CollectionUtils.isEmpty(entityPage.getRecords())) {
+            return new Page<>(entityPage.getCurrent(), entityPage.getSize(), entityPage.getTotal());
+        }
+        // 鑾峰彇id闆嗗悎
+        List<Long> ids = entityPage.getRecords().stream().map(InspectionTask::getId).toList();
+        //鐧昏浜篿ds
+        List<Long> registrantIds = entityPage.getRecords().stream().map(InspectionTask::getRegistrantId).toList();
+        // 鎵归噺鏌ヨ鐧昏浜�
+        Map<Long, SysUser> sysUserMap;
+        if (!registrantIds.isEmpty()) {
+            List<SysUser> sysUsers = sysUserMapper.selectList(registrantIds);
+            sysUserMap = sysUsers.stream().collect(Collectors.toMap(SysUser::getUserId, Function.identity()));
+        } else {
+            sysUserMap = new HashMap<>();
+        }
+        //宸℃浜篿ds
+        List<String> inspectorIds = entityPage.getRecords().stream().map(InspectionTask::getInspectorId).toList();
+
+        //鑾峰彇鎵�鏈変笉閲嶅鐨勭敤鎴稩D
+        Set<Long> allUserIds = entityPage.getRecords().stream()
+                .map(InspectionTask::getInspectorId) // 鑾峰彇"2,3"杩欐牱鐨勫瓧绗︿覆
+                .filter(StringUtils::isNotBlank)
+                .flatMap(idsStr -> Arrays.stream(idsStr.split(",")))
+                .map(idStr -> {
+                    try {
+                        return Long.parseLong(idStr.trim());
+                    } catch (NumberFormatException e) {
+                        return null;
+                    }
+                })
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        // 浣跨敤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));
+
+        //澶勭悊闄勪欢
+        Map<Long, List<StorageAttachment>> attachmentsMap = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>().in(StorageAttachment::getRecordId, ids)
+                        .eq(StorageAttachment::getRecordType, InspectionTasks.ordinal()))
+                .stream()
+                .collect(Collectors.groupingBy(StorageAttachment::getRecordId));
+        //  鎵归噺鏌ヨ鎵�鏈夐渶瑕佺殑鏂囦欢鏁版嵁
+        Set<Long> blobIds = attachmentsMap.values()
+                .stream()
+                .flatMap(List::stream)
+                .map(StorageAttachment::getStorageBlobId)
+                .collect(Collectors.toSet());
+        Map<Long, StorageBlob> blobMap = blobIds.isEmpty()
+                ? Collections.emptyMap()
+                : storageBlobMapper.selectList(new LambdaQueryWrapper<StorageBlob>().in(StorageBlob::getId, blobIds))
+                .stream()
+                .collect(Collectors.toMap(StorageBlob::getId, Function.identity()));
+
+        List<InspectionTaskDto> dtoList = entityPage.getRecords().stream().map(inspectionTask -> {
+            InspectionTaskDto dto = new InspectionTaskDto();
+            BeanUtils.copyProperties(inspectionTask, dto);  // 澶嶅埗涓诲璞″睘鎬�
+
+            // 璁剧疆鐧昏浜�
+            SysUser sysUser = sysUserMap.get(inspectionTask.getRegistrantId());
+            if (sysUser != null) {
+                dto.setRegistrant(sysUser.getNickName());
+            }
+            // 澶勭悊宸℃浜哄悕绉�
+            if (StringUtils.isNotBlank(inspectionTask.getInspectorId())) {
+                String inspectorNames = Arrays.stream(inspectionTask.getInspectorId().split(","))
+                        .map(String::trim)
+                        .map(idStr -> {
+                            try {
+                                Long userId = Long.parseLong(idStr);
+                                return userIdToNameMap.getOrDefault(userId, "鏈煡鐢ㄦ埛(" + idStr + ")");
+                            } catch (NumberFormatException e) {
+                                return "鏃犳晥ID(" + idStr + ")";
+                            }
+                        })
+                        .collect(Collectors.joining(","));
+                dto.setInspector(inspectorNames);
+            }
+
+            // 鍒濆鍖栦笁涓檮浠跺垪琛�
+            dto.setBeforeProduction(new ArrayList<>());
+            dto.setAfterProduction(new ArrayList<>());
+            dto.setProductionIssues(new ArrayList<>());
+
+            // 澶勭悊闄勪欢鍒嗙被
+            Optional.ofNullable(attachmentsMap.get(inspectionTask.getId()))
+                    .orElse(Collections.emptyList())
+                    .forEach(attachment -> {
+                        StorageBlob blob = blobMap.get(attachment.getStorageBlobId());
+                        if (blob != null) {
+                            // 鍒涘缓闄勪欢DTO
+                            StorageBlobDTO blobDto = createBlobDto(blob);
+
+                            // 鏍规嵁type鍒嗙被
+                            switch ((int) blob.getType().longValue()) {
+                                case 0:
+                                    dto.getBeforeProduction().add(blobDto);
+                                    break;
+                                case 1:
+                                    dto.getAfterProduction().add(blobDto);
+                                    break;
+                                case 2:
+                                    dto.getProductionIssues().add(blobDto);
+                                    break;
+                                default:
+                                    // 鍙�夛細璁板綍鏈垎绫荤被鍨�
+                                    break;
+                            }
+                        }
+                    });
+
+            return dto;
+        }).collect(Collectors.toList());
+
+        // 7. 鏋勫缓杩斿洖鍒嗛〉瀵硅薄
+        IPage<InspectionTaskDto> resultPage = new Page<>();
+        BeanUtils.copyProperties(entityPage, resultPage);
+        resultPage.setRecords(dtoList);
+        return resultPage;
+    }
+
+    // 鎻愬彇鍒涘缓BlobDTO鐨勫叕鍏辨柟娉�
+    private StorageBlobDTO createBlobDto(StorageBlob blob) {
+        StorageBlobDTO dto = new StorageBlobDTO();
+        BeanUtils.copyProperties(blob, dto);
+
+        // 璁剧疆URL
+        dto.setUrl(minioUtils.getPreviewUrls(
+                blob.getBucketFilename(),
+                blob.getBucketName(),
+                true
+        ));
+
+        // 璁剧疆涓嬭浇URL
+        dto.setDownloadUrl(minioUtils.getDownloadUrls(
+                blob.getBucketFilename(),
+                blob.getBucketName(),
+                blob.getOriginalFilename(),
+                true
+        ));
+        return dto;
+    }
+
+    @Override
+    public int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto) {
+        InspectionTask inspectionTask = new InspectionTask();
+        BeanUtils.copyProperties(inspectionTaskDto, inspectionTask);
+        inspectionTask.setRegistrantId(SecurityUtils.getLoginUser().getUserId());
+        inspectionTask.setRegistrant(SecurityUtils.getLoginUser().getUsername());
+        int i;
+        if (Objects.isNull(inspectionTaskDto.getId())) {
+            i = inspectionTaskMapper.insert(inspectionTask);
+        } else {
+            i = inspectionTaskMapper.updateById(inspectionTask);
+        }
+
+        if (inspectionTaskDto.getStorageBlobDTO() != null && !inspectionTaskDto.getStorageBlobDTO().isEmpty()) {
+            List<StorageAttachment> attachments = new ArrayList<>();
+
+            for (StorageBlobDTO storageBlobDTO : inspectionTaskDto.getStorageBlobDTO()) {
+                StorageAttachment storageAttachment = new StorageAttachment(
+                        StorageAttachmentFile,
+                        (long) InspectionTasks.ordinal(),
+                        inspectionTask.getId()
+                );
+                storageAttachment.setStorageBlobDTO(storageBlobDTO);
+                attachments.add(storageAttachment);
+            }
+            storageAttachmentService.saveStorageAttachment(attachments, inspectionTask.getId(), InspectionTasks, StorageAttachmentFile);
+        }
+        return i;
+    }
+
+    @Override
+    public int delByIds(Long[] ids) {
+        // 妫�鏌ュ弬鏁�
+        if (ids == null || ids.length == 0) {
+            return 0;
+        }
+        return inspectionTaskMapper.deleteBatchIds(Arrays.asList(ids));
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java
new file mode 100644
index 0000000..5ac3038
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeScanRecordServiceImpl.java
@@ -0,0 +1,194 @@
+package com.ruoyi.inspectiontask.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.dto.StorageBlobDTO;
+import com.ruoyi.basic.mapper.StorageAttachmentMapper;
+import com.ruoyi.basic.mapper.StorageBlobMapper;
+import com.ruoyi.basic.pojo.StorageAttachment;
+import com.ruoyi.basic.pojo.StorageBlob;
+import com.ruoyi.basic.service.StorageAttachmentService;
+import com.ruoyi.common.constant.StorageAttachmentConstants;
+import com.ruoyi.common.utils.MinioUtils;
+import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.inspectiontask.dto.QrCodeScanRecordDto;
+import com.ruoyi.inspectiontask.mapper.QrCodeMapper;
+import com.ruoyi.inspectiontask.mapper.QrCodeScanRecordMapper;
+import com.ruoyi.inspectiontask.pojo.QrCode;
+import com.ruoyi.inspectiontask.pojo.QrCodeScanRecord;
+import com.ruoyi.inspectiontask.service.QrCodeScanRecordService;
+import com.ruoyi.project.system.domain.SysUser;
+import com.ruoyi.project.system.mapper.SysUserMapper;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+import static com.ruoyi.common.constant.StorageAttachmentConstants.StorageAttachmentFile;
+import static com.ruoyi.common.enums.StorageAttachmentRecordType.QrCodeScanRecords;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:54
+ */
+@Service
+@Slf4j
+public class QrCodeScanRecordServiceImpl extends ServiceImpl<QrCodeScanRecordMapper, QrCodeScanRecord> implements QrCodeScanRecordService {
+
+    @Autowired
+    private QrCodeScanRecordMapper qrCodeScanRecordMapper;
+
+    @Autowired
+    private QrCodeMapper qrCodeMapper;
+
+    @Autowired
+    private StorageAttachmentService storageAttachmentService;
+
+    @Autowired
+    private StorageBlobMapper storageBlobMapper;
+
+    @Autowired
+    private StorageAttachmentMapper storageAttachmentMapper;
+
+    @Autowired
+    private MinioUtils minioUtils;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+    @Override
+    public IPage<QrCodeScanRecordDto> selectQrCodeScanRecordList(Page<QrCodeScanRecord> page, QrCodeScanRecordDto qrCodeScanRecordDto) {
+        // 1. 鏋勫缓鍩虹鏌ヨ鏉′欢
+        LambdaQueryWrapper<QrCodeScanRecord> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.orderByDesc(QrCodeScanRecord::getCreateTime);
+
+        // 2. 鎵ц鍒嗛〉鏌ヨ
+        IPage<QrCodeScanRecord> scanRecordIPage = qrCodeScanRecordMapper.selectPage(page, queryWrapper);
+
+        // 3. 鏃犳暟鎹彁鍓嶈繑鍥�
+        if (CollectionUtils.isEmpty(scanRecordIPage.getRecords())) {
+            return new Page<>(scanRecordIPage.getCurrent(), scanRecordIPage.getSize(), scanRecordIPage.getTotal());
+        }
+
+        // 4. 鎵归噺鑾峰彇鎵�鏈夎褰旾D鍜屼簩缁寸爜ID
+        List<Long> recordIds = scanRecordIPage.getRecords().stream()
+                .map(QrCodeScanRecord::getId)
+                .collect(Collectors.toList());
+
+        Set<Long> qrCodeIds = scanRecordIPage.getRecords().stream()
+                .map(QrCodeScanRecord::getQrCodeId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+        // 5. 鎵归噺鏌ヨ鍏宠仈鏁版嵁锛堜娇鐢ㄦ渶鏂癆PI锛�
+        // 5.1 鏌ヨ浜岀淮鐮佷俊鎭紙鏇挎崲selectBatchIds涓簊electByIds锛�
+        Map<Long, QrCode> qrCodeMap = qrCodeIds.isEmpty()
+                ? Collections.emptyMap()
+                : qrCodeMapper.selectBatchIds(qrCodeIds).stream()
+                .collect(Collectors.toMap(QrCode::getId, Function.identity()));
+
+        // 5.2 鏌ヨ闄勪欢鍏宠仈鍏崇郴
+        Map<Long, List<StorageAttachment>> attachmentsMap = storageAttachmentMapper
+                .selectList(new LambdaQueryWrapper<StorageAttachment>()
+                        .in(StorageAttachment::getRecordId, recordIds)
+                        .eq(StorageAttachment::getRecordType, QrCodeScanRecords.ordinal()))
+                .stream()
+                .collect(Collectors.groupingBy(StorageAttachment::getRecordId));
+
+        // 5.3 鏌ヨ鏂囦欢鏁版嵁锛堜娇鐢╯electByIds锛�
+        Set<Long> blobIds = attachmentsMap.values().stream()
+                .flatMap(List::stream)
+                .map(StorageAttachment::getStorageBlobId)
+                .collect(Collectors.toSet());
+
+        Map<Long, StorageBlob> blobMap = blobIds.isEmpty()
+                ? Collections.emptyMap()
+                : storageBlobMapper.selectBatchIds(blobIds).stream()
+                .collect(Collectors.toMap(StorageBlob::getId, Function.identity()));
+
+        // 6. 缁勮DTO鏁版嵁
+        List<QrCodeScanRecordDto> dtoList = scanRecordIPage.getRecords().stream().map(record -> {
+            QrCodeScanRecordDto dto = new QrCodeScanRecordDto();
+            BeanUtils.copyProperties(record, dto);
+
+            SysUser sysUser = sysUserMapper.selectUserById(record.getScannerId());
+            dto.setScanner(sysUser.getNickName());
+
+            // 6.1 璁剧疆浜岀淮鐮佷俊鎭�
+            Optional.ofNullable(qrCodeMap.get(record.getQrCodeId()))
+                    .ifPresent(qrCode -> {
+                        BeanUtils.copyProperties(qrCode, dto); // 澶嶅埗鍒扮埗绫�
+                        dto.setQrCode(qrCode); // 璁剧疆瀹屾暣瀵硅薄
+                    });
+
+            // 6.2 璁剧疆闄勪欢淇℃伅
+            dto.setStorageBlobDTO(
+                    Optional.ofNullable(attachmentsMap.get(record.getId()))
+                            .orElse(Collections.emptyList())
+                            .stream()
+                            .map(att -> {
+                                StorageBlobDTO blobDTO = new StorageBlobDTO();
+                                Optional.ofNullable(blobMap.get(att.getStorageBlobId()))
+                                        .ifPresent(blob -> {
+                                            BeanUtils.copyProperties(blob, blobDTO);
+                                            blobDTO.setUrl(minioUtils.getPreviewUrls(blob.getBucketFilename(), blob.getBucketName(), true));
+                                            blobDTO.setDownloadUrl(minioUtils.getDownloadUrls(blob.getBucketFilename(),blob.getBucketName(),blob.getOriginalFilename(),true));
+                                        });
+                                return blobDTO;
+                            })
+                            .filter(blobDTO -> blobDTO.getId() != null) // 杩囨护鏃犳晥闄勪欢
+                            .collect(Collectors.toList())
+            );
+            return dto;
+        }).collect(Collectors.toList());
+
+        // 7. 鏋勫缓杩斿洖鍒嗛〉瀵硅薄
+        IPage<QrCodeScanRecordDto> resultPage = new Page<>();
+        BeanUtils.copyProperties(scanRecordIPage, resultPage);
+        resultPage.setRecords(dtoList);
+
+        return resultPage;
+    }
+
+    @Override
+    public int addOrEditQrCodeRecord(QrCodeScanRecordDto qrCodeScanRecordDto) {
+        QrCodeScanRecord qrCodeScanRecord = new QrCodeScanRecord();
+        BeanUtils.copyProperties(qrCodeScanRecordDto, qrCodeScanRecord);
+        int i;
+        if (Objects.isNull(qrCodeScanRecordDto.getId())) {
+            i = qrCodeScanRecordMapper.insert(qrCodeScanRecord);
+        } else {
+            i = qrCodeScanRecordMapper.updateById(qrCodeScanRecord);
+        }
+
+        if (qrCodeScanRecordDto.getStorageBlobDTO() != null && !qrCodeScanRecordDto.getStorageBlobDTO().isEmpty()) {
+            List<StorageAttachment> attachments = new ArrayList<>();
+
+            for (StorageBlobDTO storageBlobDTO : qrCodeScanRecordDto.getStorageBlobDTO()) {
+                StorageAttachment storageAttachment = new StorageAttachment(
+                        StorageAttachmentFile,
+                        (long) QrCodeScanRecords.ordinal(),
+                        qrCodeScanRecord.getId()
+                );
+                storageAttachment.setStorageBlobDTO(storageBlobDTO);
+                attachments.add(storageAttachment);
+            }
+            storageAttachmentService.saveStorageAttachment(attachments, qrCodeScanRecord.getId(), QrCodeScanRecords, StorageAttachmentFile);
+        }
+        return i;
+    }
+
+    @Override
+    public int delByIds(Long[] ids) {
+        return qrCodeScanRecordMapper.deleteBatchIds(Arrays.asList(ids));
+    }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeServiceImpl.java
new file mode 100644
index 0000000..e88a5d1
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/QrCodeServiceImpl.java
@@ -0,0 +1,54 @@
+package com.ruoyi.inspectiontask.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+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.utils.bean.BeanUtils;
+import com.ruoyi.inspectiontask.dto.QrCodeDto;
+import com.ruoyi.inspectiontask.mapper.QrCodeMapper;
+import com.ruoyi.inspectiontask.pojo.QrCode;
+import com.ruoyi.inspectiontask.service.QrCodeService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:55
+ */
+@Service
+@Slf4j
+public class QrCodeServiceImpl extends ServiceImpl<QrCodeMapper, QrCode> implements QrCodeService {
+
+    @Autowired
+    private QrCodeMapper qrCodeMapper;
+
+    @Override
+    public IPage<QrCode> selectQrCodeList(Page page, QrCodeDto qrCodeDto) {
+        LambdaQueryWrapper<QrCode> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.orderByDesc(QrCode::getCreateTime);
+        return qrCodeMapper.selectPage(page, queryWrapper);
+    }
+
+    @Override
+    public Long addOrEditQrCode(QrCodeDto qrCodeDto) {
+        QrCode qrCode = new QrCode();
+        BeanUtils.copyProperties(qrCodeDto, qrCode);
+        if (Objects.isNull(qrCodeDto.getId())) {
+            qrCodeMapper.insert(qrCode);
+        } else {
+            qrCodeMapper.updateById(qrCode);
+        }
+        return qrCode.getId();
+    }
+
+    @Override
+    public int delByIds(Long[] ids) {
+        return qrCodeMapper.deleteBatchIds(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
new file mode 100644
index 0000000..efcd90f
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
@@ -0,0 +1,65 @@
+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.ruoyi.inspectiontask.service.TimingTaskService;
+import org.quartz.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+@Component
+@DisallowConcurrentExecution // 绂佹骞跺彂鎵ц鍚屼竴涓狫ob
+public class TimingTaskJob implements Job {
+
+    @Autowired
+    private
+    TimingTaskService timingTaskService;
+
+    @Autowired
+    private InspectionTaskMapper inspectionTaskMapper;
+
+    @Override
+    public void execute(JobExecutionContext context) throws JobExecutionException {
+        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
+        Long taskId = jobDataMap.getLong("taskId");
+
+        try {
+            // 1. 鑾峰彇瀹氭椂浠诲姟璇︽儏
+            TimingTask timingTask = timingTaskService.getById(taskId);
+            if (timingTask == null || !timingTask.isActive()) {
+                return;
+            }
+
+            // 2. 鍒涘缓骞朵繚瀛樺贰妫�浠诲姟璁板綍 - 杩欏氨鏄偍鎻愪緵鐨勪唬鐮佸簲璇ユ斁鐨勪綅缃�
+            InspectionTask inspectionTask = createInspectionTask(timingTask);
+            inspectionTaskMapper.insert(inspectionTask);
+
+            // 3. 鏇存柊瀹氭椂浠诲姟鐨勬墽琛屾椂闂�
+            timingTaskService.updateTaskExecutionTime(taskId);
+
+            // 4. 璁板綍鎵ц鏃ュ織
+//            timingTaskService.recordExecutionLog(taskId, true, "浠诲姟鎵ц鎴愬姛锛岀敓鎴愬贰妫�浠诲姟ID: " + inspectionTask.getId());
+
+        } catch (Exception e) {
+//            timingTaskService.recordExecutionLog(taskId, false, "浠诲姟鎵ц澶辫触: " + e.getMessage());
+            throw new JobExecutionException(e);
+        }
+    }
+
+    // 杩欏氨鏄偍鎻愪緵鐨勪唬鐮佸皝瑁呮垚鐨勬柟娉�
+    private InspectionTask createInspectionTask(TimingTask timingTask) {
+        InspectionTask inspectionTask = new InspectionTask();
+
+        // 澶嶅埗鍩烘湰灞炴��
+        inspectionTask.setTaskName(timingTask.getTaskName());
+        inspectionTask.setInspectorId(timingTask.getInspectorIds());
+        inspectionTask.setInspectionLocation(timingTask.getInspectionLocation());
+        inspectionTask.setRemarks("鑷姩鐢熸垚鑷畾鏃朵换鍔D: " + timingTask.getId());
+        inspectionTask.setRegistrantId(timingTask.getRegistrantId());
+        inspectionTask.setFrequencyType(timingTask.getFrequencyType());
+        inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
+
+        return inspectionTask;
+    }
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java
new file mode 100644
index 0000000..cb63d96
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskScheduler.java
@@ -0,0 +1,234 @@
+package com.ruoyi.inspectiontask.service.impl;
+
+import com.ruoyi.inspectiontask.pojo.TimingTask;
+import org.quartz.*;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.stream.Collectors;
+
+@Service
+public class TimingTaskScheduler {
+
+    @Autowired
+    private Scheduler scheduler;
+
+    /**
+     * 娣诲姞鏂颁换鍔″埌璋冨害鍣�
+     */
+    public void scheduleTimingTask(TimingTask task) throws SchedulerException {
+        JobDetail jobDetail = buildJobDetail(task);
+        Trigger trigger = buildJobTrigger(task, jobDetail);
+        scheduler.scheduleJob(jobDetail, trigger);
+    }
+
+    /**
+     * 鏇存柊宸叉湁浠诲姟
+     */
+    public void rescheduleTimingTask(TimingTask task) throws SchedulerException {
+        TriggerKey triggerKey = new TriggerKey("trigger_" + task.getId());
+
+        // 鑾峰彇鐜版湁瑙﹀彂鍣ㄥ苟杞崲涓� CronTrigger
+        Trigger oldTrigger = scheduler.getTrigger(triggerKey);
+        if (!(oldTrigger instanceof CronTrigger)) {
+            throw new SchedulerException("Existing trigger is not a CronTrigger");
+        }
+
+        // 鏋勫缓鏂拌Е鍙戝櫒
+        Trigger newTrigger = TriggerBuilder.newTrigger()
+                .withIdentity(triggerKey)
+                .withDescription(task.getTaskName())
+                .withSchedule(CronScheduleBuilder.cronSchedule(convertToCronExpression(task)))
+                .startAt(Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant()))
+                .forJob(oldTrigger.getJobKey())
+                .build();
+
+        scheduler.rescheduleJob(triggerKey, newTrigger);
+    }
+
+    /**
+     * 鏆傚仠浠诲姟
+     */
+    public void pauseTimingTask(Long taskId) throws SchedulerException {
+        JobKey jobKey = new JobKey("timingTask_" + taskId);
+        scheduler.pauseJob(jobKey);
+    }
+
+    /**
+     * 鎭㈠浠诲姟
+     */
+    public void resumeTimingTask(Long taskId) throws SchedulerException {
+        JobKey jobKey = new JobKey("timingTask_" + taskId);
+        scheduler.resumeJob(jobKey);
+    }
+
+    /**
+     * 鍒犻櫎浠诲姟
+     */
+    public void unscheduleTimingTask(Long taskId) throws SchedulerException {
+        JobKey jobKey = new JobKey("timingTask_" + taskId);
+        scheduler.deleteJob(jobKey);
+    }
+
+    private JobDetail buildJobDetail(TimingTask task) {
+        JobDataMap jobDataMap = new JobDataMap();
+        jobDataMap.put("taskId", task.getId());
+
+        return JobBuilder.newJob(TimingTaskJob.class)
+                .withIdentity("timingTask_" + task.getId())
+                .withDescription(task.getTaskName())
+                .usingJobData(jobDataMap)
+                .storeDurably()
+                .build();
+    }
+
+    private Trigger buildJobTrigger(TimingTask task, JobDetail jobDetail) {
+        String cronExpression = convertToCronExpression(task);
+
+            TriggerBuilder<CronTrigger> triggerBuilder = TriggerBuilder.newTrigger()
+                .withIdentity("trigger_" + task.getId())
+                .withDescription(task.getTaskName())
+                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression));
+
+        if (jobDetail != null) {
+            triggerBuilder.forJob(jobDetail);
+        }
+
+        if (task.getNextExecutionTime() != null) {
+            triggerBuilder.startAt(Date.from(task.getNextExecutionTime().atZone(ZoneId.systemDefault()).toInstant()));
+        }
+
+        return triggerBuilder.build();
+    }
+
+    private String convertToCronExpression(TimingTask task) {
+        // 鍙傛暟鏍¢獙
+        if (task == null || task.getFrequencyType() == null || task.getFrequencyDetail() == null) {
+            throw new IllegalArgumentException("浠诲姟鍙傛暟涓嶈兘涓虹┖");
+        }
+
+        // 浣跨敤switch纭繚鏉′欢浜掓枼
+        return switch (task.getFrequencyType().toUpperCase()) { // 缁熶竴杞负澶у啓姣旇緝
+            case "DAILY" -> convertDailyToCron(task.getFrequencyDetail());
+            case "WEEKLY" -> convertWeeklyToCron(task.getFrequencyDetail());
+            case "MONTHLY" -> convertMonthlyToCron(task.getFrequencyDetail());
+            case "QUARTERLY" -> convertQuarterlyToCron(task.getFrequencyDetail());
+            default -> throw new IllegalArgumentException("涓嶆敮鎸佺殑棰戠巼绫诲瀷: " + task.getFrequencyType());
+        };
+    }
+
+    // 姣忔棩浠诲姟杞崲
+    private String convertDailyToCron(String frequencyDetail) {
+        LocalTime time = parseTime(frequencyDetail);
+        return String.format("0 %d %d * * ?", time.getMinute(), time.getHour());
+    }
+
+    // 姣忓懆浠诲姟杞崲
+    private String convertWeeklyToCron(String frequencyDetail) {
+        String[] parts = validateAndSplit(frequencyDetail, ",", 2);
+        String daysOfWeek = convertDayNamesToCron(parts[0]);
+        LocalTime time = parseTime(parts[1]);
+        return String.format("0 %d %d ? * %s", time.getMinute(), time.getHour(), daysOfWeek);
+    }
+
+    // 姣忔湀浠诲姟杞崲
+    private String convertMonthlyToCron(String frequencyDetail) {
+        String[] parts = validateAndSplit(frequencyDetail, ",", 2);
+        int day = validateDayOfMonth(parts[0]);
+        LocalTime time = parseTime(parts[1]);
+        return String.format("0 %d %d %d * ?", time.getMinute(), time.getHour(), day);
+    }
+
+    // 姣忓搴︿换鍔¤浆鎹�
+    private String convertQuarterlyToCron(String frequencyDetail) {
+        String[] parts = validateAndSplit(frequencyDetail, ",", 3);
+        int month = validateMonth(parts[0]);  // 楠岃瘉鏈堜唤(1-12)
+        int day = validateDayOfMonth(parts[1]);  // 楠岃瘉鏃ユ湡
+        LocalTime time = parseTime(parts[2]);  // 瑙f瀽鏃堕棿
+
+        // 璁$畻瀛e害璧峰鏈堜唤(1鏈�=1, 4鏈�=4, 7鏈�=7, 10鏈�=10)
+        int quarterStartMonth = ((month - 1) / 3) * 3 + 1;
+
+        return String.format("0 %d %d %d %d/3 ?",
+                time.getMinute(),
+                time.getHour(),
+                day,
+                quarterStartMonth);
+    }
+
+    // 鏂板楠岃瘉鏈堜唤鐨勬柟娉�(1-12)
+    private int validateMonth(String monthStr) {
+        try {
+            int month = Integer.parseInt(monthStr);
+            if (month < 1 || month > 12) {
+                throw new IllegalArgumentException("鏈堜唤蹇呴』鍦�1-12涔嬮棿");
+            }
+            return month;
+        } catch (NumberFormatException e) {
+            throw new IllegalArgumentException("鏃犳晥鐨勬湀浠芥牸寮�");
+        }
+    }
+
+    // 杈呭姪鏂规硶锛氳В鏋愭椂闂�
+    private LocalTime parseTime(String timeStr) {
+        try {
+            return LocalTime.parse(timeStr);
+        } catch (DateTimeParseException e) {
+            throw new IllegalArgumentException("鏃堕棿鏍煎紡蹇呴』涓篐H:mm", e);
+        }
+    }
+
+    // 杈呭姪鏂规硶锛氶獙璇佸苟鍒嗗壊瀛楃涓�
+    private String[] validateAndSplit(String input, String delimiter, int expectedParts) {
+        String[] parts = input.split(delimiter);
+        if (parts.length != expectedParts) {
+            throw new IllegalArgumentException(
+                    String.format("鏍煎紡閿欒锛屽簲涓�%d閮ㄥ垎鐢�'%s'鍒嗛殧", expectedParts, delimiter));
+        }
+        return parts;
+    }
+
+    // 杈呭姪鏂规硶锛氶獙璇佹湀浠戒腑鐨勬棩
+    private int validateDayOfMonth(String dayStr) {
+        int day = Integer.parseInt(dayStr);
+        if (day < 1 || day > 31) {
+            throw new IllegalArgumentException("鏃ユ湡蹇呴』鍦�1-31涔嬮棿");
+        }
+        return day;
+    }
+
+    // 杈呭姪鏂规硶锛氶獙璇佸搴︿腑鐨勬湀
+    private int validateMonthInQuarter(String monthStr) {
+        int month = Integer.parseInt(monthStr);
+        if (month < 1 || month > 3) {
+            throw new IllegalArgumentException("瀛e害鏈堜唤蹇呴』鏄�1銆�2鎴�3");
+        }
+        return month;
+    }
+
+    // 杞崲鏄熸湡鍑犲悕绉�
+    private String convertDayNamesToCron(String dayNames) {
+        return Arrays.stream(dayNames.split("\\|"))
+                .map(this::convertSingleDayName)
+                .collect(Collectors.joining(","));
+    }
+
+    // 杞崲鍗曚釜鏄熸湡鍑犲悕绉�
+    private String convertSingleDayName(String dayName) {
+        switch (dayName.toUpperCase()) {
+            case "MON": return "MON";
+            case "TUE": return "TUE";
+            case "WED": return "WED";
+            case "THU": return "THU";
+            case "FRI": return "FRI";
+            case "SAT": return "SAT";
+            case "SUN": return "SUN";
+            default: throw new IllegalArgumentException("鏃犳晥鐨勬槦鏈熷嚑: " + dayName);
+        }
+    }
+}
diff --git a/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
new file mode 100644
index 0000000..511fbd7
--- /dev/null
+++ b/src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -0,0 +1,348 @@
+package com.ruoyi.inspectiontask.service.impl;
+
+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.utils.StringUtils;
+import com.ruoyi.common.utils.bean.BeanUtils;
+import com.ruoyi.inspectiontask.dto.TimingTaskDto;
+import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
+import com.ruoyi.inspectiontask.mapper.TimingTaskMapper;
+import com.ruoyi.inspectiontask.pojo.TimingTask;
+import com.ruoyi.inspectiontask.service.TimingTaskService;
+import com.ruoyi.project.system.domain.SysUser;
+import com.ruoyi.project.system.mapper.SysUserMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.quartz.SchedulerException;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.*;
+import java.util.*;
+import java.util.stream.Collectors;
+
+/**
+ * @author :yys
+ * @date : 2025/9/19 10:55
+ */
+@Service
+@Slf4j
+public class TimingTaskServiceImpl extends ServiceImpl<TimingTaskMapper, TimingTask> implements TimingTaskService {
+
+    @Autowired
+    private TimingTaskMapper timingTaskMapper;
+
+    @Autowired
+    private InspectionTaskMapper inspectionTaskMapper;
+
+    @Autowired
+    private TimingTaskScheduler timingTaskScheduler;
+
+    @Autowired
+    private SysUserMapper sysUserMapper;
+
+
+    @Override
+    public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) {
+        // 1. 鍏堝垎椤垫煡璇㈠畾鏃朵换鍔℃暟鎹�
+        IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, null);
+
+        // 2. 濡傛灉娌℃湁鏁版嵁锛岀洿鎺ヨ繑鍥炵┖鍒嗛〉
+        if (taskPage.getRecords().isEmpty()) {
+            return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
+        }
+
+        // 3. 鏀堕泦鎵�鏈夐渶瑕佹煡璇㈢殑鐢ㄦ埛ID
+        Set<Long> userIds = new HashSet<>();
+
+        // 鏀堕泦鐧昏浜篒D
+        taskPage.getRecords().forEach(task -> {
+            if (task.getRegistrantId() != null) {
+                userIds.add(task.getRegistrantId());
+            }
+        });
+
+        // 鏀堕泦宸℃浜篒D锛堝涓狪D浠ラ�楀彿鍒嗛殧锛�
+        taskPage.getRecords().forEach(task -> {
+            if (StringUtils.isNotBlank(task.getInspectorIds())) {
+                Arrays.stream(task.getInspectorIds().split(","))
+                        .filter(StringUtils::isNotBlank)
+                        .map(Long::valueOf)
+                        .forEach(userIds::add);
+            }
+        });
+
+        // 4. 鎵归噺鏌ヨ鐢ㄦ埛淇℃伅
+        Map<Long, String> userNickNameMap = new HashMap<>();
+        if (!userIds.isEmpty()) {
+            List<SysUser> users = sysUserMapper.selectUserByIds((List<Long>) userIds);
+            users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
+        }
+
+        // 5. 杞崲涓篋TO
+        List<TimingTaskDto> dtoList = taskPage.getRecords().stream().map(task -> {
+            TimingTaskDto dto = new TimingTaskDto();
+            // 澶嶅埗鍩烘湰灞炴��
+            BeanUtils.copyProperties(task, dto);
+
+            // 璁剧疆鐧昏浜烘樀绉�
+            if (task.getRegistrantId() != null) {
+                dto.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "鏈煡鐢ㄦ埛"));
+            }
+
+            // 璁剧疆宸℃浜烘樀绉板垪琛�
+            if (StringUtils.isNotBlank(task.getInspectorIds())) {
+                List<String> inspectorNickNames = Arrays.stream(task.getInspectorIds().split(","))
+                        .filter(StringUtils::isNotBlank)
+                        .map(idStr -> {
+                            Long id = Long.valueOf(idStr);
+                            return userNickNameMap.getOrDefault(id, "鏈煡鐢ㄦ埛");
+                        })
+                        .toList();
+                dto.setInspector(inspectorNickNames);
+            }
+
+            return dto;
+        }).collect(Collectors.toList());
+
+        // 6. 鏋勫缓杩斿洖鐨勫垎椤靛璞�
+        Page<TimingTaskDto> resultPage = new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
+        resultPage.setRecords(dtoList);
+        return resultPage;
+    }
+
+    @Override
+    @Transactional
+    public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException {
+        TimingTask timingTask = new TimingTask();
+        BeanUtils.copyProperties(timingTaskDto, timingTask);
+
+        // 璁剧疆鍒涘缓浜轰俊鎭拰榛樿鍊�
+        if (Objects.isNull(timingTaskDto.getId())) {
+            timingTask.setRegistrationDate(LocalDate.now());
+            timingTask.setActive(true);
+
+            // 璁$畻棣栨鎵ц鏃堕棿
+            LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask);
+            timingTask.setNextExecutionTime(firstExecutionTime);
+
+            int result = timingTaskMapper.insert(timingTask);
+            if (result > 0) {
+                // 鏂板鎴愬姛鍚庢坊鍔犲埌璋冨害鍣�
+                timingTaskScheduler.scheduleTimingTask(timingTask);
+            }
+            return result;
+        } else {
+            int result = timingTaskMapper.updateById(timingTask);
+            if (result > 0) {
+                // 鏇存柊鎴愬姛鍚庨噸鏂拌皟搴︿换鍔�
+                timingTaskScheduler.rescheduleTimingTask(timingTask);
+            }
+            return result;
+        }
+    }
+
+    private LocalDateTime calculateFirstExecutionTime(TimingTask task) {
+        // 鏍规嵁棰戠巼绫诲瀷鍜岃鎯呰绠楅娆℃墽琛屾椂闂�
+        return switch (task.getFrequencyType()) {
+            case "DAILY" ->
+                    // 濡傛灉鏄瘡澶╂墽琛岋紝璁$畻浠婂ぉ鎴栨槑澶╃殑鍏蜂綋鏃堕棿
+                    calculateDailyFirstExecution(task.getFrequencyDetail());
+            case "WEEKLY" ->
+                    // 濡傛灉鏄瘡鍛ㄦ墽琛岋紝璁$畻涓嬪懆鐨勫叿浣撴槦鏈熷嚑
+                    calculateWeeklyFirstExecution(task.getFrequencyDetail());
+            case "MONTHLY" ->
+                    // 濡傛灉鏄瘡鏈堟墽琛岋紝璁$畻涓嬩釜鏈堢殑鍏蜂綋鏃ユ湡
+                    calculateMonthlyFirstExecution(task.getFrequencyDetail());
+            case "QUARTERLY" ->
+                    // 鑷畾涔夐鐜囷紝濡傛瘡灏忔椂銆佹瘡30鍒嗛挓绛�
+                    calculateCustomFirstExecution(task.getFrequencyDetail());
+            default -> throw new IllegalArgumentException("涓嶆敮鎸佺殑棰戠巼绫诲瀷: " + task.getFrequencyType());
+        };
+    }
+
+    private LocalDateTime calculateDailyFirstExecution(String frequencyDetail) {
+        // frequencyDetail鍙兘鏄叿浣撴椂闂达紝濡� "14:30"
+        LocalTime executionTime = LocalTime.parse(frequencyDetail);
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime todayExecution = LocalDateTime.of(now.toLocalDate(), executionTime);
+
+        // 濡傛灉浠婂ぉ鐨勬椂闂村凡杩囷紝鍒欏畨鎺掓槑澶╂墽琛�
+        return now.isBefore(todayExecution) ? todayExecution : todayExecution.plusDays(1);
+    }
+
+    private LocalDateTime calculateWeeklyFirstExecution(String frequencyDetail) {
+        return null;
+    }
+
+    private LocalDateTime calculateMonthlyFirstExecution(String frequencyDetail) {
+        return null;
+    }
+
+    private LocalDateTime calculateCustomFirstExecution(String frequencyDetail) {
+        return null;
+    }
+
+    @Override
+    @Transactional
+    public void updateTaskExecutionTime(Long taskId) {
+        TimingTask task = timingTaskMapper.selectById(taskId);
+        if (task == null) {
+            throw new RuntimeException("瀹氭椂浠诲姟涓嶅瓨鍦紝ID: " + taskId);
+        }
+
+        // 鏇存柊鏈�鍚庢墽琛屾椂闂翠负褰撳墠鏃堕棿
+        task.setLastExecutionTime(LocalDateTime.now());
+
+        // 璁$畻涓嬫鎵ц鏃堕棿
+        LocalDateTime nextExecutionTime = calculateNextExecutionTime(
+                task.getFrequencyType(),
+                task.getFrequencyDetail(),
+                LocalDateTime.now()
+        );
+        task.setNextExecutionTime(nextExecutionTime);
+
+        // 鏇存柊鏁版嵁搴�
+        timingTaskMapper.updateById(task);
+    }
+
+    /**
+     * 璁$畻涓嬫鎵ц鏃堕棿
+     */
+    private LocalDateTime calculateNextExecutionTime(String frequencyType,
+                                                     String frequencyDetail,
+                                                     LocalDateTime currentTime) {
+        try {
+            return switch (frequencyType) {
+                case "DAILY" -> calculateDailyNextTime(frequencyDetail, currentTime);
+                case "WEEKLY" -> calculateWeeklyNextTime(frequencyDetail, currentTime);
+                case "MONTHLY" -> calculateMonthlyNextTime(frequencyDetail, currentTime);
+                case "QUARTERLY" -> calculateQuarterlyNextTime(frequencyDetail, currentTime);
+                default -> throw new IllegalArgumentException("涓嶆敮鎸佺殑棰戠巼绫诲瀷: " + frequencyType);
+            };
+        } catch (Exception e) {
+            throw new RuntimeException("璁$畻涓嬫鎵ц鏃堕棿澶辫触: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 璁$畻姣忔棩浠诲姟鐨勪笅娆℃墽琛屾椂闂�
+     */
+    private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
+        LocalTime executionTime = LocalTime.parse(timeStr); // 瑙f瀽鏍煎紡 "HH:mm"
+        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
+
+        // 濡傛灉浠婂ぉ鐨勬椂闂村凡杩囷紝鍒欏畨鎺掓槑澶�
+        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
+    }
+
+    /**
+     * 璁$畻姣忓懆浠诲姟鐨勪笅娆℃墽琛屾椂闂�
+     */
+    private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
+        String[] parts = detail.split(",");
+        String dayOfWeekStr = parts[0];  // 濡� "MON" 鎴� "MON|WED|FRI"
+        LocalTime time = LocalTime.parse(parts[1]); // 鏃堕棿閮ㄥ垎
+
+        // 瑙f瀽鏄熸湡鍑�(鏀寔澶氫釜鏄熸湡)
+        Set<DayOfWeek> targetDays = parseDayOfWeeks(dayOfWeekStr);
+
+        // 浠庡綋鍓嶆椂闂村紑濮嬫壘涓嬩竴涓鍚堟潯浠剁殑鏄熸湡鍑�
+        LocalDateTime nextTime = current;
+        while (true) {
+            nextTime = nextTime.plusDays(1);
+            if (targetDays.contains(nextTime.getDayOfWeek())) {
+                return LocalDateTime.of(nextTime.toLocalDate(), time);
+            }
+
+            // 闃叉鏃犻檺寰幆(鐞嗚涓婁笉浼氬彂鐢�)
+            if (nextTime.isAfter(current.plusYears(1))) {
+                throw new RuntimeException("鏃犳硶鎵惧埌涓嬫鎵ц鏃堕棿");
+            }
+        }
+    }
+
+    /**
+     * 璁$畻姣忔湀浠诲姟鐨勪笅娆℃墽琛屾椂闂�
+     */
+    private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
+        String[] parts = detail.split(",");
+        int dayOfMonth = Integer.parseInt(parts[0]);
+        LocalTime time = LocalTime.parse(parts[1]);
+
+        // 浠庝笅涓湀寮�濮嬭绠�
+        LocalDateTime nextTime = current.plusMonths(1)
+                .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
+                .with(time);
+
+        return nextTime;
+    }
+
+    /**
+     * 璁$畻姣忓搴︿换鍔$殑涓嬫鎵ц鏃堕棿
+     */
+    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
+        String[] parts = detail.split(",");
+        int quarterMonth = Integer.parseInt(parts[0]); // 1=绗�1涓湀锛�2=绗�2涓湀锛�3=绗�3涓湀
+        int dayOfMonth = Integer.parseInt(parts[1]);
+        LocalTime time = LocalTime.parse(parts[2]);
+
+        // 璁$畻褰撳墠瀛e害
+        int currentQuarter = (current.getMonthValue() - 1) / 3 + 1;
+        int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
+
+        YearMonth targetYearMonth;
+        if (currentMonthInQuarter < quarterMonth) {
+            // 鏈搴﹀唴杩樻湁鎵ц鏈轰細
+            targetYearMonth = YearMonth.from(current)
+                    .plusMonths(quarterMonth - currentMonthInQuarter);
+        } else {
+            // 闇�瑕佸埌涓嬩釜瀛e害
+            targetYearMonth = YearMonth.from(current)
+                    .plusMonths(3 - currentMonthInQuarter + quarterMonth);
+        }
+
+        // 澶勭悊鏈堟湯鏃ユ湡
+        int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
+
+        return LocalDateTime.of(
+                targetYearMonth.getYear(),
+                targetYearMonth.getMonthValue(),
+                adjustedDay,
+                time.getHour(),
+                time.getMinute()
+        );
+    }
+
+    /**
+     * 瑙f瀽鏄熸湡鍑犲瓧绗︿覆
+     */
+    private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
+        Set<DayOfWeek> days = new HashSet<>();
+        String[] dayStrs = dayOfWeekStr.split("\\|");
+
+        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;
+                default: throw new IllegalArgumentException("鏃犳晥鐨勬槦鏈熷嚑: " + dayStr);
+            }
+        }
+
+        return days;
+    }
+
+
+
+    @Override
+    public int delByIds(Long[] ids) {
+        return timingTaskMapper.deleteBatchIds(Arrays.asList(ids));
+    }
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java b/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java
new file mode 100644
index 0000000..64d9dc8
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/controller/ProcurementPlanController.java
@@ -0,0 +1,67 @@
+package com.ruoyi.procurementrecord.controller;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.framework.web.controller.BaseController;
+import com.ruoyi.framework.web.domain.AjaxResult;
+import com.ruoyi.procurementrecord.pojo.ProcurementPlan;
+import com.ruoyi.procurementrecord.service.ProcurementPlanService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * @author :yys
+ * @date : 2025/9/18 16:13
+ */
+@RestController
+@Api(tags = "閲囪喘璁″垝")
+@RequestMapping("/procurementPlan")
+public class ProcurementPlanController extends BaseController {
+
+    @Autowired
+    private ProcurementPlanService procurementPlanService;
+
+    @RequestMapping("/listPage")
+    @ApiOperation("閲囪喘璁″垝-鏌ヨ")
+    public AjaxResult listPage(Page page, ProcurementPlan procurementPlan){
+        IPage<ProcurementPlan> result = procurementPlanService.listPage(page, procurementPlan);
+        return AjaxResult.success(result);
+    }
+
+    @PostMapping("/add")
+    @ApiOperation("閲囪喘璁″垝-娣诲姞")
+    public AjaxResult add(@RequestBody ProcurementPlan procurementPlan){
+        boolean result = procurementPlanService.save(procurementPlan);
+        return result ? AjaxResult.success() : AjaxResult.error();
+    }
+
+    @PostMapping("/update")
+    @ApiOperation("閲囪喘璁″垝-淇敼")
+    public AjaxResult update(@RequestBody ProcurementPlan procurementPlan){
+        boolean result = procurementPlanService.updateById(procurementPlan);
+        return result ? AjaxResult.success() : AjaxResult.error();
+    }
+
+    @DeleteMapping("/del")
+    @ApiOperation("閲囪喘璁″垝-鍒犻櫎")
+    public AjaxResult del(@RequestBody List<Long> ids){
+        boolean result = procurementPlanService.removeByIds(ids);
+        return result ? AjaxResult.success() : AjaxResult.error();
+    }
+
+    /**
+     * 瀵煎嚭
+     * @param response
+     */
+    @PostMapping("/export")
+    public void export(HttpServletResponse response) {
+        procurementPlanService.export(response);
+    }
+
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementPlanMapper.java b/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementPlanMapper.java
new file mode 100644
index 0000000..2745574
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/mapper/ProcurementPlanMapper.java
@@ -0,0 +1,22 @@
+package com.ruoyi.procurementrecord.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.ruoyi.procurementrecord.pojo.ProcurementPlan;
+import org.apache.ibatis.annotations.Param;
+
+/**
+ * @author :yys
+ * @date : 2025/9/18 16:10
+ */
+public interface ProcurementPlanMapper extends BaseMapper<ProcurementPlan> {
+    /**
+     * 鏌ヨ閲囪喘璁″垝鍒楄〃
+     *
+     * @param page
+     * @param procurementPlan
+     * @return
+     */
+    IPage<ProcurementPlan> listPage(Page page,@Param("req") ProcurementPlan procurementPlan);
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPlan.java b/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPlan.java
new file mode 100644
index 0000000..56892a3
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/pojo/ProcurementPlan.java
@@ -0,0 +1,105 @@
+package com.ruoyi.procurementrecord.pojo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.framework.aspectj.lang.annotation.Excel;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+/**
+ * @author :yys
+ * @date : 2025/9/18 16:00
+ */
+@Data
+@TableName("procurement_plan")
+@ApiModel
+public class ProcurementPlan {
+
+    private static final long serialVersionUID = 1L;
+    /**
+     * 搴忓彿
+     */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    @ApiModelProperty(value = "缂栫爜")
+    @Excel(name = "缂栫爜")
+    private String code;
+
+    @ApiModelProperty(value = "鍚嶇О")
+    @Excel(name = "鍚嶇О")
+    private String planName;
+
+    @ApiModelProperty(value = "鎻忚堪")
+    @Excel(name = "鎻忚堪")
+    private String description;
+
+    @ApiModelProperty(value = "鐘舵��")
+    @Excel(name = "鐘舵��", readConverterExp = "disabled=绂佺敤,active=鍚敤")
+    private String status;
+
+    @ApiModelProperty(value = "鏄惁绯荤粺棰勭疆")
+    private Boolean isSystemPreset;
+
+    @ApiModelProperty(value = "鑰冭檻鐜版湁搴撳瓨")
+    private Boolean considerExistingStock;
+
+    @ApiModelProperty(value = "浠撳簱杩愯MRP鐨勬帶鍒�")
+    private Boolean warehouseControl;
+
+    @ApiModelProperty(value = "璁$畻鎬婚渶姹�")
+    private Boolean calculateTotalDemand;
+
+    @ApiModelProperty(value = "鑰冭檻瀹夊叏搴撳瓨")
+    private Boolean considerSafetyStock;
+
+    @ApiModelProperty(value = "鑰冭檻閿佸簱")
+    private Boolean considerLockedStock;
+
+    @ApiModelProperty(value = "涓嶈�冭檻鐗╂枡杈呭姪灞炴��")
+    private Boolean notConsiderMaterialAux;
+
+    @ApiModelProperty(value = "璐熷簱瀛樹綔涓洪渶姹�")
+    private Boolean negativeStockAsDemand;
+
+    @ApiModelProperty(value = "鐗╂枡")
+    private Boolean summaryMaterial;
+
+    @ApiModelProperty(value = "杈呭姪灞炴��")
+    private Boolean summaryAuxAttributes;
+
+    @ApiModelProperty(value = "闇�姹傛棩鏈�")
+    private Boolean summaryDemandDate;
+
+    @ApiModelProperty(value = "璁$畻鍏紡")
+    @Excel(name = "璁$畻鍏紡")
+    private String formula;
+
+    @ApiModelProperty(value = "鍒涘缓鏃堕棿")
+    @TableField(fill = FieldFill.INSERT)
+    private LocalDateTime createTime;
+
+    @ApiModelProperty(value = "鍒涘缓鐢ㄦ埛")
+    @TableField(fill = FieldFill.INSERT)
+    private Integer createUser;
+
+    @ApiModelProperty(value = "淇敼鏃堕棿")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @Excel(name = "鏈�鍚庤绠楁椂闂�", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime updateTime;
+
+    @ApiModelProperty(value = "淇敼鐢ㄦ埛")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Integer updateUser;
+
+    @ApiModelProperty(value = "绉熸埛ID")
+    @TableField(fill = FieldFill.INSERT)
+    private Long tenantId;
+
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/ProcurementPlanService.java b/src/main/java/com/ruoyi/procurementrecord/service/ProcurementPlanService.java
new file mode 100644
index 0000000..e96dbe6
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/service/ProcurementPlanService.java
@@ -0,0 +1,25 @@
+package com.ruoyi.procurementrecord.service;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.procurementrecord.pojo.ProcurementPlan;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * @author :yys
+ * @date : 2025/9/18 16:11
+ */
+public interface ProcurementPlanService extends IService<ProcurementPlan> {
+
+    /**
+     * 鏌ヨ
+     * @param page
+     * @param procurementPlan
+     * @return
+     */
+    IPage<ProcurementPlan> listPage(Page page, ProcurementPlan procurementPlan);
+
+    void export(HttpServletResponse response);
+}
diff --git a/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPlanServiceImpl.java b/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPlanServiceImpl.java
new file mode 100644
index 0000000..81216db
--- /dev/null
+++ b/src/main/java/com/ruoyi/procurementrecord/service/impl/ProcurementPlanServiceImpl.java
@@ -0,0 +1,41 @@
+package com.ruoyi.procurementrecord.service.impl;
+
+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.utils.poi.ExcelUtil;
+import com.ruoyi.procurementrecord.mapper.ProcurementPlanMapper;
+import com.ruoyi.procurementrecord.pojo.ProcurementPlan;
+import com.ruoyi.procurementrecord.pojo.ProcurementPriceManagement;
+import com.ruoyi.procurementrecord.service.ProcurementPlanService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * @author :yys
+ * @date : 2025/9/18 16:12
+ */
+@Service
+@Slf4j
+public class ProcurementPlanServiceImpl extends ServiceImpl<ProcurementPlanMapper, ProcurementPlan> implements ProcurementPlanService {
+
+    @Autowired
+    private ProcurementPlanMapper procurementPlanMapper;
+
+    @Override
+    public IPage<ProcurementPlan> listPage(Page page, ProcurementPlan procurementPlan) {
+        IPage<ProcurementPlan> result = procurementPlanMapper.listPage(page, procurementPlan);
+        return result;
+    }
+
+    @Override
+    public void export(HttpServletResponse response) {
+        List<ProcurementPlan> procurementPriceManagements = procurementPlanMapper.selectList(null);
+        ExcelUtil<ProcurementPlan> util = new ExcelUtil<ProcurementPlan>(ProcurementPlan.class);
+        util.exportExcel(response, procurementPriceManagements, "閲囪喘璁″垝");
+    }
+}
diff --git a/src/main/java/com/ruoyi/project/system/mapper/SysUserMapper.java b/src/main/java/com/ruoyi/project/system/mapper/SysUserMapper.java
index 9f7003a..550fa51 100644
--- a/src/main/java/com/ruoyi/project/system/mapper/SysUserMapper.java
+++ b/src/main/java/com/ruoyi/project/system/mapper/SysUserMapper.java
@@ -1,10 +1,13 @@
 package com.ruoyi.project.system.mapper;
 
+import java.util.ArrayList;
 import java.util.List;
 
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
 import org.apache.ibatis.annotations.Param;
 
 import com.ruoyi.project.system.domain.SysUser;
+import org.springframework.beans.PropertyValues;
 
 /**
  * 鐢ㄦ埛琛� 鏁版嵁灞�
@@ -134,4 +137,8 @@
      * @return 缁撴灉
      */
     public SysUser checkEmailUnique(String email);
+
+    List<SysUser> selectList(List<Long> registrantIds);
+
+    List<SysUser> selectUsersByIds(@Param("userIds") List<Long> userIds);
 }
diff --git a/src/main/resources/mapper/procurementrecord/ProcurementPlanMapper.xml b/src/main/resources/mapper/procurementrecord/ProcurementPlanMapper.xml
new file mode 100644
index 0000000..2403bf2
--- /dev/null
+++ b/src/main/resources/mapper/procurementrecord/ProcurementPlanMapper.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.procurementrecord.mapper.ProcurementPlanMapper">
+
+    <select id="listPage" resultType="com.ruoyi.procurementrecord.pojo.ProcurementPlan">
+        SELECT * FROM procurement_plan
+        <where>
+            <if test="req.status != null and req.status != ''">
+                AND status = #{req.status}
+            </if>
+            <if test="req.planName != null and req.planName != ''">
+                AND plan_name LIKE CONCAT('%',#{req.planName},'%')
+            </if>
+        </where>
+    </select>
+</mapper>
\ No newline at end of file
diff --git a/src/main/resources/mapper/system/SysUserMapper.xml b/src/main/resources/mapper/system/SysUserMapper.xml
index e1a12c6..f77e62c 100644
--- a/src/main/resources/mapper/system/SysUserMapper.xml
+++ b/src/main/resources/mapper/system/SysUserMapper.xml
@@ -158,6 +158,28 @@
  			#{item}
         </foreach>
 	</select>
+	<select id="selectList" resultType="com.ruoyi.project.system.domain.SysUser">
+		SELECT user_id, nick_name FROM sys_user
+		<where>
+			<if test="list != null and list.size() > 0">
+				user_id IN
+				<foreach item="id" collection="list" open="(" separator="," close=")">
+					#{id}
+				</foreach>
+			</if>
+			<if test="list == null or list.size() == 0">
+				1=0  <!-- 绌哄垪琛ㄦ椂杩斿洖绌虹粨鏋� -->
+			</if>
+		</where>
+	</select>
+	<select id="selectUsersByIds" resultType="com.ruoyi.project.system.domain.SysUser">
+		SELECT user_id, nick_name
+		FROM sys_user
+		WHERE user_id IN
+		<foreach collection="userIds" item="id" open="(" separator="," close=")">
+			#{id}
+		</foreach>
+	</select>
 
 	<insert id="insertUser" parameterType="com.ruoyi.project.system.domain.SysUser" useGeneratedKeys="true" keyProperty="userId">
  		insert into sys_user(

--
Gitblit v1.9.3