QC System Deep Fix: - HardRuleEngine: add null tolerance + field availability pre-check (skipped status) - SkillRunner: baseline data merge for follow-up events + field availability check - QcReportService: record-level pass rate calculation + accurate LLM XML report - iitBatchController: legacy log cleanup (eventId=null) + upsert RecordSummary - seed-iit-qc-rules: null/empty string tolerance + applicableEvents config V3.1 Architecture Design (docs only, no code changes): - QC engine V3.1 plan: 5-level data structure (CDISC ODM) + D1-D7 dimensions - Three-batch implementation strategy (A: foundation, B: bubbling, C: new engines) - Architecture team review: 4 whitepapers reviewed + feedback doc + 4 critical suggestions - CRA Agent strategy roadmap + CRA 4-tool explanation doc for clinical experts Project Member Management: - Cross-tenant member search and assignment (remove tenant restriction) - IIT project detail page enhancement with tabbed layout (KB + members) - IitProjectContext for business-side project selection - System-KB route access control adjustment for project operators Frontend: - AdminLayout sidebar menu restructure - IitLayout with project context provider - IitMemberManagePage new component - Business-side pages adapt to project context Prisma: - 2 new migrations (user-project RBAC + is_demo flag) - Schema updates for project member management Made-with: Cursor
443 lines
16 KiB
Markdown
443 lines
16 KiB
Markdown
# CRA Agent V3.0 — 用户权限与多租户三阶段实施计划
|
||
|
||
> **版本:** v1.0
|
||
> **日期:** 2026-02-28
|
||
> **状态:** 待实施
|
||
> **前置依赖:** P0 + P1 已完成(质控流水线 + ChatOrchestrator)
|
||
> **关联文档:**
|
||
> - [V3.0 全新开发计划](./V3.0全新开发计划.md)
|
||
> - [统一数字 CRA 质控平台 PRD](./统一数字%20CRA%20质控平台产品需求文档(PRD).md)
|
||
|
||
---
|
||
|
||
## 1. 问题背景
|
||
|
||
### 1.1 当前系统存在的三个核心问题
|
||
|
||
**问题 A:业务端 CRA 质控平台无法运行**
|
||
|
||
- 所有业务端页面(驾驶舱、eQuery、报告、AI 对话)从 URL 参数 `?projectId=xxx` 获取项目 ID
|
||
- 没有任何机制把登录用户与 IIT 项目关联,导致页面显示"请先选择一个项目"
|
||
- 侧边栏项目信息为硬编码假数据("IIT-2026-001")
|
||
|
||
**问题 B:运营管理端 IIT 项目管理定位不当**
|
||
|
||
- "IIT 项目管理"与"Prompt 管理""租户管理""用户管理"混在同一菜单中
|
||
- 普通项目运营人员不应看到平台级管理功能
|
||
- 药企/医院客户的项目管理人员不应看到其他企业的项目
|
||
|
||
**问题 C:用户-项目关联机制缺失**
|
||
|
||
- `IitUserMapping.systemUserId` 存储的是随意字符串(当前值为 `"FengZhiBo"`),未关联平台 `User` 表
|
||
- `IitProject` 没有 `tenantId` 字段,无法实现租户隔离
|
||
- IIT API 路由没有认证中间件,无访问控制
|
||
|
||
### 1.2 当前数据库实际状态
|
||
|
||
```
|
||
-- iit_schema.projects(1 条记录)
|
||
id: test0102-pd-study | name: test0207 | status: active
|
||
|
||
-- iit_schema.user_mappings(1 条记录)
|
||
system_user_id: FengZhiBo | wecom_user_id: FengZhiBo | role: PI
|
||
|
||
-- public.users(1 条记录)
|
||
id: user-mock-001 | name: 测试用户 | role: user
|
||
(注:此用户为占位假数据,非真实用户)
|
||
```
|
||
|
||
两张表之间没有任何外键关联。
|
||
|
||
### 1.3 当前角色体系
|
||
|
||
**平台级角色(public.users.role):**
|
||
|
||
| 角色 | 说明 | 当前使用 |
|
||
|------|------|---------|
|
||
| SUPER_ADMIN | 平台超级管理员 | 可访问运营管理端全部功能 |
|
||
| PROMPT_ENGINEER | Prompt 工程师 | 可访问运营管理端 |
|
||
| HOSPITAL_ADMIN | 医院管理员 | 已定义,未使用 |
|
||
| PHARMA_ADMIN | 药企管理员 | 已定义,未使用 |
|
||
| DEPARTMENT_ADMIN | 科室管理员 | 已定义,未使用 |
|
||
| USER | 普通用户 | 默认角色 |
|
||
|
||
**IIT 项目级角色(iit_schema.user_mappings.role):**
|
||
|
||
| 角色 | 说明 | 当前使用 |
|
||
|------|------|---------|
|
||
| PI | 主要研究者 | 已使用 |
|
||
| Sub-I | 次要研究者 | 已定义 |
|
||
| CRC | 临床研究协调员 | 已定义 |
|
||
| CRA | 临床监查员 | 已定义 |
|
||
| DM | 数据管理员 | 已定义 |
|
||
| Statistician | 统计师 | 已定义 |
|
||
| Other | 其他 | 已定义 |
|
||
|
||
**缺失的角色:**
|
||
|
||
| 角色 | 层级 | 说明 |
|
||
|------|------|------|
|
||
| **PM** | IIT 项目级 | 项目管理员,负责项目配置和管理 |
|
||
| **IIT_OPERATOR** | 平台级 | IIT 项目运营,负责为客户创建和配置项目(Phase 2 实现) |
|
||
|
||
---
|
||
|
||
## 2. 三阶段实施计划
|
||
|
||
```
|
||
Phase 1(立即) Phase 2(近期) Phase 3(中期)
|
||
让业务端能跑 用户-项目关联 多租户隔离
|
||
───────────────── → ──────────────────── → ─────────────────
|
||
自动选中活跃项目 systemUserId 关联 User IitProject 加 tenantId
|
||
Provider 注入上下文 /my-projects API 项目按租户过滤
|
||
管理端侧边栏分组 项目角色权限矩阵 PHARMA_ADMIN 自助管理
|
||
配置功能补全 IIT 路由加认证
|
||
IIT_OPERATOR 平台角色
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Phase 1:让业务端能跑(预估 0.5 天)
|
||
|
||
### 3.1 目标
|
||
|
||
- CRA 质控平台业务端页面能正常加载和显示数据
|
||
- 运营管理端 IIT 项目管理菜单位置合理
|
||
- 项目配置功能补全(定时质控、变量清单)
|
||
|
||
### 3.2 方案
|
||
|
||
#### 3.2.1 创建 IitProjectContext Provider
|
||
|
||
**设计原则:** 通用方案,适用于用户关联 0 / 1 / N 个项目的所有场景。
|
||
|
||
**核心逻辑:**
|
||
|
||
```
|
||
用户进入 /iit → IitProjectProvider 初始化
|
||
→ 调用 GET /api/v1/admin/iit-projects 获取项目列表
|
||
→ 过滤 status = 'active'
|
||
→ 0 个 → 显示空状态页:"暂无关联的 IIT 项目"
|
||
→ 1 个 → 自动选中,不弹选择器
|
||
→ N 个 → 恢复 localStorage 上次选择;若无记录,默认选第一个
|
||
→ 用户可随时通过侧边栏顶部下拉选择器切换项目
|
||
→ 切换后写入 localStorage,子页面自动刷新
|
||
→ 通过 useIitProject() hook 暴露:
|
||
{ projectId, project, projects, loading, switchProject(id) }
|
||
```
|
||
|
||
**项目选择器 UI:**
|
||
|
||
放在 IitLayout 侧边栏顶部(替换当前硬编码的项目名称),具体表现:
|
||
|
||
- 1 个项目:显示项目名称(纯文本,无下拉箭头)
|
||
- N 个项目:显示当前项目名 + 下拉箭头,点击弹出 Select 选择器
|
||
- 选择器选项:项目名称 + 项目编号(如 "原发性痛经队列研究 / IIT-2026-001")
|
||
- 切换项目后,所有子页面通过 Context 自动获取新 projectId 并刷新数据
|
||
|
||
**改动文件:**
|
||
|
||
| 文件 | 改动 |
|
||
|------|------|
|
||
| `frontend-v2/src/modules/iit/context/IitProjectContext.tsx` | 新建 Context + Provider + useIitProject hook |
|
||
| `frontend-v2/src/modules/iit/IitLayout.tsx` | 集成 Provider,侧边栏顶部改为项目选择器 |
|
||
| `frontend-v2/src/modules/iit/pages/DashboardPage.tsx` | 删除 URL 参数读取,改用 `useIitProject()` |
|
||
| `frontend-v2/src/modules/iit/pages/EQueryPage.tsx` | 同上 |
|
||
| `frontend-v2/src/modules/iit/pages/ReportsPage.tsx` | 同上 |
|
||
| `frontend-v2/src/modules/iit/pages/AiStreamPage.tsx` | 同上 |
|
||
| `frontend-v2/src/modules/iit/pages/AiChatPage.tsx` | 同上 |
|
||
|
||
#### 3.2.2 管理端侧边栏三区重组(方案 B:逻辑拆分)
|
||
|
||
**决策背景:** 概念上存在三种管理职能(平台管理、项目运营、商务运营),但当前阶段团队规模小,同一人可能跨职能操作。采用**方案 B(逻辑拆分)**:保留单一 `/admin` 入口,侧边栏按职能分为三个带标题的菜单组,通过 RBAC 控制每个角色看到哪些组。未来用户群体分化后可平滑升级为物理拆分(独立路由 + 独立 Layout)。
|
||
|
||
**侧边栏结构(SUPER_ADMIN 视角,看到全部):**
|
||
|
||
```
|
||
┌─ 平台管理 ─────────────┐
|
||
│ 运营概览 │
|
||
│ Prompt 管理 │
|
||
│ 系统知识库 │
|
||
│ LLM 配置 │
|
||
│ 系统设置 │
|
||
├─ 项目运营 ─────────────┤
|
||
│ IIT 项目管理 │
|
||
├─ 商务运营 ─────────────┤
|
||
│ 租户管理 │
|
||
│ 用户管理 │
|
||
└────────────────────────┘
|
||
```
|
||
|
||
**各角色可见性:**
|
||
|
||
| 菜单组 | SUPER_ADMIN | PROMPT_ENGINEER | IIT_OPERATOR | PHARMA_ADMIN |
|
||
|--------|:-----------:|:---------------:|:------------:|:------------:|
|
||
| 平台管理 | 全部 | Prompt 管理 + 系统知识库 | - | - |
|
||
| 项目运营 | 全部项目 | - | 全部项目 | 本租户项目 |
|
||
| 商务运营 | 全部 | - | - | - |
|
||
|
||
**改动文件:** `frontend-v2/src/framework/layout/AdminLayout.tsx`
|
||
|
||
#### 3.2.3 项目配置功能补全
|
||
|
||
**A. 定时质控配置 UI**
|
||
|
||
数据库 `iit_schema.projects` 已有 `cron_enabled` 和 `cron_expression` 字段,前端未暴露。
|
||
|
||
在 `IitProjectDetailPage.tsx` 的"REDCap 配置"Tab 中增加:
|
||
- 定时质控开关(Switch,绑定 `cronEnabled`)
|
||
- Cron 表达式输入(Input + 预设选项:"每天 8:00" / "每周一 9:00" / 自定义)
|
||
|
||
**B. 变量清单 Tab**
|
||
|
||
后端 `GET /:id/field-metadata` API 已存在。在 `IitProjectDetailPage.tsx` 新增第 5 个 Tab"变量清单",展示 REDCap 变量表格。
|
||
|
||
**C. PM 角色新增**
|
||
|
||
在 `iitUserMappingService.ts` 的 `getRoleOptions()` 中添加:
|
||
```
|
||
{ value: 'PM', label: '项目管理员 (PM)' }
|
||
```
|
||
|
||
### 3.3 Phase 1 不做的事
|
||
|
||
- 不改数据库 Schema
|
||
- 不做用户-项目关联(systemUserId 改造)
|
||
- 不加认证中间件
|
||
- 不做租户隔离
|
||
|
||
---
|
||
|
||
## 4. Phase 2:用户-项目关联 + 角色权限(预估 2 天)
|
||
|
||
### 4.1 目标
|
||
|
||
- 登录用户自动看到自己关联的 IIT 项目
|
||
- IIT 项目角色对应明确的权限
|
||
- IIT API 路由有认证保护
|
||
- 运营管理端按角色控制菜单可见性
|
||
- 新增 IIT_OPERATOR 平台角色,使项目运营人员无需 SUPER_ADMIN 权限即可创建和配置 IIT 项目
|
||
|
||
### 4.2 数据模型改动
|
||
|
||
#### 4.2.1 IitUserMapping 增强
|
||
|
||
```prisma
|
||
model IitUserMapping {
|
||
// ... 现有字段 ...
|
||
systemUserId String @map("system_user_id") // 改为必须是 User.id
|
||
// 新增:
|
||
userId String? @map("user_id") // 平台 User 表外键(可选,渐进式关联)
|
||
|
||
// 新增关联
|
||
user User? @relation(fields: [userId], references: [id])
|
||
}
|
||
```
|
||
|
||
**迁移策略:**
|
||
- 新增 `userId` 字段(nullable),不破坏现有数据
|
||
- 新建用户映射时要求填写真实 `userId`
|
||
- 旧数据逐步补齐
|
||
|
||
#### 4.2.2 IitProject 增强(为 Phase 3 预留)
|
||
|
||
```prisma
|
||
model IitProject {
|
||
// ... 现有字段 ...
|
||
// 新增:
|
||
tenantId String? @map("tenant_id") // 租户归属(Phase 3 正式启用)
|
||
}
|
||
```
|
||
|
||
### 4.3 API 改动
|
||
|
||
#### 4.3.1 新增接口
|
||
|
||
```
|
||
GET /api/v1/iit/my-projects
|
||
```
|
||
|
||
逻辑:
|
||
1. 从 JWT Token 获取当前用户 `userId`
|
||
2. 查询 `IitUserMapping WHERE userId = :userId`
|
||
3. 返回关联的项目列表 + 用户在每个项目中的角色
|
||
|
||
响应示例:
|
||
```json
|
||
{
|
||
"projects": [
|
||
{
|
||
"id": "test0102-pd-study",
|
||
"name": "test0207",
|
||
"status": "active",
|
||
"myRole": "PI",
|
||
"description": "原发性痛经队列研究"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 4.3.2 IIT 路由加认证
|
||
|
||
所有 `/api/v1/admin/iit-projects` 路由添加 `authenticate` 中间件。
|
||
|
||
#### 4.3.3 项目级权限检查
|
||
|
||
```
|
||
中间件:requireProjectRole(projectId, allowedRoles)
|
||
→ 查 IitUserMapping WHERE projectId AND userId
|
||
→ 检查 role 是否在 allowedRoles 中
|
||
```
|
||
|
||
### 4.4 项目角色权限矩阵
|
||
|
||
| 功能 | PM | PI | CRA | CRC | DM | Sub-I | Statistician |
|
||
|------|----|----|-----|-----|----|-------|-------------|
|
||
| 查看驾驶舱 | 读 | 读 | 读 | 读 | 读 | 读 | 读 |
|
||
| 查看/处理 eQuery | 读写 | 读 | 读写 | 读写 | 读 | 读 | - |
|
||
| 查看报告 | 读 | 读 | 读 | 读 | 读 | 读 | 读 |
|
||
| AI 对话 | 是 | 是 | 是 | 是 | 是 | 是 | 是 |
|
||
| 项目配置(REDCap/规则/通知/知识库) | 读写 | - | - | - | - | - | - |
|
||
| 查看变量清单 | 读 | 读 | 读 | 读 | 读 | 读 | 读 |
|
||
| 管理质控规则 | 读写 | - | 读 | - | 读 | - | - |
|
||
|
||
### 4.5 管理端菜单权限(承接 Phase 1 三区分组)
|
||
|
||
Phase 1 已完成侧边栏三区分组的 UI 结构,Phase 2 加入真正的 RBAC 控制:
|
||
|
||
| 角色 | 可进入 /admin | 平台管理 | 项目运营 | 商务运营 |
|
||
|------|:------------:|:-------:|:-------:|:-------:|
|
||
| SUPER_ADMIN | 是 | 全部 | 全部项目 | 全部 |
|
||
| PROMPT_ENGINEER | 是 | Prompt + 知识库 | - | - |
|
||
| IIT_OPERATOR(本阶段新增) | 是 | - | 全部项目 | - |
|
||
| PHARMA_ADMIN | 是 | - | 本租户项目 | - |
|
||
| HOSPITAL_ADMIN | 是 | - | 本租户项目 | - |
|
||
| USER | 否 | - | - | - |
|
||
|
||
**实现方式:**
|
||
- 路由守卫:`/admin` 入口检查 `user.role` 是否在允许列表中
|
||
- 侧边栏渲染:根据 `user.role` 过滤 `menuItems`,角色看不到的组直接不渲染
|
||
- API 层:每个管理端 API 添加 `authenticate` + `requireRole(allowedRoles)` 中间件
|
||
|
||
### 4.6 前端改动
|
||
|
||
- `IitProjectContext` 改为调用 `/api/v1/iit/my-projects`
|
||
- 根据 `myRole` 控制业务端 Tab 可见性(PM 看到更多设置入口)
|
||
- `AdminLayout.tsx` 按 `user.role` 过滤侧边栏菜单组可见性(Phase 1 已分组,此处加权限判断)
|
||
- `/admin` 路由入口添加角色守卫
|
||
|
||
---
|
||
|
||
## 5. Phase 3:多租户隔离(预估 1.5 天)
|
||
|
||
### 5.1 目标
|
||
|
||
- IIT 项目按租户隔离,药企只能看到自己的项目
|
||
- PHARMA_ADMIN 可在运营管理端管理自己租户的 IIT 项目
|
||
- SUPER_ADMIN 可跨租户查看所有项目
|
||
|
||
### 5.2 数据模型
|
||
|
||
启用 Phase 2 预留的 `tenantId` 字段,设为必填:
|
||
|
||
```prisma
|
||
model IitProject {
|
||
tenantId String @map("tenant_id")
|
||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||
}
|
||
```
|
||
|
||
### 5.3 API 改动
|
||
|
||
所有项目查询接口添加租户过滤:
|
||
|
||
```sql
|
||
-- 非 SUPER_ADMIN:
|
||
SELECT * FROM iit_schema.projects WHERE tenant_id = :currentUserTenantId
|
||
|
||
-- SUPER_ADMIN:
|
||
SELECT * FROM iit_schema.projects -- 无限制
|
||
```
|
||
|
||
### 5.4 项目创建流程
|
||
|
||
```
|
||
Phase 2 完成后:
|
||
SUPER_ADMIN 或 IIT_OPERATOR 创建项目 → 手动指定 tenantId(选择为哪个客户创建)
|
||
|
||
Phase 3 完成后(客户自助):
|
||
PHARMA_ADMIN 创建项目 → 自动绑定自己的 tenantId
|
||
```
|
||
|
||
### 5.5 前端改动
|
||
|
||
- 运营管理端项目列表:SUPER_ADMIN 看到全部 + 租户筛选器;PHARMA_ADMIN 只看到自己的
|
||
- 项目创建表单:SUPER_ADMIN 需选择租户;PHARMA_ADMIN 自动绑定
|
||
|
||
---
|
||
|
||
## 6. 实施优先级总结
|
||
|
||
| 阶段 | 核心内容 | 预估工时 | 数据库改动 | 前置条件 |
|
||
|------|---------|---------|-----------|---------|
|
||
| **Phase 1** | Provider + 管理端分组 + 配置补全 | 0.5 天 | 无 | 无 |
|
||
| **Phase 2** | userId 关联 + /my-projects + 角色权限 + 认证 + IIT_OPERATOR 角色 | 2 天 | 新增 userId 列、tenantId 列、IIT_OPERATOR 角色枚举 | Phase 1 |
|
||
| **Phase 3** | 租户隔离 + PHARMA_ADMIN 自助 | 1.5 天 | tenantId 改为必填 | Phase 2 |
|
||
|
||
### Phase 1 立即可执行的原因
|
||
|
||
- 0 数据库改动
|
||
- 只改前端代码
|
||
- 当前只有 1 个活跃项目 + 1 个测试用户,自动选中即可
|
||
- 让业务端立即可演示
|
||
|
||
### Phase 2 的触发条件
|
||
|
||
- 有真实用户需要登录系统
|
||
- 需要区分不同用户看到不同项目
|
||
- 需要保护 API 安全
|
||
|
||
### Phase 3 的触发条件
|
||
|
||
- 有多个客户(药企/医院)同时使用系统
|
||
- 需要租户级别的数据隔离
|
||
|
||
---
|
||
|
||
## 7. 附录:改动文件清单
|
||
|
||
### Phase 1
|
||
|
||
| 文件 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `frontend-v2/src/modules/iit/context/IitProjectContext.tsx` | 新建 | 项目上下文 Provider |
|
||
| `frontend-v2/src/modules/iit/IitLayout.tsx` | 修改 | 集成 Provider + 真实项目信息 |
|
||
| `frontend-v2/src/modules/iit/pages/DashboardPage.tsx` | 修改 | 改用 useIitProject() |
|
||
| `frontend-v2/src/modules/iit/pages/EQueryPage.tsx` | 修改 | 改用 useIitProject() |
|
||
| `frontend-v2/src/modules/iit/pages/ReportsPage.tsx` | 修改 | 改用 useIitProject() |
|
||
| `frontend-v2/src/modules/iit/pages/AiStreamPage.tsx` | 修改 | 改用 useIitProject() |
|
||
| `frontend-v2/src/modules/iit/pages/AiChatPage.tsx` | 修改 | 改用 useIitProject() |
|
||
| `frontend-v2/src/framework/layout/AdminLayout.tsx` | 修改 | 侧边栏分组 |
|
||
| `frontend-v2/src/modules/admin/pages/IitProjectDetailPage.tsx` | 修改 | 定时质控配置 + 变量清单 Tab |
|
||
| `backend/src/modules/admin/iit-projects/iitUserMappingService.ts` | 修改 | 添加 PM 角色 |
|
||
|
||
### Phase 2
|
||
|
||
| 文件 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `backend/prisma/schema.prisma` | 修改 | IitUserMapping 加 userId、IitProject 加 tenantId |
|
||
| `backend/src/modules/iit-manager/routes/index.ts` | 修改 | 加 /my-projects 路由 + authenticate 中间件 |
|
||
| `backend/src/common/auth/auth.middleware.ts` | 修改 | 新增 requireProjectRole 中间件 |
|
||
| `backend/prisma/schema.prisma` (Role enum) | 修改 | 新增 IIT_OPERATOR 角色枚举值 |
|
||
| `frontend-v2/src/modules/iit/context/IitProjectContext.tsx` | 修改 | 改调 /my-projects |
|
||
| `frontend-v2/src/framework/layout/AdminLayout.tsx` | 修改 | 按角色控制菜单组可见性(IIT_OPERATOR 只看项目运营) |
|
||
|
||
### Phase 3
|
||
|
||
| 文件 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| `backend/prisma/schema.prisma` | 修改 | tenantId 改为必填 |
|
||
| `backend/src/modules/admin/iit-projects/iitProjectService.ts` | 修改 | 查询加租户过滤 |
|
||
| `frontend-v2/src/modules/admin/pages/IitProjectListPage.tsx` | 修改 | 租户筛选器 |
|
||
| `frontend-v2/src/modules/admin/pages/IitProjectDetailPage.tsx` | 修改 | 创建时选择租户 |
|