spec: student-role separation design (batch-create + manual untag)
This commit is contained in:
@@ -0,0 +1,92 @@
|
|||||||
|
# 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(纯手工创建的教职工),标记操作报友好错误
|
||||||
Reference in New Issue
Block a user