Files
Neo-ZQYY/.kiro/specs/tenant-admin-web/tasks.md

410 lines
24 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.
# 实施计划租户管理后台tenant-admin-web
## 概述
按照设计文档将实施拆分为DDL 迁移 → 后端认证模块 → 后端路由模块 → 前端项目骨架 → 前端页面实现 → 联调收尾。每个任务增量构建确保无孤立代码。属性测试Hypothesis / fast-check和单元测试作为可选子任务紧跟实现步骤。
后端使用 PythonFastAPI + Pydantic前端使用 TypeScriptReact + Vite + Ant Design
## 任务
- [ ] 1. DDL 迁移:新建表 + 表结构变更
- [ ] 1.1 创建 DDL 迁移脚本 `db/zqyy_app/migrations/2026-03-xx__ns4_tenant_admin_tables.sql`
-`auth` Schema 创建 `tenant_admins`id, username, password_hash, display_name, tenant_id, managed_site_ids, is_active, created_by, created_at, last_login_at
- 创建索引 `idx_tenant_admin_tenant ON auth.tenant_admins(tenant_id)`
-`biz` Schema 创建 `excel_upload_log`id, site_id, upload_type, file_name, uploaded_by, row_count, conflict_count, resolved_count, status, error_detail, created_at, confirmed_at
- 创建索引 `idx_excel_log_site ON biz.excel_upload_log(site_id, created_at DESC)`
-`biz` Schema 创建 `salary_adjustments`id, site_id, assistant_id, assistant_name, assistant_number, salary_month, adjustment_type, amount, reason, upload_batch_id FK, created_at, created_by
- 创建索引 `(site_id, salary_month)``(assistant_id, salary_month)`
-`biz` Schema 创建 3 张 staging 表:`stg_finance_expense``stg_platform_income``stg_recharge_commission`,各含 site_id、业务字段、upload_batch_id FK、synced_at、created_at
- _需求: 14.1, 14.2, 14.3, 14.4_
- [ ] 1.2 创建 DDL 迁移脚本 `db/zqyy_app/migrations/2026-03-xx__ns4_member_clue_is_hidden.sql`
- ALTER TABLE `public.member_retention_clue` ADD COLUMN `is_hidden` BOOLEAN NOT NULL DEFAULT false
- 添加 COMMENT ON COLUMN 注释:"是否隐藏true=管理后台保留但小程序不展示)"
- _需求: 15.1, 15.2_
- [ ] 1.3 更新小程序端线索查询增加 `WHERE is_hidden = false` 条件
- 搜索现有线索查询 SQL在 WHERE 子句中追加 `AND is_hidden = false`
- _需求: 15.3_
- [ ] 2. 后端认证模块auth.tenant_admins + JWT + 依赖注入)
- [ ] 2.1 创建 `apps/backend/app/auth/tenant_admins.py`
- 定义 `CurrentTenantAdmin` dataclassadmin_id, tenant_id, managed_site_ids, display_name
- 实现 `require_tenant_admin()` 依赖注入:验证 JWT `aud=tenant-admin`,提取管理员信息
- 实现 `site_filter_clause()` 工具函数:生成 `site_id IN (...)` SQL 片段
- 实现 `verify_site_access()` 工具函数:校验 site_id 是否在 managed_site_ids 内,否则抛 403
- _需求: 1.4, 1.6, 2.1, 2.3, 17.3_
- [ ]* 2.2 编写属性测试JWT 隔离与端点保护
- **Property 2: JWT 隔离与端点保护**
- 使用 Hypothesis 生成随机 JWT payload不同 aud 值tenant-admin / xcx / 无效值)
- 验证:仅 `aud=tenant-admin``require_tenant_admin()` 返回成功,其余返回 401
- **验证: 需求 1.4, 1.6, 17.3**
- [ ]* 2.3 编写属性测试:数据隔离
- **Property 3: 数据隔离**
- 使用 Hypothesis 生成随机 managed_site_ids 集合和随机 site_id
- 验证:`verify_site_access()` 仅当 site_id ∈ managed_site_ids 时通过,否则抛 403
- **验证: 需求 2.1, 2.2, 2.3**
- [ ] 3. 后端路由tenant_auth.py登录/刷新)
- [ ] 3.1 创建 `apps/backend/app/routers/tenant_auth.py`
- 实现 `POST /api/tenant/auth/login`:接受 username + password查询 `auth.tenant_admins`bcrypt 验证,签发 JWTaud=tenant-admin, sub=admin_id, tenant_id, managed_site_ids返回 access_token + refresh_token
- 实现 `POST /api/tenant/auth/refresh`:验证 refresh_token签发新令牌对
- 登录成功时更新 `last_login_at`
- 账号禁用is_active=false返回 403
- 凭据无效返回 401错误消息不区分用户名/密码
- _需求: 1.1, 1.2, 1.3, 1.5_
- [ ] 3.2 在 `apps/backend/app/main.py` 中注册 tenant_auth router路径前缀 `/api/tenant/auth`
- _需求: 17.2_
- [ ]* 3.3 编写属性测试:认证正确性
- **Property 1: 认证正确性**
- 使用 Hypothesis 生成随机用户名+密码组合
- 验证:有效凭据返回含 `aud=tenant-admin` 的 JWT无效凭据返回 401 且错误消息统一
- **验证: 需求 1.1, 1.2**
- [ ]* 3.4 编写单元测试:登录边界条件
- 测试文件 `apps/backend/tests/unit/test_tenant_auth.py`
- 验证:禁用账号登录返回 403、用户名不存在返回 401、密码错误返回 401、JWT 过期处理、refresh_token 刷新
- _需求: 1.1, 1.2, 1.3, 1.5_
- [ ] 4. 检查点 — 认证模块验证
- 确保认证模块所有测试通过ask the user if questions arise.
- [ ] 5. 后端路由tenant_users.py用户审核 + 用户管理)
- [ ] 5.1 创建 Pydantic Schema `apps/backend/app/schemas/tenant_users.py`
- 继承 `TenantBaseModel`alias_generator=to_camel定义
- `ApplicationListItem`(昵称、手机号、球房编号、申请角色、员工编号、申请时间、状态)
- `MatchSuggestion`assistant_id/staff_id、姓名、编号、来源表
- `ApproveRequest`role、assistant_id 可选、staff_id 可选)
- `RejectRequest`reason 必填)
- `UserListItem`(姓名、角色、关联助教姓名、所属门店、账号状态)
- `UserEditRequest`role 可选、site_id 可选、status 可选)
- `UserBindingRequest`assistant_id 可选、staff_id 可选)
- _需求: 3.2, 4.1_
- [ ] 5.2 创建 `apps/backend/app/routers/tenant_users.py`
- `GET /api/tenant/applications`申请列表status 筛选 + 分页,附加 site_id IN 条件
- `GET /api/tenant/applications/{id}/match-suggestions`:通过 site_code_mapping 查 site_id并行匹配 v_dim_assistantphone, scd2_is_current=1和 v_dim_staff + v_dim_staff_exphone
- `POST /api/tenant/applications/{id}/approve`:事务内执行:更新 users.status='approved' → 写入 user_site_roles → 写入 user_assistant_binding → 更新 user_applications.status='approved' + 审核人 + 审核时间
- `POST /api/tenant/applications/{id}/reject`:更新 user_applications.status='rejected' + review_note + reviewed_at
- 非 pending 状态审核返回 409
- `GET /api/tenant/users`:已通过审核用户列表,角色筛选 + 关键词搜索 + 分页
- `PATCH /api/tenant/users/{id}`:编辑角色/门店/状态site_id 超出管辖范围返回 403
- `PUT /api/tenant/users/{id}/binding`:更新 user_assistant_binding
- 禁用用户时设 users.status='disabled'
- _需求: 3.1-3.6, 4.1-4.5_
- [ ] 5.3 在 `apps/backend/app/main.py` 中注册 tenant_users router路径前缀 `/api/tenant`
- _需求: 17.2_
- [ ]* 5.4 编写属性测试:审核通过多表一致性
- **Property 4: 审核通过多表一致性**
- 使用 Hypothesis 生成随机 pending 申请 + 角色/绑定参数
- 验证:审核通过后 users.status='approved'、user_site_roles 存在记录、user_assistant_binding 存在记录(如提供 assistant_id、user_applications.status='approved' 且审核人和时间已填写
- **验证: 需求 3.4**
- [ ]* 5.5 编写属性测试:审核拒绝状态更新
- **Property 5: 审核拒绝状态更新**
- 使用 Hypothesis 生成随机 pending 申请 + 随机拒绝原因字符串
- 验证:拒绝后 user_applications.status='rejected'、review_note 等于提交的原因、reviewed_at 非空
- **验证: 需求 3.5**
- [ ]* 5.6 编写属性测试:申请关联匹配
- **Property 6: 申请关联匹配**
- 使用 Hypothesis 生成随机手机号 + 随机助教/员工数据集
- 验证:匹配结果仅包含 phone 匹配的记录,优先 v_dim_assistant 再 v_dim_staff
- **验证: 需求 3.3**
- [ ]* 5.7 编写属性测试:用户管理编辑
- **Property 7: 用户管理编辑**
- 使用 Hypothesis 生成随机编辑参数角色、site_id 在管辖范围内、状态)
- 验证编辑后用户记录反映新值site_id 超出管辖范围时返回 403
- **验证: 需求 4.2, 4.3, 4.4**
- [ ]* 5.8 编写单元测试:用户审核与管理边界条件
- 测试文件 `apps/backend/tests/unit/test_tenant_users.py`
- 验证:非 pending 状态审核返回 409、越权修改 site_id 返回 403、禁用用户后 status='disabled'
- _需求: 3.6, 4.4, 4.5_
- [ ] 6. 后端路由tenant_excel.pyExcel 上传/校验/冲突/写入)
- [ ] 6.1 创建 Pydantic Schema `apps/backend/app/schemas/tenant_excel.py`
- 定义 4 种模板的行数据模型:`ExpenseRow``PlatformIncomeRow``SalaryAdjRow``RechargeCommissionRow`
- 定义校验结果模型:`ValidationResult`errors[], warnings[], passed_rows[], upload_id
- 定义冲突模型:`ConflictDiff`row_index, field_diffs[{field, old_value, new_value}]
- 定义确认请求:`ConfirmRequest`upload_id, resolutions[{row_index, action: replace/keep}]
- 定义上传记录模型:`UploadLogItem`(模板类型、文件名、上传人、时间、行数、冲突数、状态)
- _需求: 5.2, 7.2, 8.4_
- [ ] 6.2 创建 `apps/backend/app/routers/tenant_excel.py`
- `POST /api/tenant/excel/upload`multipart/form-data 接收文件 + upload_type 参数
- 校验文件格式(.xlsx/.xls非法返回 400
- 按模板类型执行格式校验(月份格式、枚举值、金额精度、非空等)
- salary_adj / recharge_commission 模板执行人员匹配校验v_dim_assistant → v_dim_staff 降级匹配)
- 格式校验通过后执行冲突检测(按模板主键规则匹配已有数据)
- 创建 excel_upload_log 记录status=pending返回 upload_id + 校验结果 + 冲突 diff
- `POST /api/tenant/excel/confirm`:接受 upload_id + resolutions[]
- 单事务写入目标表salary_adj → biz.salary_adjustments其余 → staging 表)
- 替换行执行 UPDATE新增行执行 INSERT
- 写入失败回滚整批log status=failed记录 error_detail
- 写入成功更新 log status=confirmed + 实际行数 + 冲突解决数 + confirmed_at
- `GET /api/tenant/excel/logs`:上传记录列表,分页,附加 site_id IN 条件
- `GET /api/tenant/excel/template/{type}`:返回空白 Excel 模板文件(含表头和格式说明)
- _需求: 5.1-5.5, 6.1-6.5, 7.1-7.5, 8.1-8.5_
- [ ] 6.3 在 `apps/backend/app/main.py` 中注册 tenant_excel router路径前缀 `/api/tenant/excel`
- _需求: 17.2_
- [ ]* 6.4 编写属性测试Excel 格式校验
- **Property 8: Excel 格式校验**
- 使用 Hypothesis 生成随机模板类型 + 随机数据行(含有效/无效混合)
- 验证:符合模板定义的行标记通过,不符合的行返回行号+列名+错误描述;通过行数 + 警告行数 + 错误行数 = 总行数
- **验证: 需求 5.1, 5.2, 5.3, 5.4, 6.5**
- [ ]* 6.5 编写属性测试:人员匹配校验
- **Property 9: 人员匹配校验**
- 使用 Hypothesis 生成随机助教姓名+编号 + 随机助教/员工数据集
- 验证:优先 v_dim_assistant 匹配,未匹配再查 v_dim_staff匹配成功填充 assistant_id失败标记 warning 不阻断
- **验证: 需求 6.1, 6.2, 6.3, 6.4**
- [ ]* 6.6 编写属性测试:冲突检测
- **Property 10: 冲突检测**
- 使用 Hypothesis 生成随机上传数据 + 随机已有数据
- 验证:按模板主键规则识别冲突行,冲突行返回逐字段 diff非冲突行标记待写入
- **验证: 需求 7.1, 7.2**
- [ ]* 6.7 编写属性测试:数据写入 round-trip
- **Property 11: 数据写入 round-trip**
- 使用 Hypothesis 生成随机数据 + 随机冲突解决方案replace/keep
- 验证写入后从目标表读取的数据与提交一致replace 用新值keep 保持旧值log status=confirmed行数正确
- **验证: 需求 7.4, 7.5, 8.1, 8.2**
- [ ]* 6.8 编写单元测试Excel 上传边界条件
- 测试文件 `apps/backend/tests/unit/test_tenant_excel.py`
- 验证:无效文件格式返回 400、写入失败回滚log status=failed、模板下载返回正确文件
- _需求: 5.5, 8.3, 8.5_
- [ ] 7. 后端路由tenant_clues.py维客线索管理
- [ ] 7.1 创建 Pydantic Schema `apps/backend/app/schemas/tenant_clues.py`
- 定义 `CustomerSearchItem`member_id、姓名、手机号脱敏、所属门店
- 定义 `ClueListItem`id、category、summary、detail、recorded_by_name、source、recorded_at、is_hidden
- 定义 `ClueEditRequest`category 枚举 6 值、summary 非空 ≤200 字符、detail 可选)
- 定义 `ClueVisibilityRequest`is_hidden: bool
- _需求: 9.1, 10.1, 11.1_
- [ ] 7.2 创建 `apps/backend/app/routers/tenant_clues.py`
- `GET /api/tenant/customers/search`keyword + site_id 参数,在管辖门店范围内搜索 v_dim_membernickname 模糊匹配 OR mobile 精确匹配scd2_is_current=1手机号脱敏返回
- `GET /api/tenant/customers/{member_id}/clues`:返回该客户在管辖门店范围内的全部线索,支持 source 和 is_hidden 筛选
- `PATCH /api/tenant/clues/{id}`:编辑线索 category/summary/detail校验 category 枚举和 summary 长度
- `DELETE /api/tenant/clues/{id}`物理删除DELETE FROM线索不存在或不在管辖范围返回 404
- `PATCH /api/tenant/clues/{id}/visibility`:切换 is_hidden 状态
- 所有线索操作先校验线索 site_id 是否在管辖范围内,不在则返回 404
- _需求: 9.1-9.4, 10.1, 11.1-11.3, 12.2-12.3, 13.1-13.4_
- [ ] 7.3 在 `apps/backend/app/main.py` 中注册 tenant_clues router路径前缀 `/api/tenant`
- _需求: 17.2_
- [ ]* 7.4 编写属性测试:客户搜索
- **Property 12: 客户搜索**
- 使用 Hypothesis 生成随机关键词 + 随机客户数据集
- 验证:结果中 nickname 包含关键词(模糊)或 mobile 等于关键词(精确),且 site_id 在管辖范围内
- **验证: 需求 9.1, 9.3**
- [ ]* 7.5 编写属性测试:线索编辑校验
- **Property 13: 线索编辑校验**
- 使用 Hypothesis 生成随机 category含有效/无效值)+ 随机 summary含空/超长)
- 验证:有效参数编辑成功且记录反映新值;无效 category 或空/超长 summary 被拒绝
- **验证: 需求 11.1, 11.2**
- [ ]* 7.6 编写属性测试:线索删除
- **Property 14: 线索删除**
- 使用 Hypothesis 生成随机线索 ID
- 验证:删除后该线索不可通过任何查询获取(物理删除)
- **验证: 需求 12.2**
- [ ]* 7.7 编写属性测试:线索隐藏/显示 round-trip
- **Property 15: 线索隐藏/显示 round-trip**
- 使用 Hypothesis 生成随机线索
- 验证:设 is_hidden=true 后小程序端查询WHERE is_hidden=false不返回该线索管理后台查询返回设回 false 后小程序端恢复返回
- **验证: 需求 13.1, 13.2, 13.3, 15.3**
- [ ]* 7.8 编写单元测试:维客线索边界条件
- 测试文件 `apps/backend/tests/unit/test_tenant_clues.py`
- 验证:线索不存在返回 404、越权访问返回 404、无效 category 返回 422、空 summary 返回 422
- _需求: 11.2, 11.3, 12.3, 13.4_
- [ ] 8. 检查点 — 后端路由模块验证
- 确保所有后端路由模块测试通过4 个路由文件均已注册到 main.pyask the user if questions arise.
- [ ] 9. 前端项目骨架apps/tenant-admin/
- [ ] 9.1 创建 `apps/tenant-admin/` 项目结构
- 初始化 `package.json`React + Vite + Ant Design + axios 依赖)
- 创建 `vite.config.ts`proxy 配置 `/api/tenant` → 后端地址)
- 创建 `tsconfig.json`
- 创建 `index.html` 入口
- _需求: 16.1_
- [ ] 9.2 创建 `apps/tenant-admin/src/services/api.ts` — API 调用封装
- axios 实例baseURL: `/api/tenant`
- 请求拦截器:从 localStorage 读取 access_token 附加 Authorization header
- 响应拦截器401 时用 refresh_token 刷新,刷新失败重定向 `/login`
- 并发刷新保护:多个 401 只触发一次 refresh其余排队
- 响应解包:`{ code: 0, data }` → 提取 `data`
- _需求: 16.4, 16.5_
- [ ] 9.3 创建 `apps/tenant-admin/src/hooks/useAuth.ts` — 认证状态管理
- 登录/登出方法、token 存储、用户信息display_name, managed_site_ids
- _需求: 1.5, 16.3_
- [ ] 9.4 创建 `apps/tenant-admin/src/App.tsx` — 路由配置 + 侧边栏布局
- react-router-dom 路由配置
- Ant Design Layout + Sider 侧边栏导航用户审核、用户管理、Excel 上传、维客线索管理)
- 未认证时重定向到 `/login`
- _需求: 16.2, 16.3_
- [ ] 9.5 创建 `apps/tenant-admin/src/components/SiteSelector/` — 门店筛选器组件
- 页面顶部门店选择器,数据源为当前管理员的 managed_site_ids
- 支持多选/全选
- _需求: 2.4_
- [ ]* 9.6 编写属性测试:响应格式一致性(前端 fast-check
- **Property 16: 响应格式一致性**
- 使用 fast-check 生成随机 API 响应数据
- 验证:成功响应符合 `{ code: 0, data }` 格式;错误响应符合 `{ code: number, message: string }` 格式;分页响应 data 包含 items/total/page/pageSize
- **验证: 需求 17.4**
- [ ] 10. 前端页面:登录页
- [ ] 10.1 创建 `apps/tenant-admin/src/pages/Login/index.tsx`
- 用户名 + 密码表单Ant Design Form + Input + Button
- 调用 `POST /api/tenant/auth/login`,成功后存储 token 并跳转首页
- 错误提示401 显示"用户名或密码错误"、403 显示"账号已被禁用"
- _需求: 1.1, 1.2, 1.3, 16.3_
- [ ] 11. 前端页面:用户审核
- [ ] 11.1 创建 `apps/tenant-admin/src/pages/UserApproval/index.tsx`
- 申请列表表格Ant Design Table支持状态筛选全部/待审核/已通过/已拒绝)+ 分页
- 展示字段:昵称、手机号、球房编号、申请角色、员工编号、申请时间、状态
- 待审核行提供"审核"操作按钮
- _需求: 3.1, 3.2_
- [ ] 11.2 实现审核操作交互
- 点击"审核"打开 Modal展示关联建议列表调用 match-suggestions API
- 通过操作:选择角色 + 关联助教/员工 → 调用 approve API
- 拒绝操作:填写拒绝原因(必填)→ 调用 reject API
- 操作成功后刷新列表
- _需求: 3.3, 3.4, 3.5_
- [ ] 12. 前端页面:用户管理
- [ ] 12.1 创建 `apps/tenant-admin/src/pages/UserManagement/index.tsx`
- 用户列表表格,支持角色筛选 + 关键词搜索 + 分页
- 展示字段:姓名、角色、关联助教姓名、所属门店、账号状态
- 每行提供"编辑"和"绑定"操作按钮
- _需求: 4.1_
- [ ] 12.2 实现编辑与绑定交互
- 编辑 Modal修改角色Select、所属门店Select限管辖范围、账号状态Switch
- 绑定 Modal修改关联助教 ID / 员工 ID
- 禁用用户时二次确认
- _需求: 4.2, 4.3, 4.4_
- [ ] 13. 前端页面Excel 上传
- [ ] 13.1 创建 `apps/tenant-admin/src/pages/ExcelUpload/index.tsx`
- 模板类型选择Radio/Select财务支出/团购收入/助教奖罚/充值业绩归属)
- 模板下载按钮(调用 template/{type} API
- 文件上传组件Ant Design Upload限 .xlsx/.xls
- 上传后展示校验结果:错误行标红、警告行黄色高亮
- 汇总展示:通过行数、警告行数、错误行数
- _需求: 5.1, 5.3, 6.4, 6.5, 8.5_
- [ ] 13.2 创建 `apps/tenant-admin/src/components/DiffTable/index.tsx` — 冲突 diff 交互表格
- 每行显示:字段名、旧值、新值、操作选项(替换/保留 Radio
- 支持"全部替换"和"全部保留"快捷操作按钮
- 确认按钮提交 resolutions 数组
- _需求: 7.3, 7.4_
- [ ] 13.3 实现确认写入与上传记录
- 无冲突时直接展示"待写入"确认按钮
- 有冲突时展示 DiffTable用户选择后确认
- 调用 confirm API 写入,成功/失败提示
- 上传记录 Tab展示历史上传记录列表模板类型、文件名、上传人、时间、行数、冲突数、状态分页
- _需求: 7.5, 8.1, 8.2, 8.4_
- [ ] 14. 前端页面:维客线索管理
- [ ] 14.1 创建 `apps/tenant-admin/src/pages/RetentionClues/index.tsx`
- 客户搜索栏Input.Search支持姓名模糊/手机号精确搜索
- 门店筛选器(复用 SiteSelector 组件)
- 搜索结果列表member_id、姓名、手机号脱敏、所属门店
- 搜索结果为空时展示"未找到匹配客户"提示
- _需求: 9.1, 9.3, 9.4_
- [ ] 14.2 实现线索列表与操作
- 选择客户后展示该客户全部线索,按大类标签分组
- 支持按来源和隐藏状态筛选
- 已隐藏线索以灰色/删除线样式区分
- 每条线索提供:编辑、删除、隐藏/显示操作按钮
- _需求: 10.1, 10.2, 10.3_
- [ ] 14.3 创建 `apps/tenant-admin/src/components/ClueEditor/index.tsx` — 线索编辑表单
- category Select6 值枚举:客户基础/消费习惯/玩法偏好/促销偏好/社交关系/重要反馈)
- summary Input必填最长 200 字符)
- detail TextArea可选
- _需求: 11.1_
- [ ] 14.4 实现删除与隐藏/显示交互
- 删除二次确认对话框Modal.confirm明确提示"删除后不可恢复"
- 隐藏/显示Switch 切换,即时调用 visibility API
- _需求: 12.1, 12.2, 13.1, 13.3_
- [ ] 15. 检查点 — 前端页面验证
- 确保所有前端页面组件渲染正常API 调用层工作正确ask the user if questions arise.
- [ ] 16. 前后端联调与集成
- [ ] 16.1 前端所有页面对接后端真实 API
- 登录页 → tenant_auth 登录/刷新
- 用户审核页 → tenant_users 申请列表/关联建议/审核
- 用户管理页 → tenant_users 用户列表/编辑/绑定
- Excel 上传页 → tenant_excel 上传/校验/冲突/确认/记录/模板下载
- 维客线索页 → tenant_clues 客户搜索/线索列表/编辑/删除/隐藏
- 验证所有页面 API 调用失败时显示友好错误提示,不出现白屏
- _需求: 1.1-1.6, 3.1-3.6, 4.1-4.5, 5.1-5.5, 9.1-9.4, 10.1-10.3, 11.1-11.3, 12.1-12.3, 13.1-13.4_
- [ ] 16.2 DDL 迁移合并到主 DDL 基线
- 执行迁移脚本到 `test_zqyy_app`
- 合并新表定义到 `docs/database/ddl/` 对应基线文件
- _需求: 14.1-14.4, 15.1-15.2_
- [ ] 17. 文档更新
- [ ] 17.1 创建/更新 BD 手册
- 新增 `docs/database/BD_Manual_tenant_admin_tables.md`tenant_admins、excel_upload_log、salary_adjustments、3 张 staging 表的字段明细、约束、索引、验证 SQL
- 更新 `docs/database/BD_Manual_retention_clue.md`(如存在):追加 is_hidden 字段说明
- _规范: db-docs.md_
- [ ] 17.2 更新后端 API 参考文档
-`apps/backend/docs/API-REFERENCE.md` 新增 4 个 tenant 路由模块文档
- 更新 `apps/backend/README.md` 路由模块摘要
- _需求: 17.1-17.4_
- [ ] 17.3 更新文档地图
-`docs/DOCUMENTATION-MAP.md` 新增 NS4 模块条目tenant-admin-web spec、BD 手册、前端项目)
- _规范: doc-map.md_
- [ ] 18. 最终检查点 — 全量验证
- 确保所有后端测试通过(单元测试 + 属性测试)
- 确保前端所有页面连接真实后端运行正常
- 确保 DDL 迁移已合并到主基线BD 手册已同步更新
- 确保 API 文档、后端 README、文档地图均已更新
- ask the user if questions arise.
## 备注
- 标记 `*` 的子任务为可选,可跳过以加速 MVP 交付
- 每个任务引用了具体的需求编号以确保可追溯性
- 属性测试覆盖 16 个正确性属性Property 1-16单元测试覆盖具体边界条件
- 检查点任务确保增量验证,避免问题累积
- 后端使用 PythonFastAPI + Pydantic + Hypothesis前端使用 TypeScriptReact + Vite + Ant Design + fast-check
- 会员信息一律通过 `member_id` JOIN `v_dim_member``scd2_is_current=1`不使用结算单冗余字段DQ-6 规则)
- 多店铺 FDW 查询采用逐 site_id 查询后合并结果的策略