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

110 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 变更审计记录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 唯一约束冲突(已通过先关闭旧回访再新建避免) |
### 回滚策略
数据库回滚(逆序执行):
```sql
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` | ⚠️ 待检查是否存在 |
| 审计记录 | ✅ 本文件 |