Kaynağa Gözat

feat(backend): 方案激活按模板包批量拆任务并兼容childId双域

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
E2E Test Bot 3 hafta önce
ebeveyn
işleme
5de86e218f

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentPlanGenerator.java

@@ -73,6 +73,9 @@ public class AssessmentPlanGenerator {
         plan.setStatus("generated");
         plan.setStatus("generated");
         plan.setSource("assessment");
         plan.setSource("assessment");
         plan.setSourceResultId(result.getId());
         plan.setSourceResultId(result.getId());
+        if (!templateIds.isEmpty()) {
+            plan.setPackageId(templateIds.iterator().next());
+        }
         plan.setTotalDays(14);
         plan.setTotalDays(14);
         plan.setCompletedDays(0);
         plan.setCompletedDays(0);
         plan.setStartDate(new Date());
         plan.setStartDate(new Date());

+ 83 - 28
cfc-backend/src/main/java/com/etotem/cfc/service/PlanActivationService.java

@@ -1,10 +1,15 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.TaskPlanInstance;
 import com.etotem.cfc.entity.TaskPlanInstance;
+import com.etotem.cfc.entity.TaskTemplateItem;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskPlanInstanceMapper;
 import com.etotem.cfc.mapper.TaskPlanInstanceMapper;
+import com.etotem.cfc.mapper.TaskTemplateItemMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
@@ -14,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional;
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 import java.util.Calendar;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Date;
+import java.util.List;
 
 
 /**
 /**
  * 方案激活服务。
  * 方案激活服务。
@@ -33,11 +39,18 @@ public class PlanActivationService {
     @Resource
     @Resource
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
+    @Resource
+    private FamilyMemberMapper familyMemberMapper;
+
+    @Resource
+    private TaskTemplateItemMapper templateItemMapper;
+
     /**
     /**
      * 激活方案:
      * 激活方案:
      * 1. 校验状态必须为 reviewed
      * 1. 校验状态必须为 reviewed
      * 2. 生成一条引导性任务(方案概览/首日任务)
      * 2. 生成一条引导性任务(方案概览/首日任务)
-     * 3. 标记方案为 active
+     * 3. 若方案关联了模板包(packageId),按模板项批量生成日常任务(任务分解)
+     * 4. 标记方案为 active
      */
      */
     @Transactional
     @Transactional
     public void activate(Long planId, Long operatorId) {
     public void activate(Long planId, Long operatorId) {
@@ -46,34 +59,68 @@ public class PlanActivationService {
         if (!"reviewed".equals(plan.getStatus())) {
         if (!"reviewed".equals(plan.getStatus())) {
             throw new IllegalStateException("方案状态异常,需要先审核通过");
             throw new IllegalStateException("方案状态异常,需要先审核通过");
         }
         }
-        User child = userMapper.selectById(plan.getChildId());
-        if (child == null) throw new IllegalStateException("孩子不存在");
+        // childId 兼容 user 或 family_members 两种 ID 域,任一存在即可激活
+        boolean childExists = userMapper.selectById(plan.getChildId()) != null
+                || familyMemberMapper.selectById(plan.getChildId()) != null;
+        if (!childExists) throw new IllegalStateException("孩子不存在");
 
 
         // 生成首日引导任务
         // 生成首日引导任务
-        Task task = new Task();
-        task.setFamilyId(plan.getFamilyId());
-        task.setChildId(plan.getChildId());
-        task.setCreatorId(operatorId);
-        task.setExecutorType("child");
-        task.setExecutorId(plan.getChildId());
-        task.setTitle("方案启动: " + plan.getName());
-        task.setDescription("请阅读并开始执行测评方案");
-        task.setPoints(2);
-        task.setCategory("成长");
-        task.setStatus("pending");
-        task.setSourceType("plan");
-        task.setSourceId(plan.getId());
-        task.setNeedReview(0);
-        task.setIsTemplate(0);
-        task.setMemberOnly(1); // B20: 方案/套餐生成的任务为会员专属
-        Calendar cal = Calendar.getInstance();
-        cal.set(Calendar.HOUR_OF_DAY, 23);
-        cal.set(Calendar.MINUTE, 59);
-        cal.set(Calendar.SECOND, 59);
-        task.setDeadline(cal.getTime());
-        task.setCreatedAt(new Date());
-        task.setUpdatedAt(new Date());
-        taskMapper.insert(task);
+        Task guideTask = new Task();
+        guideTask.setFamilyId(plan.getFamilyId());
+        guideTask.setChildId(plan.getChildId());
+        guideTask.setCreatorId(operatorId);
+        guideTask.setExecutorType("child");
+        guideTask.setExecutorId(plan.getChildId());
+        guideTask.setTitle("方案启动: " + plan.getName());
+        guideTask.setDescription("请阅读并开始执行测评方案");
+        guideTask.setPoints(2);
+        guideTask.setCategory("成长");
+        guideTask.setStatus("pending");
+        guideTask.setSourceType("plan");
+        guideTask.setSourceId(plan.getId());
+        guideTask.setNeedReview(0);
+        guideTask.setIsTemplate(0);
+        guideTask.setMemberOnly(1); // B20: 方案/套餐生成的任务为会员专属
+        fillDeadline(guideTask);
+        guideTask.setCreatedAt(new Date());
+        guideTask.setUpdatedAt(new Date());
+        taskMapper.insert(guideTask);
+
+        // 按模板包任务项批量生成日常任务(任务分解)
+        if (plan.getPackageId() != null) {
+            List<TaskTemplateItem> items = templateItemMapper.selectList(
+                    new LambdaQueryWrapper<TaskTemplateItem>()
+                            .eq(TaskTemplateItem::getPackageId, plan.getPackageId())
+                            .orderByAsc(TaskTemplateItem::getSortOrder));
+            int itemCount = 0;
+            for (TaskTemplateItem item : items) {
+                if (item.getTaskName() == null || item.getTaskName().isEmpty()) continue;
+                Task t = new Task();
+                t.setFamilyId(plan.getFamilyId());
+                t.setChildId(plan.getChildId());
+                t.setCreatorId(operatorId);
+                t.setExecutorType("child");
+                t.setExecutorId(plan.getChildId());
+                t.setTitle(item.getTaskName());
+                t.setDescription(item.getDescription());
+                t.setPoints(item.getPoints() != null ? item.getPoints() : 2);
+                t.setCategory("成长");
+                t.setStatus("pending");
+                t.setSourceType("plan");
+                t.setSourceId(plan.getId());
+                t.setNeedReview(0);
+                t.setIsTemplate(0);
+                t.setMemberOnly(1);
+                t.setFrequency(item.getFrequency() != null ? item.getFrequency() : "daily");
+                t.setDuration(item.getDuration());
+                fillDeadline(t);
+                t.setCreatedAt(new Date());
+                t.setUpdatedAt(new Date());
+                taskMapper.insert(t);
+                itemCount++;
+            }
+            log.info("方案{}激活按模板包{}生成{}条任务", planId, plan.getPackageId(), itemCount);
+        }
 
 
         // 标记方案已激活
         // 标记方案已激活
         plan.setStatus("active");
         plan.setStatus("active");
@@ -83,6 +130,14 @@ public class PlanActivationService {
         plan.setUpdatedAt(new Date());
         plan.setUpdatedAt(new Date());
         planMapper.updateById(plan);
         planMapper.updateById(plan);
 
 
-        log.info("方案激活 planId={}, childId={}, taskId={}", planId, plan.getChildId(), task.getId());
+        log.info("方案激活 planId={}, childId={}, taskId={}", planId, plan.getChildId(), guideTask.getId());
+    }
+
+    private void fillDeadline(Task task) {
+        Calendar cal = Calendar.getInstance();
+        cal.set(Calendar.HOUR_OF_DAY, 23);
+        cal.set(Calendar.MINUTE, 59);
+        cal.set(Calendar.SECOND, 59);
+        task.setDeadline(cal.getTime());
     }
     }
 }
 }