# 变更审计记录：租户管理后台审核弹窗改造（角色动态化 + 人员列表联动 + 手机号自动匹配）

| 字段 | 值 |
|------|-----|
| 日期 | 2026-03-23 17:00:00 |
| Prompt-ID | P20260323-164500 |
| Session-ID | 545eba0a |
| Session 路径 | docs/audit/session_logs/2026-03/23/18_57a8dd3c_164506 |

## 操作摘要

改造 tenant-admin 审核弹窗，实现角色下拉从数据库动态读取（不硬编码）、选择角色后联动查询人员列表（coach→ETL 助教表，其他→ETL 员工表）、手机号自动匹配、提供"无"选项。后端新增 2 个 API 端点（`GET /api/tenant/roles`、`GET /api/tenant/site-staff`）和 2 个 Schema（`RoleItem`、`StaffCandidate`）。前端 ReviewModal 组件全面改造。修复前端语法错误（中文引号与 JSX 双引号冲突、filterOption 类型断言）。

关键技术决策：不用 FDW 视图（`fdw_etl.v_dim_assistant`），因为 FDW 视图的 RLS（`current_setting('app.current_site_id')`）在跨库场景下不生效。改用 `get_etl_readonly_connection(site_id)` 直连 ETL 库查底层表 `dwd.dim_assistant` / `dwd.dim_staff`，手动加 `site_id` 过滤。

## 变更范围

- 模块：`apps/backend/`（FastAPI 路由 + Schema）、`apps/tenant-admin/`（React 前端）
- 接口：新增 `GET /api/tenant/roles`、`GET /api/tenant/site-staff`
- 数据源：`auth.roles`（业务库）、`dwd.dim_assistant` / `dwd.dim_staff`（ETL 库直连）

## 风险与回滚

- 风险：ETL 直连依赖 `get_etl_readonly_connection` 可用性；角色表数据变更影响下拉列表；`site-staff` 端点 site_code→site_id 转换依赖 `biz.sites` 数据一致性
- 回滚：删除 2 个新端点（`/roles`、`/site-staff`）+ 2 个新 Schema（`RoleItem`、`StaffCandidate`）；前端恢复原 ReviewModal 硬编码版本

## 验证

- 访问 tenant-admin http://localhost:5174 → 用户审批页 → 点击审核 → 确认角色下拉动态加载 4 个角色
- 选择 coach 角色 → 确认人员列表从 ETL 助教表加载
- 选择 staff/head_coach/manager → 确认人员列表从 ETL 员工表加载
- 选择"无" → 确认可以提交
- 前端无编译错误（Vite HMR 正常）

## 文件清单

| 文件 | 变更类型 | 说明 |
|------|----------|------|
| `apps/backend/app/routers/tenant_users.py` | 修改 | 新增 `GET /roles` 和 `GET /site-staff` 端点 |
| `apps/backend/app/schemas/tenant_users.py` | 修改 | 新增 `RoleItem`、`StaffCandidate` schema |
| `apps/tenant-admin/src/pages/UserApproval/index.tsx` | 修改 | ReviewModal 组件改造（角色动态化、人员联动、手机号匹配、"无"选项、语法修复） |


## 本次对话文件变更

### 新增文件
- `docs/audit/prompt_logs/prompt_log_20260323_165704.md`
- `docs/audit/session_logs/2026-03/23/18_57a8dd3c_164506/main_01_545eba0a.md`
- `docs/audit/session_logs/2026-03/23/18_57a8dd3c_164506/sub_01_545eba0a.md`
- `docs/audit/session_logs/2026-03/23/17_bc427aef_161938/main_01_6c7c8174.md`
- `docs/audit/session_logs/2026-03/23/17_bc427aef_161938/sub_01_545eba0a.md`

### 修改文件
- `apps/backend/app/routers/tenant_users.py`
- `apps/backend/app/schemas/tenant_users.py`
- `apps/tenant-admin/src/pages/UserApproval/index.tsx`
- `NeoZQYY.code-workspace`

### 删除文件
- `docs/audit/session_logs/2026-03/23/17_bc427aef_161938/main_01_e69d704e.md`

## 改动注解

### `apps/backend/app/routers/tenant_users.py`
- 变更类型：修改
- 原始原因：原审核弹窗角色硬编码在前端，无法适应角色增减；人员列表无联动查询，审核时无法关联助教/员工。用户要求角色从数据库动态读取，并根据角色+门店查询对应人员列表。
- 思路分析：新增 `GET /api/tenant/roles` 端点，从 `auth.roles` 表动态读取小程序可用角色（排除 `tenant_admin`/`site_admin` 管理类角色），返回 `RoleItem` 列表。新增 `GET /api/tenant/site-staff` 端点，接收 `role` + `site_id`/`site_code` 参数，根据角色类型分流查询：`coach` 查 ETL 库 `dwd.dim_assistant`（`scd2_is_current=1`），其他角色查 `dwd.dim_staff`。关键决策：放弃 FDW 视图（RLS 跨库不生效），改用 `get_etl_readonly_connection(site_id)` 直连 ETL 库底层表，手动加 `site_id` 过滤。`site-staff` 端点支持 `site_code` 参数（前端传球房编号），内部先查 `biz.sites` 转换为 `site_id`，再校验权限。
- 修改结果：租户管理员审核时可动态获取角色列表和对应人员列表。`site_admin` 受 `managed_site_ids` 权限限制，`tenant_admin` 可查所有店铺。ETL 直连方案绕过了 FDW RLS 跨库失效问题。

### `apps/backend/app/schemas/tenant_users.py`
- 变更类型：修改
- 原始原因：后端新增 2 个端点需要对应的响应 Schema。
- 思路分析：新增 `RoleItem`（角色列表项：id/code/name/description）和 `StaffCandidate`（人员候选项：id/identity_label/name/mobile/entry_time/source）。`StaffCandidate.source` 字段标识数据来源（`assistant`/`staff`），前端据此构造 `staffBinding` 值（格式 `"source:id"`）。`identity_label` 统一承载助教的 `level` 和员工的 `staff_identity` 原始值。
- 修改结果：两个新 Schema 继承 `CamelModel`，自动 snake_case→camelCase 转换，与前端 TypeScript 接口定义对齐。

### `apps/tenant-admin/src/pages/UserApproval/index.tsx`
- 变更类型：修改
- 原始原因：前端 ReviewModal 组件角色下拉硬编码 3 个角色（coach/staff/site_admin），缺少 head_coach/manager，且 site_admin 不应出现在小程序角色中。人员关联只有旧的 match-suggestions（按手机号匹配），无法列出全部人员供选择。
- 思路分析：角色下拉改为弹窗打开时调用 `GET /api/tenant/roles` 动态获取。新增 `handleRoleChange` 回调：角色变化时调用 `GET /api/tenant/site-staff` 获取人员列表，加载后按申请者手机号自动选中匹配项（前端逻辑）。人员下拉展示格式 `身份角色 - 姓名 - 手机号 - 入职时间`，首项为"无（不关联）"。`staffBinding` 值格式 `"source:id"`（如 `"assistant:123"`）或 `"none"`，提交时解析为 `assistantId`/`staffId`。修复两个语法问题：中文引号 `"无"` 与 JSX 双引号冲突（改用单引号包裹）；`filterOption` 的 `option?.label` 类型断言（改用 `String()` 包装）。
- 修改结果：审核弹窗角色下拉动态化（4 个小程序角色），人员列表按角色+门店联动查询，手机号自动匹配，支持"无"选项。前端编译通过，Vite HMR 正常。

## 合规检查

### 文档同步
- ⚠️ `apps/backend/app/routers/tenant_users.py` 新增 2 个端点后 `apps/backend/docs/API-REFERENCE.md` 和 `docs/contracts/openapi/backend-api.json` 需同步更新
- 新增端点 `GET /api/tenant/roles` 和 `GET /api/tenant/site-staff` 需补充到 API 文档

### OpenAPI Spec
- 接口代码已变更，OpenAPI spec 需重新导出（`python scripts/ops/_export_openapi.py`）

### DDL/迁移
- 无新增迁移 SQL
- ⚠️ DDL 基线待合并

### DB 文档
- 本次无数据库结构变更，BD 手册无需更新

---

<!-- AI_CHANGELOG
| 日期 | Prompt | 变更 |
|------|--------|------|
| 2026-03-23 17:00:00 | 租户管理后台审核弹窗改造 | tenant_users.py 新增 roles + site-staff 端点；tenant_users schema 新增 RoleItem + StaffCandidate；tenant-admin ReviewModal 角色动态化 + 人员联动 + 手机号匹配 + 语法修复 |
-->
