Files
Neo-ZQYY/docs/prd/Neo_Specs/NS4-tenant-admin-web.md

21 KiB
Raw Blame History

NS4租户管理后台 — tenant-admin-web

优先级:中(可与 NS1/NS2 并行,依赖 P1+P3 预估工作量:大 前置条件P1数据库基础、P3用户认证体系、admin-web-console 需求 11租户管理员账号管理 参考基准:docs/prd/specs/P10-tenant-admin-web.md


一、背景与目标

当前系统缺少面向租户管理员的独立管理界面。用户审核、Excel 数据上传、维客线索管理等运营操作无法自助完成,依赖开发人员手动操作数据库。

本 SPEC 目标:构建独立的租户管理 Web 应用,提供:

  1. 用户审核与管理(申请审核、身份编辑、店铺归属、助教/员工绑定)
  2. Excel 数据上传4 种模板:财务支出/团购收入/助教奖罚/充值业绩归属)
  3. 维客线索管理(查看、修改、删除、隐藏)

与现有系统的关系

系统 用途 用户
apps/admin-web/(系统管理后台) 平台级管理Operator 操作) 系统管理员
apps/tenant-admin/(本 SPEC 租户级管理 租户管理员
apps/miniprogram/(小程序) C 端业务 助教/管理者

租户管理员账号由系统管理后台(apps/admin-web/)的 Operator 创建,租户管理员不可自行注册。


二、技术架构

2.1 前端

  • 独立 Web 应用React + Vite + Ant Designapps/admin-web/ 同技术栈)
  • 部署路径:apps/tenant-admin/
  • 独立登录入口,与系统管理后台完全隔离
apps/tenant-admin/
├── src/
│   ├── pages/
│   │   ├── Login/                  # 登录页
│   │   ├── UserApproval/           # 用户审核
│   │   ├── UserManagement/         # 用户管理
│   │   ├── ExcelUpload/            # Excel 上传4 种模板)
│   │   └── RetentionClues/         # 维客线索管理
│   ├── components/
│   │   ├── DiffTable/              # 冲突 diff 交互组件
│   │   └── ClueEditor/             # 线索编辑组件
│   ├── services/
│   │   └── api.ts                  # API 调用封装
│   ├── hooks/
│   ├── utils/
│   └── App.tsx
├── package.json
├── vite.config.ts
└── tsconfig.json

2.2 后端

复用 apps/backend/ 的 FastAPI新增租户管理路由模块

apps/backend/app/routers/
├── tenant_auth.py          🆕 租户管理员登录/鉴权
├── tenant_users.py         🆕 用户审核 + 用户管理
├── tenant_excel.py         🆕 Excel 上传/校验/冲突处理
└── tenant_clues.py         🆕 维客线索管理

2.3 认证体系

  • 独立凭据:用户名 + 密码(非微信登录)
  • JWT 签发:与小程序 JWT 独立(不同 issuer 或 audience
  • 账号创建:由系统管理后台 Operator 创建,指定用户名、初始密码、所属租户、管辖 site_id 列表
  • 权限级别:
    • 租户级管理员:管辖该租户下所有店铺
    • 店铺级管理员:只能管理 Operator 分配的 site_id 列表内的店铺

2.4 数据隔离

  • 所有查询附加 site_id IN (管辖列表) 条件
  • FDW 查询需 SET LOCAL app.current_site_id(单店铺场景)
  • 多店铺场景下,逐 site_id 查询后合并结果

三、功能详细设计

3.1 用户审核页面

页面功能

  • 申请列表:展示所有待审核/已审核的用户申请
  • 状态筛选:全部 / 待审核(pending) / 已通过(approved) / 已拒绝(rejected)
  • 关联建议:根据申请中的球房 ID + 手机号,同时在助教表和员工信息表中匹配
  • 审核操作:通过(分配身份+关联助教/员工)/ 拒绝(填写原因)

关联匹配逻辑

用户申请球房ID + 手机号)
  → site_code_mapping 查 site_id
  → 并行匹配:
     ├── fdw_etl.v_dim_assistantphone 匹配scd2_is_current=1
     └── fdw_etl.v_dim_staff + v_dim_staff_exphone 匹配)
  → 返回匹配建议列表(可能多条)
  → 管理员选择关联目标

审核通过后操作

  1. 更新 auth.users.status = 'approved'
  2. 分配角色(助教/管理者)→ 写入 auth.user_roles
  3. 关联助教 → 写入 auth.user_assistant_binding(含 staff_id
  4. 分配 site_id → 更新 auth.users.site_id

接口设计

接口 方法 路径 说明
申请列表 GET /api/tenant/applications 支持 status 筛选、分页
关联建议 GET /api/tenant/applications/{id}/match-suggestions 返回助教+员工匹配结果
审核通过 POST /api/tenant/applications/{id}/approve body: role, assistant_id, staff_id
审核拒绝 POST /api/tenant/applications/{id}/reject body: reason

3.2 用户管理页面

页面功能

  • 用户列表:展示已通过审核的用户(姓名、角色、关联助教、店铺、状态)
  • 身份编辑:修改角色(助教↔管理者)
  • 店铺归属:修改用户的 site_id
  • 关联助教/员工:修改绑定关系
  • 禁用/启用:冻结用户账号

接口设计

接口 方法 路径 说明
用户列表 GET /api/tenant/users 支持角色筛选、搜索、分页
编辑用户 PATCH /api/tenant/users/{id} body: role, site_id, status
修改绑定 PUT /api/tenant/users/{id}/binding body: assistant_id, staff_id

3.3 Excel 上传

4 种模板

模板 1财务支出按月
列名 类型 必填 校验规则
月份 YYYY-MM 格式校验,不超过当前月
支出类别 文本 枚举:房租/水电/物业/食品饮料进货/耗材/报销/固定人员工资/其他费用
金额 数值(2) > 0精度 2 位小数
备注 文本 最长 500 字符

主键:月份 + 支出类别 写入目标:dws.dws_finance_expense_summary(通过后端 API 写入 ETL 库,或写入业务库 staging 表后由 ETL 同步)

模板 2团购平台收入按月
列名 类型 必填 校验规则
月份 YYYY-MM 格式校验
平台名称 文本 非空
收入金额 数值(2) > 0
备注 文本 最长 500 字符

主键:月份 + 平台名称 写入目标:dws.dws_platform_settlement(或业务库 staging 表)

模板 3助教奖罚按月
列名 类型 必填 校验规则
月份 YYYY-MM 格式校验
助教姓名 文本 非空
助教编号 文本 非空
类型 文本 枚举:扣款/奖金
金额 数值(2) > 0
原因 文本 非空,最长 200 字符

主键:月份 + 助教姓名 + 助教编号 + 类型 + 原因(同一助教同月可多笔) 写入目标:biz.salary_adjustments

模板 4充值业绩归属按月
列名 类型 必填 校验规则
充值日期 YYYY-MM-DD 格式校验
会员名称 文本 非空
充值金额 数值(2) > 0
归属助教 文本 非空
奖励金额 数值(2) ≥ 0

主键:充值日期 + 会员名称 + 归属助教 写入目标:dws.dws_assistant_recharge_commission(或业务库 staging 表)

人员匹配校验(模板 3/4

上传助教奖罚和充值业绩归属时,需校验助教姓名+编号是否存在:

助教姓名 + 助教编号
  → fdw_etl.v_dim_assistantnickname + assistant_number 匹配scd2_is_current=1
  → 如不匹配,尝试 fdw_etl.v_dim_staff + v_dim_staff_exname + staff_number 匹配)
  → 匹配失败:标记为校验警告(不阻断上传,但提示管理员确认)

冲突处理流程

上传 Excel
  → 后端解析 + 格式校验
  → 返回校验结果:
     ├── 格式错误行 → 前端标红,要求修正后重新上传
     ├── 无冲突行 → 标记为"待写入"
     └── 冲突行(主键已存在)→ 返回 diff 数据(旧值 vs 新值)
  → 前端展示 diff 交互表格:
     - 每行显示:字段名 | 旧值 | 新值 | 操作(替换/保留)
     - 支持"全部替换"/"全部保留"快捷操作
  → 用户确认后提交
  → 后端按选择写入

接口设计

接口 方法 路径 说明
上传解析 POST /api/tenant/excel/upload multipart/form-data返回校验结果+冲突列表
确认写入 POST /api/tenant/excel/confirm body: upload_id, resolutions[]
上传记录 GET /api/tenant/excel/logs 历史上传记录列表
模板下载 GET /api/tenant/excel/template/{type} 下载空白模板

3.4 维客线索管理

页面功能

  • 客户搜索:按客户姓名/手机号搜索(姓名从 dim_member.nickname手机从 dim_member.mobile
  • 门店筛选:按管辖 site_id 筛选
  • 线索列表:展示该客户的全部维客线索
    • 标签(大类枚举:客户基础/消费习惯/玩法偏好/促销偏好/社交关系/重要反馈)
    • 摘要(含 Emoji 前缀)
    • 详情
    • 提供人recorded_by_name
    • 来源manual / ai_consumption / ai_note
    • 记录时间
    • 隐藏状态
  • 操作:
    • 修改:编辑标签、摘要、详情
    • 删除:二次确认后物理删除
    • 隐藏/显示:切换 is_hidden 状态

数据源

  • zqyy_app.public.member_retention_clue(线索数据)
  • fdw_etl.v_dim_member客户信息nickname、mobile通过 member_id 关联)

⚠️ 会员字段断档DQ-6客户姓名/手机必须从 dim_member 获取,不可使用结算单上的冗余字段

接口设计

接口 方法 路径 说明
客户搜索 GET /api/tenant/customers/search query: keyword, site_id
线索列表 GET /api/tenant/customers/{member_id}/clues 该客户全部线索
修改线索 PATCH /api/tenant/clues/{id} body: category, summary, detail
删除线索 DELETE /api/tenant/clues/{id} 二次确认后物理删除
隐藏/显示 PATCH /api/tenant/clues/{id}/visibility body: is_hidden

四、数据库审查与新增表

4.1 现有表满足度

功能 现有表 是否满足 缺口
用户审核 auth.users, auth.user_applications 满足
用户管理 auth.users, auth.user_roles, auth.user_assistant_binding 满足
维客线索 public.member_retention_clue ⚠️ 部分 缺 is_hidden 字段
助教奖罚 不满足 需新建 biz.salary_adjustments
上传记录 不满足 需新建 biz.excel_upload_log
财务支出/团购收入/充值归属 DWS 表 ⚠️ 待定 可能需要 staging 表

4.2 需新建的表

表 1biz.salary_adjustments(助教奖罚明细)

CREATE TABLE biz.salary_adjustments (
    id BIGSERIAL PRIMARY KEY,
    site_id BIGINT NOT NULL,
    assistant_id BIGINT,              -- 匹配到的助教 ID可空匹配失败时为 NULL
    assistant_name VARCHAR(100) NOT NULL,
    assistant_number VARCHAR(50) NOT NULL,
    salary_month VARCHAR(7) NOT NULL,  -- YYYY-MM
    adjustment_type VARCHAR(20) NOT NULL CHECK (adjustment_type IN ('deduction', 'bonus')),
    amount NUMERIC(12,2) NOT NULL CHECK (amount > 0),
    reason VARCHAR(200) NOT NULL,
    upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
    created_at TIMESTAMPTZ DEFAULT NOW(),
    created_by BIGINT                  -- 上传人(租户管理员 ID
);

CREATE INDEX idx_salary_adj_site_month ON biz.salary_adjustments(site_id, salary_month);
CREATE INDEX idx_salary_adj_assistant ON biz.salary_adjustments(assistant_id, salary_month);

表 2biz.excel_upload_logExcel 上传记录)

CREATE TABLE biz.excel_upload_log (
    id BIGSERIAL PRIMARY KEY,
    site_id BIGINT NOT NULL,
    upload_type VARCHAR(30) NOT NULL CHECK (upload_type IN (
        'expense', 'platform_income', 'salary_adj', 'recharge_commission'
    )),
    file_name VARCHAR(255) NOT NULL,
    uploaded_by BIGINT NOT NULL,       -- 租户管理员 ID
    row_count INTEGER NOT NULL DEFAULT 0,
    conflict_count INTEGER NOT NULL DEFAULT 0,
    resolved_count INTEGER NOT NULL DEFAULT 0,
    status VARCHAR(20) NOT NULL DEFAULT 'pending' CHECK (status IN (
        'pending', 'confirmed', 'failed'
    )),
    error_detail JSONB,                -- 校验错误详情
    created_at TIMESTAMPTZ DEFAULT NOW(),
    confirmed_at TIMESTAMPTZ
);

CREATE INDEX idx_excel_log_site ON biz.excel_upload_log(site_id, created_at DESC);

4.3 需变更的表

public.member_retention_clue — 新增字段

ALTER TABLE public.member_retention_clue
    ADD COLUMN is_hidden BOOLEAN NOT NULL DEFAULT false;

COMMENT ON COLUMN public.member_retention_clue.is_hidden
    IS '是否隐藏true=管理后台保留但小程序不展示)';

小程序端查询线索时需增加 WHERE is_hidden = false 条件

public.member_retention_clue — 确认 source 字段

P10 spec 中提到需新增 source 字段,需确认该字段是否已在 P4/P5 阶段建立。如未建立:

ALTER TABLE public.member_retention_clue
    ADD COLUMN source VARCHAR(20) NOT NULL DEFAULT 'manual'
    CHECK (source IN ('manual', 'ai_consumption', 'ai_note'));

COMMENT ON COLUMN public.member_retention_clue.source
    IS '线索来源manual=人工录入, ai_consumption=应用3消费分析, ai_note=应用6备注分析';

4.4 Excel 写入目标表的 staging 策略

财务支出、团购收入、充值业绩归属三种模板的数据最终需要进入 DWS 层。有两种策略:

方案 A直接写入 DWS 表(通过后端 API 直连 ETL 库写入)

  • 优点:数据即时可用
  • 缺点:绕过 ETL 流程,数据一致性风险

方案 B写入业务库 staging 表ETL 定时同步

  • 优点:数据经过 ETL 标准流程,一致性有保障
  • 缺点:数据有延迟(取决于 ETL 调度频率)

建议采用方案 B需新建 3 张 staging 表:

-- biz.stg_finance_expense财务支出 staging
CREATE TABLE biz.stg_finance_expense (
    id BIGSERIAL PRIMARY KEY,
    site_id BIGINT NOT NULL,
    expense_month VARCHAR(7) NOT NULL,
    category VARCHAR(50) NOT NULL,
    amount NUMERIC(12,2) NOT NULL,
    remark TEXT,
    upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
    synced_at TIMESTAMPTZ,             -- ETL 同步时间NULL=未同步)
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- biz.stg_platform_income团购收入 staging
CREATE TABLE biz.stg_platform_income (
    id BIGSERIAL PRIMARY KEY,
    site_id BIGINT NOT NULL,
    income_month VARCHAR(7) NOT NULL,
    platform_name VARCHAR(100) NOT NULL,
    amount NUMERIC(12,2) NOT NULL,
    remark TEXT,
    upload_batch_id BIGINT REFERENCES biz.excel_upload_log(id),
    synced_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- biz.stg_recharge_commission充值业绩归属 staging
CREATE TABLE biz.stg_recharge_commission (
    id BIGSERIAL PRIMARY KEY,
    site_id BIGINT NOT NULL,
    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 REFERENCES biz.excel_upload_log(id),
    synced_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

五、租户管理员账号体系

5.1 账号存储

租户管理员账号存储在 auth.tenant_admins 表(需新建):

CREATE TABLE auth.tenant_admins (
    id BIGSERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    password_hash VARCHAR(255) NOT NULL,
    display_name VARCHAR(100),
    tenant_id BIGINT NOT NULL,         -- 所属租户
    managed_site_ids BIGINT[] NOT NULL, -- 管辖的 site_id 列表
    is_active BOOLEAN NOT NULL DEFAULT true,
    created_by BIGINT,                 -- 创建该账号的 Operator ID
    created_at TIMESTAMPTZ DEFAULT NOW(),
    last_login_at TIMESTAMPTZ
);

CREATE INDEX idx_tenant_admin_tenant ON auth.tenant_admins(tenant_id);

5.2 与小程序用户体系的隔离

  • 租户管理员使用 auth.tenant_admins 表,小程序用户使用 auth.users
  • JWT 签发时使用不同的 audaudience字段区分
  • 后端路由通过不同的认证依赖注入区分(require_tenant_admin() vs require_approved()

六、参考文档

文档 路径 用途
P10 原始 spec docs/prd/specs/P10-tenant-admin-web.md 需求定义基准
admin-web 现有代码 apps/admin-web/ 技术栈参考React + Vite + Ant Design
BD 手册-认证表 docs/database/BD_Manual_auth_tables.md auth schema 表结构
BD 手册-业务表 docs/database/BD_Manual_biz_tables.md biz schema 表结构
权限矩阵 docs/permission_matrix/ 角色-权限映射参考
DWD-DOC 标杆 docs/reports/DWD-DOC/ 金额口径权威参考
数据依赖矩阵 docs/prd/specs/00-数据依赖矩阵.md 租户管理后台数据源映射
member_retention_clue DDL db/zqyy_app/ 维客线索表结构

七、预审查清单SPEC 启动前确认)

7.1 账号与认证

  1. 租户管理员账号模型:是否需要独立的 auth.tenant_admins 表?还是复用 auth.users 表增加 user_type 字段区分?
  2. 密码策略:初始密码是否需要强制修改?密码复杂度要求?是否需要密码过期机制?
  3. 多租户隔离:一个管理员是否可以管辖多个租户?还是严格一对一?
  4. 会话管理JWT 过期时间?是否需要 refresh token是否支持多设备同时登录

7.2 用户审核

  1. 关联匹配优先级:助教表和员工信息表同时匹配到时,优先展示哪个?是否需要合并展示?
  2. 审核拒绝后:用户是否可以重新申请?重新申请是新建记录还是更新原记录?
  3. 批量审核:是否需要支持批量通过/拒绝?
  4. 审核通知:审核结果是否需要通知用户(小程序消息/微信模板消息)?

7.3 Excel 上传

  1. 写入策略:财务支出/团购收入/充值归属是直接写入 DWS 表(方案 A还是写入 staging 表由 ETL 同步(方案 B
  2. 文件大小限制:单次上传的 Excel 文件大小上限?行数上限?
  3. 历史数据:是否允许上传历史月份的数据?是否有时间范围限制?
  4. 模板版本Excel 模板是否需要版本管理?表头变更时如何兼容旧模板?
  5. 助教匹配失败处理:模板 3/4 中助教姓名+编号匹配失败时,是阻断上传还是允许上传但标记警告?

7.4 维客线索

  1. 隐藏 vs 删除:隐藏的线索是否可以恢复显示?删除是否需要软删除(保留记录但标记删除)?
  2. AI 线索保护AI 生成的线索source=ai_consumption/ai_note是否允许管理员修改/删除?修改后 source 是否变更为 manual
  3. 线索审计:线索的修改/删除操作是否需要记录操作日志(谁在什么时间做了什么操作)?
  4. 批量操作:是否需要支持批量隐藏/删除线索?

7.5 部署与运维

  1. 部署方式:租户管理后台是独立部署还是与系统管理后台共享服务器?域名/路径如何规划?
  2. 前端构建:是否与 admin-web 共享 pnpm workspace还是完全独立的 package.json
  3. 监控:是否需要独立的访问日志和错误监控?

八、任务清单草案SPEC 细化后调整)

Batch A基础设施

  • T1创建 apps/tenant-admin/ 项目骨架React + Vite + Ant Design
  • T2创建 auth.tenant_admins 表 + DDL 迁移脚本
  • T3实现租户管理员登录 APItenant_auth.py:登录/JWT 签发/鉴权中间件)
  • T4创建 biz.salary_adjustments + biz.excel_upload_log 表 + DDL 迁移脚本

Batch B用户审核与管理

  • T5实现用户审核后端 API申请列表/关联建议/审核通过/审核拒绝)
  • T6实现用户审核前端页面申请列表 + 状态筛选 + 关联建议展示 + 审核操作)
  • T7实现用户管理后端 API用户列表/编辑/绑定修改)
  • T8实现用户管理前端页面用户列表 + 身份编辑 + 店铺归属)

Batch CExcel 上传

  • T9实现 Excel 解析+校验后端4 种模板的格式校验 + 人员匹配校验)
  • T10实现冲突检测后端主键匹配 + diff 数据生成)
  • T11实现 Excel 上传前端(模板下载 + 上传 + 校验结果展示 + diff 交互 + 确认)
  • T12创建 staging 表(如采用方案 B+ 写入逻辑

Batch D维客线索管理

  • T13member_retention_clue 新增 is_hidden 字段 + DDL 迁移
  • T14实现维客线索后端 API客户搜索/线索列表/修改/删除/隐藏)
  • T15实现维客线索前端页面客户搜索 + 线索列表 + 编辑/删除/隐藏操作)
  • T16小程序端线索查询增加 WHERE is_hidden = false 条件