2026-09-04-plan-to-task-unification-design.md 9.3 KB

方案转任务统一设计(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 套)未设 sourceTypedeployPlantaskService.createTask 后任务无法溯源。
  4. 套餐方案(第 1 套)与测评方案(第 2 套)虽同表,但任务来源不同(task_plan_items vs task_template_items)、deadline 语义不同(方案结束日 vs 当天 23:59:59)、familyMemberId 一个设一个不设。

2. 范围决策(已与用户确认)

决策项 结论
覆盖范围 全部 4 套方案体系
source_type 策略 拆分枚举:新增 health_plan / training_plansource_typesource_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_typesource_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 新增统一方法,作为套餐/测评方案"方案→任务"的唯一入口:

/**
 * 统一方案任务生成器:套餐/测评方案激活时调用。
 * 幂等:sourceType=plan & sourceId=planId 已存在任务则跳过。
 * 任务来源:task_template_items(按 sort_order)。
 * deadline:当天 23:59:59;familyMemberId:统一设置。
 */
@Transactional
public int generateTasksFromTemplate(TaskPlanInstance plan, Long operatorId)

PlanActivationService.activateProblemPackageController.confirm 都改为调用它,消除 generatePlanTasksactivate 的分裂。

5.2 状态机收敛(套餐/测评)

入口 改造前 改造后
applyPackage 直接建 active generated
review 要求 generated 不变(generated → reviewed/rejected
activate 要求 reviewed 不变(reviewed → active),内部改用统一生成器
confirm(家长自助) 建 active + generatePlanTasks generated → 直接置 active(自助跳过审核)+ 统一生成器

5.3 健康方案(第 3 套)

  • HealthPlanServiceImpl.generateDailyTasksFromPlantask.setSourceType("plan")task.setSourceType("health_plan")
  • getPlanTasks 查询条件 source_type='plan'source_type='health_plan'
  • 幂等检查(第 367-373 行)同步改为 health_plan

5.4 认知训练方案(第 4 套)

  • TrainingPlanService.deployPlantaskService.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. 存量健康方案任务改枚举

    UPDATE tasks SET source_type = 'health_plan'
    WHERE source_type = 'plan'
     AND source_id IN (SELECT id FROM health_plans);
    
  3. 存量认知训练方案任务补枚举

    UPDATE tasks SET source_type = 'training_plan'
    WHERE source_id IN (SELECT task_id FROM training_plan_tasks);
    
  4. 同步 schema.sqltask_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 编译验证