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 路由
回滚策略
完整回滚(按依赖顺序)
验证步骤
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。
tenant_id 来源变更
创建管理员时,tenant_id 不再是自由输入,而是从 biz.tenants 表中选择。后端校验 tenant_id 在 biz.tenants 中存在且 is_active=true。
username 可编辑
PATCH /api/admin/tenant-admins/{id} 端点支持修改 username。修改时校验全局唯一性(大小写不敏感),冲突返回 409 Conflict。
列表过滤
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
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