Files
gongxue-base/docs/superpowers/specs/2026-07-09-student-role-separation-design.md

93 lines
3.1 KiB
Markdown
Raw 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.

# Student 角色分离设计
> 日期2026-07-09 | 状态:待审批
## 背景
钉钉同步 (`syncOneUser`) 目前对每个用户都自动创建 `Student` 实体。实际上钉钉用户分为:
- **学员** — 需要 Student 实体(考勤、班级、费用)
- **教职工** — 只需要 User登录+角色),不需要 Student
钉钉标准 API 不提供用户身份标记。采用 **批量建 + 手动摘** 策略。
## 设计
### 1. Student 状态新增 `'staff'`
`Student.status` 现有值:`active``graduated``withdrawn``archived`
新增:`'staff'` — 标记为教职工,不作为学员管理。
| status | 含义 | 学员列表显示 | 同步时覆盖? |
|--------|------|:---:|:---:|
| active | 在读学员 | ✅ | ✅ |
| graduated | 已毕业 | ❌(筛选可见) | ❌ |
| withdrawn | 已退训 | ❌(筛选可见) | ❌ |
| archived | 已归档 | ❌(归档开关) | ❌ |
| **staff** | **教职工** | **❌ 默认隐藏** | **❌ 不覆盖** |
### 2. syncOneUser 防护
```typescript
// 已有 Student 且 status 为非 active → 跳过更新
const existingStudent = await this.studentRepo.findOne({ where: { userId: user.id } });
if (existingStudent && existingStudent.status !== 'active') {
// 用户已被标记为教职工/毕业/退训,不覆盖
return; // 跳过 Student 操作
}
```
### 3. API 端点
| 端点 | 权限 | 效果 |
|------|------|------|
| `PUT /rbac/users/:id/mark-staff` | `user:edit` | Student.status → `'staff'` |
| `PUT /rbac/users/:id/mark-student` | `user:edit` | Student.status → `'active'` |
### 4. 前端改动
**账号管理页面**:操作列新增按钮
```
用户有 Student 且 status='active' → 显示 [标记为教职工]
用户有 Student 且 status='staff' → 显示 [恢复为学员]
用户无 Student → 不显示
```
**学员管理页面**:默认筛选 `status != 'staff'`,可通过状态筛选查看。
### 5. 用户操作流程
```
同步后 → 所有人在学员列表可见
管理员到「账号管理」→ 找到李老师 → 点「标记为教职工」
Student.status → 'staff'
「学员管理」→ 李老师不再显示
「考勤管理」→ 李老师的考勤数据不参与学生统计
下次同步 → 不会恢复李老师为学员
```
## 边际情况
### 变更文件
| 文件 | 变更 |
|------|------|
| `entities/student.entity.ts` | status 注释更新staff 已是有效值,无需改 schema |
| `integration/dingtalk.service.ts` | syncOneUser: 已有 Student 且 status!='active' 时跳过 |
| `rbac/rbac.service.ts` | +markAsStaff、+markAsStudent |
| `rbac/rbac.controller.ts` | +两个端点 |
| `students/students.service.ts` | findAll 默认排除 status='staff' |
| `pages/Users/index.tsx` | +标记/恢复按钮 |
| `pages/Students/index.tsx` | +staff 状态筛选 |
### 风险点
- Student 表 `status` 是 varchar不需要迁移`'staff'` 是新增有效值)
- 同步不覆盖的原则:只保护 `status != 'active'` 的 Student`active` 的依然正常更新
- 如果用户之前没有 Student纯手工创建的教职工标记操作报友好错误