# 方案转任务统一设计(Plan → Task Unification) **优先级:** P0 **预计工时:** 后端 3d + 迁移 1d **状态:** 设计待确认 **日期:** 2026-09-04 **关联文档:** `2026-09-04-task-system-unification-design.md`(统一任务系统总设计,本设计为其"方案转任务"子模块深化) ## 1. 背景与问题 系统存在 **4 套并行的"方案转任务"体系**,各自独立演进,`source_type` 语义混乱: | # | 方案体系 | 方案表 | 任务来源 | source_type | source_id 指向 | 状态机 | 生成方法 | |---|---------|--------|---------|-------------|----------------|--------|---------| | 1 | 套餐方案 | `task_plan_instances` | `task_plan_items`(镜像) | `plan` | `task_plan_instances.id` | active(无审核) | `TaskPlanService.generatePlanTasks` | | 2 | 测评方案 | `task_plan_instances` | `task_template_items`(直读) | `plan` | `task_plan_instances.id` | generated→reviewed→active | `PlanActivationService.activate` | | 3 | 健康方案 | `health_plans` | 方案文本正则解析 / plan_json | `plan` | `health_plans.id` | draft→pending_review→published | `HealthPlanServiceImpl.generateDailyTasksFromPlan` | | 4 | 认知训练方案 | `training_plans` | `training_plan_items` | **未设** | — | draft→published | `TrainingPlanService.deployPlan` | ### 核心矛盾 **`source_type='plan'` 被 3 套体系共用,但 `source_id` 指向 3 张不同的表**(`task_plan_instances` / `health_plans` / 无)。导致: 1. `ProblemCompletionService`(第 106-110 行)按 `source_type='plan'` + `source_id IN (planIds)` 统计完成度时,`planIds` 来自 `task_plan_instances`,但健康方案任务也标 `source_type='plan'`,若 `source_id` 撞上 `task_plan_instances.id` 会被误统计。 2. `HealthPlanServiceImpl.getPlanTasks`(第 196 行)按 `source_type='plan'` + `source_id=healthPlanId` 查任务,可能误捞套餐方案任务。 3. 认知训练方案(第 4 套)**未设 sourceType**,`deployPlan` 走 `taskService.createTask` 后任务无法溯源。 4. 套餐方案(第 1 套)与测评方案(第 2 套)虽同表,但任务来源不同(`task_plan_items` vs `task_template_items`)、deadline 语义不同(方案结束日 vs 当天 23:59:59)、`familyMemberId` 一个设一个不设。 ## 2. 范围决策(已与用户确认) | 决策项 | 结论 | |--------|------| | 覆盖范围 | **全部 4 套**方案体系 | | source_type 策略 | **拆分枚举**:新增 `health_plan` / `training_plan`,`source_type` 与 `source_id` 一一对应 | | 套餐/测评方案 | 保留 `plan` 枚举(同表 `task_plan_instances`),收敛为单一任务生成器 | | 任务来源 | 套餐/测评统一从 `task_template_items` 读取(`task_plan_items` 降级为审计快照) | | deadline 语义 | 统一为**当天 23:59:59**(对齐 activate 现有语义) | | `familyMemberId` | 统一设置 | | 状态机 | 套餐/测评统一 `generated → reviewed → active` | | 前端家长端 | **本次不做**(由其他进程负责) | ## 3. source_type 枚举设计 `tasks.source_type` 最终枚举(在总设计文档第 3 节基础上扩展): | source_type | 含义 | source_id 指向 | |-------------|------|----------------| | `self` | 用户创建给自己 | — | | `assigned` | 他人分配(家庭/规划师) | — | | `plan` | 套餐/测评方案实例化 | `task_plan_instances.id` | | `health_plan` | **健康方案实例化(新增)** | `health_plans.id` | | `training_plan` | **认知训练方案实例化(新增)** | `training_plans.id` | | `onboarding` | 新人引导 | — | | `ai` | AI 对话生成 | conversation_id | | `template` | 模板快速创建 | template_id | | `article` | 文章阅读联动 | article_id | | `activity` | 活动签到联动 | activity_id | | `purchase` | 购买/消费联动 | — | > **关键收益**:拆分后 `source_type` 与 `source_id` 一一对应,`ProblemCompletionService` 只查 `source_type='plan'` 天然隔离健康方案任务,无需额外过滤。 ## 4. 数据模型变更 ### 4.1 `tasks` 表 `source_type` 列当前为 `VARCHAR(20)`,需扩展注释说明新增枚举(无需改列类型,20 足够容纳 `training_plan`)。**无 DDL 变更**,仅语义扩展。 ### 4.2 `task_plan_instances` 表(补齐缺失列) `TaskPlanInstance` 实体已声明以下字段,但 `schema.sql` 建表语句缺失,需补齐(否则 `review`/`activate` 写这些字段会 `Unknown column` 报错): | 字段 | 类型 | 说明 | |------|------|------| | `source` | VARCHAR(20) | manual/assessment/template | | `source_result_id` | BIGINT | 关联测评结果 | | `remark` | TEXT | 方案备注 | | `reviewed_by` | BIGINT | 审核人 | | `reviewed_at` | DATETIME | 审核时间 | | `review_comment` | TEXT | 审核意见 | | `confirmed_by` | BIGINT | 确认人 | | `confirmed_at` | DATETIME | 确认时间 | | `activated_at` | DATETIME | 激活时间 | ### 4.3 `task_plan_items` 表 **降级为审计快照**:保留 `applyPackage` 时的写入,不再作为任务生成来源。不物理删除(保留只读备份,符合总设计第 4.4 节)。 ## 5. 后端改造 ### 5.1 单一任务生成器(核心) 在 `TaskPlanService` 新增统一方法,作为套餐/测评方案"方案→任务"的**唯一**入口: ```java /** * 统一方案任务生成器:套餐/测评方案激活时调用。 * 幂等:sourceType=plan & sourceId=planId 已存在任务则跳过。 * 任务来源:task_template_items(按 sort_order)。 * deadline:当天 23:59:59;familyMemberId:统一设置。 */ @Transactional public int generateTasksFromTemplate(TaskPlanInstance plan, Long operatorId) ``` `PlanActivationService.activate` 和 `ProblemPackageController.confirm` 都改为调用它,消除 `generatePlanTasks` 与 `activate` 的分裂。 ### 5.2 状态机收敛(套餐/测评) | 入口 | 改造前 | 改造后 | |------|--------|--------| | `applyPackage` | 直接建 `active` | 建 `generated` | | `review` | 要求 `generated` | 不变(`generated → reviewed/rejected`) | | `activate` | 要求 `reviewed` | 不变(`reviewed → active`),内部改用统一生成器 | | `confirm`(家长自助) | 建 active + generatePlanTasks | 建 `generated` → 直接置 `active`(自助跳过审核)+ 统一生成器 | ### 5.3 健康方案(第 3 套) - `HealthPlanServiceImpl.generateDailyTasksFromPlan` 中 `task.setSourceType("plan")` → `task.setSourceType("health_plan")` - `getPlanTasks` 查询条件 `source_type='plan'` → `source_type='health_plan'` - 幂等检查(第 367-373 行)同步改为 `health_plan` ### 5.4 认知训练方案(第 4 套) - `TrainingPlanService.deployPlan` 中 `taskService.createTask` 后,补设 `sourceType="training_plan"` + `sourceId=planId` - 需在 `CreateTaskDTO` 增加 `sourceType`/`sourceId` 字段,`TaskService.createSingleTask` 透传 ### 5.5 幂等与空壳修复 - `MarketController.purchasePackage`(入口3):补幂等(同家庭同套餐已有实例则复用)+ 补调任务生成,消除"active 但零任务"空壳 - `TaskPlanService.cancelPlan`:取消时级联清理 `sourceType='plan' & sourceId=planId` 的未完成任务(pending 状态) ## 6. 数据迁移 在 `DatabaseInitializer.runMigrations()` 新增迁移: 1. **补齐 `task_plan_instances` 缺失列**(`ensureColumn` 逐个添加,见 4.2) 2. **存量健康方案任务改枚举**: ```sql UPDATE tasks SET source_type = 'health_plan' WHERE source_type = 'plan' AND source_id IN (SELECT id FROM health_plans); ``` 3. **存量认知训练方案任务补枚举**: ```sql UPDATE tasks SET source_type = 'training_plan' WHERE source_id IN (SELECT task_id FROM training_plan_tasks); ``` 4. **同步 schema.sql**:`task_plan_instances` 建表语句补齐缺失列 > **注意**:迁移 2/3 依赖 `health_plans` / `training_plan_tasks` 表存在,需先确认表存在再执行(幂等 try-catch)。 ## 7. 测试 新增 `TaskPlanServiceTest`(或集成测试)覆盖: - [ ] 幂等:重复生成不重复建任务 - [ ] 状态机:`generated` 不能直接 activate、`reviewed` 才能 activate - [ ] 空壳修复:purchasePackage 后必有任务 - [ ] cancelPlan 级联清理未完成任务 - [ ] 健康方案任务 `source_type='health_plan'`,`ProblemCompletionService` 不误统计 - [ ] 认知训练方案任务 `source_type='training_plan'` 可溯源 ## 8. 范围边界(明确不做) | 不做 | 原因 | |------|------| | 前端家长端 plans 页面 | 其他进程负责 | | 健康方案/认知训练方案状态机统一 | 各自状态机已独立成熟,本次只统一 source_type 溯源 | | `task_plan_items` 物理删除 | 保留只读备份 | | 家庭挑战/五维打卡合并 | 总设计文档已明确本次不合并 | ## 9. 验收标准 - [ ] `tasks.source_type` 支持 `health_plan` / `training_plan` 新枚举 - [ ] 套餐/测评方案统一走 `generateTasksFromTemplate`,deadline=当天 23:59:59,familyMemberId 统一设置 - [ ] 套餐/测评状态机统一 `generated → reviewed → active` - [ ] 健康方案任务 `source_type='health_plan'`,`ProblemCompletionService` 不再误统计 - [ ] 认知训练方案任务 `source_type='training_plan'` 可溯源 - [ ] `task_plan_instances` 缺失列补齐,`review`/`activate` 不再 `Unknown column` - [ ] 存量数据迁移完成(健康/认知训练任务枚举修正) - [ ] 通过 `mvn clean compile` 编译验证