Files
railwaysystem/docs/development/issue-02-02-plan-course.md
T

110 lines
6.2 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.
# ISSUE-02-02 本期培训课程引入与配置 开发与验收记录
## 1. 基线与分支信息
- **基线 Commit:** `4682df0399d86fa73105ea23ba2daee06cfb0fa0` (`integration/wave1a-foundation`)
- **开发分支:** `feat/issue-02-02-plan-course`
- **Worktree 路径:** `/Users/wzb/Desktop/项目/RailwaySystem-worktrees/issue-02-02-plan-course`
---
## 2. 3 个正式公开 Endpoint
| 序号 | HTTP Method | Path | 权限标识 | 业务说明 |
|:---|:---|:---|:---|:---|
| 1 | `GET` | `/train/plans/{planId}/courses` | `train:plan:query` | 查询指定培训班本期已配置课程列表及课程元数据 |
| 2 | `POST` | `/train/plans/{planId}/courses` | `train:plan:edit` | 引入课程库公共课程配置至本期计划 (UC-03) |
| 3 | `DELETE` | `/train/plans/{planId}/courses/{planCourseId}` | `train:plan:edit` | 移除本期课程(受下游引用与生命周期守护) |
- **控制器归属:** `com.ruoyi.web.controller.train.TrainPlanCourseController`
- **应用编排归属:** `com.ruoyi.train.service.PlanAppService` (严格复用既有 11 个应用服务之一,未新增第 12 个 Application Service)。
---
## 3. 请求与响应模型 (DTO / VO)
### 3.1 请求对象 (`PlanCourseAddDTO`)
- 严格遵循 API Contract V1.0.2 契约,仅暴露以下两个 JSON 字段:
- `cIds`: `List<String>`, `@NotEmpty` (课程主键 ID 集合,绑定 `@JsonProperty("cIds")`)
- `remark`: `String`, `@Size(max = 500)` (配置说明)
- 经单元测试反射与 Jackson 序列化静态校验,确认不存在 `courseIds` 等多余或模糊属性。
### 3.2 响应对象 (`PlanCourseItemVO`)
- `planCourseId`: `Long` (本期课程实例唯一主键)
- `planId`: `String` (培训班编号)
- `cId`: `String` (课程主数据编号)
- `cCode`: `String` (课程业务编码)
- `cName`: `String` (课程名称)
- `cType`: `String` (课程类型)
- `cDuration`: `Integer` (标准学时)
- `teachingObjective`: `String` (教学目标)
- `sortNo`: `Integer` (排序号)
- `remark`: `String` (配置说明)
- `createTime`: `Date` (创建时间)
---
## 4. 生命周期规则与复用
- 严格复用 `TrainClassPlan` 冻结的生命周期与计算规则 (`effective_plan_status`):
- `status = 0` (未开始) 与 `status = 1` (进行中):允许配置课程与移除课程;
- `status = 2` (已结束) 与 `status = 3` (已取消):只读锁定,禁止任何增删改课程操作,抛出 `40012` (`PLAN_STATUS_LOCKED`)。
- 不在课程服务层自行发明独立的时间比对判断,确保前台视图与后台判断无状态分裂。
---
## 5. 唯一约束与并发边界 (UNIQUE & Concurrency)
- 数据库级约束:`CONSTRAINT uq_plan_course UNIQUE (plan_id, c_id)`。
- 应用层前置校验:
- 请求体内自包含查重(使用 `LinkedHashSet` 校验,重复抛 `40020` `PLAN_COURSE_DUPLICATED`);
- 已存在配置查重(`selectExistingCIdsByPlanId` 查重,存在交集抛 `40020` `PLAN_COURSE_DUPLICATED`)。
- 并发兜底机制:
- 当高并发下两个事务同时穿透前置检查并执行批量插入时,数据库触发 `uq_plan_course` 唯一约束异常;
- 应用层 `PlanAppServiceImpl` 捕获 `DuplicateKeyException` 及 `uq_plan_course` 冲突异常,精确转换为 `40020` `PLAN_COURSE_DUPLICATED`,防止裸 500 或数据库异常泄漏。
---
## 6. 下游引用保护与删除阻断
- **课次引用 (`train_lesson`):** 若存在已排定课次,阻断删除并返回 `40021` (`PLAN_COURSE_HAS_LESSONS`);
- **成绩项引用 (`train_score_item`):** 若已关联成绩考核项目,阻断删除并返回 `40022` (`PLAN_COURSE_HAS_SCORE_ITEMS`);
- **外部学习记录 (`train_study_duration`):** 若已产生学习时长记录,阻断删除并抛出业务异常;
- **级联删除:** 数据库外键定义为 `ON DELETE NO ACTION`,代码层严格拦截,严禁任何形式的 cascade delete。
---
## 7. Contract Gap 登记
1. **Course 不存在 / 停用错误码缺失:**
- API Contract `10_permission_and_error_codes.md` 仅针对开班计划定义了 `40410` (`PLAN_NOT_FOUND`),未给课程库分配 `COURSE_NOT_FOUND` 或 `COURSE_INACTIVE` 的专有 40xxx 错误码。
- 实现方案:抛出带明确错误信息的 `ServiceException`(消息为“课程不存在或已被删除”、“课程已被停用,无法配置到本期培训计划中”),坚决不复用或污染 `40020`。
2. **StudyDuration 下游引用错误码缺失:**
- 错误码 Catalog 仅定义了 `40021` (Lessons) 与 `40022` (ScoreItems),未定义 `PLAN_COURSE_HAS_STUDY_DURATIONS` 的专属错误码。
- 实现方案:fail closed 拦截,抛出提示“该课程已产生学习记录,不可直接移除”,禁止物理级联删除。
---
## 8. 自动化测试结果
- 单元测试套件:`com.ruoyi.train.service.impl.PlanCourseTest` 与 `PlanAppServiceImplTest`
- 测试总数:35 个测试用例全部通过(0 failures, 0 errors, 0 skipped)。
- 覆盖场景:
1. 正常批量配置课程与元数据返回;
2. 请求体内自重复课程拒绝 (`40020`);
3. 数据库已配置课程重复添加拦截 (`40020`, AC-01);
4. 计划不存在拦截 (`40410`);
5. 课程不存在拦截(独立异常语义,非 40020);
6. 课程停用拦截(独立异常语义,非 40020);
7. 已结束/已取消计划拒绝修改 (`40012`);
8. 批量选课 `@Transactional` 原子事务注解验证;
9. 本期课程列表查询与字段回显 (AC-02);
10. 正常移除本期课程;
11. 锁定状态计划禁止移除课程 (`40012`);
12. 移除不存在或跨计划的课程配置拦截;
13. 存在排定课次时拒绝删除 (`40021`);
14. 存在成绩考核项时拒绝删除 (`40022`);
15. 存在外部学习时长记录时拒绝删除;
16. `PlanCourseAddDTO` 严格单一 JSON 属性契约测试;
17. 并发插入数据库唯一键冲突转换映射为 `40020` 测试;
18. 实体不变量检验(确保 `TrainClassPlan` 不含有单课程字段)。
---
## 9. 规范与红线遵循
- **DB 真实写入:** 否(测试全部使用 JVM InMemory Fake Repository,未连接真实数据库)。
- **Generator 执行:** 否(未执行任何代码生成器覆写或额外 DDL)。
- **Commit 状态:** 未执行 `git commit` / `git merge` / `git rebase` / `git tag`。