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

13 KiB
Raw Blame History

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 Addon warning,与本 Issue 文件无关。
  • Final Review 本轮未修改任何前端文件;沿用上一轮 ESLint/build 结果。
  • 最终 git diff --check、git status --short、git diff --stat 结果以交付回复为准。
  • 未执行 commit/merge/rebase,等待技术负责人复审。