Files
railwaysystem/docs/issues-v1/06_acceptance_matrix.md
T

115 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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}$<br/>*(严禁跨班打卡串签)* | `ISSUE-07-01` | `AttendanceAppService` | `ATTENDANCE_PLAN_MISMATCH (40080)` | `AttendanceInvariantTest.testCrossPlanCheckinBlocked` |
| **INV-02** | $\text{studentScore.enrollment.plan\_id} == \text{scoreItem.plan\_id}$<br/>*(严禁跨班打分录入)* | `ISSUE-08-02` | `ScoreAppService` | `SCORE_PLAN_MISMATCH (40093)` | `ScoreInvariantTest.testCrossPlanScoreEntryBlocked` |
| **INV-03** | $\text{study.enrollment.plan\_id} == \text{planCourse.plan\_id}$<br/>*(外部学习记录本期课程与班级一致)* | `ISSUE-09-01` | `LearningAppService` | `STUDY_EVENT_INVALID (40100)` | `LearningInvariantTest.testCrossPlanCourseStudyBlocked` |
| **INV-04** | $\text{scoreItem.planCourse.plan\_id} == \text{scoreItem.plan\_id}$<br/>*(考核科目挂接课程必须属于本期计划)* | `ISSUE-08-01` | `ScoreAppService` | `SCORE_ITEM_COURSE_MISMATCH (40091)` | `ScoreConfigInvariantTest.testExternalCourseBindingBlocked` |
| **INV-05** | $0 \le \text{studentScore.score} \le \text{scoreItem.full\_score}$<br/>*(录入分值非负且不得超过满分上限)* | `ISSUE-08-02` | `ScoreAppService` | `SCORE_EXCEEDS_FULL_SCORE (40092)` | `ScoreEntryValidationTest.testExceedFullScoreBlocked` |
| **POL-01** | $\text{report\_time} \le \text{plan.report\_deadline}$<br/>*(报到生命周期时限策略,支持告警与强制放行)* | `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 的判定条件,彻底消除了模糊验收隐患。