Files
Neo-ZQYY/docs/audit/changes/2026-04-08__fix13-recall-events-refactor.md
Neo 14a12342b5 chore(audit): 补追 96 份未入仓审计孤本 — 覆盖 2026-02-26 ~ 2026-04-08
这些审计记录原本堆积在 docs/audit/changes/changes/ 嵌套误产物目录下(由开发机迁移
79d3c2e 前后的不明批量操作产生)。由于同期 .gitignore 屏蔽了 docs/audit/ 全目录,
它们从未入过 git 任何分支 history。删除即永久丢失。

按 docs/specs/audit-gap-recovery/tasks.md 阶段 1 执行,将全部 96 份 D 类孤本
(主目录无同名、git history 亦无记录)复制到 docs/audit/changes/ 主目录入仓。

涵盖主题: P1-P18 全栈集成 / 多模块累积变更 / ETL bug 修复 / 业务日切 /
   召回与任务引擎改造 / 租户管理与审批 / 董事会财务 / 客户与助教详情 /
   DDL 基线合并 / Kiro 到 Claude Code 迁移

阶段 2(B 类内容漂移 1 份)和阶段 4(嵌套目录删除)独立推进。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 06:35:42 +08:00

5.4 KiB
Raw Blame History

变更审计记录Fix-13 回滚手动完成 + 广义召回完成机制

字段
日期 2026-04-08 15:08:50

操作摘要

Fix-13 看板审计修复计划,包含两部分改动。

第一部分:回滚手动完成路径。 用户确认回访任务应通过提交备注自动完成note_service 中 completed_by_note 逻辑),不需要手动完成按钮。删除 POST /{task_id}/complete 接口、ManualCompleteRequest 模型及 manual_complete_task() 函数。completion_type 字段保留recall_detector 自动完成仍写 'auto'

第二部分:广义召回完成机制。 原 recall_detector 只检测有 active 任务的客户到店。需求要求所有 MAIN 关系对的关联客户到店都算一次广义召回,都要分配回访任务。重写 recall_detector新增 biz.recall_events 表记录召回事件ON CONFLICT 按天去重),看板召回数改从该表统计。

变更文件

修改

文件 改动要点
apps/backend/app/routers/xcx_tasks.py 删除 POST /{task_id}/complete 接口、ManualCompleteRequest 模型、pydantic BaseModel, Field 导入
apps/backend/app/services/task_manager.py 删除 manual_complete_task() 函数(约 47 行)
apps/backend/app/services/recall_detector.py 重写。扫描范围从"有 active 任务"扩大为"所有 os_label='MAIN' 的关联客户"。新增写 recall_events 表ON CONFLICT 按天去重)。无 active 任务的客户到店也生成 follow_up_visit48h 过期)
apps/backend/app/services/board_service.py _query_coach_tasks() 召回数改从 recall_events 统计(天然去重),回访数保持从 coach_tasks 统计
db/zqyy_app/schemas/biz.sql 新增 recall_events 表定义
docs/database/ddl/zqyy_app__biz.sql 同步源 DDL

改动注解

apps/backend/app/services/recall_detector.py(高风险)

完全重写。核心变化:

  • 扫描范围:从"有 active 任务的客户"扩大为"所有 os_label='MAIN' 的关联客户"
  • 新增逻辑:每次检测到客户到店,写入 biz.recall_events(按天去重)
  • 新增逻辑:无 active 任务的客户到店也生成 follow_up_visit 类型回访任务48h 过期)
  • 每个 site_id 两次 _fdw_context 调用

apps/backend/app/services/board_service.py(高风险)

_query_coach_tasks() 中召回数数据源从 coach_tasks 改为 recall_events 表,天然去重不重复叠加。回访数统计逻辑不变。

apps/backend/app/routers/xcx_tasks.py(高风险)

删除手动完成接口及相关模型。路由数从 8 个减少到 7 个。

apps/backend/app/services/task_manager.py(高风险)

删除 manual_complete_task() 函数(约 47 行),其余逻辑不变。

db/zqyy_app/schemas/biz.sql(高风险)

新增 recall_events 表,详见数据库变更节。

docs/database/ddl/zqyy_app__biz.sql

同步源 DDL无额外逻辑。

数据库变更

新增

对象 类型 说明
biz.recall_events 召回事件记录8 字段id, site_id, assistant_id, member_id, pay_time, task_id, task_type, created_at
biz.recall_events_id_seq 序列 recall_events 主键序列
recall_events_pkey 主键约束 PK on id
recall_events_task_id_fkey 外键约束 FK → coach_tasks(id)
idx_recall_events_site_assistant_member_day 唯一索引 (site_id, assistant_id, member_id, date(pay_time)),按天去重
idx_recall_events_assistant_pay 索引 (assistant_id, pay_time),查询优化

风险与回滚

风险

级别 描述
首次运行 recall_detector 会为所有历史有结算的 MAIN 关系对写 recall_events + 生成回访任务,数据量可能很大
ETL 连接开销,每个 site_id 两次 _fdw_context
coach_tasks 唯一约束冲突(已通过先关闭旧回访再新建避免)

回滚策略

数据库回滚(逆序执行):

DROP INDEX IF EXISTS biz.idx_recall_events_assistant_pay;
DROP INDEX IF EXISTS biz.idx_recall_events_site_assistant_member_day;
ALTER TABLE biz.recall_events DROP CONSTRAINT IF EXISTS recall_events_task_id_fkey;
ALTER TABLE biz.recall_events DROP CONSTRAINT IF EXISTS recall_events_pkey;
DROP TABLE IF EXISTS biz.recall_events;
DROP SEQUENCE IF EXISTS biz.recall_events_id_seq;

后端代码回退:git checkout HEAD~1 -- apps/backend/app/services/recall_detector.py apps/backend/app/services/board_service.py apps/backend/app/routers/xcx_tasks.py apps/backend/app/services/task_manager.py

验证

验证项 结果
模块导入 recall_detector 通过
路由验证 xcx_tasks 7 个路由,无 /complete 通过
数据库表结构recall_events 8 字段) 通过
唯一索引(按天去重) 通过
FK 约束(→ coach_tasks 通过
后端 pytest ⚠️ 无法运行dashscope 依赖未安装,非本次改动引入)

合规检查

检查项 结果
DDL 文档同步 docs/database/ddl/zqyy_app__biz.sql 已同步
RLS 双 Schema 规则 ⚠️ recall_events 为业务库表,非 ETL 层,不适用
API 文档 apps/backend/docs/API-REFERENCE.md ⚠️ 待检查是否存在
后端 README apps/backend/README.md ⚠️ 待检查是否存在
审计记录 本文件