# 架构决策:FollowUpTask 回访状态机与再次预约归因 > 日期:2026-08-02 > 状态:Accepted / Code Complete > 本体:`entity:follow_up_task`、`rule:BR-FU-001`、`rule:BR-FU-002` ## 1. 决策 `ReportLead` 继续保存宠主同意、来源报告、建议日期与退订凭证等留资事实;门店员工的领取、联系、改期、关闭和再次预约改由独立 `FollowUpTask` 承载。不得继续用 `ReportLead.remind_status` 同时表达同意事实和多人协作状态。 留资成功后,同一 `sourceLeadId` 至多存在一个开放任务。重复提交复用开放任务并同步建议日期;上一轮已终结后再次提交,创建新的任务轮次。历史迁移只为 `remind_status=pending` 的线索回填任务,不推测 `sent/canceled/unsubscribed` 的真实处理结果。 `ReportLead.remind_status` 暂保留为兼容投影:新建或改期为 `pending`,无意向/无效联系方式为 `canceled`,真实再次预约为 `sent`,退订为 `unsubscribed`。Admin 回访工作流与经营统计不再以该兼容字段为事实源。 ## 2. 状态机 ```text pending --领取--> in_progress in_progress --未接通/稍后联系--> pending(attempt_count + 1,更新 due_date) in_progress --暂无意向/联系方式无效--> completed(attempt_count + 1) in_progress --真实预约创建成功--> completed / rebooked(attempt_count + 1) pending|in_progress --宠主退订--> canceled / unsubscribed(不增加员工联系次数) ``` - `pending` 不允许有领取人;`in_progress` 必须有领取人。 - 只有领取该任务的当前员工才能改期、关闭或发起再次预约。 - 两名员工并发领取时通过悲观锁和 `@Version` 防止静默覆盖;他人已领取返回 409。 - `completed/canceled` 为终态,不回退。跨店任务按不存在返回 404。 - `no_answer/follow_later` 仅是非终态联系结果;`rebooked/not_interested/invalid_contact/unsubscribed` 仅是终态结果。 ## 3. 真实再次预约归因 `rebooked` 不是员工可手工选择的关闭结果。只有 `POST /api/appointment/create` 在同一事务中满足下列条件,才能写入: 1. `followUpTaskId` 属于当前门店,处于 `in_progress` 且由当前员工领取; 2. 任务的 canonical `StoreCustomer` 与本次代客预约解析出的客户一致; 3. 预约通过未来时间、服务时长和每个半小时桶容量校验并真实保存; 4. 保存后任务写 `rebooked_appointment_id`,同时落 `follow_up_completed` 与 `rebook_created` 事实。 无号源、客户不匹配、状态冲突或预约保存失败时,整笔事务回滚,任务保持处理中。漏斗中的“回访后再次预约”只统计 `rebook_created`,不从手机号相同或后续页面点击推断。 ## 4. 权限、隐私与审计 - `GET/POST /api/admin/follow-up-tasks/**` 仅本店 boss/staff,`storeId` 和操作人均从 session 派生。 - 同店已认证员工为了实际电话联系可在开放任务中看到完整手机号;终态任务只返回脱敏手机号。工作台、客户时间线、BusinessEvent 和 AuditLog 只保留脱敏或低敏字段。 - BusinessEvent metadata 仅允许 `dueDate/attemptOutcome/outcome/attemptCount/bookingOrigin` 等白名单标量,不存手机号、备注、微信标识、token、IP 或请求体。 - 领取、改期、关闭和真实再次预约均写不可变低敏 AuditLog;退订由公开凭证触发,不伪装成员工联系次数或操作人。 ## 5. 迁移与验收 迁移:`backend/db/migrations/20260802_create_follow_up_task.sql`,固定在 StoreCustomer 时间线迁移后第八个执行。末尾五项验证必须为 0: 1. 待处理历史线索无开放任务; 2. 任务客户归属或 canonical 范围无效; 3. 状态、结果、领取人、联系回执或真实预约引用不一致; 4. 同一线索存在多个开放任务; 5. 任务缺少创建事实。 生产只读预检同步检查上述不变量,并额外要求 `rebooked_appointment_id` 指向同店、未删除的真实预约以及对应 `rebook_created` 事件。 自动化验收覆盖:历史回填、状态机、并发领取冲突、跨店 404、非领取人拒绝、退订取消、真实预约成功闭环、容量失败不闭环、客户时间线低敏投影、Admin 控制器权限与 production build。