Files
Neo-ZQYY/docs/specs/admin-web-enhancement/tasks.md
Neo 2a7a5d68aa feat: 2026-04-15~04-20 累积变更基线 — 多主线合流
主线 1: rns1-customer-coach-api + 04-miniapp-core-business 后端实施
  - 新增 GET /xcx/coaches/{id}/banner 轻量接口
  - performance/records 加 coach_id 参数 + view_board_coach 权限分流
  - coach/customer/performance/board/task 服务层重构
  - fdw_queries 结算单粒度聚合 + consumption_summary 视图统一
  - task_generator 回访宽限 72h + UPSERT 替代策略 + Step 5 保底清理
  - recall_detector settle_type=3 双重限制 + 门店级 resolved

主线 2: 小程序权限分流 + 新增 coach-service-records 管理者视角业绩明细页
  - perf-progress 共享模块去重 task-list/coach-detail 动画逻辑
  - isScattered 散客标记端到端
  - foodDetail/phoneFull/creator* 字段透传

主线 3: P19 指数回测框架 Phase 1+2
  - 3 个指数表 stat_date 日快照模式
  - 新增 DWS_INDEX_BACKFILL / DWS_TASK_SIMULATION 工具任务
  - task_engine 升级 HTTP 实时 + 推演回测双模式

主线 4: Core 维度层启用
  - 新增 CORE_DIM_SYNC 任务(DWD → core 4 维度表)
  - 修复 app 视图空查询问题

主线 5: member_project_tag 改为 LAST_30_VISITS 消费次数窗口

主线 6: 2 个迁移 SQL 已执行(stat_date + member_project_tag 新窗口)
  - schema 基线与 DDL 快照同步

主线 7: 开发机路径迁移 C:\NeoZQYY → C:\Project\NeoZQYY(约 95% 改动量)

附带: 新建运维脚本(churned_customer_report / simulate_historical_tasks /
      backfill_index_snapshots)+ tools/task-analysis/ 任务分析工具

合计 157 文件。未包含中间产物(tmp/ .playwright-mcp/ inspect-* excel/sheet 分析 txt)。
审计记录见下一个 commit。

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

309 lines
17 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.
# 实施计划NS4.1 + P16 — Admin-Web 管理后台增强
> 权威参考:实施过程中如遇细节不明确,应优先查阅 PRD 原文:
> - `docs/prd/Neo_Specs/NS4.1-tenant-admin-redesign.md` — NS4.1 完整设计(数据模型 DDL、页面布局、接口设计、迁移步骤、边界条件
> - `docs/prd/specs/P16-task-min-run-interval.md` — P16 完整设计调度器逻辑、API 扩展、前端变更、边界条件)
## 概述
按依赖关系分两条并行线实施:模块 ANS4.1 注册体系 + 租户管理员重构)和模块 BP16 调度任务间隔。两者改动文件无重叠可交替执行。整体顺序DDL 迁移 → 后端 API → 前端页面 → 数据迁移/切换 → 收尾。
后端使用 PythonFastAPI + Pydantic前端使用 TypeScriptReact + Vite + Ant Design
## 任务
### 阶段一DDL 迁移(模块 A + B
- [x] 1. DDL 迁移 — 模块 A注册体系四张新表
- [x] 1.1 创建迁移脚本 `db/zqyy_app/migrations/2026-03-22__ns41_registry_tables.sql`
- CREATE TABLE `biz.connectors`id SERIAL PK, connector_key VARCHAR(50) UNIQUE, display_name, is_active, created_at
- CREATE TABLE `biz.tenants`id SERIAL PK, connector_id FK, tenant_id BIGINT, tenant_name, is_active, created_at, updated_at, UNIQUE(connector_id, tenant_id)
- CREATE TABLE `biz.sites`id SERIAL PK, tenant_id FK, site_id BIGINT UNIQUE, site_name, site_code VARCHAR(6) UNIQUE, site_label, is_active, created_at, updated_at
- CREATE TABLE `biz.site_code_history`id SERIAL PK, site_id BIGINT, site_code VARCHAR(6) UNIQUE, is_current BOOLEAN, created_at, retired_at
- INSERT 种子数据connectors('feiqiu')、tenants(朗朗桌球)
- INSERT 迁移数据:从 `auth.site_code_mapping` 迁移真实数据到 `biz.sites`,创建 `site_code_history` 记录
- 编写回滚脚本(逆序 DROP TABLE
- _需求: A1.1, A1.2, A1.3, A1.4, A1.5_
- [x] 2. DDL 迁移 — 模块 Bscheduled_tasks 新增字段
- [x] 2.1 创建迁移脚本 `db/zqyy_app/migrations/2026-03-22__p16_min_run_interval.sql`
- ALTER TABLE `scheduled_tasks` ADD COLUMN `min_run_interval_value` INTEGER NOT NULL DEFAULT 0
- ALTER TABLE `scheduled_tasks` ADD COLUMN `min_run_interval_unit` VARCHAR(20) NOT NULL DEFAULT 'minutes'
- ALTER TABLE `scheduled_tasks` ADD COLUMN `last_success_at` TIMESTAMPTZ
- 添加 COMMENT ON COLUMN 注释
- 编写回滚脚本ALTER TABLE DROP COLUMN
- _需求: B1.1, B1.2_
### 阶段二:后端 API — 模块 A注册体系 + 管理员重构)
- [x] 3. 后端 Schema — 注册体系
- [x] 3.1 创建 `apps/backend/app/schemas/admin_registry.py`
- 定义 `TenantItem`id, tenant_id, tenant_name, connector_name, is_active
- 定义 `SiteItem`id, site_id, site_name, site_code, site_label, is_active
- 定义 `UpdateSiteCodeRequest`new_code: str
- 定义 `SiteCodeResult`site_id, old_code, new_code, history_cleaned
- 定义 `SiteCodeHistoryItem`id, site_code, is_current, created_at, retired_at
- _需求: A2.1, A2.2, A2.4, A2.5_
- [x] 3.2 修改 `apps/backend/app/schemas/admin_tenant_admins.py`
- `TenantAdminListItem` 新增 `tenant_name` 字段
- `TenantAdminCreateRequest` 添加字段说明注释tenant_id 从 biz.tenants 选择)
- _需求: A2.6_
- [x] 4. 后端路由 — 注册体系 API
- [x] 4.1 创建 `apps/backend/app/routers/admin_registry.py`
- `GET /api/admin/tenants` — 所有活跃租户列表JOIN biz.connectors 获取 connector_name
- `GET /api/admin/tenants/{tenant_id}/sites` — 指定租户下所有活跃店铺
- `PUT /api/admin/sites/{site_id}/site-code` — 设置/修改简写ID事务内执行
- 校验格式6 位3+3统一大写
- 校验全局唯一biz.sites + biz.site_code_history
- 事务:旧 code 标记 retired → 新 code 插入 history → 更新 sites.site_code
- 检查旧 code 是否有未审核申请引用,决定是否清理历史记录
- `GET /api/admin/sites/{site_id}/site-code-history` — 简写ID 变更历史
- _需求: A2.1, A2.2, A2.4, A2.5, A3.1, A3.2, A3.3, A3.4_
- [x] 4.2 在 `apps/backend/app/main.py` 中注册 admin_registry router
- _需求: A2.1_
- [x] 5. 后端路由 — 管理员 CRUD 扩展
- [x] 5.1 修改 `apps/backend/app/routers/admin_tenant_admins.py`
- 新增 `DELETE /api/admin/tenant-admins/{id}` — 软删除is_active=false已禁用返回 409
- 修改 `POST /api/admin/tenant-admins` — 创建时校验 tenant_id 在 biz.tenants 中存在
- 修改 `GET /api/admin/tenant-admins` — 默认 is_active=true新增 include_inactive 查询参数
- 修改 `PATCH /api/admin/tenant-admins/{id}` — 支持修改 `username`(校验全局唯一性,冲突返回 409
- 列表查询 JOIN biz.tenants 获取 tenant_name
- _需求: A2.3, A2.6, A2.7, A2.8, A4.1_
- [x] 6. 编写属性测试 — 模块 A
- [x] 6.1 创建 `tests/test_site_code_props.py`
- **Property 1: 简写ID 全局唯一性** — 使用 Hypothesis 生成随机 code验证已存在的 code 被拒绝
- **Property 2: 简写ID 变更事务完整性** — 验证事务后 sites.site_code、history.is_current、history.retired_at 状态一致
- **Property 3: 简写ID 格式校验** — 生成随机字符串,验证仅 6 位 3+3 格式通过
- **验证: 需求 A3.1, A3.2**
- [x] 6.2 创建 `tests/test_tenant_admin_props.py`(扩展已有文件或新建)
- **Property 4: 租户管理员软删除一致性** — 删除后默认列表不返回include_inactive 返回
- **验证: 需求 A2.3, A2.7**
- [x] 7. 检查点 — 模块 A 后端验证
- 确保注册体系 API 和管理员 CRUD 扩展所有测试通过ask the user if questions arise.
### 阶段三:后端 API — 模块 B调度器间隔
- [x] 8. 后端 Schema + 路由 — 调度器间隔
- [x] 8.1 修改 `apps/backend/app/schemas/schedules.py`
- `CreateScheduleRequest` 新增 `min_run_interval_value`int, default=0`min_run_interval_unit`str, default='minutes'
- `UpdateScheduleRequest` 新增同上两个可选字段
- `ScheduleResponse` 新增 `min_run_interval_value``min_run_interval_unit``last_success_at`
- _需求: B3.1, B3.2_
- [x] 8.2 修改 `apps/backend/app/services/scheduler.py`
- 新增 `_convert_interval_to_seconds(value, unit)` 辅助函数
- 扩展 `check_and_enqueue()` SQL 查询:新增读取 min_run_interval_value, min_run_interval_unit, last_run_at, last_status
- 新增并发检查last_status == 'running' → 跳过,日志记录 skipped_concurrent
- 新增间隔检查min_run_interval_value > 0 且 now - last_run_at < min_interval → 跳过,推进 next_run_at
- _需求: B2.1, B2.2, B2.4_
- [x] 8.3 修改任务完成回调(`scheduler.py``task_queue.py`
- 成功时:`last_status='completed'`, `last_success_at=NOW()`
- 失败时:`last_status='failed'`last_success_at 不变)
- _需求: B2.3_
- [x] 8.4 修改 `apps/backend/app/routers/schedules.py`
- 创建/更新端点支持新字段写入
- 列表端点响应包含新字段
- `POST /api/schedules/{id}/run` 新增 `force: bool = False` 查询参数
- force=false 时检查并发和间隔,不满足返回 409
- force=true 时绕过所有检查
- _需求: B3.1, B3.2, B3.3, B3.4, B3.5_
- [x] 9. 编写属性测试 — 模块 B
- [x] 9.1 创建 `tests/test_scheduler_interval_props.py`
- **Property 7: 间隔转换正确性** — 生成随机 (value, unit),验证秒数计算正确
- **Property 8: 调度器间隔跳过正确性** — 生成随机任务状态,验证跳过/执行决策
- **Property 9: 调度器并发跳过正确性** — last_status='running' 时跳过
- **Property 10: 强制执行绕过所有检查** — force=true 时无论状态都执行
- **Property 11: last_success_at 仅成功时更新** — 成功更新,失败不变
- **验证: 需求 B2.1, B2.2, B2.3, B2.4, B3.4**
- [x] 10. 检查点 — 模块 B 后端验证
- 确保调度器间隔逻辑和 API 扩展所有测试通过ask the user if questions arise.
### 阶段四ETL 店铺同步(模块 A
- [x] 11. 后端 — 店铺信息增量同步
- [x] 11.1 实现同步逻辑(新建 service 或在 admin_registry 路由中实现)
- 通过 FDW 读取 ETL 库 `dwd.dim_site``scd2_is_current=1`
- 对比 `biz.sites`:新增店铺 INSERTsite_code 留空tenant_id 通过 dim_site.tenant_id 关联 biz.tenants名称/标签变更 UPDATE
- 不删除已有店铺记录
- _需求: A5.1, A5.2_
- [x] 11.2 实现手动触发端点
- `POST /api/admin/sites/sync` — 手动触发同步,返回同步结果(新增数/更新数)
- _需求: A5.3_
- [x] 11.3 执行一次初始同步(数据迁移补数据)
- 在 DDL 迁移(任务 1完成后、代码切换任务 15之前调用同步逻辑补充 `auth.site_code_mapping` 中没有但 `dwd.dim_site` 中有的店铺
- 输出同步结果(新增数/更新数)供验证使用
- _需求: A1b.1, A1b.2_
- [x] 11.4 预留定时触发入口(随 ETL DWD 完成后通过内部 API 触发)
- _需求: A5.4_
### 阶段五:前端页面
- [x] 12. 前端 — 模块 A租户管理员页面重构
- [x] 12.1 创建 `apps/admin-web/src/api/registry.ts`
- 封装 `GET /api/admin/tenants``GET /api/admin/tenants/{id}/sites` API 调用
- 封装 `PUT /api/admin/sites/{site_id}/site-code``GET /api/admin/sites/{site_id}/site-code-history`
- 封装 `POST /api/admin/sites/sync`(手动同步)
- _需求: A4.5_
- [x] 12.2 修改 `apps/admin-web/src/api/tenantAdmins.ts`
- 新增 `deleteTenantAdmin(id)` API 调用
- 修改 `listTenantAdmins` 支持 `include_inactive` 参数
- _需求: A4.1_
- [x] 12.3 重构 `apps/admin-web/src/pages/TenantAdmins/index.tsx`
- 列表页新增「删除」操作按钮Popconfirm 二次确认 → 调用 DELETE API
- 列表页新增「显示已禁用」Switch 开关
- 列表页新增「简写ID」操作按钮打开简写ID 管理弹窗)
- 列表新增「租户」列(显示 tenant_name
- 编辑弹窗中 `username` 改为可编辑需校验唯一性409 时提示"用户名已存在"`tenant_id` 只读
- _需求: A4.1, A4.3, A2.8_
- [x] 12.4 实现 2 步创建流程
- 使用 Ant Design Steps 组件
- 第 1 步选择租户Select数据源 GET /api/admin/tenants→ 输入用户名/密码/显示名称 → 选择管辖门店Select multiple数据源 GET /api/admin/tenants/{id}/sites
- 第 2 步展示所选租户下所有店铺可为每个店铺设置简写ID可跳过
- _需求: A4.2_
- [x] 12.5 实现简写ID 管理弹窗
- Modal 内嵌 Table店铺名称、当前 ID、操作修改
- 修改行Input + 保存/取消按钮格式校验6 位 3+3
- 变更历史区域:展示 site_code_history 列表
- _需求: A4.3, A4.4_
- [x] 13. 前端 — 模块 BScheduleTab 扩展
- [x] 13.1 修改 `apps/admin-web/src/components/ScheduleTab.tsx`
- 创建/编辑表单新增「最小运行间隔」行InputNumber数值+ Select单位分钟/小时/天)
- 数值为 0 时显示 placeholder "无限制"
- 位置:在调度类型配置区域下方
- _需求: B4.1_
- [x] 13.2 修改列表表格
- 新增「最小间隔」列:显示格式如"10 天"、"1 小时"、"无限制"value=0 时)
- 新增「上次成功」列:显示 last_success_at 的相对时间dayjs fromNow
- _需求: B4.2_
- [x] 13.3 修改手动执行确认框
- 新增 Checkbox「强制执行忽略最小间隔默认不勾选
- 勾选后调用 `POST /api/schedules/{id}/run?force=true`
- 不勾选时调用 `POST /api/schedules/{id}/run`409 时展示错误提示
- _需求: B4.3, B4.4_
- [x] 14. 检查点 — 前端页面验证
- 确保所有前端组件渲染正常API 调用层工作正确ask the user if questions arise.
### 阶段六:数据迁移与代码切换
- [x] 15. site_code 查询源切换
- [x] 15.1 修改 `apps/backend/app/routers/tenant_users.py`
- `match-suggestions` 中的 site_code 查询从 `auth.site_code_mapping` 切换到 `biz.sites` + `biz.site_code_history`
- _需求: A6.1_
- [x] 15.2 搜索并修改所有其他引用 `auth.site_code_mapping` 的代码
- 小程序端用户申请时的 site_code 验证
- 其他后端路由中的 site_code 查询
- _需求: A6.1, A6.2_
- [x] 15.3 验证切换后功能正常
- 用户申请流程中 site_code 查询正确
- 关联建议匹配正确
- _需求: A6.3_
- [x] 16. 废弃原表
- [x] 16.1 验证 `biz.sites` 数据与 `auth.site_code_mapping` 一致
- 编写验证 SQL 对比两表数据
- _需求: A1.5_
- [x] 16.2 重命名原表为 `auth._archived_site_code_mapping`
- _需求: A1.6_
### 阶段七:收尾
- [x] 17. 数据库变更审计与 DDL 合并
- [x] 17.1 审计本次实现中对数据库的所有改动
- 检查新建表biz.connectors/tenants/sites/site_code_history、新增字段scheduled_tasks 三字段、废弃表auth.site_code_mapping
- [x] 17.2 执行两个迁移脚本到测试库(`test_zqyy_app`
- 验证新表和新字段已正确创建(使用 BD 手册中的验证 SQL
- [x] 17.3 合并到主 DDL 基线文件
- 模块 A 新表 → `docs/database/ddl/zqyy_app__biz.sql`
- 模块 B 新字段 → `docs/database/ddl/zqyy_app__public.sql`
- [x] 17.4 验证回滚脚本可执行(任务 1、2 中已编写)
- [x] 18. BD 手册更新
- [x] 18.1 创建 `docs/database/BD_Manual_biz_registry_tables.md`
- 覆盖 biz.connectors、biz.tenants、biz.sites、biz.site_code_history 四张表
- 包含:字段明细、约束与索引、验证 SQL≥3 条)、回滚策略
- _规范: db-docs.md_
- [x] 18.2 更新 `docs/database/BD_Manual_tenant_admin_tables.md`
- 补充软删除逻辑说明、tenant_id 从 biz.tenants 选择的变更
- [x] 18.3 创建/更新 `docs/database/BD_Manual_scheduled_tasks.md`
- 新增 min_run_interval_value、min_run_interval_unit、last_success_at 字段说明
- 包含:字段明细、约束、验证 SQL、回滚策略
- [x] 19. 前后端联调与集成验证
- [x] 19.1 启动后端服务,使用测试库验证各端点完整请求-响应链路
- 验证注册体系 APItenants/sites/site-codeJSON 响应结构与 Schema 定义一致
- 验证调度器 APIschedules新增字段和 force 参数正常工作
- 验证权限校验在真实请求中生效
- [x] 19.2 前端联调验证
- 确认租户管理员页面能正确调用新增 API 并渲染数据2 步创建、删除、简写ID 管理)
- 确认 ScheduleTab 扩展字段正确展示和提交
- 验证空数据/降级场景下前端不崩溃
- [x] 20. 文档同步更新
- [x] 20.1 更新后端 API 参考文档
-`apps/backend/docs/API-REFERENCE.md` 新增 admin_registry 路由模块文档
- 更新 schedules 路由模块文档(新增字段和 force 参数)
- 更新 `apps/backend/README.md` 路由模块摘要
- [x] 20.2 更新 admin-web README
-`apps/admin-web/README.md` 更新页面说明租户管理员重构、ScheduleTab 扩展)
- [x] 20.3 更新文档地图
-`docs/DOCUMENTATION-MAP.md` 新增本次模块条目BD 手册、Spec
- _规范: doc-map.md_
- [x] 21. 最终检查点 — 全量验证
- 运行 Monorepo 属性测试:`cd C:\Project\NeoZQYY && pytest tests/ -v`
- 运行后端单元测试:`cd apps/backend && pytest tests/ -v`
- 确保所有属性测试Property 1-11和单元测试全部通过
- 确保 DDL 迁移已合并到主基线
- 确保 BD 手册已同步更新
- 确保 API 文档、后端 README、admin-web README、文档地图均已更新
- 确保前端页面连接真实后端运行正常(租户管理员页面 + ScheduleTab
- 确保 `auth.site_code_mapping` 已废弃重命名
- ask the user if questions arise.
- [x] 22. 服务清理
- [x] 22.1 关闭浏览器、停止后端和前端服务、清理资源
- 停止 uvicorn 后端进程controlPwshProcess stop
- 停止前端开发服务器controlPwshProcess stop
## 备注
- 标记 `*` 的子任务为可选(属性测试),可跳过以加速 MVP
- 每个任务引用了具体的需求编号以确保可追溯性A1-A6 对应 NS4.1B1-B4 对应 P16
- 属性测试验证 11 个正确性属性Property 1-11单元测试验证具体边界条件
- 检查点任务确保增量验证,避免问题累积(任务 7、10、14、21
- 模块 A 和模块 B 改动文件无重叠,可交替执行
- 后端使用 PythonFastAPI + Pydantic + Hypothesis前端使用 TypeScriptReact + Vite + Ant Design
- 数据迁移采用渐进策略:新建表 → 迁移数据 → 切换代码 → 验证 → 废弃原表
- 收尾阶段遵循 `spec-closing-checklist.md`(全栈类 Spec步骤 1-6 全覆盖):
- 步骤 1最终测试→ 任务 21
- 步骤 2前后端联调→ 任务 19
- 步骤 3DDL 合并)→ 任务 17
- 步骤 4BD 手册)→ 任务 18
- 步骤 5文档同步→ 任务 20
- 步骤 6服务清理→ 任务 22