13 KiB
ISSUE-02-01 培训计划生命周期与基础管理开发验收
1. 基线与范围
- 分支:
feat/issue-02-01-plan - 起始 HEAD:
7fcaea29b8810546fd972ba0c9d366c71a357d63 - Issue 基线:
issues-v1.0 - API 基线:
api-v2-contract-v1.0.1(4873e5f555ecfe7d1d21d313007c2c8f6f65d6b6) - Generator 策略为
PARTIAL,本轮未执行 Generator Import。 - 本 Issue 只实现 Plan 自身的创建、编辑、列表、详情、结束和稳定上下文;未实现 PlanCourse、Enrollment、Lesson、Accommodation、Attendance、Score、Study、Statistics。
2. 旧实现审计
| 旧文件 | 判定 | 处理 |
|---|---|---|
TrainClassPlan.java |
MODIFY | 按 Database V2 删除 cId/tId/createUser,增加 reportDeadline/delFlag,使用标准审计字段。 |
TrainClassPlanMapper.java |
MODIFY | 移除物理删除能力,增加名称预查重、生命周期行锁读取和状态更新。 |
TrainClassPlanMapper.xml |
REPLACE | 旧单课程/单教师 SQL 替换为 V2 字段、有效状态、列表筛选、概览只读计数及行锁 SQL。 |
ITrainClassPlanService.java |
MODIFY | 保留为基础持久化边界,不承载生命周期编排。 |
TrainClassPlanServiceImpl.java |
MODIFY | 只封装 Mapper。 |
TrainClassPlanController.java |
DELETE | 删除旧 /train/plan CRUD、删除和导出端点。 |
TrainPlanController.java |
REPLACE | 使用正式 /train/plans Contract,只依赖 PlanAppService。 |
build_ui/src/api/train/plan.js |
MODIFY | 使用正式复数路径,删除 delete API,增加 complete Command。 |
build_ui/src/views/train/plan/index.vue |
REPLACE | 仅呈现和编辑 Plan 自身,不绑定单课程或单教师。 |
旧模型的 Plan = 单课程 + 单教师 已从 Domain、Mapper、Controller DTO/VO、前端 API 和页面中消除。课程属于 ISSUE-02-02 的 PlanCourse,教师属于具体 Lesson;本 Issue 未提前实现这些写能力。
3. 正式 P0 API 与 Controller 契约
| Priority | Method | Path | Permission | Request | Response |
|---|---|---|---|---|---|
| P0 | GET | /train/plans |
train:plan:list |
PlanQuery:pageNum/pageSize 由分页框架接管,业务字段为 planName/planStatus/startTimeBegin/startTimeEnd |
TableDataInfo<PlanItemVO> |
| P0 | POST | /train/plans |
train:plan:add |
PlanCreateDTO |
Contract 未命名专用响应 VO;当前为标准 AjaxResult,data 为 plan_id |
| P0 | GET | /train/plans/{planId} |
train:plan:query |
Path planId |
AjaxResult<PlanDetailVO> |
| P0 | PUT | /train/plans/{planId} |
train:plan:edit |
Path planId + PlanUpdateDTO |
标准空 AjaxResult |
| P0 | POST | /train/plans/{planId}/complete |
train:plan:complete |
Path planId,无 Request Body |
标准空 AjaxResult |
TrainPlanController 没有 Contract 外 Endpoint;旧 TrainClassPlanController 已删除,不存在双 Controller 路由。Controller 未接收或返回 TrainClassPlan Persistence Domain。
3.1 实际 DTO/VO 清单
PlanCreateDTO:planName/startTime/endTime/reportDeadline/recruitNum/trainAddress/applyCondition/remark。来源以 Module B 1.2 的具体 JSON 和校验规则为准。PlanUpdateDTO:planName/startTime/endTime/reportDeadline/recruitNum/trainAddress/applyCondition/remark,不接受planStatus。Module B 1.4 明列名称、地址、人数、报到截止和备注;Issue 业务目标及 Service Boundary 同时要求起止时间维护与课次边界检查,因此保留起止时间。applyCondition的更新字段在目录中未逐项展开,登记为 Contract 表述缺口,不修改冻结文档。PlanQuery:planName/planStatus/startTimeBegin/startTimeEnd;分页字段由 RuoYi 接管。PlanItemVO:planId/planName/planStatus/startTime/endTime/reportDeadline/trainAddress/recruitNum/courseCount/enrolledCount。PlanDetailVO:planId/planName/planStatus/startTime/endTime/reportDeadline/trainAddress/recruitNum/applyCondition/remark/createBy/createTime/updateBy/updateTime。
docs/api-v2/06_dto_vo_catalog.md 的 PlanCreateDTO 摘要未列 applyCondition,但 docs/api-v2/03_module_b_plan_enrollment_schedule_api.md 1.2 的确认 JSON 明确包含它;实现采用更具体的 Module B 定义,此差异作为 Contract Gap 记录。
4. PlanAppService 边界
listPlans:读取分页概览,不做写操作。getPlan:读取PlanDetailVO,不存在时抛出 Plan not found。createPlan:名称规范化与友好查重、时间/人数校验、生成技术主键、强制初始状态 0。updatePlan:行锁读取、有效状态校验、名称和字段校验后更新;客户端不能直接改状态。completePlan:先确认至少一个真实 Enrollment Completion Port 已接线,再行锁读取、校验并更新 Plan 状态,随后在同一同步事务调用 Port。
Controller 只处理 HTTP、Bean Validation、权限、日志和标准响应,不调用 Mapper 或编排多步业务。
5. 生命周期与 effective status
- 创建只写
plan_status=0。 - DB 不会因时间到达而将 0 写成 1;
TrainClassPlanMapper.xml#effectivePlanStatusPolicy是唯一生产规则源:持久化状态为 0 且start_time <= CURRENT_TIMESTAMP时,只读结果呈现为 1。 - 列表输出、
planStatus筛选、详情、编辑的FOR UPDATE读取、complete 的FOR UPDATE读取均复用该 SQL fragment。Application Service 不再重复时间判断。 - 查询不会写数据库,也不会根据
end_time自动结束;结束只能由正式 complete Command 写状态 2。 - 状态 0 与派生状态 1 均允许 complete;Contract 未要求
now >= end_time,实现未增加该条件。 - 状态 2 重复 complete 返回
40014;状态 3 complete 返回40012。 - 状态 2/3 禁止编辑。状态 3 仅作历史只读兼容;没有 cancel Endpoint,前端没有取消按钮。
- 后续业务若需判断 Plan 状态,应通过
ITrainClassPlanService的正式读取/锁读取取得已应用 effective status 的 Plan,而不是自行复制时间判断。
6. Enrollment Completion Port 审计
- 接口:
PlanEnrollmentCompletionPort。 - Main/生产实现类:无。
@Configuration/@Bean绑定:无。- 构造注入:
PlanAppServiceImpl使用ObjectProvider<PlanEnrollmentCompletionPort>收集已注册 adapter;Bean 缺失不阻断应用启动,但执行 complete 时会 fail closed。 - 测试实现:测试类内部
RecordingEnrollmentPort,另有 lambda throwing fake 用于异常传播;都不进入生产 runtime。 - 分类:Case C — 只有接口与测试 Fake,生产尚未接线。
没有注册 silent no-op 实现,也没有伪造 Enrollment UPDATE。当前没有生产 adapter 时,completePlan 在任何 Plan 查询或状态更新前抛出无业务错误码的内部 IllegalStateException;请求失败,Plan 不会产生单边 status=2。ISSUE-03-01 必须实现真实 adapter,并以集成测试验证 enrollment_status=4、complete_time 以及任一步失败时的跨表回滚。
AC-03 拆分
- AC-03a — UNIT_PASS:单元测试验证 Plan 状态更新、Completion Port 恰好调用一次、缺失 Port 时 fail closed、端口异常不被吞掉;同时确认
completePlan使用@Transactional(rollbackFor=Exception.class)且端口在方法内同步调用。 - AC-03b — INTEGRATION_PENDING (ISSUE-03-01):真实 Enrollment 批量
status=4 + complete_time及跨表提交/回滚尚未验证。 - Integration Safety — UNIT_PASS:Port missing 时在 Plan update 前失败,状态更新调用次数为 0,Plan 保持原状态。
当前证据只能表述为“transaction orchestration seam 已验证”;不能表述为完整跨表事务或 Enrollment runtime 已通过。
7. 错误码映射与 Contract Gap
| 常量 | 值 | 冻结语义与来源 | 当前触发位置 |
|---|---|---|---|
PLAN_TIME_INVALID |
40010 | API 10 错误码表;Module B 1.2;ISSUE-02-01 | create/update 的 start >= end;reportDeadline > startTime 同属计划时间参数校验 |
PLAN_NAME_DUPLICATED |
40011 | API 10;Module B 1.2;ISSUE-02-01 | create/update 的 Service 友好名称预查重 |
PLAN_STATUS_LOCKED |
40012 | API 10;ISSUE-02-01 | 状态 2/3 编辑;状态 3 complete |
PLAN_LESSON_OUT_OF_BOUNDS |
40013 | API 10(UC-02) | 本 Issue 不实现 Lesson,当前无触发点;待 Scheduling 边界接入,未伪造校验 |
PLAN_ALREADY_COMPLETED |
40014 | API 10(UC-29);ISSUE-02-01 | 状态 2 重复 complete |
PLAN_NOT_FOUND |
40410 | 冻结 Issue Backlog 的多个 Plan 依赖用例 | detail/update/complete 的计划不存在 |
API 10 的全局错误码表未列 40410、Module B 1.3 也未为详情逐项列 not-found code,但冻结 Issue Backlog 已使用 PLAN_NOT_FOUND (40410)。实现未发明新码;此目录间缺口仅登记,不修改冻结文档。招生人数由 DTO Bean Validation 处理,Service 防御校验使用普通 ServiceException,不再错误复用 40010,也未新增业务错误码。
8. 名称唯一与技术主键
- Database V2 没有
UNIQUE(plan_name);当前只有 Service 层忽略首尾空格与大小写的友好重复预检查,并发强唯一性不保证。本 Issue 不新增 UNIQUE。 - “自动编号”仅指
plan_id = IdUtils.fastSimpleUUID()生成的 32 位 UUID 技术主键。没有、也未发明plan_code或plan_no物理字段。
9. 测试依赖与 14 个测试方法
ruoyi-train/pom.xml 仅新增:
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
没有新增 Mockito、Spring Boot Test、H2 或生产 runtime 依赖。全部 14 个测试使用 JVM InMemoryPlanService Fake 和测试 Port,不 Mock JDBC、不连接也不写真实数据库:
| # | @Test 方法 |
验证规则 |
|---|---|---|
| 1 | createPlanShouldGenerateIdAndForceInitialStatus |
32 位 plan_id、名称 trim、强制状态 0、审计人,并通过反射确认 Domain 无 cId/tId |
| 2 | createPlanShouldRejectReversedOrEqualTime |
start > end 与 start == end 均返回 40010 |
| 3 | createPlanShouldRejectReportDeadlineAfterStart |
报到截止晚于开班返回 40010 |
| 4 | createPlanShouldRejectDuplicatedName |
名称 trim/忽略大小写预查重返回 40011 |
| 5 | getPlanShouldReturnDetailAndRejectMissingPlan |
detail VO 映射及不存在返回 40410 |
| 6 | getPlanShouldExposeInProgressStatusAfterStartTime |
0→1 有效状态只读呈现,且持久化 Fake 中仍为 0,证明查询不回写 |
| 7 | updatePlanShouldUpdateEditablePlan |
状态 0/1 可编辑与审计更新 |
| 8 | updatePlanShouldRejectCompletedOrCancelledPlan |
状态 2、状态 3 分别禁止 edit,均返回 40012 |
| 9 | completePlanShouldUpdateStatusAndInvokeEnrollmentBoundary |
Plan→2、Port 参数/次数及事务注解 |
| 10 | completePlanShouldPropagateEnrollmentPortFailure |
Port 运行时异常保持原异常向事务代理传播;Fake 记录更新发生在 Port 调用前 |
| 11 | completePlanShouldFailClosedWhenEnrollmentPortMissing |
无 Port 时抛内部异常、状态更新调用为 0、Plan 保持状态 0 |
| 12 | completePlanShouldRejectRepeatedCompletion |
重复 complete 返回 40014,Port 不重复调用 |
| 13 | completePlanShouldRejectMissingOrCancelledPlan |
不存在返回 40410;状态 3 complete 返回 40012 |
| 14 | listPlansShouldDelegatePlanQuery |
list 委托并返回 PlanItemVO |
该单元测试不能证明真实 Spring 事务回滚或 Enrollment SQL;这些证据属于 AC-03b 集成门禁。
10. Database 与权限影响
- 未执行 Generator Import;无 DDL、迁移、ALTER/CREATE/DROP。
- 未修改
007_dicts.sql、sys_menu、sys_dict、sys_role、sys_role_menu。 - 未写任何测试/正式业务数据;测试均为 JVM 内存数据。
- 后端权限严格为
train:plan:list/query/add/edit/complete;前端按钮只使用train:plan:add/edit/complete,无system:plan:*。
11. Acceptance Criteria
| AC | 状态 | 证据与边界 |
|---|---|---|
| AC-01 逆序日期返回 40010 | UNIT_PASS | 逆序和相等边界均覆盖。 |
| AC-02 状态 2 修改返回 40012 | UNIT_PASS | 同时覆盖历史状态 3。 |
| AC-03a Plan complete orchestration seam | UNIT_PASS | 状态更新、Port 一次调用、缺失 Port fail closed、异常传播及事务注解已验证。 |
| AC-03b 30 名 Enrollment 真实联动 | INTEGRATION_PENDING (ISSUE-03-01) | 未实现 Enrollment adapter/SQL,后续必须验证 status=4、complete_time 与跨表回滚。 |
| Integration Safety | UNIT_PASS | Port missing 时 update 调用为 0,Plan 不会单边持久化为 2。 |
12. 验证记录
mvn test -pl ruoyi-train -am -f RuoYi-Vue/pom.xml:14 tests,0 failures,0 errors,0 skipped,BUILD SUCCESS。mvn compile -DskipTests -f RuoYi-Vue/pom.xml:8 个 Reactor 模块全部成功,BUILD SUCCESS。- 评审指定 ESLint(主目录已安装 executable +
--resolve-plugins-relative-to,工作目录仍为本 Issue worktree):退出码 0,无 error/warning。 npm run build:prod:build complete;仅有仓库既有src/views/graph/antv/index.vue的@antv/x6 Addonwarning,与本 Issue 文件无关。- Final Review 本轮未修改任何前端文件;沿用上一轮 ESLint/build 结果。
- 最终
git diff --check、git status --short、git diff --stat结果以交付回复为准。 - 未执行 commit/merge/rebase,等待技术负责人复审。