diff --git a/apps/admin/src/pages/Classrooms/classroom-fields.integration.test.ts b/apps/admin/src/pages/Classrooms/classroom-fields.integration.test.ts
new file mode 100644
index 0000000..f3843e5
--- /dev/null
+++ b/apps/admin/src/pages/Classrooms/classroom-fields.integration.test.ts
@@ -0,0 +1,16 @@
+import { describe, expect, it } from 'vitest';
+import { CLASSROOM_VISIBLE_FIELDS } from './classroom-fields';
+
+describe('classroom visible fields', () => {
+ it('excludes obsolete course and supervisor metadata', () => {
+ expect(CLASSROOM_VISIBLE_FIELDS).toEqual([
+ 'name',
+ 'building',
+ 'floor',
+ 'roomType',
+ 'capacity',
+ 'status',
+ 'notes',
+ ]);
+ });
+});
diff --git a/apps/admin/src/pages/Classrooms/classroom-fields.ts b/apps/admin/src/pages/Classrooms/classroom-fields.ts
new file mode 100644
index 0000000..43fe469
--- /dev/null
+++ b/apps/admin/src/pages/Classrooms/classroom-fields.ts
@@ -0,0 +1,9 @@
+export const CLASSROOM_VISIBLE_FIELDS = [
+ 'name',
+ 'building',
+ 'floor',
+ 'roomType',
+ 'capacity',
+ 'status',
+ 'notes',
+] as const;
diff --git a/apps/admin/src/pages/Classrooms/index.tsx b/apps/admin/src/pages/Classrooms/index.tsx
index 8e67ccd..e3e9fe8 100644
--- a/apps/admin/src/pages/Classrooms/index.tsx
+++ b/apps/admin/src/pages/Classrooms/index.tsx
@@ -154,8 +154,6 @@ const ClassroomsPage: React.FC = () => {
render: (v: string) => {v || '-'},
},
{ title: '容量', dataIndex: 'capacity', width: 80 },
- { title: '课程类型', dataIndex: 'courseType', width: 100, render: (v: string) => v || '-' },
- { title: '负责人', dataIndex: 'supervisor', width: 100, render: (v: string) => v || '-' },
{
title: '状态', width: 100,
dataIndex: 'status',
@@ -325,12 +323,6 @@ const ClassroomsPage: React.FC = () => {
-
-
-
-
-
-
diff --git a/apps/server/src/classroom-rentals/classroom-rentals.service.ts b/apps/server/src/classroom-rentals/classroom-rentals.service.ts
index 26f1510..54be700 100644
--- a/apps/server/src/classroom-rentals/classroom-rentals.service.ts
+++ b/apps/server/src/classroom-rentals/classroom-rentals.service.ts
@@ -546,7 +546,6 @@ export class ClassroomRentalsService {
floor: c.floor,
roomType: c.roomType,
capacity: c.capacity,
- supervisor: c.supervisor,
})),
organizations: Array.from(organizationMap.values()),
matrix,
diff --git a/apps/server/src/classrooms/classroom-template.spec.ts b/apps/server/src/classrooms/classroom-template.spec.ts
new file mode 100644
index 0000000..0444342
--- /dev/null
+++ b/apps/server/src/classrooms/classroom-template.spec.ts
@@ -0,0 +1,7 @@
+import { CLASSROOM_TEMPLATE_HEADERS } from './classroom-template';
+
+describe('classroom import template', () => {
+ it('contains only classroom fields used by the product', () => {
+ expect(CLASSROOM_TEMPLATE_HEADERS).toEqual(['教室名', '楼栋', '楼层', '类型', '容量']);
+ });
+});
diff --git a/apps/server/src/classrooms/classroom-template.ts b/apps/server/src/classrooms/classroom-template.ts
new file mode 100644
index 0000000..f4cc29d
--- /dev/null
+++ b/apps/server/src/classrooms/classroom-template.ts
@@ -0,0 +1,9 @@
+export const CLASSROOM_TEMPLATE_COLUMNS = [
+ { header: '教室名', key: 'name', width: 15 },
+ { header: '楼栋', key: 'building', width: 12 },
+ { header: '楼层', key: 'floor', width: 8 },
+ { header: '类型', key: 'roomType', width: 10 },
+ { header: '容量', key: 'capacity', width: 10 },
+];
+
+export const CLASSROOM_TEMPLATE_HEADERS = CLASSROOM_TEMPLATE_COLUMNS.map(({ header }) => header);
diff --git a/apps/server/src/classrooms/classrooms.controller.ts b/apps/server/src/classrooms/classrooms.controller.ts
index da3aca4..1422ef9 100644
--- a/apps/server/src/classrooms/classrooms.controller.ts
+++ b/apps/server/src/classrooms/classrooms.controller.ts
@@ -22,6 +22,7 @@ import { OperationLogsService } from '../operation-logs/operation-logs.service';
import { extractRequestInfo } from '../common/request-utils';
import { RequirePermission } from '../auth/decorators/permission.decorator';
import * as ExcelJS from 'exceljs';
+import { CLASSROOM_TEMPLATE_COLUMNS } from './classroom-template';
@UseGuards(JwtAuthGuard)
@Controller('classrooms')
@@ -50,15 +51,7 @@ export class ClassroomsController {
async downloadTemplate(@Res() res: Response) {
const workbook = new ExcelJS.Workbook();
const ws = workbook.addWorksheet('教室导入模板');
- ws.columns = [
- { header: '教室名', key: 'name', width: 15 },
- { header: '楼栋', key: 'building', width: 12 },
- { header: '楼层', key: 'floor', width: 8 },
- { header: '类型', key: 'roomType', width: 10 },
- { header: '容量', key: 'capacity', width: 10 },
- { header: '课程类型', key: 'courseType', width: 16 },
- { header: '负责人', key: 'supervisor', width: 12 },
- ];
+ ws.columns = CLASSROOM_TEMPLATE_COLUMNS;
ws.getRow(1).font = { bold: true };
ws.getRow(1).fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FFE0E0E0' } };
ws.addRow({
@@ -67,8 +60,6 @@ export class ClassroomsController {
floor: 2,
roomType: '大',
capacity: 60,
- courseType: '尊享培优班',
- supervisor: '张老师',
});
ws.addRow({
name: 'B301',
@@ -76,8 +67,6 @@ export class ClassroomsController {
floor: 3,
roomType: '次大',
capacity: 40,
- courseType: '专业课集训班',
- supervisor: '李老师',
});
ws.addRow({
name: 'B405',
@@ -85,8 +74,6 @@ export class ClassroomsController {
floor: 4,
roomType: '小',
capacity: 20,
- courseType: '',
- supervisor: '',
});
// 说明sheet
@@ -97,8 +84,6 @@ export class ClassroomsController {
'1. 教室名必填,建议采用「楼栋+房号」如 A201、B301',
'2. 类型可填 大 / 次大 / 小,为空默认「大」',
'3. 同名教室会自动跳过(不覆盖)',
- '4. 课程类型可填尊享培优班、专业课集训班等产品班级',
- '5. 负责人为班主任/对接人',
].forEach((note) => ws2.addRow({ note }));
res.setHeader(
@@ -207,8 +192,6 @@ export class ClassroomsController {
floor: Number(row.getCell(3).value) || undefined,
roomType: String(row.getCell(4).value || '') || undefined,
capacity: Number(row.getCell(5).value) || undefined,
- courseType: String(row.getCell(6).value || '') || undefined,
- supervisor: String(row.getCell(7).value || '') || undefined,
});
});
const result = await this.service.batchImport(rows);
diff --git a/apps/server/src/classrooms/classrooms.service.ts b/apps/server/src/classrooms/classrooms.service.ts
index d99142e..c379914 100644
--- a/apps/server/src/classrooms/classrooms.service.ts
+++ b/apps/server/src/classrooms/classrooms.service.ts
@@ -130,7 +130,6 @@ export class ClassroomsService {
floor?: number;
capacity?: number;
roomType?: string;
- courseType?: string;
}[],
) {
let imported = 0;
diff --git a/apps/server/src/classrooms/dto/classroom.dto.ts b/apps/server/src/classrooms/dto/classroom.dto.ts
index 703470e..6f65bb3 100644
--- a/apps/server/src/classrooms/dto/classroom.dto.ts
+++ b/apps/server/src/classrooms/dto/classroom.dto.ts
@@ -21,13 +21,6 @@ export class CreateClassroomDto {
@IsString()
roomType?: string; // 大 / 次大 / 小
- @IsOptional()
- @IsString()
- courseType?: string;
-
- @IsOptional()
- @IsString()
- supervisor?: string;
@IsOptional()
@IsString()
@@ -56,13 +49,6 @@ export class UpdateClassroomDto {
@IsString()
roomType?: string;
- @IsOptional()
- @IsString()
- courseType?: string;
-
- @IsOptional()
- @IsString()
- supervisor?: string;
@IsOptional()
@IsString()
diff --git a/apps/server/src/database/database-migrations.service.ts b/apps/server/src/database/database-migrations.service.ts
index 5d4b691..b40d799 100644
--- a/apps/server/src/database/database-migrations.service.ts
+++ b/apps/server/src/database/database-migrations.service.ts
@@ -14,6 +14,26 @@ export class DatabaseMigrationsService implements OnApplicationBootstrap {
await this.backfillOrganizations();
await this.normalizeClassDates();
await this.protectAttendanceHistory();
+ await this.removeUnusedClassroomColumns();
+ }
+
+ private async removeUnusedClassroomColumns(): Promise {
+ const runner = this.dataSource.createQueryRunner();
+ await runner.connect();
+ try {
+ const tables = await runner.getTables(['classrooms']);
+ if (tables.length === 0) return;
+
+ const table = await runner.getTable('classrooms');
+ const columnNames = new Set(table?.columns.map((column) => column.name) ?? []);
+ for (const columnName of ['course_type', 'supervisor']) {
+ if (columnNames.has(columnName)) {
+ await runner.query(`ALTER TABLE classrooms DROP COLUMN ${columnName}`);
+ }
+ }
+ } finally {
+ await runner.release();
+ }
}
private async ensureAiConfigTable(): Promise {
diff --git a/apps/server/src/database/database-migrations.spec.ts b/apps/server/src/database/database-migrations.spec.ts
index c0fee6e..6000976 100644
--- a/apps/server/src/database/database-migrations.spec.ts
+++ b/apps/server/src/database/database-migrations.spec.ts
@@ -10,6 +10,13 @@ interface MockTable {
name: string;
columns: MockColumn[];
}
+interface MockRunner {
+ release: jest.Mock;
+ connect: jest.Mock;
+ query: jest.Mock;
+ getTables: jest.Mock;
+ getTable: jest.Mock;
+}
function mockRunner(overrides: {
getTables?: MockTable[];
@@ -28,10 +35,10 @@ function mockRunner(overrides: {
query.mockRejectedValue(overrides.queryError);
}
- return { release, connect, query, getTables, getTable };
+ return { release, connect, query, getTables, getTable } satisfies MockRunner;
}
-function createDataSource(runner: ReturnType, dbType: string = 'better-sqlite3') {
+function createDataSource(runner: MockRunner, dbType: string = 'better-sqlite3') {
return {
options: { type: dbType },
createQueryRunner: jest.fn().mockReturnValue(runner),
@@ -46,12 +53,13 @@ interface MigrationsPrivate {
normalizeClassDates(): Promise;
ensureCourseAttendanceSchema(): Promise;
protectAttendanceHistory(): Promise;
+ removeUnusedClassroomColumns(): Promise;
}
describe('DatabaseMigrationsService — ensureAiConfigTable', () => {
let service: MigrationsPrivate & DatabaseMigrationsService;
- async function bootstrap(runner: ReturnType) {
+ async function bootstrap(runner: MockRunner) {
const dataSource = createDataSource(runner);
const module: TestingModule = await Test.createTestingModule({
providers: [
@@ -220,7 +228,7 @@ describe('DatabaseMigrationsService — course attendance schema', () => {
describe('DatabaseMigrationsService — protectAttendanceHistory', () => {
let service: MigrationsPrivate & DatabaseMigrationsService;
- async function bootstrap(runner: ReturnType, dbType: string = 'better-sqlite3') {
+ async function bootstrap(runner: MockRunner, dbType: string = 'better-sqlite3') {
const dataSource = createDataSource(runner, dbType);
const module: TestingModule = await Test.createTestingModule({
providers: [
@@ -416,7 +424,49 @@ describe('DatabaseMigrationsService — protectAttendanceHistory', () => {
});
});
-async function bootstrapCourseAttendance(runner: ReturnType) {
+describe('DatabaseMigrationsService — classroom cleanup', () => {
+ let cleanupService: MigrationsPrivate & DatabaseMigrationsService;
+
+ async function bootstrapClassroomCleanup(runner: MockRunner) {
+ const dataSource = createDataSource(runner);
+ const module: TestingModule = await Test.createTestingModule({
+ providers: [
+ DatabaseMigrationsService,
+ { provide: getDataSourceToken(), useValue: dataSource },
+ ],
+ }).compile();
+ cleanupService = module.get(DatabaseMigrationsService);
+ }
+
+ it('drops legacy classroom fields when present', async () => {
+ const runner = mockRunner({
+ getTables: [{ name: 'classrooms', columns: [] }],
+ getTable: {
+ name: 'classrooms',
+ columns: [{ name: 'id' }, { name: 'course_type' }, { name: 'supervisor' }],
+ },
+ });
+ await bootstrapClassroomCleanup(runner);
+
+ await cleanupService.removeUnusedClassroomColumns();
+
+ expect(runner.query).toHaveBeenCalledWith('ALTER TABLE classrooms DROP COLUMN course_type');
+ expect(runner.query).toHaveBeenCalledWith('ALTER TABLE classrooms DROP COLUMN supervisor');
+ expect(runner.release).toHaveBeenCalled();
+ });
+
+ it('does nothing when the classrooms table is absent', async () => {
+ const runner = mockRunner({ getTables: [] });
+ await bootstrapClassroomCleanup(runner);
+
+ await cleanupService.removeUnusedClassroomColumns();
+
+ expect(runner.query).not.toHaveBeenCalled();
+ expect(runner.release).toHaveBeenCalled();
+ });
+});
+
+async function bootstrapCourseAttendance(runner: MockRunner) {
const dataSource = createDataSource(runner);
const module: TestingModule = await Test.createTestingModule({
providers: [
diff --git a/apps/server/src/entities/classroom.entity.ts b/apps/server/src/entities/classroom.entity.ts
index c57f2c2..2bb29bf 100644
--- a/apps/server/src/entities/classroom.entity.ts
+++ b/apps/server/src/entities/classroom.entity.ts
@@ -20,11 +20,6 @@ export class Classroom {
@Column({ name: 'room_type', type: 'varchar', length: 20, default: '大' })
roomType: string; // 大 / 次大 / 小
- @Column({ name: 'course_type', length: 50, nullable: true })
- courseType: string;
-
- @Column({ length: 50, nullable: true })
- supervisor: string;
@Column({ type: 'varchar', length: 20, default: 'reserved' })
status: string;
diff --git a/docs/superpowers/plans/2026-07-13-classroom-field-simplification.md b/docs/superpowers/plans/2026-07-13-classroom-field-simplification.md
new file mode 100644
index 0000000..de54e5c
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-13-classroom-field-simplification.md
@@ -0,0 +1,225 @@
+# Classroom Field Simplification Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Remove the unused classroom `courseType` and `supervisor` fields from the admin UI, API contract, persistence model, and Excel import flow.
+
+**Architecture:** Perform a clean cutover across every classroom caller. Add an idempotent bootstrap migration that drops the legacy columns on SQLite and MySQL, while leaving scheduling unchanged because schedule `subject` and class `classType` already own those concepts.
+
+**Tech Stack:** React 19, Ant Design 6, NestJS 11, TypeORM 0.3, Jest, Vitest, SQLite, MySQL 8
+
+## Global Constraints
+
+- Do not add a replacement abstraction or compatibility alias.
+- Preserve classroom name, building, floor, capacity, room type, status, and notes behavior.
+- Do not change schedule form fields or schedule conflict behavior.
+- Use test-first changes and run focused tests before package builds.
+
+---
+
+### Task 1: Drop Legacy Classroom Columns
+
+**Files:**
+- Modify: `apps/server/src/database/database-migrations.spec.ts`
+- Modify: `apps/server/src/database/database-migrations.service.ts:11`
+- Modify: `apps/server/src/entities/classroom.entity.ts:23`
+
+**Interfaces:**
+- Consumes: TypeORM `DataSource`, `QueryRunner`, and the existing bootstrap migration sequence.
+- Produces: private `removeUnusedClassroomColumns(): Promise` called during application bootstrap.
+
+- [ ] **Step 1: Write failing migration tests**
+
+Extend `MigrationsPrivate` with `removeUnusedClassroomColumns(): Promise`. Add focused tests proving that the method:
+
+```typescript
+it('drops legacy classroom fields when present', async () => {
+ const runner = mockRunner({
+ getTable: {
+ name: 'classrooms',
+ columns: [{ name: 'id' }, { name: 'course_type' }, { name: 'supervisor' }],
+ },
+ });
+ await bootstrapClassroomCleanup(runner);
+
+ await service.removeUnusedClassroomColumns();
+
+ expect(runner.query).toHaveBeenCalledWith('ALTER TABLE classrooms DROP COLUMN course_type');
+ expect(runner.query).toHaveBeenCalledWith('ALTER TABLE classrooms DROP COLUMN supervisor');
+ expect(runner.release).toHaveBeenCalled();
+});
+
+it('does nothing when classrooms or legacy fields are absent', async () => {
+ const runner = mockRunner({ getTables: [], getTable: { name: 'classrooms', columns: [] } });
+ await bootstrapClassroomCleanup(runner);
+
+ await service.removeUnusedClassroomColumns();
+
+ expect(runner.query).not.toHaveBeenCalled();
+ expect(runner.release).toHaveBeenCalled();
+});
+```
+
+Use a local `bootstrapClassroomCleanup` helper matching the existing Nest testing-module setup.
+
+- [ ] **Step 2: Run tests and verify RED**
+
+Run: `npm test -w apps/server -- database/database-migrations.spec.ts --runInBand`
+
+Expected: FAIL because `removeUnusedClassroomColumns` does not exist.
+
+- [ ] **Step 3: Implement the idempotent migration**
+
+Call `await this.removeUnusedClassroomColumns();` from `onApplicationBootstrap()`. Implement the method with one query runner: inspect `classrooms`, return when absent, and issue one `ALTER TABLE classrooms DROP COLUMN ` per present legacy column. Always release the runner in `finally`.
+
+Remove these entity properties and decorators:
+
+```typescript
+courseType: string;
+supervisor: string;
+```
+
+- [ ] **Step 4: Run migration tests and verify GREEN**
+
+Run: `npm test -w apps/server -- database/database-migrations.spec.ts --runInBand`
+
+Expected: PASS.
+
+### Task 2: Remove Fields From API and Excel Import
+
+**Files:**
+- Modify: `apps/server/src/classrooms/dto/classroom.dto.ts:3`
+- Modify: `apps/server/src/classrooms/classrooms.controller.ts:48`
+- Modify: `apps/server/src/classrooms/classrooms.service.ts:126`
+- Create: `apps/server/src/classrooms/classrooms.controller.spec.ts`
+
+**Interfaces:**
+- Consumes: `CreateClassroomDto`, `UpdateClassroomDto`, classroom Excel template/import endpoints.
+- Produces: classroom requests and spreadsheets containing only `name`, `building`, `floor`, `roomType`, `capacity`, and optional `notes` where applicable.
+
+- [ ] **Step 1: Write a failing Excel contract test**
+
+Instantiate `ClassroomsController` with mocked service/log dependencies, invoke `downloadTemplate`, capture the workbook response buffer, load it with ExcelJS, and assert:
+
+```typescript
+expect(worksheet.getRow(1).values).toEqual([
+ undefined,
+ '教室名',
+ '楼栋',
+ '楼层',
+ '类型',
+ '容量',
+]);
+```
+
+Also assert the usage notes no longer mention course type or supervisor.
+
+- [ ] **Step 2: Run test and verify RED**
+
+Run: `npm test -w apps/server -- classrooms/classrooms.controller.spec.ts --runInBand`
+
+Expected: FAIL because the generated template still includes `课程类型` and `负责人`.
+
+- [ ] **Step 3: Remove fields from server contracts**
+
+Delete `courseType` and `supervisor` from both DTO classes. Remove them from:
+
+- template columns, example rows, and usage notes;
+- parsed import rows;
+- `batchImport` row type.
+
+Keep the existing field order `name`, `building`, `floor`, `roomType`, `capacity` consistent between export and import.
+
+- [ ] **Step 4: Run focused server tests**
+
+Run: `npm test -w apps/server -- classrooms/classrooms.controller.spec.ts database/database-migrations.spec.ts --runInBand`
+
+Expected: PASS.
+
+### Task 3: Simplify Classroom Admin UI
+
+**Files:**
+- Modify: `apps/admin/src/pages/Classrooms/index.tsx:143`
+- Create: `apps/admin/src/pages/Classrooms/classroom-fields.ts`
+- Create: `apps/admin/src/pages/Classrooms/classroom-fields.integration.test.ts`
+
+**Interfaces:**
+- Consumes: classroom records returned by `/classrooms`.
+- Produces: exported `CLASSROOM_VISIBLE_FIELDS` used to document/test the UI contract; list and form without `courseType` or `supervisor`.
+
+- [ ] **Step 1: Write a failing UI contract test**
+
+Create a small contract test:
+
+```typescript
+import { describe, expect, it } from 'vitest';
+import { CLASSROOM_VISIBLE_FIELDS } from './classroom-fields';
+
+describe('classroom visible fields', () => {
+ it('excludes obsolete course and supervisor metadata', () => {
+ expect(CLASSROOM_VISIBLE_FIELDS).toEqual([
+ 'name',
+ 'building',
+ 'floor',
+ 'roomType',
+ 'capacity',
+ 'status',
+ 'notes',
+ ]);
+ });
+});
+```
+
+- [ ] **Step 2: Run test and verify RED**
+
+Run: `npm test -w apps/admin -- src/pages/Classrooms/classroom-fields.integration.test.ts`
+
+Expected: FAIL because `classroom-fields.ts` does not exist.
+
+- [ ] **Step 3: Implement minimal UI cleanup**
+
+Create the constant exactly as asserted. Remove the `课程类型` and `负责人` table columns and the corresponding two `Form.Item` blocks. Do not alter scheduling pages.
+
+- [ ] **Step 4: Run focused admin test**
+
+Run: `npm test -w apps/admin -- src/pages/Classrooms/classroom-fields.integration.test.ts`
+
+Expected: PASS.
+
+### Task 4: Verify Clean Cutover
+
+**Files:**
+- Verify: `apps/server/src/entities/classroom.entity.ts`
+- Verify: `apps/server/src/classrooms/`
+- Verify: `apps/admin/src/pages/Classrooms/`
+
+**Interfaces:**
+- Consumes: completed Tasks 1–3.
+- Produces: buildable admin/server packages with no classroom `courseType` or `supervisor` path.
+
+- [ ] **Step 1: Run focused tests together**
+
+Run: `npm test -w apps/server -- classrooms/classrooms.controller.spec.ts database/database-migrations.spec.ts --runInBand`
+
+Run: `npm test -w apps/admin -- src/pages/Classrooms/classroom-fields.integration.test.ts`
+
+Expected: all PASS.
+
+- [ ] **Step 2: Run package typechecks/builds**
+
+Run: `npm run typecheck -w apps/server`
+
+Run: `npm run build -w apps/admin`
+
+Expected: both exit successfully.
+
+- [ ] **Step 3: Smoke-test observable contracts**
+
+Start the existing app and verify:
+
+1. Adding/editing a classroom shows no course type or supervisor field.
+2. Classroom list shows neither obsolete column.
+3. Downloaded import template has five headers: 教室名、楼栋、楼层、类型、容量.
+4. Creating a schedule still selects and saves a classroom normally.
+
+Expected: all four scenarios succeed without console or API errors.
diff --git a/docs/superpowers/specs/2026-07-13-classroom-field-simplification-design.md b/docs/superpowers/specs/2026-07-13-classroom-field-simplification-design.md
new file mode 100644
index 0000000..ee1240d
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-13-classroom-field-simplification-design.md
@@ -0,0 +1,31 @@
+# 教室字段精简设计
+
+## 目标
+
+删除教室中没有业务联动的“课程类型”和“负责人/班主任”,避免与班级班型、真实教师关系重复维护。
+
+## 范围
+
+- 教室创建和编辑表单删除 `courseType`、`supervisor`。
+- 教室列表删除“课程类型”“负责人”两列。
+- 教室创建、更新 DTO 删除对应字段。
+- 教室实体删除对应列,并通过数据库迁移删除已有列。
+- Excel 导入、导入模板和示例删除对应字段。
+- 排课保持现状:`subject` 表示科目,班型来自所选班级的 `classType`,教室不重复记录班型或课程类型。
+
+## 数据流
+
+教室只维护名称、楼栋、楼层、容量、规格、状态和备注。创建排课时选择班级、科目、教师、教室及时间;系统继续按教室和时间检测排课及租赁冲突,不增加课程类型匹配规则。
+
+## 兼容策略
+
+采用干净切换:所有调用方同时迁移,不保留 DTO 字段、实体列、别名或兼容逻辑。迁移删除历史 `course_type`、`supervisor` 数据。
+
+## 验证
+
+- 管理端构建通过。
+- 服务端相关测试和构建通过。
+- 创建、编辑教室时不再显示或提交两个字段。
+- 教室列表不再显示两列。
+- Excel 模板及导入不再包含两个字段。
+- 创建排课仍可正常选择教室并保存。