Files
gongxue-base/docs/superpowers/plans/2026-07-13-classroom-field-simplification.md

8.0 KiB
Raw Blame History

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 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:

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:

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

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