chore: 文档与 IDE 配置整理

- .kiro/specs/ → docs/specs/(41 个历史需求 spec 迁移,移除 .config.kiro)
- CLAUDE.md 三层拆分:根文件精简 + apps/backend/CLAUDE.md + .claude/commands/
- 新增 /spec-close、/pre-change 两个工作流命令
- DDL 基线刷新(从测试库重新导出 11 个文件,dws 35→38 表,biz 18→21 表)
- BD_Manual → BD_manual 命名统一(48 个文件)
- 修复 3 处文档与数据库不一致(auth.users.status 默认值、scheduled_tasks 字段、RLS 视图数)
- 新增 BD_manual_public_rbac_tables.md(public schema 8 张 RBAC/工作流表)
- 合并 biz.trigger_jobs 文档(10→12 字段,归档独立文档)
- docs/database/README.md 索引更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Neo
2026-04-06 00:02:37 +08:00
parent 8228b3fa37
commit 70324d8542
185 changed files with 13595 additions and 1219 deletions

View File

@@ -0,0 +1,544 @@
# BD 手册租户管理后台表NS4 tenant-admin-web
## 概述
NS4 租户管理后台新增 6 张表,分布在 `auth``biz` 两个 Schema 中。`auth.tenant_admins` 为租户管理员认证表,与小程序 `auth.users`(微信登录)完全隔离;`biz` Schema 下 5 张表支撑 Excel 数据上传功能上传日志、助教奖罚、3 张 staging 暂存表)。
所有表位于 `zqyy_app` / `test_zqyy_app` 数据库。
## 变更说明
| 库 | Schema | 表 | 变更类型 | 说明 |
|----|--------|---|---------|------|
| zqyy_app | auth | tenant_admins | 新建 | 租户管理员认证表 |
| zqyy_app | biz | excel_upload_log | 新建 | Excel 上传记录表 |
| zqyy_app | biz | salary_adjustments | 新建 | 助教奖罚明细表 |
| zqyy_app | biz | stg_finance_expense | 新建 | 财务支出暂存表 |
| zqyy_app | biz | stg_platform_income | 新建 | 团购收入暂存表 |
| zqyy_app | biz | stg_recharge_commission | 新建 | 充值业绩归属暂存表 |
---
## 1. auth.tenant_admins — 租户管理员表
独立于小程序 `auth.users`,使用用户名+密码登录JWT `aud=tenant-admin` 隔离。
### 表结构
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
| username | VARCHAR(50) | UNIQUE NOT NULL | 登录用户名 |
| password_hash | VARCHAR(255) | NOT NULL | bcrypt 哈希密码 |
| display_name | VARCHAR(100) | — | 显示名称 |
| tenant_id | BIGINT | NOT NULL | 所属租户 ID |
| managed_site_ids | BIGINT[] | NOT NULL | 管辖门店 ID 列表(数据隔离依据) |
| admin_type | VARCHAR(20) | NOT NULL DEFAULT 'tenant_admin' | 管理员类型tenant_admin租户管理员/ site_admin店铺管理员CHECK 约束 |
| is_active | BOOLEAN | DEFAULT true | 账号状态false=禁用,登录返回 403仅控制启用/禁用,与软删除无关) |
| deleted_at | TIMESTAMPTZ | DEFAULT NULL | 软删除时间戳NULL=正常,非 NULL=已删除(与 is_active 分离) |
| created_by | BIGINT | — | 创建者(管理员 ID |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
| last_login_at | TIMESTAMPTZ | — | 最后登录时间 |
### 约束与索引
| 名称 | 类型 | 列 | 说明 |
|------|------|---|------|
| tenant_admins_pkey | PRIMARY KEY | id | 主键 |
| tenant_admins_username_key | UNIQUE | username | 用户名唯一 |
| chk_admin_type | CHECK | admin_type | admin_type IN ('tenant_admin', 'site_admin') |
| idx_tenant_admin_tenant | INDEX (btree) | tenant_id | 按租户查询 |
| idx_tenant_admins_active_not_deleted | INDEX (btree, partial) | is_active WHERE deleted_at IS NULL | 加速列表和登录查询(仅索引未删除记录) |
| idx_tenant_admins_username_lower | UNIQUE (partial) | LOWER(username) WHERE deleted_at IS NULL | 大小写不敏感唯一约束2026-03-23 |
---
## 2. biz.excel_upload_log — Excel 上传记录表
记录每次 Excel 上传的批次信息,支撑上传→校验→冲突→确认的完整流程。
### 表结构
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
| site_id | BIGINT | NOT NULL | 门店 ID |
| upload_type | VARCHAR(30) | NOT NULL, CHECK | 模板类型(见枚举) |
| file_name | VARCHAR(255) | NOT NULL | 原始文件名 |
| uploaded_by | BIGINT | NOT NULL | 上传人(管理员 ID |
| row_count | INTEGER | DEFAULT 0 | 数据行数 |
| conflict_count | INTEGER | DEFAULT 0 | 冲突行数 |
| resolved_count | INTEGER | DEFAULT 0 | 已解决冲突数 |
| status | VARCHAR(20) | NOT NULL, CHECK | 批次状态(见枚举) |
| error_detail | JSONB | — | 错误详情 / 临时缓存上传数据 |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 上传时间 |
| confirmed_at | TIMESTAMPTZ | — | 确认写入时间 |
### upload_type 枚举值
| 值 | 说明 |
|----|------|
| expense | 财务支出 |
| platform_income | 团购收入 |
| salary_adj | 助教奖罚 |
| recharge_commission | 充值业绩归属 |
### status 枚举值
| 值 | 说明 |
|----|------|
| pending | 待确认(已上传校验通过,等待用户确认写入) |
| confirmed | 已确认(数据已写入目标表) |
| failed | 失败(写入过程出错,已回滚) |
### 约束与索引
| 名称 | 类型 | 列 | 说明 |
|------|------|---|------|
| excel_upload_log_pkey | PRIMARY KEY | id | 主键 |
| CHECK (upload_type) | CHECK | upload_type | 限制模板类型枚举 |
| CHECK (status) | CHECK | status | 限制状态枚举 |
| idx_excel_log_site | INDEX (btree) | (site_id, created_at DESC) | 按门店+时间查询 |
---
## 3. biz.salary_adjustments — 助教奖罚明细表
直接写入 biz Schema非 staging记录助教扣款/奖金明细。
### 表结构
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
| site_id | BIGINT | NOT NULL | 门店 ID |
| assistant_id | BIGINT | — | 匹配到的助教 ID可空人员匹配失败时为 NULL |
| assistant_name | VARCHAR(100) | NOT NULL | 助教姓名Excel 原始值) |
| assistant_number | VARCHAR(50) | NOT NULL | 助教编号Excel 原始值) |
| salary_month | VARCHAR(7) | NOT NULL | 月份(格式 YYYY-MM |
| adjustment_type | VARCHAR(20) | NOT NULL, CHECK | 类型deduction扣款/ bonus奖金 |
| amount | NUMERIC(12,2) | NOT NULL, CHECK (> 0) | 金额(正数) |
| reason | VARCHAR(200) | NOT NULL | 原因说明 |
| upload_batch_id | BIGINT | FK → excel_upload_log(id) | 上传批次 |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
| created_by | BIGINT | — | 上传人(管理员 ID |
### adjustment_type 枚举值
| 值 | Excel 中文值 | 说明 |
|----|-------------|------|
| deduction | 扣款 | 扣款 |
| bonus | 奖金 | 奖金 |
### 约束与索引
| 名称 | 类型 | 列 | 说明 |
|------|------|---|------|
| salary_adjustments_pkey | PRIMARY KEY | id | 主键 |
| CHECK (adjustment_type) | CHECK | adjustment_type | 限制类型枚举 |
| CHECK (amount) | CHECK | amount | 金额必须 > 0 |
| salary_adjustments_upload_batch_id_fkey | FOREIGN KEY | upload_batch_id → excel_upload_log(id) | 关联上传批次 |
| idx_salary_adj_site_month | INDEX (btree) | (site_id, salary_month) | 按门店+月份查询 |
| idx_salary_adj_assistant_month | INDEX (btree) | (assistant_id, salary_month) | 按助教+月份查询 |
---
## 4. biz.stg_finance_expense — 财务支出暂存表
通过 Excel 上传写入,等待 ETL 同步到正式表。
### 表结构
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
| site_id | BIGINT | NOT NULL | 门店 ID |
| expense_month | VARCHAR(7) | NOT NULL | 月份(格式 YYYY-MM |
| category | VARCHAR(50) | NOT NULL | 支出类别8 值枚举) |
| amount | NUMERIC(12,2) | NOT NULL | 金额 |
| remark | TEXT | — | 备注 |
| upload_batch_id | BIGINT | FK → excel_upload_log(id) | 上传批次 |
| synced_at | TIMESTAMPTZ | — | ETL 同步时间NULL=未同步) |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
### 冲突检测主键
`(site_id, expense_month, category)` — 同门店同月份同类别视为冲突。
### 约束与索引
| 名称 | 类型 | 列 | 说明 |
|------|------|---|------|
| stg_finance_expense_pkey | PRIMARY KEY | id | 主键 |
| stg_finance_expense_upload_batch_id_fkey | FOREIGN KEY | upload_batch_id → excel_upload_log(id) | 关联上传批次 |
---
## 5. biz.stg_platform_income — 团购收入暂存表
### 表结构
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
| site_id | BIGINT | NOT NULL | 门店 ID |
| income_month | VARCHAR(7) | NOT NULL | 月份(格式 YYYY-MM |
| platform_name | VARCHAR(100) | NOT NULL | 平台名称 |
| amount | NUMERIC(12,2) | NOT NULL | 收入金额 |
| remark | TEXT | — | 备注 |
| upload_batch_id | BIGINT | FK → excel_upload_log(id) | 上传批次 |
| synced_at | TIMESTAMPTZ | — | ETL 同步时间NULL=未同步) |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
### 冲突检测主键
`(site_id, income_month, platform_name)` — 同门店同月份同平台视为冲突。
### 约束与索引
| 名称 | 类型 | 列 | 说明 |
|------|------|---|------|
| stg_platform_income_pkey | PRIMARY KEY | id | 主键 |
| stg_platform_income_upload_batch_id_fkey | FOREIGN KEY | upload_batch_id → excel_upload_log(id) | 关联上传批次 |
---
## 6. biz.stg_recharge_commission — 充值业绩归属暂存表
### 表结构
| 列名 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | BIGSERIAL | PRIMARY KEY | 自增主键 |
| site_id | BIGINT | NOT NULL | 门店 ID |
| recharge_date | DATE | NOT NULL | 充值日期 |
| member_name | VARCHAR(100) | NOT NULL | 会员名称 |
| recharge_amount | NUMERIC(12,2) | NOT NULL | 充值金额 |
| assigned_assistant | VARCHAR(100) | NOT NULL | 归属助教 |
| reward_amount | NUMERIC(12,2) | NOT NULL | 奖励金额 |
| upload_batch_id | BIGINT | FK → excel_upload_log(id) | 上传批次 |
| synced_at | TIMESTAMPTZ | — | ETL 同步时间NULL=未同步) |
| created_at | TIMESTAMPTZ | DEFAULT NOW() | 创建时间 |
### 冲突检测主键
`(site_id, recharge_date, member_name, assigned_assistant)` — 同门店同日期同会员同助教视为冲突。
### 约束与索引
| 名称 | 类型 | 列 | 说明 |
|------|------|---|------|
| stg_recharge_commission_pkey | PRIMARY KEY | id | 主键 |
| stg_recharge_commission_upload_batch_id_fkey | FOREIGN KEY | upload_batch_id → excel_upload_log(id) | 关联上传批次 |
---
## 兼容性
- **后端 API**5 个新路由模块tenant_auth / tenant_users / tenant_excel / tenant_clues / admin_tenant_admins全部依赖上述表
- **ETL**3 张 staging 表stg_finance_expense / stg_platform_income / stg_recharge_commission`synced_at` 字段供 ETL 同步标记ETL 读取 `synced_at IS NULL` 的行进行同步
- **小程序**:无直接影响(租户管理后台独立认证体系)
- **管理后台admin-web**:新增租户管理员 CRUD 页面,调用 admin_tenant_admins 路由
## 回滚策略
### 完整回滚(按依赖顺序)
```sql
BEGIN;
-- 先删除有外键依赖的表
DROP TABLE IF EXISTS biz.salary_adjustments CASCADE;
DROP TABLE IF EXISTS biz.stg_finance_expense CASCADE;
DROP TABLE IF EXISTS biz.stg_platform_income CASCADE;
DROP TABLE IF EXISTS biz.stg_recharge_commission CASCADE;
-- 再删除被引用的表
DROP TABLE IF EXISTS biz.excel_upload_log CASCADE;
-- 最后删除认证表
DROP TABLE IF EXISTS auth.tenant_admins CASCADE;
-- 清理序列CASCADE 已处理,此处为显式确认)
DROP SEQUENCE IF EXISTS biz.excel_upload_log_id_seq;
DROP SEQUENCE IF EXISTS biz.salary_adjustments_id_seq;
DROP SEQUENCE IF EXISTS biz.stg_finance_expense_id_seq;
DROP SEQUENCE IF EXISTS biz.stg_platform_income_id_seq;
DROP SEQUENCE IF EXISTS biz.stg_recharge_commission_id_seq;
DROP SEQUENCE IF EXISTS auth.tenant_admins_id_seq;
COMMIT;
```
## 验证步骤
```sql
-- 1. 确认 auth.tenant_admins 表存在且列完整12 列,含 admin_type + deleted_at
SELECT column_name, data_type, is_nullable, column_default
FROM information_schema.columns
WHERE table_schema = 'auth' AND table_name = 'tenant_admins'
ORDER BY ordinal_position;
-- 预期12 行id, username, password_hash, display_name, tenant_id,
-- managed_site_ids, admin_type, is_active, deleted_at, created_by, created_at, last_login_at
-- 2. 确认 tenant_admins 唯一约束
SELECT conname, contype FROM pg_constraint
WHERE conrelid = 'auth.tenant_admins'::regclass;
-- 预期:包含 tenant_admins_pkey (p) 和 tenant_admins_username_key (u)
-- 3. 确认 biz.excel_upload_log 表存在且 CHECK 约束正确
SELECT conname, pg_get_constraintdef(oid) FROM pg_constraint
WHERE conrelid = 'biz.excel_upload_log'::regclass AND contype = 'c';
-- 预期2 行upload_type CHECK 和 status CHECK
-- 4. 确认 biz.salary_adjustments 外键和 CHECK 约束
SELECT conname, contype, pg_get_constraintdef(oid) FROM pg_constraint
WHERE conrelid = 'biz.salary_adjustments'::regclass AND contype IN ('c', 'f');
-- 预期3 行adjustment_type CHECK, amount CHECK, upload_batch_id FK
-- 5. 确认 3 张 staging 表的外键
SELECT c.conname, c.conrelid::regclass AS table_name
FROM pg_constraint c
WHERE c.confrelid = 'biz.excel_upload_log'::regclass AND c.contype = 'f';
-- 预期4 行salary_adjustments + 3 张 staging 表各 1 个 FK
-- 6. 确认索引
SELECT schemaname, tablename, indexname FROM pg_indexes
WHERE tablename IN ('tenant_admins', 'excel_upload_log', 'salary_adjustments',
'stg_finance_expense', 'stg_platform_income', 'stg_recharge_commission')
ORDER BY tablename, indexname;
-- 预期tenant_admins 3 个pkey + idx_tenant_admin_tenant + idx_tenant_admins_active_not_deleted
-- excel_upload_log 2 个pkey + idx_excel_log_site
-- salary_adjustments 3 个pkey + idx_salary_adj_site_month + idx_salary_adj_assistant_month
-- stg_* 各 1 个pkey
-- 7. 确认 6 张表均可查询
SELECT 'auth.tenant_admins' AS tbl, COUNT(*) FROM auth.tenant_admins
UNION ALL SELECT 'biz.excel_upload_log', COUNT(*) FROM biz.excel_upload_log
UNION ALL SELECT 'biz.salary_adjustments', COUNT(*) FROM biz.salary_adjustments
UNION ALL SELECT 'biz.stg_finance_expense', COUNT(*) FROM biz.stg_finance_expense
UNION ALL SELECT 'biz.stg_platform_income', COUNT(*) FROM biz.stg_platform_income
UNION ALL SELECT 'biz.stg_recharge_commission', COUNT(*) FROM biz.stg_recharge_commission;
-- 预期6 行,各表行数 ≥ 0
```
## NS4.1 变更补充2026-03-22
### auth.tenant_admins 行为变更
NS4.1 对 `auth.tenant_admins` 表做了以下调整:
#### deleted_at 软删除字段(已合并入主 DDL
`deleted_at` 字段已合并到主迁移脚本 `2026-03-20__ns4_tenant_admin_tables.sql` 中,不再需要独立的 ALTER TABLE 迁移。字段语义:
- `NULL` = 正常记录
-`NULL` = 已删除(时间戳记录删除时间)
-`is_active` 分离:`is_active` 控制启用/禁用,`deleted_at` 控制软删除
- 部分索引 `idx_tenant_admins_active_not_deleted` 仅索引 `deleted_at IS NULL` 的记录
#### 软删除逻辑
`DELETE /api/admin/tenant-admins/{id}` 端点实际执行软删除:将 `is_active` 设置为 `false`,不物理删除行。已禁用的管理员再次删除返回 409。
```sql
-- 软删除
UPDATE auth.tenant_admins SET is_active = false WHERE id = :id AND is_active = true;
```
#### tenant_id 来源变更
创建管理员时,`tenant_id` 不再是自由输入,而是从 `biz.tenants` 表中选择。后端校验 `tenant_id``biz.tenants` 中存在且 `is_active=true`
```sql
-- 创建时校验
SELECT id FROM biz.tenants WHERE id = :tenant_id AND is_active = true;
```
#### username 可编辑
`PATCH /api/admin/tenant-admins/{id}` 端点支持修改 `username`。修改时校验全局唯一性(大小写不敏感),冲突返回 409 Conflict。
```sql
-- 唯一性校验(大小写不敏感)
SELECT id FROM auth.tenant_admins WHERE LOWER(username) = LOWER(:new_username) AND id != :current_id AND deleted_at IS NULL;
```
#### 列表过滤
`GET /api/admin/tenant-admins` 端点默认只返回 `is_active=true` 的记录。新增 `include_inactive` 查询参数,设为 `true` 时返回所有记录(含已禁用)。
列表查询 JOIN `biz.tenants` 获取 `tenant_name` 字段。
---
## 关联文件
- DDL 基线auth`docs/database/ddl/zqyy_app__auth.sql`
- DDL 基线biz`docs/database/ddl/zqyy_app__biz.sql`
- 迁移脚本:`db/zqyy_app/migrations/2026-03-20__ns4_tenant_admin_tables.sql`
- 迁移脚本:`db/zqyy_app/migrations/2026-03-23__case_insensitive_username.sql`
- 迁移脚本:`db/zqyy_app/migrations/2026-03-23__cleanup_roles_add_admin_type.sql`
- 后端路由:`apps/backend/app/routers/tenant_auth.py``tenant_users.py``tenant_excel.py``tenant_clues.py``admin_tenant_admins.py``tenant_site_admins.py`
- 后端 Schema`apps/backend/app/schemas/tenant_excel.py``admin_tenant_admins.py`
- 认证模块:`apps/backend/app/auth/tenant_admins.py`
- Spec`.kiro/specs/tenant-admin-web/`
---
## NS4.2 变更补充2026-03-23
### auth.tenant_admins 用户名大小写不敏感
#### 变更说明
登录、创建、编辑管理员时,用户名统一转小写存储,查询使用 `LOWER()` 比较。避免 `Admin``admin` 被视为不同账号。
#### 新增索引
| 名称 | 类型 | 列 | 说明 |
|------|------|---|------|
| idx_tenant_admins_username_lower | UNIQUE (partial) | LOWER(username) WHERE deleted_at IS NULL | 大小写不敏感唯一约束 |
#### 代码变更
| 文件 | 变更 |
|------|------|
| `tenant_auth.py` | 登录查询 `WHERE LOWER(username) = LOWER(%s)` |
| `admin_tenant_admins.py` | 创建 `INSERT ... VALUES (LOWER(%s), ...)`;编辑 `SET username = LOWER(%s)`;唯一性校验 `LOWER()` 比较 |
#### 兼容性
- 后端 API登录和 CRUD 接口透明兼容,无需前端改动
- ETL无影响
- 小程序:无影响(独立认证体系)
- 管理后台admin-web无需改动后端统一处理
#### 回滚策略
```sql
-- 1. 删除函数索引
DROP INDEX IF EXISTS auth.idx_tenant_admins_username_lower;
-- 2. 代码回滚:恢复 WHERE username = %s不带 LOWER
-- 注意:已小写化的用户名不可逆,但不影响功能
```
#### 验证 SQL
```sql
-- 1. 确认索引存在
SELECT indexname FROM pg_indexes
WHERE tablename = 'tenant_admins' AND indexname = 'idx_tenant_admins_username_lower';
-- 预期1 行
-- 2. 确认无大写用户名残留
SELECT username FROM auth.tenant_admins WHERE username != LOWER(username);
-- 预期0 行
-- 3. 确认大小写不敏感登录可用
SELECT id FROM auth.tenant_admins WHERE LOWER(username) = LOWER('Admin') AND deleted_at IS NULL;
-- 预期:与 SELECT ... WHERE LOWER(username) = 'admin' 结果一致
```
---
## NS4.3 变更补充2026-03-23
### 角色体系隔离 + 店铺管理员支持
#### 变更说明
| 对象 | 变更类型 | 说明 |
|------|---------|------|
| `auth.tenant_admins.admin_type` | 新增字段 | `VARCHAR(20) NOT NULL DEFAULT 'tenant_admin'`,区分租户管理员(`tenant_admin`)和店铺管理员(`site_admin` |
| `chk_admin_type` | 新增约束 | `CHECK (admin_type IN ('tenant_admin', 'site_admin'))` |
| `auth.roles``site_admin` / `tenant_admin` | 删除 | 小程序 RBAC 体系不需要这两个角色,租户/店铺管理员的区分通过 `admin_type` 列实现 |
| `auth.role_permissions` | 删除关联 | 删除 `site_admin` / `tenant_admin` 对应的 10 条权限映射 |
| `auth.roles``head_coach` / `manager` | 新增 | 小程序端新增教练和管理员角色 |
#### 业务规则
- 租户管理员(`admin_type='tenant_admin'`):可管理所有管辖门店,可创建/编辑/删除店铺管理员
- 店铺管理员(`admin_type='site_admin'`):仅可管理分配的门店,不可创建其他管理员
- 登录后界面一致,仅数据范围不同(由 `managed_site_ids` 控制)
- 用户名格式:`{第一个管辖店铺的 site_code} + 最长 50 字符`
- 只有 `admin_type='tenant_admin'` 的管理员可以访问 `/api/tenant/site-admins/*` 端点
#### 新增后端端点(店铺管理员 CRUD
| 方法 | 路径 | 说明 | 权限 |
|------|------|------|------|
| GET | `/api/tenant/site-admins` | 列出店铺管理员 | 仅 tenant_admin |
| POST | `/api/tenant/site-admins` | 创建店铺管理员 | 仅 tenant_admin |
| PATCH | `/api/tenant/site-admins/{id}` | 编辑店铺管理员 | 仅 tenant_admin |
| DELETE | `/api/tenant/site-admins/{id}` | 删除店铺管理员(软删除) | 仅 tenant_admin |
| POST | `/api/tenant/site-admins/{id}/reset-password` | 重置密码 | 仅 tenant_admin |
#### 代码变更
| 文件 | 变更 |
|------|------|
| `tenant_auth.py` | 登录查询加入 `admin_type`JWT 签发包含 `admin_type` |
| `tenant_admins.py` | `CurrentTenantAdmin` dataclass 加入 `admin_type` 字段 |
| `tenant_site_admins.py` | 新增路由模块5 个 CRUD 端点 |
| `main.py` | 注册 `tenant_site_admins` 路由 |
| `admin_tenant_admins.py` | SQL 查询加入 `ta.admin_type` 列 |
| `xcx_auth.py` | `dev-switch-role` 硬编码更新(删除 site_admin/tenant_admin新增 head_coach/manager |
#### 兼容性
| 组件 | 影响 |
|------|------|
| 后端 API | 直接依赖。JWT 新增 `admin_type` 字段;新增 5 个店铺管理员端点admin-web 列表接口返回 `admin_type` |
| 租户管理后台tenant-admin | 直接依赖。菜单根据 `adminType` 动态显示;新增店铺管理员管理页面 |
| 管理后台admin-web | 间接依赖。租户管理员列表新增"类型"列显示 |
| 小程序 | 间接依赖。`auth.roles` 删除 site_admin/tenant_admin新增 head_coach/manager |
| ETL | 无影响 |
#### 回滚策略
```sql
BEGIN;
-- 1. 回滚 admin_type 列
ALTER TABLE auth.tenant_admins DROP CONSTRAINT IF EXISTS chk_admin_type;
ALTER TABLE auth.tenant_admins DROP COLUMN IF EXISTS admin_type;
-- 2. 回滚角色变更(恢复 site_admin/tenant_admin删除 head_coach/manager
DELETE FROM auth.role_permissions
WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('head_coach', 'manager'));
DELETE FROM auth.roles WHERE code IN ('head_coach', 'manager');
INSERT INTO auth.roles (code, name, description)
VALUES ('site_admin', '店铺管理员', '单店管理员,可查看所有看板和审核用户'),
('tenant_admin', '租户管理员', '连锁管理员,可管理多店铺和所有功能');
-- 恢复 site_admin/tenant_admin 的权限映射(各 5 条)
INSERT INTO auth.role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM auth.roles r, auth.permissions p
WHERE r.code IN ('site_admin', 'tenant_admin');
-- 3. 代码回滚:移除 tenant_site_admins 路由JWT 移除 admin_type恢复 dev-switch-role 硬编码
COMMIT;
```
#### 验证 SQL
```sql
-- 1. 确认 admin_type 列存在且默认值正确
SELECT column_name, data_type, column_default
FROM information_schema.columns
WHERE table_schema = 'auth' AND table_name = 'tenant_admins' AND column_name = 'admin_type';
-- 预期admin_type, character varying, 'tenant_admin'::character varying
-- 2. 确认 CHECK 约束
SELECT conname, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid = 'auth.tenant_admins'::regclass AND conname = 'chk_admin_type';
-- 预期chk_admin_type, CHECK ((admin_type)::text = ANY (...))
-- 3. 确认角色体系4 条,无 site_admin/tenant_admin
SELECT code, name FROM auth.roles ORDER BY id;
-- 预期coach, staff, head_coach, manager
-- 4. 确认角色-权限映射11 条)
SELECT r.code, COUNT(rp.permission_id) AS perm_count
FROM auth.roles r
JOIN auth.role_permissions rp ON r.id = rp.role_id
GROUP BY r.code ORDER BY r.code;
-- 预期coach=2, head_coach=2, manager=5, staff=2共 11 条head_coach 仅 view_tasks+view_board
```