diff --git a/docs/superpowers/specs/2026-07-30-optional-administrative-class-design.md b/docs/superpowers/specs/2026-07-30-optional-administrative-class-design.md new file mode 100644 index 0000000..1836d5c --- /dev/null +++ b/docs/superpowers/specs/2026-07-30-optional-administrative-class-design.md @@ -0,0 +1,210 @@ +# 学校组织模式与可选行政班设计 + +## 背景与结论 + +产品需要同时服务两类学校: + +- 标准学校:学生属于行政班,教师从行政班组建教学班; +- 珠江学校:不使用行政班,由教师直接维护教学班学生名单。 + +两类需求共用同一个产品、同一套代码库和同一套数据库。行政班不是系统的必备实体,而是某些学校启用的学生归属方式;教学班始终是实训、任务、成绩和学习进度的业务主体。 + +不建立长期客户分支,也不重建项目。短期交付若必须使用分支,分支中的差异必须回收为学校级配置或明确的扩展点。 + +## 目标与非目标 + +### 目标 + +1. 让每所学校独立选择是否启用行政班。 +2. 珠江学校由教师创建教学班、添加和维护教学班学生。 +3. 标准学校保持现有行政班和教学班逻辑,避免数据或操作回归。 +4. 学生可以加入多个教学班;任务、成绩和进度只以教学班为准。 +5. 复用现有 `school_class.class_type`、`created_by` 与 `teaching_class_student` 数据模型。 + +### 非目标 + +- 本期不拆分为两套部署或两套数据库结构。 +- 本期不将学校管理员的全校学生管理能力开放给珠江学校。 +- 本期不引入通用流程引擎或任意层级组织树。 +- 本期不改变外部教务系统同步的既有语义;后续可将其作为第三种名单来源扩展。 + +## 方案选择 + +| 方案 | 结论 | 原因 | +| --- | --- | --- | +| 客户长期分支 | 不采用 | 版本升级、缺陷修复和安全补丁都需要反复合并,维护成本随客户数增长。 | +| 独立项目 | 不采用 | 当前差异集中在组织结构与名单维护,不足以证明业务核心已经分化。 | +| 学校级组织模式配置 | 采用 | 差异可被稳定规则表达,公共任务、成绩、实训等能力继续复用。 | + +## 核心模型 + +```text +学校 + ├─ 学生身份(必属学校,以“学校 + 学号”去重) + ├─ 行政班(可选,仅 ADMIN_CLASS_ENABLED 学校) + └─ 教学班(实训业务主体) + └─ 教学班学生成员(学生可加入多个教学班) + └─ 实训任务、提交、成绩、学习进度 +``` + +### 学校组织配置 + +新增一张一对一配置表 `school_product_config`,避免以后不断向 `school` 表加入互不相关的定制字段。 + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `school_id` | VARCHAR(64), PK | 对应 `school.school_id` | +| `organization_mode` | VARCHAR(32) | `ADMIN_CLASS_ENABLED` 或 `TEACHING_CLASS_ONLY` | +| `student_roster_owner` | VARCHAR(32) | `SCHOOL_ADMIN`、`TEACHER`;预留 `EXTERNAL_SYNC` | +| `create_time` / `update_time` | DATETIME | 审计时间 | + +配置必须由平台超管维护,学校管理员和教师只读取当前学校配置。创建新学校时必须创建配置记录,默认值为: + +```text +organization_mode = ADMIN_CLASS_ENABLED +student_roster_owner = SCHOOL_ADMIN +``` + +珠江学校配置为: + +```text +organization_mode = TEACHING_CLASS_ONLY +student_roster_owner = TEACHER +``` + +不允许在学校已存在行政班或学生数据时直接切换组织模式;应先完成数据核查/迁移,或新建学校租户。 + +### 珠江学校的数据规则 + +| 对象 | 规则 | +| --- | --- | +| 学生 `Userinfo` | `school_id` 必填;`school_class_id` 可为空;院系、专业在珠江模式下可为空。 | +| 行政班 `SchoolClass` | 不允许创建 `class_type=ADMIN`;行政班列表、导入入口和相关菜单不展示。 | +| 教学班 `SchoolClass` | 必须为 `class_type=TEACHING`,`created_by` 必填且等于创建教师。`school_major_id` 允许为空。 | +| 教学班成员 `TeachingClassStudent` | `teaching_class_id`、`student_user_id` 必填;`admin_class_id` 改为可为空。保留 `(teaching_class_id, student_user_id)` 唯一约束。 | +| 学生身份去重 | 导入及新增均以 `school_id + student_number`(现有学号/账号字段的业务含义需统一)查重,不能仅按姓名查重。 | + +`school_class_id` 只能继续作为历史兼容字段或行政班归属,不能再作为“学生当前教学班”的唯一来源。所有实训上下文应通过 `teaching_class_student` 查询教学班成员关系。 + +## 业务流程 + +### 珠江:教师维护名单 + +1. 教师进入“我的教学班”,创建教学班;系统写入 `class_type=TEACHING`、`created_by=当前教师`。 +2. 教师在教学班内选择“添加学生”:单个新增、批量导入,或搜索并加入本校已有学生。 +3. 系统用学校和学号查找学生: + - 找到已有学生:仅新增当前教学班成员关系; + - 未找到学生:创建本校学生身份后新增成员关系; + - 已在当前教学班:返回重复明细,不创建重复记录。 +4. 教师可编辑自己教学班成员的基本资料,并移出该教学班。 +5. 移出教学班仅删除/失效成员关系,绝不删除 `Userinfo`、提交、成绩或其他教学班成员关系。 + +学生首次被任何教师导入后成为学校范围的学生身份。后续教师只能把已有学生加入自己的教学班;如允许编辑共享身份资料,必须记录操作者和修改时间,并以学号唯一性校验为准。 + +### 标准学校:保持既有逻辑 + +1. 学校管理员维护学生及行政班。 +2. 教师可从一个或多个行政班生成教学班,或导入名单。 +3. 导入教学班时保留现有“学生属于指定行政班/专业范围”的校验。 +4. `TeachingClassStudent.admin_class_id` 记录学生行政班快照。 + +所有涉及“行政班存在”或“成员必须来自行政班”的规则,都以当前学校的 `organization_mode` 为前提。不得让珠江模式经过标准模式的行政班校验。 + +## 权限与界面 + +### 珠江学校 + +| 角色 | 可见模块 | 关键限制 | +| --- | --- | --- | +| 平台超管 | 学校与学校配置 | 可查看学校数据;配置变更须通过平台端。 | +| 学校管理员 | 学校基础信息、教师管理 | 不显示院系、专业、行政班和全校学生管理。 | +| 教师 | 我的教学班、教学班学生、任务和成绩 | 只可操作 `created_by=本人` 的教学班及其成员。 | +| 学生 | 已加入教学班的实训内容 | 仅访问自己有效教学班中的任务、进度和成绩。 | + +### 前端行为 + +- 登录后返回或单独读取学校产品配置,并存入用户会话状态。 +- 菜单、路由守卫、页面操作按钮均按角色和学校配置判断;隐藏菜单不是权限控制,后端必须执行相同校验。 +- 珠江教师创建教学班页不展示“从行政班组建”,只提供“空教学班后添加学生”及“按学生名单创建教学班”。 +- 珠江导入模板只包含学生必要身份字段,不包含院系、专业、行政班列。 +- 标准学校继续使用现有模板与页面流程。 + +## 后端改造边界 + +现有代码已具备 `SchoolClass.classType`、`SchoolClass.createdBy`、`TeachingClassStudent` 和教师教学班成员导入入口。改造应优先在既有服务/控制器上补充配置判断,而不是复制一套珠江控制器。 + +### 配置读取 + +新增 `SchoolProductConfig`、Mapper 与 `SchoolProductConfigService`,对外提供: + +```text +getRequiredConfig(schoolId) +requiresAdminClass(schoolId) +isTeacherRosterManaged(schoolId) +``` + +服务层必须获取登录用户所属学校后自行读取配置;不能信任前端传入的组织模式。 + +### 教师教学班接口 + +可沿用现有教学班与成员导入接口,但应收敛为以下语义: + +```text +POST /api/.../teaching-classes +GET /api/.../teaching-classes/mine +GET /api/.../teaching-classes/{id}/students +POST /api/.../teaching-classes/{id}/students +POST /api/.../teaching-classes/{id}/students/import +PATCH /api/.../teaching-classes/{id}/students/{studentId} +DELETE /api/.../teaching-classes/{id}/students/{studentId} +``` + +每个教学班接口先验证:班级存在、`class_type=TEACHING`、归属学校等于操作者学校、`created_by` 等于操作者。名单接口在珠江模式下不得调用行政班、专业或院系的必填校验。 + +### 需要重点检查的现有逻辑 + +- 教学班成员导入与“按学生名单创建教学班”当前会调用行政班/专业范围校验;应按组织模式分支为标准校验与珠江校验。 +- 教学班成员写入当前会传入学生的 `schoolClassId`;珠江模式传入 `NULL`。 +- 学生任务、成绩、AI 评价和学习进度的“当前教学班”解析必须优先使用 `TeachingClassStudent`;若学生可并行处于多个教学班,调用方必须显式传入 `teachingClassId` 或让学生选择上下文,禁止任取第一条成员记录。 +- 删除学生的接口必须在教学班上下文中仅移除成员关系;任何全局删除学生账号的行为不应向教师开放。 + +## 数据迁移与上线 + +1. 新增 `school_product_config`,为所有已有学校写入标准学校默认配置。 +2. 将 `teaching_class_student.admin_class_id` 改为可空;保留已有数据。 +3. 确认 `school_class.school_major_id`、学生的院系/专业字段在数据库层允许为空;若不允许,仅为珠江场景放宽为空。 +4. 为珠江创建配置,发布后先由一名教师做小范围名单导入验收。 +5. 不删除行政班、专业、院系或历史 `school_class_id` 数据,不对标准学校做批量数据重写。 +6. 配置切换设置为受控操作:出现行政班、学生归属或任务数据时,后端拒绝直接切换并返回可操作的迁移提示。 + +## 审计与错误处理 + +记录教学班创建、学生身份创建、成员加入、成员移出和成员资料变更,至少包含学校、教学班、学生、操作者、时间、来源(手工/导入)。 + +典型错误: + +- 教师操作非本人教学班:403。 +- 非珠江学校以珠江模板导入:返回“当前学校启用了行政班,请使用标准导入方式”。 +- 珠江学校请求创建行政班:返回“当前学校未启用行政班”。 +- 学号已存在但姓名冲突:返回冲突行,不自动覆盖既有学生身份。 +- 学生已在当前教学班:返回重复明细,整批导入的处理策略统一为“重复行跳过并回显”;其他校验错误则整批拒绝。 + +## 验收标准 + +1. 珠江教师可创建教学班、导入新学生、将已有学生加入另一教学班,且成员 `admin_class_id` 为空。 +2. 珠江教师不需要创建或选择行政班、院系、专业即可完成名单维护。 +3. 珠江学校管理员看不到且无法调用行政班、院系、专业、全校学生管理功能。 +4. 珠江教师不能操作其他教师教学班或其成员。 +5. 标准学校仍需经行政班规则组建教学班,原有行政班成员导入与任务分配测试通过。 +6. 学生在多个教学班时,任务、成绩、AI 评价与学习进度都按明确的 `teachingClassId` 隔离。 +7. 移出教学班不会删除学生身份、历史任务提交或其他教学班的成员资格。 +8. 非法切换学校组织模式被后端拒绝;前端隐藏入口不能绕过后端授权。 + +## 实施顺序 + +1. 编写数据库迁移与配置实体/服务单元测试。 +2. 完成学校创建、查询与平台配置管理接口。 +3. 调整教学班创建、学生新增、名单导入、成员移出服务的配置分支。 +4. 调整学生当前教学班解析,使多教学班上下文明确。 +5. 修改前端会话配置、菜单与教学班名单页面/导入模板。 +6. 覆盖珠江模式、标准模式、跨教师越权、多教学班和历史回归测试。