Преглед изворни кода

feat(task-plan): 状态机收敛generated→active,confirm改走applyAndActivate

iwt пре 2 недеља
родитељ
комит
1132b276df

+ 6 - 24
cfc-backend/src/main/java/com/etotem/cfc/controller/ProblemPackageController.java

@@ -4,14 +4,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.ApplyPackageDTO;
 import com.etotem.cfc.entity.ProblemDomain;
-import com.etotem.cfc.entity.TaskPlanInstance;
 import com.etotem.cfc.entity.TaskTemplateItem;
 import com.etotem.cfc.entity.TaskTemplatePackage;
 import com.etotem.cfc.entity.UserIntent;
 import com.etotem.cfc.entity.PackageOrder;
 import com.etotem.cfc.mapper.PackageOrderMapper;
 import com.etotem.cfc.mapper.ProblemDomainMapper;
-import com.etotem.cfc.mapper.TaskPlanInstanceMapper;
 import com.etotem.cfc.mapper.UserIntentMapper;
 import com.etotem.cfc.service.MembershipService;
 import com.etotem.cfc.service.TaskPlanService;
@@ -45,9 +43,6 @@ public class ProblemPackageController {
     @Resource
     private TaskPlanService taskPlanService;
 
-    @Resource
-    private TaskPlanInstanceMapper planInstanceMapper;
-
     @Resource
     private UserIntentMapper userIntentMapper;
 
@@ -132,25 +127,12 @@ public class ProblemPackageController {
             return Result.error("请先购买套餐后再确认方案");
         }
 
-        // 幂等:同一家庭同一套餐已有激活中的方案实例则复用
-        TaskPlanInstance plan = planInstanceMapper.selectOne(
-                new LambdaQueryWrapper<TaskPlanInstance>()
-                        .eq(TaskPlanInstance::getFamilyId, familyId)
-                        .eq(TaskPlanInstance::getPackageId, packageId)
-                        .eq(TaskPlanInstance::getStatus, "active")
-                        .last("LIMIT 1"));
-        Long planId;
-        if (plan == null) {
-            ApplyPackageDTO dto = new ApplyPackageDTO();
-            dto.setPackageId(packageId);
-            TaskPlanInstance created = taskPlanService.applyPackage(dto, familyId);
-            planId = created.getId();
-        } else {
-            planId = plan.getId();
-        }
-
-        // 生成方案任务(memberOnly=1,幂等)
-        int taskCount = taskPlanService.generatePlanTasks(planId, familyId, userId);
+        // 应用套餐并激活(幂等复用 + 自助置 active + 统一生成任务)
+        ApplyPackageDTO dto = new ApplyPackageDTO();
+        dto.setPackageId(packageId);
+        Map<String, Object> applied = taskPlanService.applyAndActivate(dto, familyId, userId);
+        Long planId = (Long) applied.get("planId");
+        int taskCount = (Integer) applied.get("taskCount");
 
         // 推进旅程 stage=6(方案已确认,任务已生成)
         UserIntent intent = findIntent(userId);

+ 41 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/TaskPlanService.java

@@ -21,7 +21,9 @@ import org.springframework.transaction.annotation.Transactional;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -80,7 +82,7 @@ public class TaskPlanService implements TaskPlanServiceInterface {
         Date now = new Date();
         instance.setStartDate(now);
         instance.setEndDate(new Date(now.getTime() + (long) pkg.getCycleDays() * 24 * 60 * 60 * 1000));
-        instance.setStatus("active");
+        instance.setStatus("generated"); // 状态机: generated → reviewed → active(见统一设计)
         instance.setTotalDays(pkg.getCycleDays());
         instance.setCompletedDays(0);
         instance.setCreatedAt(now);
@@ -268,6 +270,44 @@ public class TaskPlanService implements TaskPlanServiceInterface {
         return count;
     }
 
+    /**
+     * 家长自助应用套餐并激活(confirm / purchase 共用):
+     * 1. 幂等:同家庭同套餐已有 active 实例则复用
+     * 2. 新建则 applyPackage(generated)→ 置 active(自助跳过审核)
+     * 3. 统一生成器生成任务
+     */
+    @Transactional
+    public Map<String, Object> applyAndActivate(ApplyPackageDTO dto, Long familyId, Long userId) {
+        TaskPlanInstance plan = findActiveByFamilyAndPackage(familyId, dto.getPackageId());
+        boolean created = false;
+        if (plan == null) {
+            plan = applyPackage(dto, familyId);
+            created = true;
+        }
+        if (created) {
+            Date now = new Date();
+            plan.setStatus("active");
+            plan.setActivatedAt(now);
+            plan.setUpdatedAt(now);
+            instanceMapper.updateById(plan);
+        }
+        int taskCount = generateTasksFromTemplate(plan, userId);
+        Map<String, Object> result = new HashMap<>();
+        result.put("planId", plan.getId());
+        result.put("taskCount", taskCount);
+        return result;
+    }
+
+    /** 查询同家庭同套餐的 active 实例(幂等复用) */
+    public TaskPlanInstance findActiveByFamilyAndPackage(Long familyId, Long packageId) {
+        return instanceMapper.selectOne(
+                new LambdaQueryWrapper<TaskPlanInstance>()
+                        .eq(TaskPlanInstance::getFamilyId, familyId)
+                        .eq(TaskPlanInstance::getPackageId, packageId)
+                        .eq(TaskPlanInstance::getStatus, "active")
+                        .last("LIMIT 1"));
+    }
+
     /** 当天 23:59:59 截止(与 TaskService.getTodayTasks 区间对齐) */
     private Date buildTodayDeadline() {
         Calendar cal = Calendar.getInstance();

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

@@ -5,6 +5,7 @@ import com.etotem.cfc.entity.TaskPlanInstance;
 import com.etotem.cfc.entity.TaskPlanItem;
 
 import java.util.List;
+import java.util.Map;
 
 public interface TaskPlanServiceInterface {
     TaskPlanInstance applyPackage(ApplyPackageDTO dto, Long familyId);
@@ -14,5 +15,7 @@ public interface TaskPlanServiceInterface {
     boolean cancelPlan(Long id);
     boolean updateCompletedDays(Long id, int days);
     int generateTasksFromTemplate(TaskPlanInstance plan, Long operatorId);
+    Map<String, Object> applyAndActivate(ApplyPackageDTO dto, Long familyId, Long userId);
+    TaskPlanInstance findActiveByFamilyAndPackage(Long familyId, Long packageId);
     List<TaskPlanInstance> getChildPlans(Long memberId);
 }