Files
Neo-ZQYY/docs/database/BD_manual_tenant_admin_tables.md
Neo 70324d8542 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>
2026-04-06 00:02:37 +08:00

545 lines
23 KiB
Markdown
Raw Permalink 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.
# 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
```