Переглянути джерело

feature: 智能控制V2版本 第七版(调试)

Jayhaw 1 тиждень тому
батько
коміт
94b71f65a5
38 змінених файлів з 1243 додано та 224 видалено
  1. 363 0
      docs/定时规则使用示例.md
  2. 9 9
      src/main/java/cn/sciento/farm/automationv2/api/controller/IrrigationTaskController.java
  3. 134 6
      src/main/java/cn/sciento/farm/automationv2/api/controller/IrrigationTaskScheduleController.java
  4. 9 0
      src/main/java/cn/sciento/farm/automationv2/api/dto/BallValuesDTO.java
  5. 73 7
      src/main/java/cn/sciento/farm/automationv2/api/dto/ScheduleRuleDTO.java
  6. 1 1
      src/main/java/cn/sciento/farm/automationv2/app/handler/DeviceControlHelper.java
  7. 9 0
      src/main/java/cn/sciento/farm/automationv2/app/handler/impl/CloseGroupNodeHandler.java
  8. 3 0
      src/main/java/cn/sciento/farm/automationv2/app/handler/impl/OpenGroupNodeHandler.java
  9. 8 0
      src/main/java/cn/sciento/farm/automationv2/app/handler/impl/SetPumpPressureNodeHandler.java
  10. 19 0
      src/main/java/cn/sciento/farm/automationv2/app/handler/impl/StartPumpNodeHandler.java
  11. 9 0
      src/main/java/cn/sciento/farm/automationv2/app/handler/impl/StopPumpNodeHandler.java
  12. 27 11
      src/main/java/cn/sciento/farm/automationv2/app/job/IrrigationScheduledJob.java
  13. 21 67
      src/main/java/cn/sciento/farm/automationv2/app/service/QuartzManagementService.java
  14. 10 1
      src/main/java/cn/sciento/farm/automationv2/app/service/SafeShutdownService.java
  15. 33 20
      src/main/java/cn/sciento/farm/automationv2/app/service/TaskLogService.java
  16. 4 11
      src/main/java/cn/sciento/farm/automationv2/app/service/TaskTriggerService.java
  17. 6 5
      src/main/java/cn/sciento/farm/automationv2/app/service/irrigationTask/IrrigationTaskService.java
  18. 55 41
      src/main/java/cn/sciento/farm/automationv2/app/service/irrigationTask/impl/IrrigationTaskServiceImpl.java
  19. 37 0
      src/main/java/cn/sciento/farm/automationv2/app/service/taskSchedule/IrrigationTaskScheduleService.java
  20. 126 16
      src/main/java/cn/sciento/farm/automationv2/app/service/taskSchedule/impl/IrrigationTaskScheduleServiceImpl.java
  21. 11 0
      src/main/java/cn/sciento/farm/automationv2/config/QuartzConfig.java
  22. 9 9
      src/main/java/cn/sciento/farm/automationv2/domain/business/impl/DeviceBusinessImpl.java
  23. 128 9
      src/main/java/cn/sciento/farm/automationv2/domain/entity/TaskScheduleRule.java
  24. 6 3
      src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskLog.java
  25. 15 4
      src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskMainVO.java
  26. 5 0
      src/main/java/cn/sciento/farm/automationv2/domain/repository/TaskGroupConfigRepository.java
  27. 9 0
      src/main/java/cn/sciento/farm/automationv2/domain/repository/TaskScheduleRuleRepository.java
  28. 2 0
      src/main/java/cn/sciento/farm/automationv2/domain/service/ExecutionPlanGenerator.java
  29. 9 0
      src/main/java/cn/sciento/farm/automationv2/domain/valueobject/DeviceInfo.java
  30. 2 0
      src/main/java/cn/sciento/farm/automationv2/domain/valueobject/ExecutionNode.java
  31. 25 0
      src/main/java/cn/sciento/farm/automationv2/domain/valueobject/IrrigationTaskDetailVO.java
  32. 23 0
      src/main/java/cn/sciento/farm/automationv2/infra/init/WfautoV2DataInit.java
  33. 11 0
      src/main/java/cn/sciento/farm/automationv2/infra/repository/impl/TaskGroupConfigRepositoryImpl.java
  34. 14 0
      src/main/java/cn/sciento/farm/automationv2/infra/repository/impl/TaskScheduleRuleRepositoryImpl.java
  35. 2 2
      src/main/resources/bootstrap.yml
  36. 1 1
      src/main/resources/mapper/IrrigationGroupMapper.xml
  37. 7 0
      src/main/resources/messages/messages_wfautoV2_en_US.properties
  38. 8 1
      src/main/resources/messages/messages_wfautoV2_zh_CN.properties

+ 363 - 0
docs/定时规则使用示例.md

@@ -0,0 +1,363 @@
+ # 定时规则使用示例
+
+ ## 概述
+
+ 定时规则支持两种模式:
+  1. **CRON模式**:重复周期调度(适用于固定周期的常规灌溉计划)
+  2. **SIMPLE模式**:间隔周期调度(适用于临时性或有限次数的灌溉计划)
+
+ ## 一、CRON模式
+
+ ### 1.1 CRON模式参数说明
+
+| 参数           | 类型     | 必填 | 说明                   | 示例                  |
+| -------------- | -------- | ---- | ---------------------- | --------------------- |
+| taskId         | Long     | 是   | 关联的任务ID           | 123                   |
+| ruleName       | String   | 否   | 规则名称(用于标识)   | "每周一三五凌晨2点"   |
+| scheduleType   | String   | 是   | 定时类型,固定为"CRON" | "CRON"                |
+| executeTime    | String   | 是   | 执行时间(HH:mm:ss)   | "02:00:00"            |
+| repeatWeekdays | String   | 是   | 重复周期-星期几        | "1,3,5"               |
+| validStartDate | DateTime | 否   | 有效开始时间           | "2026-03-01 00:00:00" |
+| validEndDate   | DateTime | 否   | 有效结束时间           | "2026-12-31 23:59:59" |
+| enabled        | Boolean  | 否   | 是否启用(默认true)   | true                  |
+
+ **星期对照表:**
+ - 1 = 周一(Monday)
+ - 2 = 周二(Tuesday)
+ - 3 = 周三(Wednesday)
+ - 4 = 周四(Thursday)
+ - 5 = 周五(Friday)
+ - 6 = 周六(Saturday)
+ - 7 = 周日(Sunday)
+
+ ### 1.2 CRON模式使用示例
+
+ #### 示例1:每周一、三、五凌晨2点执行
+
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "每周一三五凌晨2点",
+   "scheduleType": "CRON",
+   "executeTime": "02:00:00",
+   "repeatWeekdays": "1,3,5",
+   "enabled": true
+ }
+ ```
+
+ 后端会自动生成 CRON 表达式:`0 0 2 ? * MON,WED,FRI`
+
+ #### 示例2:每周二、四下午5点执行(带有效期)
+
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "每周二四下午5点(春季灌溉)",
+   "scheduleType": "CRON",
+   "executeTime": "17:00:00",
+   "repeatWeekdays": "2,4",
+   "validStartDate": "2026-03-01 00:00:00",
+   "validEndDate": "2026-05-31 23:59:59",
+   "enabled": true
+ }
+ ```
+
+ 后端会自动生成 CRON 表达式:`0 0 17 ? * TUE,THU`
+
+ 有效期说明:
+ - 在 2026-03-01 之前不会触发
+ - 在 2026-05-31 之后不再触发
+ - 只在有效期内的每周二、四下午5点触发
+
+ #### 示例3:每天凌晨3点执行
+
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "每天凌晨3点",
+   "scheduleType": "CRON",
+   "executeTime": "03:00:00",
+   "repeatWeekdays": "1,2,3,4,5,6,7",
+   "enabled": true
+ }
+ ```
+
+ 后端会自动生成 CRON 表达式:`0 0 3 ? * MON,TUE,WED,THU,FRI,SAT,SUN`
+
+ #### 示例4:仅周末执行(周六、周日上午8点)
+
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "周末灌溉",
+   "scheduleType": "CRON",
+   "executeTime": "08:00:00",
+   "repeatWeekdays": "6,7",
+   "enabled": true
+ }
+ ```
+
+ 后端会自动生成 CRON 表达式:`0 0 8 ? * SAT,SUN`
+
+---
+
+ ## 二、SIMPLE模式
+
+ ### 2.1 SIMPLE模式参数说明
+
+| 参数         | 类型     | 必填 | 说明                      | 示例                  |
+| ------------ | -------- | ---- | ------------------------- | --------------------- |
+| taskId       | Long     | 是   | 关联的任务ID              | 123                   |
+| ruleName     | String   | 否   | 规则名称(用于标识)      | "连续10天灌溉计划"    |
+| scheduleType | String   | 是   | 定时类型,固定为"SIMPLE"  | "SIMPLE"              |
+| startTime    | DateTime | 是   | 起始时间(包含日期+时间) | "2026-03-10 06:00:00" |
+| intervalDays | Integer  | 是   | 执行间隔天数              | 1                     |
+| totalTimes   | Integer  | 是   | 执行总次数                | 10                    |
+| enabled      | Boolean  | 否   | 是否启用(默认true)      | true                  |
+
+ ### 2.2 SIMPLE模式使用示例
+
+ #### 示例1:连续10天,每天早上6点执行
+
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "连续10天灌溉计划",
+   "scheduleType": "SIMPLE",
+   "startTime": "2026-03-10 06:00:00",
+   "intervalDays": 1,
+   "totalTimes": 10,
+   "enabled": true
+ }
+ ```
+
+ 执行计划:
+ - 第1次:2026-03-10 06:00:00
+ - 第2次:2026-03-11 06:00:00
+ - 第3次:2026-03-12 06:00:00
+ - ...
+ - 第10次:2026-03-19 06:00:00(执行完毕后自动停用)
+
+ #### 示例2:每隔3天执行一次,共执行5次
+
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "隔天灌溉计划",
+   "scheduleType": "SIMPLE",
+   "startTime": "2026-03-15 07:30:00",
+   "intervalDays": 3,
+   "totalTimes": 5,
+   "enabled": true
+ }
+ ```
+
+ 执行计划:
+ - 第1次:2026-03-15 07:30:00
+ - 第2次:2026-03-18 07:30:00
+ - 第3次:2026-03-21 07:30:00
+ - 第4次:2026-03-24 07:30:00
+ - 第5次:2026-03-27 07:30:00(执行完毕后自动停用)
+
+ #### 示例3:每周执行一次,共执行4次
+
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "月度维护计划",
+   "scheduleType": "SIMPLE",
+   "startTime": "2026-04-01 10:00:00",
+   "intervalDays": 7,
+   "totalTimes": 4,
+   "enabled": true
+ }
+ ```
+
+ 执行计划:
+ - 第1次:2026-04-01 10:00:00
+ - 第2次:2026-04-08 10:00:00
+ - 第3次:2026-04-15 10:00:00
+ - 第4次:2026-04-22 10:00:00(执行完毕后自动停用)
+
+---
+
+ ## 三、API接口使用示例
+
+ ### 3.1 创建定时规则
+
+ **请求:**
+ ```http
+ POST /v2/{tenantId}/irrigation-task-schedule
+ Content-Type: application/json
+
+ {
+   "taskId": 123,
+   "ruleName": "每周一三五凌晨2点",
+   "scheduleType": "CRON",
+   "executeTime": "02:00:00",
+   "repeatWeekdays": "1,3,5",
+   "enabled": true
+ }
+ ```
+
+ **响应:**
+ ```json
+ {
+   "id": 456,
+   "taskId": 123,
+   "ruleName": "每周一三五凌晨2点",
+   "scheduleType": "CRON",
+   "cronExpression": "0 0 2 ? * MON,WED,FRI",
+   "executeTime": "02:00:00",
+   "repeatWeekdays": "1,3,5",
+   "enabled": true,
+   "status": 1,
+   "quartzJobName": "TASK_123_RULE_456",
+   "quartzTriggerName": "TRIGGER_123_RULE_456"
+ }
+ ```
+
+ ### 3.2 查询任务的所有定时规则
+
+ **请求:**
+ ```http
+ GET /v2/{tenantId}/irrigation-task-schedule/task/123
+ ```
+
+ **响应:**
+ ```json
+ [
+   {
+     "id": 456,
+     "taskId": 123,
+     "ruleName": "每周一三五凌晨2点",
+     "scheduleType": "CRON",
+     "cronExpression": "0 0 2 ? * MON,WED,FRI",
+     "executeTime": "02:00:00",
+     "repeatWeekdays": "1,3,5",
+     "enabled": true,
+     "status": 1
+   },
+   {
+     "id": 457,
+     "taskId": 123,
+     "ruleName": "连续10天灌溉计划",
+     "scheduleType": "SIMPLE",
+     "startTime": "2026-03-10 06:00:00",
+     "intervalDays": 1,
+     "totalTimes": 10,
+     "executedCount": 3,
+     "enabled": true,
+     "status": 1
+   }
+ ]
+ ```
+
+ ### 3.3 更新定时规则
+
+ **请求:**
+ ```http
+ PUT /v2/{tenantId}/irrigation-task-schedule/456
+ Content-Type: application/json
+
+ {
+   "taskId": 123,
+   "ruleName": "每周一三五凌晨3点(已调整)",
+   "scheduleType": "CRON",
+   "executeTime": "03:00:00",
+   "repeatWeekdays": "1,3,5",
+   "enabled": true
+ }
+ ```
+
+ ### 3.4 启用/禁用定时规则
+
+ **启用:**
+ ```http
+ POST /v2/{tenantId}/irrigation-task-schedule/456/enable
+ ```
+
+ **禁用:**
+ ```http
+ POST /v2/{tenantId}/irrigation-task-schedule/456/disable
+ ```
+
+ ### 3.5 删除定时规则
+
+ **删除单个:**
+ ```http
+ DELETE /v2/{tenantId}/irrigation-task-schedule/456
+ ```
+
+ **批量删除:**
+ ```http
+ DELETE /v2/{tenantId}/irrigation-task-schedule/batch
+ Content-Type: application/json
+
+ [456, 457, 458]
+ ```
+
+---
+
+ ## 四、多规则配置示例
+
+ 一个任务可以同时配置多个定时规则,CRON模式和SIMPLE模式可以共存。
+
+ ### 示例:组合配置
+
+ 为同一个任务(taskId=123)配置以下规则:
+
+ #### 规则1:工作日早上灌溉
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "工作日早上灌溉",
+   "scheduleType": "CRON",
+   "executeTime": "06:00:00",
+   "repeatWeekdays": "1,2,3,4,5",
+   "enabled": true
+ }
+ ```
+
+ #### 规则2:周末中午灌溉
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "周末中午灌溉",
+   "scheduleType": "CRON",
+   "executeTime": "12:00:00",
+   "repeatWeekdays": "6,7",
+   "enabled": true
+ }
+ ```
+
+ #### 规则3:临时加强灌溉(连续7天)
+ ```json
+ {
+   "taskId": 123,
+   "ruleName": "临时加强灌溉",
+   "scheduleType": "SIMPLE",
+   "startTime": "2026-03-15 18:00:00",
+   "intervalDays": 1,
+   "totalTimes": 7,
+   "enabled": true
+ }
+ ```
+
+---
+
+ ## 五、注意事项
+
+ ### 5.1 CRON模式注意事项
+
+  1. **自动生成CRON表达式**:后端会根据 `executeTime` 和 `repeatWeekdays` 自动生成标准的 CRON 表达式,前端无需手动构造
+  2. **有效期控制**:可通过 `validStartDate` 和 `validEndDate` 控制规则的生效时间范围
+  3. **星期格式**:`repeatWeekdays` 使用数字表示,多个星期用逗号分隔
+  4. **时间格式**:`executeTime` 必须为 HH:mm:ss 格式
+
+ ### 5.2 SIMPLE模式注意事项
+
+  1. **自动停用**:当已执行次数达到 `totalTimes` 后,规则会自动标记为已完成状态
+  2. **执行计数**:系统会自动记录 `executedCount`,无需手动维护
+  3. **间隔计算**:`intervalDays` 表示两次执行之间的天数间隔
+  4. **首次执行**:`startTime` 即为第一次执行的时间

+ 9 - 9
src/main/java/cn/sciento/farm/automationv2/api/controller/IrrigationTaskController.java

@@ -14,6 +14,7 @@ import cn.sciento.farm.automationv2.domain.entity.IrrigationTask;
 import cn.sciento.farm.automationv2.domain.entity.TaskExecution;
 import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskLog;
 import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
+import cn.sciento.farm.automationv2.domain.valueobject.IrrigationTaskDetailVO;
 import cn.sciento.farm.automationv2.domain.valueobject.IrrigationTaskVO;
 import cn.sciento.swagger.annotation.Permission;
 import io.choerodon.mybatis.pagehelper.annotation.PageableDefault;
@@ -140,7 +141,7 @@ public class IrrigationTaskController {
     @GetMapping("/{id}")
     @ApiOperation("查询任务详情")
     @Permission(level = ResourceLevel.ORGANIZATION)
-    public ResponseEntity<IrrigationTask> getTask(@PathVariable Long tenantId,@PathVariable Long id) {
+    public ResponseEntity<IrrigationTaskDetailVO> getTask(@PathVariable Long tenantId, @PathVariable Long id) {
         return Results.success(taskService.getById(id));
     }
 
@@ -161,13 +162,12 @@ public class IrrigationTaskController {
     @PutMapping
     @ApiOperation("更新任务")
     @Permission(level = ResourceLevel.ORGANIZATION)
-    public ResponseEntity<IrrigationTask> updateTask(@PathVariable Long tenantId,@RequestBody IrrigationTask task) {
-        if (task.getId() == null){
+    public ResponseEntity<IrrigationTaskDetailVO> updateTask(@PathVariable Long tenantId,@RequestBody IrrigationTaskDetailVO vo) {
+        if (vo.getTask().getId() == null){
             throw new CommonException("wfautoV2.parameter.incomplete");
         }
-        task.setTenantId(tenantId);
-        return Results.success(taskService.update(task));
-
+        vo.getTask().setTenantId(tenantId);
+        return Results.success(taskService.update(vo));
     }
 
     /**
@@ -177,9 +177,8 @@ public class IrrigationTaskController {
     @ApiOperation("删除任务")
     @Permission(level = ResourceLevel.ORGANIZATION)
     @Transactional(rollbackFor = Exception.class)
-    public ResponseEntity<Integer> deleteTask(@PathVariable Long id,@RequestBody IrrigationTask task) {
-        task.setId(id);
-        return Results.success(taskService.delete(task));
+    public ResponseEntity<Integer> deleteTask(@PathVariable Long id) {
+        return Results.success(taskService.delete(id));
     }
 
     /**
@@ -244,6 +243,7 @@ public class IrrigationTaskController {
      * 取消执行(触发安全关闭)
      */
     @PostMapping("/{executionId}/cancel")
+    @Permission(level = ResourceLevel.ORGANIZATION)
     public ResponseEntity cancelExecution(@PathVariable Long tenantId,@PathVariable Long executionId) {
         taskService.cancelExecution(executionId);
         return Results.success();

+ 134 - 6
src/main/java/cn/sciento/farm/automationv2/api/controller/IrrigationTaskScheduleController.java

@@ -1,45 +1,173 @@
 package cn.sciento.farm.automationv2.api.controller;
 
 
+import cn.sciento.core.exception.CommonException;
 import cn.sciento.core.iam.ResourceLevel;
 import cn.sciento.core.util.Results;
 import cn.sciento.farm.automationv2.api.dto.ScheduleRuleDTO;
+import cn.sciento.farm.automationv2.app.service.QuartzManagementService;
 import cn.sciento.farm.automationv2.app.service.taskSchedule.IrrigationTaskScheduleService;
 import cn.sciento.farm.automationv2.domain.entity.TaskScheduleRule;
 import cn.sciento.swagger.annotation.Permission;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
 import org.springframework.http.ResponseEntity;
 import org.springframework.web.bind.annotation.*;
 
 import javax.validation.Valid;
+import java.util.List;
 
 /**
- * 灌溉任务管理API
+ * 灌溉任务定时规则管理API
+ *
+ * 支持两种定时模式:
+ * 1. CRON模式:重复周期调度,支持按星期+时间重复执行,可设置有效期
+ * 2. SIMPLE模式:间隔周期调度,支持按固定间隔执行指定次数
+ *
+ * 一个任务可以配置多个定时规则,每个规则独立触发
  */
-@Api("灌溉任务-定时接口")
+@Api(tags = "灌溉任务-定时规则接口")
 @RestController
 @RequestMapping("/v2/{tenantId}/irrigation-task-schedule")
 public class IrrigationTaskScheduleController {
 
     private IrrigationTaskScheduleService service;
+    private QuartzManagementService quartzManagementService;
 
-    public IrrigationTaskScheduleController(IrrigationTaskScheduleService service) {
+    public IrrigationTaskScheduleController(IrrigationTaskScheduleService service, QuartzManagementService quartzManagementService) {
         this.service = service;
+        this.quartzManagementService = quartzManagementService;
     }
 
     /**
-     * 创建定时
+     * 创建定时规则
      *
+     * CRON模式示例:
+     * {
+     *   "taskId": 123,
+     *   "ruleName": "每周一三五凌晨2点",
+     *   "scheduleType": "CRON",
+     *   "executeTime": "02:00:00",
+     *   "repeatWeekdays": "1,3,5",
+     *   "validStartDate": "2026-03-01 00:00:00",
+     *   "validEndDate": "2026-12-31 23:59:59",
+     *   "enabled": true
+     * }
+     *
+     * SIMPLE模式示例:
+     * {
+     *   "taskId": 123,
+     *   "ruleName": "连续10天灌溉计划",
+     *   "scheduleType": "SIMPLE",
+     *   "startTime": "2026-03-10 06:00:00",
+     *   "intervalDays": 1,
+     *   "totalTimes": 10,
+     *   "enabled": true
+     * }
      */
     @PostMapping
-    @ApiOperation("定时任务")
+    @ApiOperation(value = "创建定时规则", notes = "为指定任务创建CRON或SIMPLE模式的定时规则")
     @Permission(level = ResourceLevel.ORGANIZATION)
     public ResponseEntity<TaskScheduleRule> createSchedule(@PathVariable Long tenantId,
-                                                       @Valid @RequestBody ScheduleRuleDTO ruleDTO) {
+                                                           @Valid @RequestBody ScheduleRuleDTO ruleDTO) {
 
         ruleDTO.setTenantId(tenantId);
         return Results.success(service.create(ruleDTO));
     }
 
+    /**
+     * 查询任务的所有定时规则
+     */
+    @GetMapping
+    @ApiOperation(value = "查询任务的所有定时规则", notes = "获取指定任务的所有定时规则列表")
+    @Permission(level = ResourceLevel.ORGANIZATION)
+    public ResponseEntity<List<TaskScheduleRule>> listByTask(@PathVariable Long tenantId,
+                                                             TaskScheduleRule rule) {
+        rule.setTenantId(tenantId);
+        return Results.success(service.listByTask(rule));
+    }
+
+    /**
+     * 查询单个定时规则详情
+     */
+    @GetMapping("/{ruleId}")
+    @ApiOperation(value = "查询定时规则详情", notes = "根据规则ID查询定时规则详细信息")
+    @Permission(level = ResourceLevel.ORGANIZATION)
+    public ResponseEntity<TaskScheduleRule> getDetail(@PathVariable Long tenantId,
+                                                      @PathVariable Long ruleId) {
+
+        return Results.success(service.getById(tenantId, ruleId));
+    }
+
+    /**
+     * 更新定时规则
+     */
+    @PutMapping
+    @ApiOperation(value = "更新定时规则", notes = "更新现有定时规则的配置")
+    @Permission(level = ResourceLevel.ORGANIZATION)
+    public ResponseEntity<TaskScheduleRule> updateSchedule(@PathVariable Long tenantId,
+                                                           @Valid @RequestBody TaskScheduleRule rule) {
+        if (rule.getId() == null){
+            throw new CommonException("wfautoV2.parameter.incomplete");
+        }
+        rule.setTenantId(tenantId);
+        return Results.success(service.update(rule));
+    }
+
+    /**
+     * 删除定时规则
+     */
+    @DeleteMapping("/{ruleId}")
+    @ApiOperation(value = "删除定时规则", notes = "删除指定的定时规则,同时移除对应的Quartz调度")
+    @Permission(level = ResourceLevel.ORGANIZATION)
+    public ResponseEntity<Integer> deleteSchedule(@PathVariable Long tenantId,
+                                                  @PathVariable Long ruleId) {
+
+        return Results.success(service.delete(tenantId, ruleId));
+    }
+
+    /**
+     * 查询单个定时规则详情
+     */
+    @PostMapping("/compensate")
+    @ApiOperation(value = "立即执行一次定时任务", notes = "立即执行一次定时任务")
+    @Permission(level = ResourceLevel.ORGANIZATION)
+    public ResponseEntity test(@PathVariable Long tenantId,
+                                                      @RequestBody TaskScheduleRule rule) {
+
+        quartzManagementService.triggerRuleNow(rule);
+        return Results.success();
+    }
+
+
+//    /**
+//     * 启用/禁用定时规则
+//     */
+//    @PostMapping("/enable")
+//    @ApiOperation(value = "启用定时规则", notes = "启用指定的定时规则,开始执行调度")
+//    @Permission(level = ResourceLevel.ORGANIZATION)
+//    public ResponseEntity<Integer> enableSchedule(@PathVariable Long tenantId,
+//                                                  @RequestBody TaskScheduleRule rule) {
+//        if (rule.getTaskId() == null || rule.getEnabledFlag() == null){
+//            throw new CommonException("wfautoV2.parameter.incomplete");
+//        }
+//        rule.setTenantId(tenantId);
+//        return Results.success(service.enable(rule));
+//    }
+
+
+//    /**
+//     * 批量删除定时规则
+//     */
+//    @DeleteMapping("/batch")
+//    @ApiOperation(value = "批量删除定时规则", notes = "批量删除多个定时规则")
+//    @Permission(level = ResourceLevel.ORGANIZATION)
+//    public ResponseEntity<Void> batchDelete(
+//            @ApiParam(value = "租户ID", required = true) @PathVariable Long tenantId,
+//            @ApiParam(value = "规则ID列表", required = true) @RequestBody List<Long> ruleIds) {
+//
+//        service.batchDelete(tenantId, ruleIds);
+//        return Results.success();
+//    }
 }

+ 9 - 0
src/main/java/cn/sciento/farm/automationv2/api/dto/BallValuesDTO.java

@@ -1,5 +1,6 @@
 package cn.sciento.farm.automationv2.api.dto;
 
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import javax.validation.constraints.NotBlank;
@@ -45,5 +46,13 @@ public class BallValuesDTO {
      */
     private Integer targetPressureKpa;
 
+    /**
+     * 球阀类型:直通/三通
+     */
+    private Integer vtype;
+
+    @ApiModelProperty("品牌")
+    private String brand;
+
 }
 

+ 73 - 7
src/main/java/cn/sciento/farm/automationv2/api/dto/ScheduleRuleDTO.java

@@ -1,17 +1,27 @@
 package cn.sciento.farm.automationv2.api.dto;
 
+import cn.sciento.farm.automationv2.domain.entity.TaskScheduleRule;
 import cn.sciento.farm.automationv2.domain.enums.ScheduleType;
+import cn.sciento.farm.automationv2.domain.enums.TaskScheduleRuleStatus;
+import cn.sciento.farm.automationv2.infra.constant.BaseConstant;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
 
 import javax.validation.constraints.NotBlank;
+import javax.validation.constraints.NotNull;
 import java.time.LocalDateTime;
 
 /**
+ * 定时规则DTO
+ * 支持两种模式:CRON模式(重复周期)和SIMPLE模式(间隔周期)
+ *
  * @author jayhaw
  * @date 2026/3/14 14:34
  */
 @Data
+@ApiModel("定时规则DTO")
 public class ScheduleRuleDTO {
 
     public String DEFAULT_NAME = "定时";
@@ -19,49 +29,105 @@ public class ScheduleRuleDTO {
     /**
      * 租户Id
      */
-    @NotBlank(message = "租户Id不能为空")
+    @ApiModelProperty(value = "租户Id", required = true)
     private Long tenantId;
 
     /**
      * 任务Id
      */
-    @NotBlank(message = "任务Id不能为空")
+    @ApiModelProperty(value = "任务Id", required = true)
+    @NotNull(message = "任务Id不能为空")
     private Long taskId;
 
     /**
      * 规则名称(用于标识和管理)
      */
+    @ApiModelProperty(value = "规则名称", example = "每周一三五凌晨2点")
     private String ruleName;
 
     /**
      * 定时类型:CRON / SIMPLE
      */
-    @NotBlank(message = "定时类型不能为空")
+    @ApiModelProperty(value = "定时类型:CRON(重复周期)/ SIMPLE(间隔周期)", required = true)
+    @NotNull(message = "定时类型不能为空")
     private ScheduleType scheduleType;
 
+    // ================== CRON 模式字段 ==================
+
     /**
-     * Cron表达式(scheduleType=CRON时必填)
+     * Cron表达式(scheduleType=CRON时,可由后端根据executeTime和repeatWeekdays自动生成
      */
+    @ApiModelProperty(value = "Cron表达式(可选,后端自动生成)", example = "0 0 2 ? * MON,WED,FRI")
     private String cronExpression;
 
     /**
-     * 起始时间(scheduleType=SIMPLE时必填)
+     * 执行时间
+     */
+    @ApiModelProperty(value = "执行时间(CRON模式必填)", example = "02:00:00")
+    private String executeTime;
+
+    /**
+     * 重复周期 - 星期几(scheduleType=CRON时必填)
+     * 1=周一, 2=周二, 3=周三, 4=周四, 5=周五, 6=周六, 7=周日
      */
+    @ApiModelProperty(value = "重复周期-星期几(CRON模式必填)", example = "1,3,5", notes = "1=周一,2=周二,3=周三,4=周四,5=周五,6=周六,7=周日")
+    private String repeatWeekdays;
+
+    /**
+     * 有效开始时间(scheduleType=CRON时可选)
+     */
+    @ApiModelProperty(value = "有效开始时间(CRON模式可选)", example = "2026-03-01 00:00:00")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime startTime;
+    private LocalDateTime validStartDate;
+
+    /**
+     * 有效结束时间(scheduleType=CRON时可选)
+     */
+    @ApiModelProperty(value = "有效结束时间(CRON模式可选)", example = "2026-12-31 23:59:59")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime validEndDate;
+
+    // ================== SIMPLE 模式字段 ==================
 
     /**
      * 执行间隔天数(scheduleType=SIMPLE时必填)
      */
+    @ApiModelProperty(value = "执行间隔天数(SIMPLE模式必填)", example = "1")
     private Integer intervalDays;
 
     /**
      * 执行总次数(scheduleType=SIMPLE时必填)
      */
+    @ApiModelProperty(value = "执行总次数(SIMPLE模式必填)", example = "10")
     private Integer totalTimes;
 
+    // ================== 通用字段 ==================
+
     /**
      * 是否启用(默认true)
      */
-    private Boolean enabled;
+    @ApiModelProperty(value = "是否启用", example = "true")
+    private Integer enabledFlag;
+
+    public TaskScheduleRule buildRule(){
+        return TaskScheduleRule.builder()
+                .taskId(taskId)
+                .ruleName(DEFAULT_NAME)
+                .executeTime(executeTime)
+                .repeatWeekdays(repeatWeekdays)
+                .validStartDate(validStartDate)
+                .validEndDate(validEndDate)
+                .scheduleType(scheduleType)
+                .cronExpression(cronExpression)
+//                .startTime(startTime)
+                .intervalDays(intervalDays)
+                .totalTimes(totalTimes)
+                .executedCount(0)
+                .enabledFlag(enabledFlag != null ? enabledFlag : BaseConstant.ENABLE)
+                .status(TaskScheduleRuleStatus.ACTIVE.getCode())
+                .tenantId(getTenantId())
+                .build();
+    }
+    
+
 }

+ 1 - 1
src/main/java/cn/sciento/farm/automationv2/app/handler/DeviceControlHelper.java

@@ -96,7 +96,7 @@ public class DeviceControlHelper {
     public static GatewayControlDto buildStopPumpDto(DeviceInfo device, Long tenantId) {
         GatewayControlDto dto = new GatewayControlDto();
         dto.setConcentratorId(device.getDeviceId());
-        dto.setEn(0); // 关闭
+        dto.setEn(5); // 关闭
         dto.setTenantId(tenantId);
         dto.setRemark("本次控制由智能控制V2调用 - 关闭水泵");
         dto.setStaffName("第三方远程调用");

+ 9 - 0
src/main/java/cn/sciento/farm/automationv2/app/handler/impl/CloseGroupNodeHandler.java

@@ -4,7 +4,9 @@ import cn.sciento.farm.automationv2.api.dto.GatewayControlDto;
 import cn.sciento.farm.automationv2.app.context.ExecutionContext;
 import cn.sciento.farm.automationv2.app.handler.DeviceControlHelper;
 import cn.sciento.farm.automationv2.app.handler.NodeHandler;
+import cn.sciento.farm.automationv2.app.service.TaskLogService;
 import cn.sciento.farm.automationv2.domain.business.DeviceBusiness;
+import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
 import cn.sciento.farm.automationv2.domain.enums.AckStatus;
 import cn.sciento.farm.automationv2.domain.enums.NodeType;
 import cn.sciento.farm.automationv2.domain.valueobject.DeviceInfo;
@@ -14,6 +16,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**
@@ -27,6 +30,7 @@ public class CloseGroupNodeHandler implements NodeHandler {
 
     private final DeviceBusiness deviceBusiness;
     private final AckManager ackManager;
+    private final TaskLogService logService;
 
     @Override
     public NodeType getType() {
@@ -62,6 +66,11 @@ public class CloseGroupNodeHandler implements NodeHandler {
                     result = deviceBusiness.control(dto);
                     log.info("关闭电磁阀,executionId={}, deviceId={}, result={}",
                             executionId, device.getDeviceId(), result);
+                    IrrigationTaskMainVO mainVO = logService.getByTaskMainData(context.getTask().getId());
+                    if (mainVO != null){
+                        mainVO.setGroupEndTime(LocalDateTime.now());
+                        logService.saveMain(context.getTask().getId(),mainVO);
+                    }
                 } else if ("BALL_VALVE".equals(device.getDeviceType())) {
                     // 球阀归零(角度设为0)
                     GatewayControlDto dto = DeviceControlHelper.buildBallValveAngleCloseDto(device, 0, tenantId);

+ 3 - 0
src/main/java/cn/sciento/farm/automationv2/app/handler/impl/OpenGroupNodeHandler.java

@@ -4,7 +4,9 @@ import cn.sciento.farm.automationv2.api.dto.GatewayControlDto;
 import cn.sciento.farm.automationv2.app.context.ExecutionContext;
 import cn.sciento.farm.automationv2.app.handler.DeviceControlHelper;
 import cn.sciento.farm.automationv2.app.handler.NodeHandler;
+import cn.sciento.farm.automationv2.app.service.TaskLogService;
 import cn.sciento.farm.automationv2.domain.business.DeviceBusiness;
+import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
 import cn.sciento.farm.automationv2.domain.enums.AckStatus;
 import cn.sciento.farm.automationv2.domain.enums.NodeType;
 import cn.sciento.farm.automationv2.domain.valueobject.DeviceInfo;
@@ -14,6 +16,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 /**

+ 8 - 0
src/main/java/cn/sciento/farm/automationv2/app/handler/impl/SetPumpPressureNodeHandler.java

@@ -4,7 +4,9 @@ import cn.sciento.farm.automationv2.api.dto.GatewayControlDto;
 import cn.sciento.farm.automationv2.app.context.ExecutionContext;
 import cn.sciento.farm.automationv2.app.handler.DeviceControlHelper;
 import cn.sciento.farm.automationv2.app.handler.NodeHandler;
+import cn.sciento.farm.automationv2.app.service.TaskLogService;
 import cn.sciento.farm.automationv2.domain.business.DeviceBusiness;
+import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
 import cn.sciento.farm.automationv2.domain.enums.AckStatus;
 import cn.sciento.farm.automationv2.domain.enums.NodeType;
 import cn.sciento.farm.automationv2.domain.valueobject.DeviceInfo;
@@ -27,6 +29,7 @@ public class SetPumpPressureNodeHandler implements NodeHandler {
 
     private final DeviceBusiness deviceBusiness;
     private final AckManager ackManager;
+    private final TaskLogService logService;
 
     @Override
     public NodeType getType() {
@@ -77,6 +80,11 @@ public class SetPumpPressureNodeHandler implements NodeHandler {
                         AckStatus.SUCCESS, null);
                 log.info("设置水泵压力成功,executionId={}, pumpId={}, pressureKpa={}",
                         executionId, pumpId, targetPressure);
+                IrrigationTaskMainVO mainVO = logService.getByTaskMainData(context.getTask().getId());
+                if (mainVO != null){
+                    mainVO.setTargetPressure(targetPressure);
+                    logService.saveMain(context.getTask().getId(),mainVO);
+                }
             } else {
                 ackManager.updateAckStatus(executionId, nodeIndex, pumpId,
                         AckStatus.FAIL, "设置水泵压力返回失败,result=" + result);

+ 19 - 0
src/main/java/cn/sciento/farm/automationv2/app/handler/impl/StartPumpNodeHandler.java

@@ -4,16 +4,22 @@ import cn.sciento.farm.automationv2.api.dto.GatewayControlDto;
 import cn.sciento.farm.automationv2.app.context.ExecutionContext;
 import cn.sciento.farm.automationv2.app.handler.DeviceControlHelper;
 import cn.sciento.farm.automationv2.app.handler.NodeHandler;
+import cn.sciento.farm.automationv2.app.service.TaskLogService;
 import cn.sciento.farm.automationv2.domain.business.DeviceBusiness;
+import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
 import cn.sciento.farm.automationv2.domain.enums.AckStatus;
 import cn.sciento.farm.automationv2.domain.enums.NodeType;
 import cn.sciento.farm.automationv2.domain.valueobject.DeviceInfo;
 import cn.sciento.farm.automationv2.domain.valueobject.ExecutionNode;
+import cn.sciento.farm.automationv2.infra.constant.RedisConstant;
 import cn.sciento.farm.automationv2.infra.redis.AckManager;
+import com.alibaba.fastjson.JSON;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDateTime;
 import java.util.Collections;
 
 /**
@@ -25,6 +31,7 @@ import java.util.Collections;
 public class StartPumpNodeHandler implements NodeHandler {
 
     private final DeviceBusiness deviceBusiness;
+    private final TaskLogService logService;
     private final AckManager ackManager;
 
     @Override
@@ -67,6 +74,8 @@ public class StartPumpNodeHandler implements NodeHandler {
                 ackManager.updateAckStatus(executionId, nodeIndex, pumpId,
                         AckStatus.SUCCESS, null);
                 log.info("启动水泵成功,executionId={}, pumpId={}", executionId, pumpId);
+                // 开启时间
+                buildMainStartTime(context.getTask().getId());
             } else {
                 ackManager.updateAckStatus(executionId, nodeIndex, pumpId,
                         AckStatus.FAIL, "启动水泵返回失败,result=" + result);
@@ -82,4 +91,14 @@ public class StartPumpNodeHandler implements NodeHandler {
         log.info("启动水泵指令处理完成,executionId={}, nodeIndex={}, pumpId={}",
                 executionId, nodeIndex, pumpId);
     }
+
+    private void buildMainStartTime(Long id) {
+        IrrigationTaskMainVO mainVO = logService.getByTaskMainData(id);
+        if (mainVO != null){
+            mainVO.setTotalStartTime(LocalDateTime.now());
+            logService.saveMain(id,mainVO);
+        }
+
+    }
+
 }

+ 9 - 0
src/main/java/cn/sciento/farm/automationv2/app/handler/impl/StopPumpNodeHandler.java

@@ -4,7 +4,9 @@ import cn.sciento.farm.automationv2.api.dto.GatewayControlDto;
 import cn.sciento.farm.automationv2.app.context.ExecutionContext;
 import cn.sciento.farm.automationv2.app.handler.DeviceControlHelper;
 import cn.sciento.farm.automationv2.app.handler.NodeHandler;
+import cn.sciento.farm.automationv2.app.service.TaskLogService;
 import cn.sciento.farm.automationv2.domain.business.DeviceBusiness;
+import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
 import cn.sciento.farm.automationv2.domain.enums.AckStatus;
 import cn.sciento.farm.automationv2.domain.enums.NodeType;
 import cn.sciento.farm.automationv2.domain.valueobject.DeviceInfo;
@@ -14,6 +16,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
+import java.time.LocalDateTime;
 import java.util.Collections;
 
 /**
@@ -26,6 +29,7 @@ public class StopPumpNodeHandler implements NodeHandler {
 
     private final DeviceBusiness deviceBusiness;
     private final AckManager ackManager;
+    private final TaskLogService logService;
 
     @Override
     public NodeType getType() {
@@ -67,6 +71,11 @@ public class StopPumpNodeHandler implements NodeHandler {
                 ackManager.updateAckStatus(executionId, nodeIndex, pumpId,
                         AckStatus.SUCCESS, null);
                 log.info("关闭水泵成功,executionId={}, pumpId={}", executionId, pumpId);
+                IrrigationTaskMainVO mainVO = logService.getByTaskMainData(context.getTask().getId());
+                if (mainVO != null){
+                    mainVO.setTotalEndTime(LocalDateTime.now());
+                    logService.saveMain(context.getTask().getId(),mainVO);
+                }
             } else {
                 ackManager.updateAckStatus(executionId, nodeIndex, pumpId,
                         AckStatus.FAIL, "关闭水泵返回失败,result=" + result);

+ 27 - 11
src/main/java/cn/sciento/farm/automationv2/app/job/IrrigationScheduledJob.java

@@ -1,6 +1,9 @@
 package cn.sciento.farm.automationv2.app.job;
 
 import cn.sciento.farm.automationv2.app.service.TaskTriggerService;
+import cn.sciento.farm.automationv2.domain.entity.TaskScheduleRule;
+import cn.sciento.farm.automationv2.domain.repository.TaskScheduleRuleRepository;
+import cn.sciento.farm.automationv2.infra.constant.BaseConstant;
 import lombok.extern.slf4j.Slf4j;
 import org.quartz.JobDataMap;
 import org.quartz.JobExecutionContext;
@@ -26,6 +29,13 @@ public class IrrigationScheduledJob extends QuartzJobBean {
     @Autowired
     private TaskTriggerService taskTriggerService;
 
+    @Autowired
+    private TaskScheduleRuleRepository scheduleRuleRepository;
+
+
+    public IrrigationScheduledJob() {
+    }
+
     /**
      * JobDataMap参数key
      */
@@ -39,21 +49,17 @@ public class IrrigationScheduledJob extends QuartzJobBean {
         Long taskId = dataMap.getLong(PARAM_TASK_ID);
         Long ruleId = dataMap.getLong(PARAM_RULE_ID);
 
-        if (taskId == null) {
-            log.error("任务ID为空,jobKey={}", context.getJobDetail().getKey());
-            return;
-        }
-
-        if (ruleId == null) {
-            log.error("规则ID为空,taskId={}, jobKey={}", taskId, context.getJobDetail().getKey());
-            return;
-        }
-
         log.info("定时规则触发,taskId={}, ruleId={}, jobKey={}, fireTime={}",
                 taskId, ruleId, context.getJobDetail().getKey(), context.getFireTime());
 
         try {
-            // 触发任务(发送MQ消息后立即返回)
+            if (!checkSchedule(ruleId)){
+                log.error("定时规则不存在/已超出时限/已禁用,taskId={}, ruleId={}, jobKey={}",
+                        taskId, ruleId, context.getJobDetail().getKey());
+                return;
+            }
+
+            // 触发任务
             Long executionId = taskTriggerService.scheduledTrigger(taskId);
 
             log.info("定时规则触发成功,taskId={}, ruleId={}, executionId={}, nextFireTime={}",
@@ -67,4 +73,14 @@ public class IrrigationScheduledJob extends QuartzJobBean {
             throw new JobExecutionException("定时规则触发失败", e);
         }
     }
+
+    private Boolean checkSchedule(Long ruleId) {
+        TaskScheduleRule taskScheduleRule = scheduleRuleRepository.selectByPrimaryKey(ruleId);
+        if (taskScheduleRule != null && BaseConstant.ENABLE.equals(taskScheduleRule.getEnabledFlag())){
+            if (taskScheduleRule.isWithinValidPeriod()){
+                return Boolean.TRUE;
+            }
+        }
+        return Boolean.FALSE;
+    }
 }

+ 21 - 67
src/main/java/cn/sciento/farm/automationv2/app/service/QuartzManagementService.java

@@ -10,6 +10,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.quartz.*;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.ZoneId;
 import java.util.Date;
 import java.util.List;
@@ -76,31 +78,24 @@ public class QuartzManagementService {
                 trigger = TriggerBuilder.newTrigger()
                         .withIdentity(triggerKey)
                         .withDescription(rule.getRuleName())
-                        .withSchedule(CronScheduleBuilder.cronSchedule(rule.getCronExpression())
+                        .withSchedule(CronScheduleBuilder.cronSchedule(rule.generateCronExpression())
                                 .withMisfireHandlingInstructionDoNothing())
                         .build();
 
-                log.info("创建CRON定时规则调度,taskId={}, ruleId={}, cron={}, nextFireTime={}",
-                        task.getId(), rule.getId(), rule.getCronExpression(), trigger.getNextFireTime());
-
             } else if (rule.isSimpleMode()) {
                 // SIMPLE 模式
                 SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
+                        // 间隔的单位
                         .withIntervalInMilliseconds(rule.getIntervalDays() * 24 * 3600 * 1000L)
                         .withRepeatCount(rule.getTotalTimes() - 1)
                         .withMisfireHandlingInstructionNextWithRemainingCount();
-
                 trigger = TriggerBuilder.newTrigger()
                         .withIdentity(triggerKey)
                         .withDescription(rule.getRuleName())
-                        .startAt(Date.from(rule.getStartTime().atZone(ZoneId.systemDefault()).toInstant()))
+                        .startAt(buildDate(rule.getExecuteTime()))
                         .withSchedule(scheduleBuilder)
                         .build();
 
-                log.info("创建SIMPLE定时规则调度,taskId={}, ruleId={}, startTime={}, intervalDays={}, totalTimes={}, nextFireTime={}",
-                        task.getId(), rule.getId(), rule.getStartTime(), rule.getIntervalDays(),
-                        rule.getTotalTimes(), trigger.getNextFireTime());
-
             } else {
                 throw new IllegalArgumentException("不支持的定时类型: " + rule.getScheduleType());
             }
@@ -118,6 +113,22 @@ public class QuartzManagementService {
         }
     }
 
+    private Date buildDate(String executeTime) {
+        LocalTime startTime = LocalTime.parse(executeTime); // 解析为 LocalTime
+
+        // 获取当前日期时间
+        LocalDateTime now = LocalDateTime.now();
+        LocalDateTime startDateTime = LocalDateTime.of(now.toLocalDate(), startTime);
+
+        // 如果今天的这个时间点已经过去,则顺延到明天
+        if (startDateTime.isBefore(now)) {
+            startDateTime = startDateTime.plusDays(1);
+        }
+
+        // 转换为 Date
+        return Date.from(startDateTime.atZone(ZoneId.systemDefault()).toInstant());
+    }
+
     /**
      * 为任务的所有启用规则创建调度
      *
@@ -283,61 +294,4 @@ public class QuartzManagementService {
         }
     }
 
-    // ================== 兼容旧版API(已废弃) ==================
-
-    /**
-     * @deprecated 已废弃,请使用 scheduleTaskRule(IrrigationTask, TaskScheduleRule)
-     */
-    @Deprecated
-    public void addCronJob(Long taskId, String cronExpression) {
-        throw new UnsupportedOperationException("此方法已废弃,请使用基于规则的调度管理");
-    }
-
-    /**
-     * @deprecated 已废弃,请使用 scheduleTaskRule(IrrigationTask, TaskScheduleRule)
-     */
-    @Deprecated
-    public void addSimpleJob(Long taskId, int intervalDays, int totalTimes) {
-        throw new UnsupportedOperationException("此方法已废弃,请使用基于规则的调度管理");
-    }
-
-    /**
-     * @deprecated 已废弃,请使用 unscheduleAllTaskRules(Long)
-     */
-    @Deprecated
-    public void deleteJob(Long taskId) {
-        unscheduleAllTaskRules(taskId);
-    }
-
-    /**
-     * @deprecated 已废弃,请使用 pauseTaskRule(TaskScheduleRule)
-     */
-    @Deprecated
-    public void pauseJob(Long taskId) {
-        throw new UnsupportedOperationException("此方法已废弃,请指定具体规则");
-    }
-
-    /**
-     * @deprecated 已废弃,请使用 resumeTaskRule(TaskScheduleRule)
-     */
-    @Deprecated
-    public void resumeJob(Long taskId) {
-        throw new UnsupportedOperationException("此方法已废弃,请指定具体规则");
-    }
-
-    /**
-     * @deprecated 已废弃,请使用 triggerRuleNow(TaskScheduleRule)
-     */
-    @Deprecated
-    public void triggerJobNow(Long taskId) {
-        throw new UnsupportedOperationException("此方法已废弃,请指定具体规则");
-    }
-
-    /**
-     * @deprecated 已废弃,请使用 ruleJobExists(TaskScheduleRule)
-     */
-    @Deprecated
-    public boolean jobExists(Long taskId) {
-        throw new UnsupportedOperationException("此方法已废弃,请指定具体规则");
-    }
 }

+ 10 - 1
src/main/java/cn/sciento/farm/automationv2/app/service/SafeShutdownService.java

@@ -4,6 +4,7 @@ import cn.sciento.farm.automationv2.api.dto.GatewayControlDto;
 import cn.sciento.farm.automationv2.app.handler.DeviceControlHelper;
 import cn.sciento.farm.automationv2.domain.business.DeviceBusiness;
 import cn.sciento.farm.automationv2.domain.entity.TaskExecution;
+import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
 import cn.sciento.farm.automationv2.domain.enums.AckStatus;
 import cn.sciento.farm.automationv2.domain.repository.TaskExecutionRepository;
 import cn.sciento.farm.automationv2.domain.valueobject.DeviceInfo;
@@ -16,6 +17,7 @@ import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 
+import java.time.LocalDateTime;
 import java.util.*;
 import java.util.stream.Collectors;
 
@@ -37,6 +39,7 @@ public class SafeShutdownService {
     private final DeviceBusiness deviceBusiness;
     private final AckManager ackManager;
     private final ExecutionPlanStore executionPlanStore;
+    private final TaskLogService logService;
 
     /**
      * 安全关闭所有已开启的设备
@@ -81,6 +84,12 @@ public class SafeShutdownService {
             boolean success = closeDevice(executionId, openedDevices.pump, execution.getTenantId());
             if (success) {
                 successDevices.add(openedDevices.pump.getDeviceId());
+                IrrigationTaskMainVO mainVO = logService.getByTaskMainData(execution.getTaskId());
+                if (mainVO != null){
+                    mainVO.setTotalEndTime(LocalDateTime.now());
+                    mainVO.setGroupEndTime(LocalDateTime.now());
+                    logService.saveMain(execution.getTaskId(),mainVO);
+                }
             } else {
                 failedDevices.add(openedDevices.pump.getDeviceId());
             }
@@ -135,7 +144,7 @@ public class SafeShutdownService {
             // 根据设备类型构造控制DTO并调用Feign
             if ("PUMP".equals(deviceType)) {
                 dto = DeviceControlHelper.buildStopPumpDto(device, tenantId);
-                result = deviceBusiness.control(dto);
+                result = deviceBusiness.pumpControl(dto);
                 log.info("关闭水泵,executionId={}, deviceId={}, result={}", executionId, deviceId, result);
             } else if ("FERTILIZER".equals(deviceType)) {
                 dto = DeviceControlHelper.buildStopFertilizerDto(device, tenantId);

+ 33 - 20
src/main/java/cn/sciento/farm/automationv2/app/service/TaskLogService.java

@@ -41,7 +41,7 @@ public class TaskLogService {
 
     private static final long DEFAULT_TTL_HOUR = 3;
 
-    private void saveMain(Long taskId, IrrigationTaskMainVO mainVO) {
+    public void saveMain(Long taskId, IrrigationTaskMainVO mainVO) {
         save(taskId, mainVO, DEFAULT_TTL_HOUR, TimeUnit.HOURS);
     }
 
@@ -49,6 +49,15 @@ public class TaskLogService {
         return RedisConstant.TASK_EXECUTION_LOG + taskId;
     }
 
+    public IrrigationTaskMainVO getByTaskMainData(Long taskId) {
+        String key = RedisConstant.TASK_EXECUTION_LOG + taskId;
+        String json = stringRedisTemplate.opsForValue().get(key);
+        if (json == null || json.isEmpty()) {
+            return null;
+        }
+        return JSON.parseObject(json, IrrigationTaskMainVO.class);
+    }
+
     private void save(Long taskId, IrrigationTaskMainVO mainVO, long ttl, TimeUnit unit) {
         if (taskId == null || mainVO == null) {
             return;
@@ -120,21 +129,22 @@ public class TaskLogService {
             }
 
             // 创建日志对象
-            IrrigationTaskLog log = new IrrigationTaskLog();
-            log.setMainData(mainData);
-            log.setGroups(groups);
-            log.setTaskId(task.getId());
-            log.setTenantId(task.getTenantId());
-            log.setOrganizationId(task.getOrganizationId());
-            log.setExecutionId(executionId);
-            log.setTaskBeginTime(LocalDateTime.now());
-
+            IrrigationTaskLog taskLog = new IrrigationTaskLog();
+//            log.setMainData(mainData);
+            taskLog.setGroups(groups);
+            taskLog.setTaskId(task.getId());
+            taskLog.setTenantId(task.getTenantId());
+            taskLog.setOrganizationId(task.getOrganizationId());
+            taskLog.setExecutionId(executionId);
+            taskLog.setTaskBeginTime(LocalDateTime.now());
+            taskLog.setPumpId(task.getPumpDeviceId());
+            taskLog.setPumpName(task.getPumpDeviceName());
             // 保存到MongoDB
-            taskLogRepository.save(log);
+            taskLogRepository.save(taskLog);
 
-            this.log.info("任务日志初始化成功,executionId={}, 灌区数量={}", executionId, groups.size());
+            log.info("任务日志初始化成功,executionId={}, 灌区数量={}", executionId, groups.size());
         } catch (Exception e) {
-            this.log.error("初始化任务日志失败,executionId={}", executionId, e);
+            log.error("初始化任务日志失败,executionId={}", executionId, e);
         }
     }
 
@@ -186,12 +196,14 @@ public class TaskLogService {
             }
 
             // 更新主数据当前灌区
-            if (taskLog.getMainData() != null && taskLog.getGroups() != null && zoneIndex < taskLog.getGroups().size()) {
+            IrrigationTaskMainVO mainData = getByTaskMainData(taskLog.getTaskId());
+            if (mainData != null && taskLog.getGroups() != null && zoneIndex < taskLog.getGroups().size()) {
                 IrrigationTaskGroupVO currentGroup = taskLog.getGroups().get(zoneIndex);
-                taskLog.getMainData().setGroupName(currentGroup.getGroupName());
-                taskLog.getMainData().setGroupBeginTime(currentGroup.getGroupBeginTime());
-                taskLog.getMainData().setGroupNum(zoneIndex);
-                saveMain(taskLog.getTaskId(),taskLog.getMainData());
+                mainData.setGroupName(currentGroup.getGroupName());
+                mainData.setPlanTime(currentGroup.getPlanTime());
+                mainData.setGroupBeginTime(currentGroup.getGroupBeginTime());
+                mainData.setGroupNum(zoneIndex);
+                saveMain(taskLog.getTaskId(), mainData);
             }
 
             taskLogRepository.save(taskLog);
@@ -259,8 +271,9 @@ public class TaskLogService {
             return;
         }
         IrrigationTaskLog taskLog = logOpt.get();
-        taskLog.getMainData().setGroupNum(taskLog.getMainData().getTotalGroup());
-        saveMain(taskLog.getTaskId(),taskLog.getMainData());
+        IrrigationTaskMainVO mainData = getByTaskMainData(taskLog.getTaskId());
+        mainData.setGroupNum(mainData.getTotalGroup());
+        saveMain(taskLog.getTaskId(),mainData);
         taskLog.setTaskEndTime(LocalDateTime.now());
         taskLogRepository.save(taskLog);
     }

+ 4 - 11
src/main/java/cn/sciento/farm/automationv2/app/service/TaskTriggerService.java

@@ -65,23 +65,19 @@ public class TaskTriggerService {
      */
     @Transactional(rollbackFor = Exception.class)
     public Long triggerTask(Long taskId, TriggerType triggerType) {
-        log.info("触发任务,taskId={}, triggerType={}", taskId, triggerType);
 
         // 加载任务
         IrrigationTask task = irrigationTaskRepository.selectByPrimaryKey(taskId);
         if (task == null) {
-            log.error("任务不存在,taskId={}", taskId);
-            throw new IllegalArgumentException("任务不存在");
+            throw new CommonException("wfautoV2.task.notFound");
         }
 
         // 检查任务是否启用
         if (!BaseConstant.ENABLE.equals(task.getEnabledFlag())) {
-            log.warn("任务未启用,taskId={}", taskId);
-            throw new IllegalStateException("任务未启用,无法触发");
+            throw new CommonException("wfautoV2.task.notEnable");
         }
         // 检查任务是否已经启动
         if (!TaskStatus.FREE.getCode().equals(task.getStatus())) {
-            log.warn("任务已经启动,taskId={}", taskId);
             throw new CommonException("wfautoV2.task.hadAction");
         }
 
@@ -95,9 +91,7 @@ public class TaskTriggerService {
         // 加载任务灌区配置(灌区列表 + 灌溉时长)
         List<TaskGroupConfig> groupConfigs = taskGroupConfigRepository.queryByTaskId(taskId);
         if (groupConfigs == null || groupConfigs.isEmpty()) {
-            // TODO 执行结束
-            log.error("任务未配置灌区,taskId={}", taskId);
-            throw new IllegalStateException("任务未配置灌区");
+            throw new CommonException("wfautoV2.task.notGroup");
         }
 
         // 查询灌溉组模板(设备信息)
@@ -114,8 +108,7 @@ public class TaskTriggerService {
         for (TaskGroupConfig config : groupConfigs) {
             IrrigationGroup group = groupMap.get(config.getGroupId());
             if (group == null) {
-                log.error("灌溉组不存在,groupId={}", config.getGroupId());
-                throw new IllegalStateException("灌溉组不存在,groupId=" + config.getGroupId());
+                throw new CommonException("wfautoV2.group.notFound",config.getGroupId());
             }
             zones.add(ZoneConfigView.from(group, config,task));
         }

+ 6 - 5
src/main/java/cn/sciento/farm/automationv2/app/service/irrigationTask/IrrigationTaskService.java

@@ -10,6 +10,7 @@ import cn.sciento.farm.automationv2.domain.entity.IrrigationTask;
 import cn.sciento.farm.automationv2.domain.entity.TaskExecution;
 import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskLog;
 import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
+import cn.sciento.farm.automationv2.domain.valueobject.IrrigationTaskDetailVO;
 import cn.sciento.farm.automationv2.domain.valueobject.IrrigationTaskVO;
 import io.choerodon.mybatis.pagehelper.domain.PageRequest;
 
@@ -31,14 +32,14 @@ public interface IrrigationTaskService {
      * @param id
      * @return irrigationGroup
      */
-    IrrigationTask getById(Long id);
+    IrrigationTaskDetailVO getById(Long id);
 
     /**
      * 编辑
-     * @param task
+     * @param vo
      * @return irrigationGroup
      */
-    IrrigationTask update(IrrigationTask task);
+    IrrigationTaskDetailVO update(IrrigationTaskDetailVO vo);
 
     /**
      *获取page
@@ -57,10 +58,10 @@ public interface IrrigationTaskService {
 
     /**
      * 删除
-     * @param task
+     * @param id
      * @return
      */
-    Integer delete(IrrigationTask task);
+    Integer delete(Long id);
 
     /**
      * 获取头部数据

+ 55 - 41
src/main/java/cn/sciento/farm/automationv2/app/service/irrigationTask/impl/IrrigationTaskServiceImpl.java

@@ -20,6 +20,7 @@ import cn.sciento.farm.automationv2.domain.enums.TaskStatus;
 import cn.sciento.farm.automationv2.domain.enums.TriggerType;
 import cn.sciento.farm.automationv2.domain.repository.*;
 import cn.sciento.farm.automationv2.domain.valueobject.ExecutionPlan;
+import cn.sciento.farm.automationv2.domain.valueobject.IrrigationTaskDetailVO;
 import cn.sciento.farm.automationv2.domain.valueobject.IrrigationTaskVO;
 import cn.sciento.farm.automationv2.infra.constant.BaseConstant;
 import cn.sciento.farm.automationv2.infra.constant.RedisConstant;
@@ -109,18 +110,31 @@ public class IrrigationTaskServiceImpl implements IrrigationTaskService {
     }
 
     @Override
-    public IrrigationTask getById(Long id) {
-        return irrigationTaskRepository.selectByPrimaryKey(id);
+    public IrrigationTaskDetailVO getById(Long id) {
+        IrrigationTaskDetailVO vo = new IrrigationTaskDetailVO();
+        IrrigationTask irrigationTask = irrigationTaskRepository.selectByPrimaryKey(id);
+        if (irrigationTask != null){
+            vo.setTask(irrigationTask);
+            vo.setGroupConfigs(taskGroupConfigRepository.queryByTaskId(id));
+        }
+        return vo;
     }
 
     @Override
-    public IrrigationTask update(IrrigationTask task) {
-        IrrigationTask result = getById(task.getId());
+    public IrrigationTaskDetailVO update(IrrigationTaskDetailVO vo) {
+        Long taskId = vo.getTask().getId();
+        IrrigationTask result = irrigationTaskRepository.selectByPrimaryKey(taskId);
         if (result == null) {
             throw new CommonException("wfautoV2.parameter.error");
         }
-        irrigationTaskRepository.updateByPrimaryKeySelective(task);
-        return task;
+        Integer num = irrigationTaskRepository.updateByPrimaryKeySelective(vo.getTask());
+        if (num > 0){
+            // 覆盖
+            taskGroupConfigRepository.deleteByTask(taskId);
+            // 添加
+            taskGroupConfigRepository.addBatchByTask(vo.getGroupConfigs(),taskId);
+        }
+        return vo;
     }
 
     @Override
@@ -132,7 +146,7 @@ public class IrrigationTaskServiceImpl implements IrrigationTaskService {
 
     @Override
     public Integer enable(IrrigationTask task) {
-        IrrigationTask result = getById(task.getId());
+        IrrigationTask result = irrigationTaskRepository.selectByPrimaryKey(task.getId());
         if (result == null) {
             throw new CommonException("wfautoV2.parameter.error");
         }
@@ -147,8 +161,8 @@ public class IrrigationTaskServiceImpl implements IrrigationTaskService {
     }
 
     @Override
-    public Integer delete(IrrigationTask task) {
-        IrrigationTask result = getById(task.getId());
+    public Integer delete(Long id) {
+        IrrigationTask result = irrigationTaskRepository.selectByPrimaryKey(id);
         if (result == null) {
             throw new CommonException("wfautoV2.parameter.error");
         }
@@ -158,7 +172,7 @@ public class IrrigationTaskServiceImpl implements IrrigationTaskService {
         result.setEnabledFlag(BaseConstant.DELETE);
         Integer delResult = irrigationTaskRepository.updateByPrimaryKeySelective(result);
         if (delResult > 0){
-            taskGroupConfigRepository.deleteByTask(task.getId());
+            taskGroupConfigRepository.deleteByTask(id);
         }
         return delResult;
     }
@@ -188,39 +202,39 @@ public class IrrigationTaskServiceImpl implements IrrigationTaskService {
 
     @Override
     public void cancelExecution(Long executionId) {
-        try {
-            // 加载执行实例
-            TaskExecution execution = taskExecutionRepository.selectByPrimaryKey(executionId);
-            if (execution == null) {
-                throw new CommonException("wfautoV2.task.execution.notFound");
-            }
+        // 加载执行实例
+        TaskExecution execution = taskExecutionRepository.selectByPrimaryKey(executionId);
+        if (execution == null) {
+            throw new CommonException("wfautoV2.task.execution.notFound");
+        }
 
-            // 检查是否可以取消
-            if (execution.isTerminal()) {
-                throw new CommonException("wfautoV2.task.execution.hadCancel");
-            }
+        // 检查是否可以取消
+        if (execution.isTerminal()) {
+            throw new CommonException("wfautoV2.task.execution.hadCancel");
+        }
 
-            // 更新状态为CANCELLED
-            execution.markAsCancelled();
-            execution.setFinishedAt(LocalDateTime.now());
-            taskExecutionRepository.updateByPrimaryKeySelective(execution);
-
-            // 触发安全关闭
-            SafeShutdownService.ShutdownResult result = safeShutdownService.shutdown(executionId);
-
-            // 更新安全关闭结果
-            execution.setSafeCloseStatus(result.isSuccess() ? "SUCCESS" : "PARTIAL");
-            execution.setSafeCloseDetails(String.format(
-                    "{\"success\":[%s],\"failed\":[%s]}",
-                    String.join(",", result.getSuccessDevices().stream()
-                            .map(d -> "\"" + d + "\"").toArray(String[]::new)),
-                    String.join(",", result.getFailedDevices().stream()
-                            .map(d -> "\"" + d + "\"").toArray(String[]::new))
-            ));
-            taskExecutionRepository.updateByPrimaryKeySelective(execution);
-
-        } catch (Exception e) {
-            throw new CommonException("wfautoV2.task.execution.cancelError",e.getMessage());
+        // 更新状态为CANCELLED
+        execution.markAsCancelled();
+        execution.setFinishedAt(LocalDateTime.now());
+        taskExecutionRepository.updateByPrimaryKeySelective(execution);
+
+        // 触发安全关闭
+        SafeShutdownService.ShutdownResult result = safeShutdownService.shutdown(executionId);
+
+        // 更新安全关闭结果
+        execution.setSafeCloseStatus(result.isSuccess() ? "SUCCESS" : "PARTIAL");
+        execution.setSafeCloseDetails(String.format(
+                "{\"success\":[%s],\"failed\":[%s]}",
+                String.join(",", result.getSuccessDevices().stream()
+                        .map(d -> "\"" + d + "\"").toArray(String[]::new)),
+                String.join(",", result.getFailedDevices().stream()
+                        .map(d -> "\"" + d + "\"").toArray(String[]::new))
+        ));
+        taskExecutionRepository.updateByPrimaryKeySelective(execution);
+        IrrigationTask irrigationTask = irrigationTaskRepository.selectByPrimaryKey(execution.getTaskId());
+        if (irrigationTask != null){
+            irrigationTask.setStatus(TaskStatus.FREE.getCode());
+            irrigationTaskRepository.updateByPrimaryKeySelective(irrigationTask);
         }
     }
 }

+ 37 - 0
src/main/java/cn/sciento/farm/automationv2/app/service/taskSchedule/IrrigationTaskScheduleService.java

@@ -13,6 +13,8 @@ import cn.sciento.farm.automationv2.domain.entity.mongo.IrrigationTaskMainVO;
 import cn.sciento.farm.automationv2.domain.valueobject.IrrigationTaskVO;
 import io.choerodon.mybatis.pagehelper.domain.PageRequest;
 
+import java.util.List;
+
 /**
  * @author Jayhaw
  * @description
@@ -27,5 +29,40 @@ public interface IrrigationTaskScheduleService {
      */
     TaskScheduleRule create(ScheduleRuleDTO ruleDTO);
 
+    /**
+     * 列表获取
+     * @param rule
+     * @return
+     */
+    List<TaskScheduleRule> listByTask(TaskScheduleRule rule);
+
+    /**
+     * 详情
+     * @param tenantId
+     * @param ruleId
+     * @return
+     */
+    TaskScheduleRule getById(Long tenantId,Long ruleId);
+
+    /**
+     * 编辑
+     * @param rule
+     * @return TaskScheduleRule
+     */
+    TaskScheduleRule update(TaskScheduleRule rule);
+
+
+    /**
+     * 删除
+     * @param tenantId
+     * @return Integer
+     */
+    Integer delete(Long tenantId,Long ruleId);
 
+//    /**
+//     * 启用/禁用
+//     * @param rule
+//     * @return Integer
+//     */
+//    Integer enable(TaskScheduleRule rule);
 }

+ 126 - 16
src/main/java/cn/sciento/farm/automationv2/app/service/taskSchedule/impl/IrrigationTaskScheduleServiceImpl.java

@@ -66,21 +66,8 @@ public class IrrigationTaskScheduleServiceImpl implements IrrigationTaskSchedule
         if (irrigationTask == null){
             throw new CommonException("wfautoV2.task.notFound");
         }
-
-        TaskScheduleRule rule = TaskScheduleRule.builder()
-                .taskId(ruleDTO.getTaskId())
-                .ruleName(ruleDTO.DEFAULT_NAME)
-                .scheduleType(ruleDTO.getScheduleType())
-                .cronExpression(ruleDTO.getCronExpression())
-                .startTime(ruleDTO.getStartTime())
-                .intervalDays(ruleDTO.getIntervalDays())
-                .totalTimes(ruleDTO.getTotalTimes())
-                .executedCount(0)
-                .enabled(ruleDTO.getEnabled() != null ? ruleDTO.getEnabled() : true)
-                .status(TaskScheduleRuleStatus.ACTIVE.getCode())
-                .tenantId(ruleDTO.getTenantId())
-                .build();
-
+        // 构建
+        TaskScheduleRule rule = ruleDTO.buildRule();
         // 插入规则
         taskScheduleRuleRepository.insertSelective(rule);
 
@@ -88,7 +75,7 @@ public class IrrigationTaskScheduleServiceImpl implements IrrigationTaskSchedule
         rule.fillQuartzNames();
         taskScheduleRuleRepository.updateByPrimaryKeySelective(rule);
         // 为启用的规则创建Quartz调度
-        if (Boolean.TRUE.equals(rule.getEnabled())) {
+        if (BaseConstant.ENABLE.equals(rule.getEnabledFlag())) {
             try {
                 quartzManagementService.scheduleTaskRule(irrigationTask, rule);
             } catch (Exception e) {
@@ -98,4 +85,127 @@ public class IrrigationTaskScheduleServiceImpl implements IrrigationTaskSchedule
         }
         return rule;
     }
+
+    @Override
+    public List<TaskScheduleRule> listByTask(TaskScheduleRule rule) {
+        return taskScheduleRuleRepository.listByTask(rule);
+    }
+
+    @Override
+    public TaskScheduleRule getById(Long tenantId, Long ruleId) {
+        return taskScheduleRuleRepository.selectByPrimaryKey(ruleId);
+    }
+
+    @Override
+    public TaskScheduleRule update(TaskScheduleRule rule) {
+        // 验证规则是否存在
+        TaskScheduleRule existingRule = taskScheduleRuleRepository.selectByPrimaryKey(rule.getId());
+        if (existingRule == null) {
+            throw new CommonException("wfautoV2.scheduleRule.notFound");
+        }
+
+        // 验证任务是否存在
+        IrrigationTask irrigationTask = taskRepository.selectByPrimaryKey(rule.getTaskId());
+        if (irrigationTask == null) {
+            throw new CommonException("wfautoV2.task.notFound");
+        }
+
+        // 更新规则
+        taskScheduleRuleRepository.updateByPrimaryKeySelective(rule);
+
+        // 重新获取更新后的规则
+        TaskScheduleRule updatedRule = taskScheduleRuleRepository.selectByPrimaryKey(rule.getId());
+
+        // 如果规则已启用,需要重新调度
+        if (BaseConstant.ENABLE.equals(updatedRule.getEnabledFlag())) {
+            try {
+                // 先移除旧的调度
+                quartzManagementService.unscheduleTaskRule(updatedRule);
+                // 重新创建调度
+                quartzManagementService.scheduleTaskRule(irrigationTask, updatedRule);
+            } catch (Exception e) {
+                log.error("更新规则调度失败,taskId={}, ruleId={}",
+                        updatedRule.getTaskId(), updatedRule.getId(), e);
+            }
+        } else {
+            // 如果规则被禁用,移除调度
+            try {
+                quartzManagementService.unscheduleTaskRule(updatedRule);
+            } catch (Exception e) {
+                log.error("移除规则调度失败,taskId={}, ruleId={}",
+                        updatedRule.getTaskId(), updatedRule.getId(), e);
+            }
+        }
+
+        return updatedRule;
+    }
+
+    @Override
+    public Integer delete(Long tenantId, Long ruleId) {
+        // 验证规则是否存在
+        TaskScheduleRule rule = taskScheduleRuleRepository.selectByPrimaryKey(ruleId);
+        if (rule == null) {
+            throw new CommonException("wfautoV2.scheduleRule.notFound");
+        }
+
+        // 如果规则已启用,先移除Quartz调度
+        if (BaseConstant.ENABLE.equals(rule.getEnabledFlag())) {
+            throw new CommonException("wfautoV2.scheduleRule.enabled");
+        }
+        // 保底操作
+        quartzManagementService.unscheduleTaskRule(rule);
+        // 删除规则
+        return taskScheduleRuleRepository.deleteByPrimaryKey(ruleId);
+    }
+
+//    @Override
+//    public Integer enable(TaskScheduleRule rule) {
+//        // 验证任务是否存在
+//        IrrigationTask irrigationTask = taskRepository.selectByPrimaryKey(rule.getTaskId());
+//        if (irrigationTask == null) {
+//            throw new CommonException("wfautoV2.task.notFound");
+//        }
+//
+//        // 获取任务的所有规则并更新启用状态
+//        TaskScheduleRule queryRule = new TaskScheduleRule();
+//        queryRule.setTaskId(rule.getTaskId());
+//        queryRule.setTenantId(rule.getTenantId());
+//        List<TaskScheduleRule> rules = taskScheduleRuleRepository.listByTask(queryRule);
+//
+//        int updateCount = 0;
+//        for (TaskScheduleRule scheduleRule : rules) {
+//            // 更新启用状态
+//            TaskScheduleRule updateRule = new TaskScheduleRule();
+//            updateRule.setId(scheduleRule.getId());
+//            updateRule.setEnabledFlag(rule.getEnabledFlag());
+//            taskScheduleRuleRepository.updateByPrimaryKeySelective(updateRule);
+//            updateCount++;
+//
+//            // 重新获取完整的规则信息
+//            TaskScheduleRule fullRule = taskScheduleRuleRepository.selectByPrimaryKey(scheduleRule.getId());
+//
+//            // 根据启用状态处理Quartz调度
+//            if (BaseConstant.ENABLE.equals(rule.getEnabledFlag())) {
+//                // 启用规则:创建调度
+//                try {
+//                    quartzManagementService.scheduleTaskRule(irrigationTask, fullRule);
+//                    log.info("启用规则并创建调度成功,ruleId={}, jobName={}",
+//                            fullRule.getId(), fullRule.getQuartzJobName());
+//                } catch (Exception e) {
+//                    log.error("启用规则时创建调度失败,ruleId={}", fullRule.getId(), e);
+//                }
+//            } else {
+//                // 禁用规则:移除调度
+//                try {
+//                    quartzManagementService.removeTaskRuleSchedule(fullRule);
+//                    log.info("禁用规则并移除调度成功,ruleId={}, jobName={}",
+//                            fullRule.getId(), fullRule.getQuartzJobName());
+//                } catch (Exception e) {
+//                    log.error("禁用规则时移除调度失败,ruleId={}", fullRule.getId(), e);
+//                }
+//            }
+//        }
+//
+//        return updateCount;
+//    }
 }

+ 11 - 0
src/main/java/cn/sciento/farm/automationv2/config/QuartzConfig.java

@@ -1,8 +1,11 @@
 package cn.sciento.farm.automationv2.config;
 
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.scheduling.quartz.SchedulerFactoryBean;
+import org.springframework.scheduling.quartz.SpringBeanJobFactory;
 
 import javax.sql.DataSource;
 import java.util.Properties;
@@ -14,6 +17,9 @@ import java.util.Properties;
 @Configuration
 public class QuartzConfig {
 
+    @Autowired
+    private ApplicationContext applicationContext;
+
     /**
      * 配置Quartz调度器工厂
      */
@@ -21,6 +27,11 @@ public class QuartzConfig {
     public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource) {
         SchedulerFactoryBean factory = new SchedulerFactoryBean();
 
+        // 使用 Spring 的 JobFactory,支持依赖注入
+        SpringBeanJobFactory jobFactory = new SpringBeanJobFactory();
+        jobFactory.setApplicationContext(applicationContext);
+        factory.setJobFactory(jobFactory);
+
         // 使用应用的数据源
         factory.setDataSource(dataSource);
 

+ 9 - 9
src/main/java/cn/sciento/farm/automationv2/domain/business/impl/DeviceBusinessImpl.java

@@ -25,23 +25,23 @@ public class DeviceBusinessImpl implements DeviceBusiness {
 
     @Override
     public Integer control(GatewayControlDto dto) {
-//        ResponseEntity<String> result = feign.control(dto.getTenantId(), dto);
-//        return ResponseUtils.getResponse(result, Integer.class);
-        return 1;
+        ResponseEntity<String> result = feign.control(dto.getTenantId(), dto);
+        return ResponseUtils.getResponse(result, Integer.class);
+//        return 1;
     }
 
     @Override
     public Integer pumpPressure(GatewayControlDto dto) {
-//        ResponseEntity<String> result = feign.pumpPressure(dto.getTenantId(), dto);
-//        return ResponseUtils.getResponse(result, Integer.class);
-        return 1;
+        ResponseEntity<String> result = feign.pumpPressure(dto.getTenantId(), dto);
+        return ResponseUtils.getResponse(result, Integer.class);
+//        return 1;
     }
 
     @Override
     public Integer pumpControl(GatewayControlDto dto) {
-//        ResponseEntity<String> result = feign.pumpControl(dto.getTenantId(), dto);
-//        return ResponseUtils.getResponse(result, Integer.class);
-        return 1;
+        ResponseEntity<String> result = feign.pumpControl(dto.getTenantId(), dto);
+        return ResponseUtils.getResponse(result, Integer.class);
+//        return 1;
     }
 
 }

+ 128 - 9
src/main/java/cn/sciento/farm/automationv2/domain/entity/TaskScheduleRule.java

@@ -1,8 +1,11 @@
 package cn.sciento.farm.automationv2.domain.entity;
 
+import cn.sciento.core.exception.CommonException;
 import cn.sciento.farm.automationv2.domain.enums.ScheduleType;
 import cn.sciento.farm.automationv2.domain.enums.TaskScheduleRuleStatus;
+import cn.sciento.farm.automationv2.infra.constant.BaseConstant;
 import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import io.choerodon.mybatis.domain.AuditDomain;
 import io.swagger.annotations.ApiModel;
 import lombok.AllArgsConstructor;
@@ -60,13 +63,36 @@ public class TaskScheduleRule extends AuditDomain {
      */
     private String cronExpression;
 
-    // ================== SIMPLE 模式字段 ==================
+    /**
+     * 执行时间(schedule_type=CRON和SIMPLE 共用)
+     * 格式:"HH:mm:ss",例如:"02:00:00"
+     * 用于前端展示和生成 cronExpression
+     */
+    private String executeTime;
+
+    /**
+     * 重复周期 - 星期几(schedule_type=CRON时必填)
+     * 存储格式:"1,3,5" 表示周一、周三、周五
+     * 1=周一, 2=周二, 3=周三, 4=周四, 5=周五, 6=周六, 7=周日
+     * 用于前端展示和生成 cronExpression
+     */
+    private String repeatWeekdays;
 
     /**
-     * 起始时间(schedule_type=SIMPLE时必填)
+     * 有效开始日期(schedule_type=CRON时可选)
+     * 在此日期之前不会触发任务
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
-    private LocalDateTime startTime;
+    private LocalDateTime validStartDate;
+
+    /**
+     * 有效结束日期(schedule_type=CRON时可选)
+     * 在此日期之后不再触发任务
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime validEndDate;
+
+    // ================== SIMPLE 模式字段 ==================
 
     /**
      * 执行间隔天数(schedule_type=SIMPLE时必填)
@@ -86,11 +112,6 @@ public class TaskScheduleRule extends AuditDomain {
     // ================== 状态控制 ==================
 
     /**
-     * 是否启用:true启用 false禁用
-     */
-    private Boolean enabled;
-
-    /**
      * 规则状态:ACTIVE活跃 / COMPLETED已完成 / DISABLED已禁用
      * ACTIVE: 规则正在生效
      * COMPLETED: SIMPLE模式下已达到执行次数上限,自动停用
@@ -130,6 +151,7 @@ public class TaskScheduleRule extends AuditDomain {
     /**
      * 是否为 CRON 模式
      */
+    @JsonIgnore
     public boolean isCronMode() {
         return ScheduleType.CRON.equals(scheduleType);
     }
@@ -137,6 +159,7 @@ public class TaskScheduleRule extends AuditDomain {
     /**
      * 是否为 SIMPLE 模式
      */
+    @JsonIgnore
     public boolean isSimpleMode() {
         return ScheduleType.SIMPLE.equals(scheduleType);
     }
@@ -144,6 +167,7 @@ public class TaskScheduleRule extends AuditDomain {
     /**
      * SIMPLE 模式是否已达到执行次数上限
      */
+    @JsonIgnore
     public boolean reachedExecutionLimit() {
         return isSimpleMode()
                 && executedCount != null
@@ -154,6 +178,7 @@ public class TaskScheduleRule extends AuditDomain {
     /**
      * 增加已执行次数
      */
+    @JsonIgnore
     public void incrementExecutedCount() {
         if (executedCount == null) {
             executedCount = 0;
@@ -164,13 +189,15 @@ public class TaskScheduleRule extends AuditDomain {
     /**
      * 是否有效(启用且未完成)
      */
+    @JsonIgnore
     public boolean isActive() {
-        return Boolean.TRUE.equals(enabled) && TaskScheduleRuleStatus.ACTIVE.getCode().equals(status);
+        return BaseConstant.ENABLE.equals(enabledFlag) && TaskScheduleRuleStatus.ACTIVE.getCode().equals(status);
     }
 
     /**
      * 生成 Quartz Job 名称
      */
+    @JsonIgnore
     public static String generateJobName(Long taskId, Long ruleId) {
         return String.format("TASK_%d_RULE_%d", taskId, ruleId);
     }
@@ -178,6 +205,7 @@ public class TaskScheduleRule extends AuditDomain {
     /**
      * 生成 Quartz Trigger 名称
      */
+    @JsonIgnore
     public static String generateTriggerName(Long taskId, Long ruleId) {
         return String.format("TRIGGER_%d_RULE_%d", taskId, ruleId);
     }
@@ -185,10 +213,101 @@ public class TaskScheduleRule extends AuditDomain {
     /**
      * 填充Quartz相关字段
      */
+    @JsonIgnore
     public void fillQuartzNames() {
         if (this.taskId != null && this.id != null) {
             this.quartzJobName = generateJobName(this.taskId, this.id);
             this.quartzTriggerName = generateTriggerName(this.taskId, this.id);
+
+        }
+    }
+
+    /**
+     * 根据 executeTime 和 repeatWeekdays 生成 CRON 表达式
+     *
+     * @return Cron 表达式,例如:"0 0 2 ? * MON,WED,FRI"
+     * @throws IllegalArgumentException 如果参数格式不正确
+     */
+    @JsonIgnore
+    public String generateCronExpression() {
+        if (!isCronMode()) {
+            throw new CommonException("wfautoV2.task.schedule.typeError");
+        }
+        // 预留字段,如果存在则直接试用
+        if (cronExpression != null && !cronExpression.equals("")){
+            return cronExpression;
+        }
+        if (executeTime == null || executeTime.isEmpty()) {
+            throw new CommonException("wfautoV2.parameter.incomplete");
+        }
+
+        if (repeatWeekdays == null || repeatWeekdays.isEmpty()) {
+            throw new CommonException("wfautoV2.parameter.incomplete");
+        }
+
+        // 解析执行时间 "HH:mm:ss"
+        String[] timeParts = executeTime.split(":");
+        if (timeParts.length != 3) {
+            throw new CommonException("wfautoV2.parameter.error");
+        }
+
+        String hour = timeParts[0];
+        String minute = timeParts[1];
+        String second = timeParts[2];
+
+        // 转换星期数字为 Cron 格式
+        // 输入:"1,3,5" -> 输出:"MON,WED,FRI"
+        String[] weekdayNumbers = repeatWeekdays.split(",");
+        StringBuilder cronWeekdays = new StringBuilder();
+
+        for (int i = 0; i < weekdayNumbers.length; i++) {
+            if (i > 0) {
+                cronWeekdays.append(",");
+            }
+            cronWeekdays.append(numberToWeekday(weekdayNumbers[i].trim()));
+        }
+
+        // 构造 Cron 表达式:秒 分 时 日 月 周
+        return String.format("%s %s %s ? * %s", second, minute, hour, cronWeekdays.toString());
+    }
+
+    /**
+     * 数字转换为 Cron 星期格式
+     * 1=MON, 2=TUE, 3=WED, 4=THU, 5=FRI, 6=SAT, 7=SUN
+     */
+    @JsonIgnore
+    private String numberToWeekday(String number) {
+        switch (number) {
+            case "1": return "MON";
+            case "2": return "TUE";
+            case "3": return "WED";
+            case "4": return "THU";
+            case "5": return "FRI";
+            case "6": return "SAT";
+            case "7": return "SUN";
+            default: throw new IllegalArgumentException("无效的星期数字: " + number);
+        }
+    }
+
+    /**
+     * 检查当前时间是否在有效日期范围内
+     */
+    @JsonIgnore
+    public boolean isWithinValidPeriod() {
+        if (!isCronMode()) {
+            return true;
         }
+
+        LocalDateTime now = LocalDateTime.now();
+
+        if (validStartDate != null && now.isBefore(validStartDate)) {
+            return false;
+        }
+
+        if (validEndDate != null && now.isAfter(validEndDate)) {
+            return false;
+        }
+
+        return true;
     }
 }

+ 6 - 3
src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskLog.java

@@ -31,9 +31,6 @@ public class IrrigationTaskLog {
     @Id
     private String id;
 
-    @ApiModelProperty("主要的数据")
-    private IrrigationTaskMainVO mainData;
-
     @ApiModelProperty("灌区列表数据")
     private List<IrrigationTaskGroupVO> groups;
 
@@ -43,6 +40,12 @@ public class IrrigationTaskLog {
     @ApiModelProperty("租户Id")
     private Long tenantId;
 
+    @ApiModelProperty("水泵Id")
+    private Long pumpId;
+
+    @ApiModelProperty("水泵名称")
+    private String pumpName;
+
     @ApiModelProperty("基地Id")
     private Long organizationId;
 

+ 15 - 4
src/main/java/cn/sciento/farm/automationv2/domain/entity/mongo/IrrigationTaskMainVO.java

@@ -5,6 +5,7 @@ import cn.sciento.farm.automationv2.domain.enums.TriggerType;
 import com.fasterxml.jackson.annotation.JsonFormat;
 import io.swagger.annotations.ApiModelProperty;
 import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
 
 import javax.persistence.GeneratedValue;
 import javax.persistence.Id;
@@ -31,6 +32,7 @@ public class IrrigationTaskMainVO {
 
     @ApiModelProperty("当前灌溉组开始时间")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime groupBeginTime;
 
     @ApiModelProperty("当前压力")
@@ -39,14 +41,23 @@ public class IrrigationTaskMainVO {
     @ApiModelProperty("目标压力")
     private Integer targetPressure;
 
-    @ApiModelProperty("已灌溉时长")
-    private Long useTime;
+    @ApiModelProperty("灌溉组结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime groupEndTime;
 
     @ApiModelProperty("计划灌溉时长")
     private Long planTime;
 
-    @ApiModelProperty("总已灌溉时长")
-    private Long totalUseTime;
+    @ApiModelProperty("总开始时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime totalStartTime;
+
+    @ApiModelProperty("总结束时间")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime totalEndTime;
 
     @ApiModelProperty("总计划灌溉时长")
     private Long totalPlanTime;

+ 5 - 0
src/main/java/cn/sciento/farm/automationv2/domain/repository/TaskGroupConfigRepository.java

@@ -37,4 +37,9 @@ public interface TaskGroupConfigRepository extends BaseRepository<TaskGroupConfi
      */
     void deleteByTask(Long taskId);
 
+    /**
+     * 根据任务添加
+     * @param list
+     */
+    void addBatchByTask(List<TaskGroupConfig> list,Long taskId);
 }

+ 9 - 0
src/main/java/cn/sciento/farm/automationv2/domain/repository/TaskScheduleRuleRepository.java

@@ -3,6 +3,8 @@ package cn.sciento.farm.automationv2.domain.repository;
 import cn.sciento.farm.automationv2.domain.entity.TaskScheduleRule;
 import cn.sciento.mybatis.base.BaseRepository;
 
+import java.util.List;
+
 /**
  * 任务定时规则 Repository
  *
@@ -11,4 +13,11 @@ import cn.sciento.mybatis.base.BaseRepository;
  */
 public interface TaskScheduleRuleRepository extends BaseRepository<TaskScheduleRule> {
 
+
+    /**
+     * 列表获取
+     * @param rule
+     * @return
+     */
+    List<TaskScheduleRule> listByTask(TaskScheduleRule rule);
 }

+ 2 - 0
src/main/java/cn/sciento/farm/automationv2/domain/service/ExecutionPlanGenerator.java

@@ -365,6 +365,8 @@ public class ExecutionPlanGenerator {
                 DeviceInfo device = DeviceInfo.builder()
                         .deviceId(Long.parseLong(json.getString("deviceId")))
                         .deviceType(deviceType)
+                        .brand(json.getString("deviceName"))
+                        .vtype(json.getInteger("vtype"))
                         .deviceName(json.getString("deviceName"))
                         .switchId(json.getLong("switchId"))
                         .sw(json.getInteger("sw"))

+ 9 - 0
src/main/java/cn/sciento/farm/automationv2/domain/valueobject/DeviceInfo.java

@@ -1,6 +1,7 @@
 package cn.sciento.farm.automationv2.domain.valueobject;
 
 import cn.sciento.farm.automationv2.domain.enums.AckStatus;
+import io.swagger.annotations.ApiModelProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -83,4 +84,12 @@ public class DeviceInfo {
      * 设备参数(如球阀的目标角度)
      */
     private Object params;
+
+    /**
+     * 球阀类型:直通/三通
+     */
+    private Integer vtype;
+
+    @ApiModelProperty("品牌")
+    private String brand;
 }

+ 2 - 0
src/main/java/cn/sciento/farm/automationv2/domain/valueobject/ExecutionNode.java

@@ -8,6 +8,7 @@ import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
+import org.springframework.format.annotation.DateTimeFormat;
 
 import java.time.LocalDateTime;
 import java.util.List;
@@ -67,6 +68,7 @@ public class ExecutionNode {
      * 节点开始执行时间
      */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private LocalDateTime startedAt;
 
     /**

+ 25 - 0
src/main/java/cn/sciento/farm/automationv2/domain/valueobject/IrrigationTaskDetailVO.java

@@ -0,0 +1,25 @@
+package cn.sciento.farm.automationv2.domain.valueobject;
+
+import cn.sciento.farm.automationv2.api.dto.CreateTaskRequest;
+import cn.sciento.farm.automationv2.domain.entity.IrrigationTask;
+import cn.sciento.farm.automationv2.domain.entity.TaskGroupConfig;
+import lombok.Data;
+
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+
+/**
+ * 轮灌任务VO
+ */
+@Data
+public class IrrigationTaskDetailVO {
+
+    private IrrigationTask task;
+
+    /**
+     * 灌区配置列表
+     */
+    private List<TaskGroupConfig> groupConfigs;
+}

+ 23 - 0
src/main/java/cn/sciento/farm/automationv2/infra/init/WfautoV2DataInit.java

@@ -0,0 +1,23 @@
+package cn.sciento.farm.automationv2.infra.init;
+
+import cn.sciento.core.message.MessageAccessor;
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.*;
+
+/**
+ * @author wumu
+ */
+@Component
+public class WfautoV2DataInit implements SmartInitializingSingleton {
+
+    @Override
+    public void afterSingletonsInstantiated() {
+        // 加入消息文件
+        MessageAccessor.addBasenames("classpath:messages/messages_wfautoV2");
+        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("wfautoV2-init-thread-%d").build();
+        ExecutorService executorService = new ThreadPoolExecutor(3, 3, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),namedThreadFactory);
+    }
+}

+ 11 - 0
src/main/java/cn/sciento/farm/automationv2/infra/repository/impl/TaskGroupConfigRepositoryImpl.java

@@ -39,4 +39,15 @@ public class TaskGroupConfigRepositoryImpl extends BaseRepositoryImpl<TaskGroupC
             batchDeleteByPrimaryKey(taskGroupConfigs);
         }
     }
+
+    @Override
+    public void addBatchByTask(List<TaskGroupConfig> list,Long taskId) {
+        if (list != null && list.size()>0){
+            for (TaskGroupConfig config : list) {
+                config.setId(null);
+                config.setTaskId(taskId);
+            }
+            batchInsertSelective(list);
+        }
+    }
 }

+ 14 - 0
src/main/java/cn/sciento/farm/automationv2/infra/repository/impl/TaskScheduleRuleRepositoryImpl.java

@@ -5,6 +5,8 @@ import cn.sciento.farm.automationv2.domain.repository.TaskScheduleRuleRepository
 import cn.sciento.mybatis.base.impl.BaseRepositoryImpl;
 import org.springframework.stereotype.Service;
 
+import java.util.List;
+
 /**
  * 任务定时规则 Repository 实现
  *
@@ -14,4 +16,16 @@ import org.springframework.stereotype.Service;
 @Service
 public class TaskScheduleRuleRepositoryImpl extends BaseRepositoryImpl<TaskScheduleRule> implements TaskScheduleRuleRepository {
 
+    @Override
+    public List<TaskScheduleRule> listByTask(TaskScheduleRule rule) {
+        TaskScheduleRule index = new TaskScheduleRule();
+        index.setTenantId(rule.getTenantId());
+        if (rule.getTaskId() != null){
+            index.setTaskId(rule.getTaskId());
+        }
+        if (rule.getEnabledFlag() != null){
+            index.setEnabledFlag(rule.getEnabledFlag());
+        }
+        return select(index);
+    }
 }

+ 2 - 2
src/main/resources/bootstrap.yml

@@ -18,9 +18,9 @@ spring:
     nacos:
       config:
         server-addr: 192.168.10.18:8848
-        namespace: stong-test-train
+        namespace: stong-test-demo1
       discovery:
         server-addr: 192.168.10.18:8848
-        namespace: stong-test-train
+        namespace: stong-test-demo1
         metadata:
           VERSION: 1.0.0

+ 1 - 1
src/main/resources/mapper/IrrigationGroupMapper.xml

@@ -44,7 +44,7 @@
         <foreach collection="ids" item="id" open="(" separator="," close=")">
             #{id}
         </foreach>
-        AND enabled_flag = 1
+--         AND enabled_flag = 1
     </select>
 
     <!-- 以下方法已过时,灌溉组已独立于任务 -->

+ 7 - 0
src/main/resources/messages/messages_wfautoV2_en_US.properties

@@ -10,11 +10,18 @@ wfautoV2.bean.updateError=Failed to update
 wfautoV2.parameter.incomplete=Incomplete parameter
 wfautoV2.parameter.error=error parameter
 
+wfautoV2.group.notFound=Irrigation groups do not exist
 wfautoV2.group.hadTask=If there are bound tasks in the current Wheel and Tank Group, delete the tasks first
 wfautoV2.task.enabled=Disable the |task before deleting it
 wfautoV2.task.notFound=The mission does not exist
+wfautoV2.task.notEnable=The task is not enabled
 wfautoV2.task.hadRunning=The task is running and cannot be disabled for the time being
 wfautoV2.task.hadAction=The task has already started and cannot be started repeatedly
+wfautoV2.task.notGroup=The task does not have an irrigation area configured
 
 wfautoV2.task.execution.notFound=The task instance does not exist
 wfautoV2.task.execution.hadCancel=The task execution has been terminated and cannot be canceled
+
+wfautoV2.task.schedule.typeError=Only the recurring pattern can generate CRON expressions
+wfautoV2.scheduleRule.notFound=Timing rules do not exist
+wfautoV2.scheduleRule.enabled=Timing rules are enabled and cannot be deleted

+ 8 - 1
src/main/resources/messages/messages_wfautoV2_zh_CN.properties

@@ -10,12 +10,19 @@ wfautoV2.bean.updateError=\u66F4\u65B0\u5931\u8D25
 wfautoV2.parameter.incomplete=\u53C2\u6570\u4E0D\u5B8C\u6574
 wfautoV2.parameter.error=\u53C2\u6570\u9519\u8BEF
 
+wfautoV2.group.notFound=\u704C\u6E89\u7EC4\u4E0D\u5B58\u5728,\u9519\u8BEF\u4FE1\u606F: {0}
 wfautoV2.group.hadTask=\u5F53\u524D\u8F6E\u7F50\u7EC4\u5B58\u5728\u5DF2\u7ED1\u5B9A\u4EFB\u52A1\uFF0C\u8BF7\u5148\u5220\u9664\u4EFB\u52A1
 wfautoV2.task.enabled=\u8BF7\u5148\u7981\u7528\u4EFB\u52A1\u518D\u5220\u9664
 wfautoV2.task.notFound=\u4EFB\u52A1\u4E0D\u5B58\u5728
+wfautoV2.task.notEnable=\u4EFB\u52A1\u672A\u542F\u7528
 wfautoV2.task.hadRunning=\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\uFF0C\u6682\u65F6\u65E0\u6CD5\u7981\u7528
 wfautoV2.task.hadAction=\u4EFB\u52A1\u5DF2\u7ECF\u542F\u52A8\uFF0C\u65E0\u6CD5\u91CD\u590D\u542F\u52A8
+wfautoV2.task.notGroup=\u4EFB\u52A1\u672A\u914D\u7F6E\u704C\u533A
 
 wfautoV2.task.execution.notFound=\u4EFB\u52A1\u5B9E\u4F8B\u4E0D\u5B58\u5728
 wfautoV2.task.execution.hadCancel=\u4EFB\u52A1\u6267\u884C\u5DF2\u7EC8\u6B62\uFF0C\u65E0\u6CD5\u53D6\u6D88
-wfautoV2.task.execution.cancelError=\u4EFB\u52A1\u6267\u884C\u53D6\u6D88\u5931\u8D25,\u9519\u8BEF\u4FE1\u606F: {0}
+wfautoV2.task.execution.cancelError=\u4EFB\u52A1\u6267\u884C\u53D6\u6D88\u5931\u8D25,\u9519\u8BEF\u4FE1\u606F: {0}
+
+wfautoV2.task.schedule.typeError=\u53EA\u6709\u91CD\u590D\u5468\u671F\u6A21\u5F0F\u624D\u80FD\u751F\u6210CRON\u8868\u8FBE\u5F0F
+wfautoV2.scheduleRule.notFound=\u5B9A\u65F6\u89C4\u5219\u4E0D\u5B58\u5728
+wfautoV2.scheduleRule.enabled=\u5B9A\u65F6\u89C4\u5219\u5DF2\u542F\u7528\uFF0C\u65E0\u6CD5\u5220\u9664