8.0 KiB
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<void>called during application bootstrap. -
Step 1: Write failing migration tests
Extend MigrationsPrivate with removeUnusedClassroomColumns(): Promise<void>. Add focused tests proving that the method:
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 <name> per present legacy column. Always release the runner in finally.
Remove these entity properties and decorators:
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 optionalnoteswhere 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:
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;
batchImportrow 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_FIELDSused to document/test the UI contract; list and form withoutcourseTypeorsupervisor. -
Step 1: Write a failing UI contract test
Create a small contract test:
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
courseTypeorsupervisorpath. -
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:
- Adding/editing a classroom shows no course type or supervisor field.
- Classroom list shows neither obsolete column.
- Downloaded import template has five headers: 教室名、楼栋、楼层、类型、容量.
- Creating a schedule still selects and saves a classroom normally.
Expected: all four scenarios succeed without console or API errors.