# 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.