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

23 KiB
Raw Blame History

BD 手册租户管理后台表NS4 tenant-admin-web

概述

NS4 租户管理后台新增 6 张表,分布在 authbiz 两个 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) 关联上传批次

兼容性

  • 后端 API5 个新路由模块tenant_auth / tenant_users / tenant_excel / tenant_clues / admin_tenant_admins全部依赖上述表
  • ETL3 张 staging 表stg_finance_expense / stg_platform_income / stg_recharge_commissionsynced_at 字段供 ETL 同步标记ETL 读取 synced_at IS NULL 的行进行同步
  • 小程序:无直接影响(租户管理后台独立认证体系)
  • 管理后台admin-web:新增租户管理员 CRUD 页面,调用 admin_tenant_admins 路由

回滚策略

完整回滚(按依赖顺序)

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;

验证步骤

-- 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。

-- 软删除
UPDATE auth.tenant_admins SET is_active = false WHERE id = :id AND is_active = true;

tenant_id 来源变更

创建管理员时,tenant_id 不再是自由输入,而是从 biz.tenants 表中选择。后端校验 tenant_idbiz.tenants 中存在且 is_active=true

-- 创建时校验
SELECT id FROM biz.tenants WHERE id = :tenant_id AND is_active = true;

username 可编辑

PATCH /api/admin/tenant-admins/{id} 端点支持修改 username。修改时校验全局唯一性(大小写不敏感),冲突返回 409 Conflict。

-- 唯一性校验(大小写不敏感)
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 基线authdocs/database/ddl/zqyy_app__auth.sql
  • DDL 基线bizdocs/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.pytenant_users.pytenant_excel.pytenant_clues.pyadmin_tenant_admins.pytenant_site_admins.py
  • 后端 Schemaapps/backend/app/schemas/tenant_excel.pyadmin_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() 比较。避免 Adminadmin 被视为不同账号。

新增索引

名称 类型 说明
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无需改动后端统一处理

回滚策略

-- 1. 删除函数索引
DROP INDEX IF EXISTS auth.idx_tenant_admins_username_lower;
-- 2. 代码回滚:恢复 WHERE username = %s不带 LOWER
-- 注意:已小写化的用户名不可逆,但不影响功能

验证 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.rolessite_admin / tenant_admin 删除 小程序 RBAC 体系不需要这两个角色,租户/店铺管理员的区分通过 admin_type 列实现
auth.role_permissions 删除关联 删除 site_admin / tenant_admin 对应的 10 条权限映射
auth.roleshead_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_typeJWT 签发包含 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 无影响

回滚策略

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

-- 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