# 实施计划:租户管理后台(tenant-admin-web) ## 概述 按照设计文档,将实施拆分为:DDL 迁移 → 后端认证模块 → 后端路由模块 → 前端项目骨架 → 前端页面实现 → 联调收尾。每个任务增量构建,确保无孤立代码。属性测试(Hypothesis / fast-check)和单元测试作为可选子任务紧跟实现步骤。 后端使用 Python(FastAPI + Pydantic),前端使用 TypeScript(React + 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` dataclass(admin_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 验证,签发 JWT(aud=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_assistant(phone, scd2_is_current=1)和 v_dim_staff + v_dim_staff_ex(phone) - `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.py(Excel 上传/校验/冲突/写入) - [ ] 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_member(nickname 模糊匹配 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.py,ask 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 Select(6 值枚举:客户基础/消费习惯/玩法偏好/促销偏好/社交关系/重要反馈) - 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),单元测试覆盖具体边界条件 - 检查点任务确保增量验证,避免问题累积 - 后端使用 Python(FastAPI + Pydantic + Hypothesis),前端使用 TypeScript(React + Vite + Ant Design + fast-check) - 会员信息一律通过 `member_id` JOIN `v_dim_member`(`scd2_is_current=1`),不使用结算单冗余字段(DQ-6 规则) - 多店铺 FDW 查询采用逐 site_id 查询后合并结果的策略