# RailwaySystem Phase 3:端到端验收矩阵与追踪表 (Acceptance Matrix)
> **设计目标:** 建立从 PRD 业务用例 (UC) → API Contract → Issue → 后端组件 → 前端组件 → 权限字符 → 测试用例 → 验收准则的全链路双向追踪体系,确保 53 个 P0 API 100% 落实且每个硬业务规则有唯一责任人。
---
## 1. 5 大跨表硬业务不变量与 1 项报到生命周期策略责任归属矩阵
系统确立的 5 大跨表拓扑一致性“硬不变量”与 1 项生命周期时限策略,责任全部落实到具体的唯一主责 Issue:
| 编号 | 规则公式 / 核心定义 | 唯一主责 Issue | 主责 Application Service | 拦截错误码 | 破坏性测试用例 |
|:---:|:---|:---:|:---:|:---:|:---|
| **INV-01** | $\text{attendance.enrollment.plan\_id} == \text{lesson.planCourse.plan\_id}$
*(严禁跨班打卡串签)* | `ISSUE-07-01` | `AttendanceAppService` | `ATTENDANCE_PLAN_MISMATCH (40080)` | `AttendanceInvariantTest.testCrossPlanCheckinBlocked` |
| **INV-02** | $\text{studentScore.enrollment.plan\_id} == \text{scoreItem.plan\_id}$
*(严禁跨班打分录入)* | `ISSUE-08-02` | `ScoreAppService` | `SCORE_PLAN_MISMATCH (40093)` | `ScoreInvariantTest.testCrossPlanScoreEntryBlocked` |
| **INV-03** | $\text{study.enrollment.plan\_id} == \text{planCourse.plan\_id}$
*(外部学习记录本期课程与班级一致)* | `ISSUE-09-01` | `LearningAppService` | `STUDY_EVENT_INVALID (40100)` | `LearningInvariantTest.testCrossPlanCourseStudyBlocked` |
| **INV-04** | $\text{scoreItem.planCourse.plan\_id} == \text{scoreItem.plan\_id}$
*(考核科目挂接课程必须属于本期计划)* | `ISSUE-08-01` | `ScoreAppService` | `SCORE_ITEM_COURSE_MISMATCH (40091)` | `ScoreConfigInvariantTest.testExternalCourseBindingBlocked` |
| **INV-05** | $0 \le \text{studentScore.score} \le \text{scoreItem.full\_score}$
*(录入分值非负且不得超过满分上限)* | `ISSUE-08-02` | `ScoreAppService` | `SCORE_EXCEEDS_FULL_SCORE (40092)` | `ScoreEntryValidationTest.testExceedFullScoreBlocked` |
| **POL-01** | $\text{report\_time} \le \text{plan.report\_deadline}$
*(报到生命周期时限策略,支持告警与强制放行)* | `ISSUE-05-03` | `ReportAppService` | `REPORT_DEADLINE_WARNING (40051)` | `ReportLifecyclePolicyTest.testOverdueWarningAndOverride` |
---
## 2. 53 个 P0 API 全生命周期全链路追踪矩阵 (PRD → API → Issue → Test)
> **覆盖率确认:** 涵盖 API Contract 正文全部 53 个 P0 API(包含 Module A 20 个、Module B 17 个、Module C 9 个、Module D 7 个),覆盖率达成 53/53 = 100%。严格杜绝任何未经评审的私自增改。
### 2.1 Module A:人员与基础资源 (16 个 P0 核心 API + 4 个宿舍基础 API)
| 序号 | 来源 UC | HTTP Method & Path | 对应 Issue ID | 后端实现组件 | 前端组件 / 页面 | 权限字符 | 自动化测试类 | 验收判定条件 (Acceptance Criteria) |
|:---:|:---|:---|:---:|:---|:---|:---|:---|:---|
| 1 | - | `GET /train/teacher/list` | `ISSUE-01-01` | `TrainTeacherController.list` | `views/train/teacher/index.vue` | `train:teacher:list` | `TeacherApiTest.testList` | 分页返回教师列表,包含脱敏与职称 |
| 2 | - | `GET /train/teacher/{tId}` | `ISSUE-01-01` | `TrainTeacherController.getInfo` | `views/train/teacher/index.vue` | `train:teacher:query` | `TeacherApiTest.testGet` | 正确回显指定教师详细信息 |
| 3 | - | `POST /train/teacher` | `ISSUE-01-01` | `TrainTeacherController.add` | 教师新增弹窗组件 | `train:teacher:add` | `TeacherApiTest.testAdd` | 手机号重复抛出 40001,正常新增落库 |
| 4 | - | `PUT /train/teacher/{tId}` | `ISSUE-01-01` | `TrainTeacherController.edit` | 教师编辑弹窗组件 | `train:teacher:edit` | `TeacherApiTest.testEdit` | 正确更新教师职务与联系电话 |
| 5 | - | `GET /train/student/list` | `ISSUE-01-02` | `TrainStudentController.list` | `views/train/student/index.vue` | `train:student:list` | `StudentApiTest.testList` | 分页返回学员主档,身份证掩码脱敏展示 |
| 6 | - | `GET /train/student/{sId}` | `ISSUE-01-02` | `TrainStudentController.getInfo` | `views/train/student/index.vue` | `train:student:query` | `StudentApiTest.testGet` | 返回学员完整信息,身份证默认掩码 |
| 7 | - | `POST /train/student` | `ISSUE-01-02` | `TrainStudentController.add` | 学员录入弹窗组件 | `train:student:add` | `StudentApiTest.testAdd` | 安全散列查重防重,可逆密文存入数据库 |
| 8 | - | `GET /train/course/list` | `ISSUE-01-03` | `TrainCourseController.list` | `views/train/course/index.vue` | `train:course:list` | `CourseApiTest.testList` | 分页展示公共课程库,不含冗余版本字段 |
| 9 | - | `GET /train/course/{cId}` | `ISSUE-01-03` | `TrainCourseController.getInfo` | `views/train/course/index.vue` | `train:course:query` | `CourseApiTest.testGet` | 正确获取课程学时与教学目标 |
| 10 | - | `POST /train/course` | `ISSUE-01-03` | `TrainCourseController.add` | 课程录入弹窗组件 | `train:course:add` | `CourseApiTest.testAdd` | 课程编码重复拦截抛 40003,新增成功 |
| 11 | - | `GET /train/classroom/list` | `ISSUE-01-04` | `TrainClassroomController.list` | `views/train/classroom/index.vue` | `train:classroom:list` | `ClassroomApiTest.testList` | 分页返回教室及核定容纳人数 |
| 12 | - | `GET /train/classroom/{classRoomId}` | `ISSUE-01-04` | `TrainClassroomController.getInfo` | `views/train/classroom/index.vue` | `train:classroom:list` | `ClassroomApiTest.testGet` | 回显教室详情及绑定的班牌信息 |
| 13 | - | `POST /train/classroom` | `ISSUE-01-04` | `TrainClassroomController.add` | 教室录入弹窗组件 | `train:classroom:edit` | `ClassroomApiTest.testAdd` | 容量非负校验,正常新增教室 |
| 14 | - | `GET /train/training-site/list` | `ISSUE-01-04` | `TrainTrainingSiteController.list` | `views/train/site/index.vue` | `train:site:list` | `SiteApiTest.testList` | 分页返回实训场地(VR/3D演练区) |
| 15 | - | `GET /train/training-site/{siteId}` | `ISSUE-01-04` | `TrainTrainingSiteController.getInfo` | `views/train/site/index.vue` | `train:site:list` | `SiteApiTest.testGet` | 回显实训场地设备类型与容纳人数 |
| 16 | - | `POST /train/training-site` | `ISSUE-01-04` | `TrainTrainingSiteController.add` | 实训场地录入弹窗组件 | `train:site:edit` | `SiteApiTest.testAdd` | 场地名称查重校验,正常新增场地 |
| *17*| - | `GET /train/dormitory/list` | `ISSUE-01-05` | `TrainDormitoryController.list` | `views/train/dormitory/index.vue`| `train:dormitory:list`| `DormitoryApiTest.testList` | 分页返回房间、核定床位、已住与剩余 |
| *18*| - | `GET /train/dormitory/{dormId}` | `ISSUE-01-05` | `TrainDormitoryController.getInfo` | `views/train/dormitory/index.vue`| `train:dormitory:list`| `DormitoryApiTest.testGet` | 回显房间当前入住学员列表 |
| *19*| - | `POST /train/dormitory` | `ISSUE-01-05` | `TrainDormitoryController.add` | 宿舍录入弹窗组件 | `train:dormitory:edit`| `DormitoryApiTest.testAdd` | 同楼栋房号重复拦截抛 40006,新增成功 |
| *20*| - | `GET /train/dormitory/options` | `ISSUE-01-05` | `TrainDormitoryController.options` | 楼栋楼层级联选择器组件 | `train:dormitory:list`| `DormitoryApiTest.testOptions`| 结构化返回楼栋-楼层-房间级联树 |
*(注:*项为 Module A 中宿舍基础维护接口,经 CG-01 裁决正式确认纳入一期 P0 范围)*
---
### 2.2 Module B:开班计划与排课 (17 个 P0 核心 API)
| 序号 | 来源 UC | HTTP Method & Path | 对应 Issue ID | 后端实现组件 | 前端组件 / 页面 | 权限字符 | 自动化测试类 | 验收判定条件 (Acceptance Criteria) |
|:---:|:---|:---|:---:|:---|:---|:---|:---|:---|
| 17 | - | `GET /train/plans` | `ISSUE-02-01` | `TrainPlanController.list` | `views/train/plan/index.vue` | `train:plan:list` | `PlanLifecycleTest.testList` | 分页返回培训班列表与生命周期状态 |
| 18 | UC-01 | `POST /train/plans` | `ISSUE-02-01` | `PlanAppService.createPlan` | 创建培训计划表单弹窗 | `train:plan:add` | `PlanLifecycleTest.testCreate`| 时间起止校验、自动生成唯一计划编号 |
| 19 | - | `GET /train/plans/{planId}` | `ISSUE-02-01` | `TrainPlanController.getInfo` | 计划详情页基本信息卡片 | `train:plan:query` | `PlanLifecycleTest.testGet` | 回显计划负责人、招生上限与周期 |
| 20 | UC-02 | `PUT /train/plans/{planId}` | `ISSUE-02-01` | `PlanAppService.updatePlan` | 编辑培训计划表单弹窗 | `train:plan:edit` | `PlanLifecycleTest.testUpdate`| 已结训状态禁止修改抛 40012,正常更新 |
| 21 | UC-29 | `POST /train/plans/{planId}/complete` | `ISSUE-02-01` | `PlanAppService.completePlan` | 计划工作台“结训归档”按钮 | `train:plan:complete`| `PlanLifecycleTest.testComplete`| 状态置为已结束,学员状态置为已完成 |
| 22 | - | `GET /train/plans/{planId}/workbench`| `ISSUE-10-01` | `PlanAppService.getWorkbench` | `views/train/plan/workbench.vue` | `train:plan:query` | `PlanWorkbenchTest.testWorkbench`| 聚合返回班级工作台驾驶舱全部指标 |
| 23 | - | `GET /train/plans/{planId}/courses` | `ISSUE-02-02` | `TrainPlanCourseController.list` | 计划详情-本期课程配置 Tab | `train:plan:query` | `PlanCourseTest.testList` | 返回本期已选课程及生成的 planCourseId |
| 24 | UC-03 | `POST /train/plans/{planId}/courses` | `ISSUE-02-02` | `PlanAppService.configureCourses` | 课程库穿梭选择器弹窗 | `train:plan:edit` | `PlanCourseTest.testConfigure`| 重复选课抛 40020,成功引入本期课程 |
| 25 | UC-06 | `GET /train/plans/{planId}/enrollments`| `ISSUE-03-01` | `EnrollmentAppService.list` | 计划详情-参训人员名录表格 | `train:enrollment:list`| `EnrollmentCoreTest.testList` | 分页返回学员参训花名册与报到状态 |
| 26 | UC-04 | `POST /train/plans/{planId}/enrollments`| `ISSUE-03-01` | `EnrollmentAppService.addSingle` | 添加参训学员弹窗组件 | `train:enrollment:add` | `EnrollmentCoreTest.testAdd` | 招生超限抛 40031,生成 enrollmentId |
| 27 | UC-05 | `POST /train/plans/{planId}/enrollments/batch`| `ISSUE-03-02` | `EnrollmentAppService.importBatch` | Excel 名单导入上传模态框 | `train:enrollment:import`| `EnrollmentBatchTest.testImport`| 解析 500 行花名册,错误行明细回显 |
| 28 | - | `GET /train/enrollments/{enrollmentId}` | `ISSUE-03-01` | `EnrollmentAppService.getDetail` | 学员参训全景卡片组件 | `train:enrollment:query`| `EnrollmentCoreTest.testGet` | 聚合回显住宿、考勤、成绩与学时总览 |
| 29 | UC-07 | `POST /train/plans/{planId}/lessons` | `ISSUE-04-01` | `SchedulingAppService.createLesson` | 排课弹窗(教师/场地/时段) | `train:lesson:add` | `LessonSchedulingTest.testCreate`| 四维冲突检测拦截,行排他锁防穿透 |
| 30 | UC-08 | `PUT /train/lessons/{lessonId}` | `ISSUE-04-01` | `SchedulingAppService.updateLesson` | 修改课次弹窗组件 | `train:lesson:edit` | `LessonSchedulingTest.testUpdate`| 已有打卡考勤记录时严禁修改上课时间 |
| 31 | UC-10 | `POST /train/lessons/check-conflict` | `ISSUE-04-01` | `SchedulingAppService.checkConflict` | 排课表单时段失焦预检组件 | `train:lesson:query` | `LessonConflictTest.testCheck` | 实时返回冲突原因与冲突课次列表 |
| 32 | UC-11 | `GET /train/plans/{planId}/timetable` | `ISSUE-04-02` | `SchedulingAppService.getTimetable` | 计划详情-完整课程表日历视图 | `train:plan:query` | `PlanTimetableTest.testTimetable`| 按日历周/日嵌套聚合课表,零失真展示 |
| 33 | UC-15 | `POST /train/enrollments/{enrollmentId}/report`| `ISSUE-05-03` | `ReportAppService.confirmReport` | 学员报到确认操作按钮 / 弹窗 | `train:report:confirm` | `ReportLifecycleTest.testReport` | 逾期未特批提示 40051,状态置为已报到 |
---
### 2.3 Module C:住宿、门禁与考勤 (9 个 P0 核心 API)
| 序号 | 来源 UC | HTTP Method & Path | 对应 Issue ID | 后端实现组件 | 前端组件 / 页面 | 权限字符 | 自动化测试类 | 验收判定条件 (Acceptance Criteria) |
|:---:|:---|:---|:---:|:---|:---|:---|:---|:---|
| 34 | UC-12 | `POST /train/plans/{planId}/accommodations/preallocate`| `ISSUE-05-01` | `AccommodationAppService.preallocate` | 批量预分配宿舍策略弹窗 | `train:accommodation:assign` | `AccommodationLockTest.testPrealloc` | `SELECT FOR UPDATE` 排他锁防超卖,严格性别隔离 |
| 35 | UC-13 | `PUT /train/enrollments/{enrollmentId}/accommodation` | `ISSUE-05-02` | `AccommodationAppService.adjust` | 单人调房 Dialog 弹窗 | `train:accommodation:adjust` | `AccommodationAdjustTest.testAdjust` | 目标房间满员抛 40062,跨性别调房拦截 |
| 36 | - | `PUT /train/enrollments/{enrollmentId}/accommodation/day-student`| `ISSUE-05-02` | `AccommodationAppService.setDayStudent` | 设置走读操作按钮 | `train:accommodation:adjust` | `AccommodationAdjustTest.testDay` | 走读设置成功,立即释放已占用房间床位 |
| 37 | UC-14 | `GET /train/plans/{planId}/accommodations` | `ISSUE-05-02` | `AccommodationAppService.list` | 计划详情-住宿分配台账列表 | `train:accommodation:list` | `AccommodationListTest.testList` | 分页返回全班住宿床位与门禁下发状态 |
| 38 | UC-16 | `POST /train/accommodations/{accommodationId}/auth/sync`| `ISSUE-06-02` | `ExternalIntegrationAppService.syncDoor` | 住宿列表“重试门禁同步”按钮 | `train:dormauth:sync` | `DoorAuthAsyncTest.testRetrySync` | 事务外调用 Adapter,失败更新状态为 2 |
| 39 | UC-17 | `POST /train/attendance/sign-in` | `ISSUE-07-01` | `AttendanceAppService.recordSignIn` | 班牌刷脸模拟回调接口 | `train:attendance:checkin` | `AttendanceInvariantTest.testSignIn` | **强制落实 INV-01 拦截跨班串签**,幂等查重 |
| 40 | UC-18 | `POST /train/attendance/sign-out` | `ISSUE-07-01` | `AttendanceAppService.recordSignOut` | 班牌签退刷脸模拟回调接口 | `train:attendance:checkin` | `AttendanceSignInTest.testSignOut` | 课次签退落库,自动计算出勤考勤结论 |
| 41 | UC-19 | `PUT /train/attendance/{checkinId}` | `ISSUE-07-02` | `AttendanceAppService.adjustAttendance` | 考勤异常修改 Dialog 弹窗 | `train:attendance:edit` | `AttendanceAdjustTest.testAdjust` | 修改考勤结论,强制标记 `manual_flag = 1` |
| 42 | - | `GET /train/lessons/{lessonId}/attendance` | `ISSUE-07-02` | `AttendanceAppService.getLessonRoster` | 教师端-课次考勤名单抽屉组件 | `train:attendance:list` | `AttendanceRosterTest.testRoster` | 左联花名册,准确显示应到/实到与缺勤名单 |
---
### 2.4 Module D:成绩、学习与统计 (7 个 P0 核心 API)
| 序号 | 来源 UC | HTTP Method & Path | 对应 Issue ID | 后端实现组件 | 前端组件 / 页面 | 权限字符 | 自动化测试类 | 验收判定条件 (Acceptance Criteria) |
|:---:|:---|:---|:---:|:---|:---|:---|:---|:---|
| 43 | - | `GET /train/plans/{planId}/score-items` | `ISSUE-08-01` | `ScoreAppService.getScoreItems` | 计划详情-考核科目配置表格 | `train:score:query` | `ScoreConfigTest.testList` | 返回本期考核项目、满分值与百分比权重 |
| 44 | UC-20 | `POST /train/plans/{planId}/score-items` | `ISSUE-08-01` | `ScoreAppService.configureScoreItems` | 成绩项目配置与权重保存按钮 | `train:score:config` | `ScoreConfigInvariantTest.testConfig` | **落实 INV-04 课程归属**,权重总和必须 100% |
| 45 | UC-21 | `POST /train/scores/entry` | `ISSUE-08-02` | `ScoreAppService.recordScores` | 成绩台账矩阵单元格打分输入 | `train:score:edit` | `ScoreInvariantTest.testScoreEntry` | **落实 INV-02 跨班与 INV-05 满分上限拦截** |
| 46 | - | `GET /train/plans/{planId}/scores/matrix` | `ISSUE-08-02` | `ScoreAppService.getScoreMatrix` | 计划详情-全班成绩台账大表 | `train:score:query` | `ScoreMatrixTest.testMatrix` | 动态行转列呈现科目,准确计算加权综合分 |
| 47 | UC-23 | `GET /train/enrollments/{enrollmentId}/scores` | `ISSUE-08-02` | `ScoreAppService.getStudentScoreReport` | 学员个人成绩单卡片弹窗 | `train:score:query` | `ScoreReportTest.testStudentScores` | 呈现单人各科目得分、满分、权重与加权总分 |
| 48 | UC-24 | `POST /train/study-records/receive` | `ISSUE-09-01` | `LearningAppService.receiveRecord` | 外部系统实训学时推送端点 | 系统放行 / 签名验证 | `LearningInvariantTest.testReceive` | **落实 INV-03 课程计划一致**,防重事件 ID 幂等三态响应 |
| 49 | UC-26 | `GET /train/plans/{planId}/statistics/overview` | `ISSUE-10-02` | `StatisticsAppService.getOverview` | 计划详情-培训成效统计看板 | `train:statistics:query` | `PlanStatisticsTest.testOverview` | 聚合报到率、出勤率、加权平均分与总实训学时 |
---
## 3. 验收追踪闭环结论
- **PRD 用例追踪:** 17 个 P0 核心用例全部精确映射到对应的 API、Issue 与自动化测试类;
- **API 覆盖闭环:** 53 个 P0 API 无一遗漏全部被 Issue 认领(53/53,100% 覆盖率);
- **规则硬边界:** 5 项跨表硬不变量由 `ISSUE-07-01`、`ISSUE-08-01`、`ISSUE-08-02`、`ISSUE-09-01` 唯一主责承揽,1 项生命周期时限策略由 `ISSUE-05-03` 唯一承揽;
- **质量闸门保证:** 每一个 Issue 均配备了严格对应的自动化测试类和基于 Given/When/Then 的判定条件,彻底消除了模糊验收隐患。