Explorar o código

feat(health): approveAndPublish 返回 taskGenerated,任务生成失败不再静默

iwt hai 2 días
pai
achega
33381ff259

+ 4 - 3
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthPlanController.java

@@ -1,6 +1,7 @@
 package com.etotem.cfc.controller;
 
 import com.etotem.cfc.common.Result;
+import com.etotem.cfc.dto.PlanApproveResultDTO;
 import com.etotem.cfc.service.PurchaseService;
 import com.etotem.cfc.entity.HealthPlan;
 import com.etotem.cfc.entity.Task;
@@ -197,7 +198,7 @@ public class HealthPlanController {
 
     @Operation(summary = "规划师审核通过并发布(自动生成任务)")
     @PostMapping("/pending-review/approve")
-    public Result<HealthPlan> approvePlan(
+    public Result<PlanApproveResultDTO> approvePlan(
             @RequestBody Map<String, Object> params,
             @RequestAttribute("userId") Long userId,
             @RequestAttribute("role") String role) {
@@ -207,8 +208,8 @@ public class HealthPlanController {
         Long planId = ParamUtils.getLong(params.get("planId"));
         if (planId == null) return Result.error("planId不能为空");
         String comment = (String) params.get("comment");
-        HealthPlan plan = healthPlanService.approveAndPublish(planId, userId, comment);
-        return Result.success(plan);
+        PlanApproveResultDTO result = healthPlanService.approveAndPublish(planId, userId, comment);
+        return Result.success(result);
     }
 
     @Operation(summary = "规划师驳回方案")

+ 16 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/PlanApproveResultDTO.java

@@ -0,0 +1,16 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+
+/**
+ * 方案审核通过结果。
+ * taskGenerated=false 表示方案已发布但未生成任何任务,前端必须提示用户。
+ */
+@Data
+public class PlanApproveResultDTO {
+    private Long planId;
+    private String status;
+    private Boolean taskGenerated;
+    private Integer taskCount;
+    private String message;
+}

+ 2 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/HealthPlanService.java

@@ -1,6 +1,7 @@
 package com.etotem.cfc.service;
 
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.etotem.cfc.dto.PlanApproveResultDTO;
 import com.etotem.cfc.entity.HealthPlan;
 import com.etotem.cfc.entity.Task;
 import java.util.List;
@@ -20,7 +21,7 @@ public interface HealthPlanService {
     /** 规划师编辑方案内容 */
     HealthPlan updatePlanContent(Long planId, String planContent, String planJson);
     /** 规划师审核通过并发布(同时生成任务) */
-    HealthPlan approveAndPublish(Long planId, Long reviewedBy, String comment);
+    PlanApproveResultDTO approveAndPublish(Long planId, Long reviewedBy, String comment);
     /** 规划师驳回方案 */
     HealthPlan rejectPlan(Long planId, Long reviewedBy, String comment);
     /**

+ 19 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/impl/HealthPlanServiceImpl.java

@@ -2,6 +2,7 @@ package com.etotem.cfc.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.etotem.cfc.dto.PlanApproveResultDTO;
 import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.FiveDimensionSelfCheck;
 import com.etotem.cfc.entity.HealthPlan;
@@ -578,7 +579,7 @@ public class HealthPlanServiceImpl implements HealthPlanService {
     }
 
     @Override
-    public HealthPlan approveAndPublish(Long planId, Long reviewedBy, String comment) {
+    public PlanApproveResultDTO approveAndPublish(Long planId, Long reviewedBy, String comment) {
         HealthPlan plan = healthPlanMapper.selectById(planId);
         if (plan == null) throw new RuntimeException("方案不存在");
         if (!"pending_review".equals(plan.getStatus()) && !"draft".equals(plan.getStatus())) {
@@ -590,12 +591,28 @@ public class HealthPlanServiceImpl implements HealthPlanService {
         plan.setReviewComment(comment);
         plan.setUpdatedAt(new Date());
         healthPlanMapper.updateById(plan);
+        boolean taskGenerated = true;
+        int taskCount = 0;
         try {
             generateDailyTasksFromPlan(plan);
+            List<Task> generated = taskMapper.selectList(new QueryWrapper<Task>()
+                    .eq("source_type", "health_plan")
+                    .eq("source_id", planId));
+            taskCount = generated == null ? 0 : generated.size();
+            taskGenerated = taskCount > 0;
         } catch (Exception e) {
+            // 方案已发布,任务生成失败不能静默——必须让调用方知道
+            taskGenerated = false;
+            taskCount = 0;
             log.warn("方案{}发布时生成任务失败: {}", planId, e.getMessage());
         }
-        return plan;
+        PlanApproveResultDTO dto = new PlanApproveResultDTO();
+        dto.setPlanId(planId);
+        dto.setStatus(plan.getStatus());
+        dto.setTaskGenerated(taskGenerated);
+        dto.setTaskCount(taskCount);
+        dto.setMessage(taskGenerated ? "方案已发布并生成任务" : "方案已发布,但未生成任何任务,请检查方案内容");
+        return dto;
     }
 
 @Override

+ 41 - 0
cfc-backend/src/test/java/com/etotem/cfc/unit/HealthPlanReviewGuardTest.java

@@ -1,12 +1,17 @@
 package com.etotem.cfc.unit;
 
+import com.etotem.cfc.dto.PlanApproveResultDTO;
 import com.etotem.cfc.entity.HealthPlan;
+import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.mapper.HealthPlanMapper;
+import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.service.impl.HealthPlanServiceImpl;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.Collections;
 
 import static org.junit.jupiter.api.Assertions.*;
 import static org.mockito.ArgumentMatchers.any;
@@ -16,14 +21,19 @@ class HealthPlanReviewGuardTest {
 
     private HealthPlanServiceImpl service;
     private HealthPlanMapper mapper;
+    private TaskMapper taskMapper;
 
     @BeforeEach
     void setUp() throws Exception {
         service = new HealthPlanServiceImpl();
         mapper = mock(HealthPlanMapper.class);
+        taskMapper = mock(TaskMapper.class);
         Field f = HealthPlanServiceImpl.class.getDeclaredField("healthPlanMapper");
         f.setAccessible(true);
         f.set(service, mapper);
+        Field tf = HealthPlanServiceImpl.class.getDeclaredField("taskMapper");
+        tf.setAccessible(true);
+        tf.set(service, taskMapper);
     }
 
     private HealthPlan planWithStatus(String status) {
@@ -109,4 +119,35 @@ class HealthPlanReviewGuardTest {
         assertEquals("方案不存在",
                 assertThrows(RuntimeException.class, () -> service.getPlanForFamily(99L, 10L)).getMessage());
     }
+
+    // ---- approveAndPublish taskGenerated ----
+
+    @Test
+    void 任务生成成功时taskGenerated为true() {
+        HealthPlan p = planWithStatus("pending_review");
+        when(mapper.selectById(5L)).thenReturn(p);
+        when(mapper.updateById(any(HealthPlan.class))).thenReturn(1);
+        when(taskMapper.selectList(any())).thenReturn(Arrays.asList(new Task(), new Task()));
+
+        PlanApproveResultDTO r = service.approveAndPublish(5L, 1L, "ok");
+
+        assertEquals("published", r.getStatus());
+        assertTrue(r.getTaskGenerated());
+        assertEquals(Integer.valueOf(2), r.getTaskCount());
+    }
+
+    @Test
+    void 未生成任何任务时taskGenerated为false但方案仍发布() {
+        HealthPlan p = planWithStatus("pending_review");
+        when(mapper.selectById(5L)).thenReturn(p);
+        when(mapper.updateById(any(HealthPlan.class))).thenReturn(1);
+        when(taskMapper.selectList(any())).thenReturn(Collections.emptyList());
+
+        PlanApproveResultDTO r = service.approveAndPublish(5L, 1L, "ok");
+
+        assertEquals("published", r.getStatus(), "任务生成失败不得回滚方案发布");
+        assertFalse(r.getTaskGenerated());
+        assertEquals(Integer.valueOf(0), r.getTaskCount());
+        assertTrue(r.getMessage().contains("未生成任何任务"));
+    }
 }