# 培训计划模块(training) > 证据来源:[SRC-FEAT-01] [SRC-FEAT-02] [SRC-SQL-01] [SRC-API-01] [SRC-CODE-01:module/training/] --- ## 1. 模块定位 - **模块目标**:将知识内容和考试任务组合成有时间范围的培训计划,分配给指定学员,并跟踪每位学员的完成进度。 - **解决问题**:组织端无法跟踪员工是否真正完成学习目标;培训计划模块实现"分配→执行→进度可视"的闭环管理。 --- ## 2. 功能清单 [SRC-FEAT-01:模块六] [SRC-FEAT-02:3.4 我的培训] [SRC-API-01:4.5] | 功能名称 | 功能描述 | 输入 | 输出 | 依赖模块 | |---------|---------|------|------|---------| | 创建培训计划 | 设置计划基本信息(名称/描述/时间范围/所属部门) | TrainingPlanQueryDTO(title, start_date, end_date) | TrainingPlanVO | system | | 关联知识内容 | 在培训计划中添加多个知识,设置必修/选修和排序 | plan_id, [{knowledge_id, required, sort_order}] | - | knowledge | | 关联考试任务 | 在培训计划中添加多个考试,设置必考/选考和排序(V1.0.1+)| plan_id, [{exam_id, required, sort_order}] | - | exam | | 分配培训对象 | 指定培训计划的参与者(部门/小组/个人) | plan_id, [{target_type, target_id}] | - | system | | 培训计划列表(讲师端) | 查询本部门的培训计划,含状态和统计信息 | 分页参数 | PageResult | - | | 培训计划详情(讲师端) | 查看计划详情,含关联知识/考试/对象/进度汇总 | planId | TrainingPlanDetailVO | - | | 更新/删除培训计划 | 修改计划信息(未开始时可修改,进行中时受限)| planId, TrainingPlanQueryDTO | - | - | | 学员-我的培训列表 | 查询分配给当前学员的培训计划(进行中优先) | - | PageResult | auth | | 学员-培训详情 | 查看某培训计划详情:课程列表+学习状态+考试列表+通过状态 | planId | TrainingDetailVO | knowledge, exam | | 进度计算 | 按必修知识完成数 + 必考通过数 / 总必修项 计算进度% | planId, userId | Integer(0~100) | knowledge, exam | | 知识进度同步 | 学员学习某知识时,若 source=TRAINING 则同步更新 tr_plan_progress | planId, userId, knowledgeId | - | knowledge | --- ## 3. 核心逻辑 ### 3.1 业务规则 [SRC-FEAT-01:模块六] [SRC-API-01:5.4] **培训计划状态自动流转**: - `tr_plan.status` 根据 `start_date` / `end_date` 与当前日期比较自动判定: - `NOW() < start_date` → NOT_STARTED - `start_date ≤ NOW() ≤ end_date` → IN_PROGRESS - `NOW() > end_date` → ENDED - [ASSUMPTION] 状态字段在查询时实时计算,或由定时任务定期刷新(PRD 未明确) **培训进度计算公式**(V1.0.1)[SRC-API-01:5.4]: ``` progress% = (必修知识完成数 + 必考考试通过数) / (必修知识总数 + 必考考试总数) × 100 ``` - 必修知识完成:`km_knowledge_progress.status = COMPLETED` 且 `plan_id = 当前计划` - 必考通过:在 `tr_plan_exam` 中 `required=1` 的考试,学员在 `ex_exam_record` 中存在 `passed=1` 的记录 - 选修知识/选考考试不计入进度(但在详情页展示) - 总必修项为 0 时,进度 = 100%(无内容的计划视为自动完成) **知识完成与培训进度联动**: - 学员调用 `knowledge.updateProgress()` 时,若 `source=TRAINING` 且 `planId != null`: 1. 更新 `km_knowledge_progress` 2. 若 status 变为 COMPLETED,则更新 `tr_plan_progress`(plan_id, user_id, knowledge_id 唯一记录) **考试通过与培训进度联动**: - 学员考试提交后,`TrainingProgressService` 查询该 exam_id 被哪些计划引用(`tr_plan_exam`),若有关联且 `passed=1`,则重新计算相关计划的进度(或标记为需要刷新) **多对象分配逻辑**: - 分配对象类型支持 DEPARTMENT(0) / GROUP(1) / USER(2) 混合 - 当指定 DEPARTMENT 时,该部门下所有 STUDENT 账号都被纳入 - 当指定 GROUP 时,该小组所有成员纳入 - 已被纳入的学员在查询时去重(多路径覆盖同一人不重复统计) ### 3.2 校验逻辑 | 校验项 | 规则 | 失败响应 | |-------|------|---------| | 培训时间范围 | start_date < end_date | 400:培训结束日期必须晚于开始日期 | | 关联知识状态 | 关联到计划的知识必须是 PUBLISHED 状态 | 400:请先发布知识再关联到培训计划 | | 关联考试状态 | 关联到计划的考试必须是 IN_PROGRESS 或 NOT_STARTED | 400:已结束的考试不可关联 | | 进行中计划修改 | [ASSUMPTION] IN_PROGRESS 状态下,不允许修改培训对象和时间范围 | 400:培训进行中,不可修改 | | 知识引用保护 | 已关联计划的知识被下架时触发警告 | knowledge 模块负责,见知识库模块 6 | | 学员查看非自己的计划 | Service 层校验该学员是否在 tr_plan_target 覆盖范围内 | 403:无权访问该培训计划 | ### 3.3 状态流转 ```mermaid stateDiagram-v2 [*] --> NOT_STARTED : 创建培训计划(start_date 未到) NOT_STARTED --> IN_PROGRESS : start_date 到达(自动/定时) IN_PROGRESS --> ENDED : end_date 到达(自动/定时) NOT_STARTED --> [*] : 逻辑删除(未开始可删除) ENDED --> [*] : 归档 note right of IN_PROGRESS : 学员可学习和考试 note right of ENDED : 仅供历史查阅 ``` **学员培训进度状态(非独立表字段,实时计算)**: - 0%:尚未开始学习 - 1%~99%:学习中 - 100%:全部必修项完成(培训通过) --- ## 4. 数据结构 [SRC-SQL-01:四、培训模块] ### 4.1 涉及数据表 **tr_plan** | 字段 | 类型 | 说明 | |------|------|------| | id | BIGINT | 主键 | | title | VARCHAR(200) NOT NULL | 计划标题 | | description | TEXT | 计划描述 | | start_date | DATE NOT NULL | 开始日期 | | end_date | DATE NOT NULL | 结束日期 | | department_id | BIGINT NOT NULL | 所属部门(数据隔离键) | | status | TINYINT | 0-未开始 / 1-进行中 / 2-已结束 | | creator_id | BIGINT NOT NULL | 创建人 | **tr_plan_knowledge**(培训计划-知识关联) | 字段 | 类型 | 说明 | |------|------|------| | plan_id | BIGINT NOT NULL | 培训计划 ID | | knowledge_id | BIGINT NOT NULL | 知识 ID | | required | TINYINT DEFAULT 1 | 1-必修 / 0-选修 | | sort_order | INT | 排序 | **tr_plan_exam**(培训计划-考试关联,V1.0.1) | 字段 | 类型 | 说明 | |------|------|------| | plan_id | BIGINT NOT NULL | 培训计划 ID | | exam_id | BIGINT NOT NULL | 考试 ID | | required | TINYINT DEFAULT 1 | 1-必考 / 0-选考 | | sort_order | INT | 排序 | | 唯一约束 | uk_plan_exam | (plan_id, exam_id) | **tr_plan_target**(培训计划-分配对象) | 字段 | 类型 | 说明 | |------|------|------| | plan_id | BIGINT NOT NULL | 培训计划 ID | | target_type | TINYINT NOT NULL | 0-部门 / 1-小组 / 2-个人 | | target_id | BIGINT NOT NULL | 对应实体 ID | **tr_plan_progress**(知识级别进度,供快速查询) | 字段 | 类型 | 说明 | |------|------|------| | plan_id | BIGINT NOT NULL | 培训计划 ID | | user_id | BIGINT NOT NULL | 学员 ID | | knowledge_id | BIGINT NOT NULL | 知识 ID | | completed | TINYINT DEFAULT 0 | 0-未完成 / 1-已完成 | | complete_time | DATETIME | 完成时间 | ### 4.2 数据关联 - `tr_plan_knowledge.plan_id` → `tr_plan.id` - `tr_plan_knowledge.knowledge_id` → `km_knowledge.id` - `tr_plan_exam.plan_id` → `tr_plan.id` - `tr_plan_exam.exam_id` → `ex_exam.id` - `tr_plan_target` 多态关联:type=0→`sys_department.id`;type=1→`sys_group.id`;type=2→`sys_user.id` - 进度计算同时读取 `km_knowledge_progress`(source=TRAINING, plan_id)和 `ex_exam_record`(passed=1) --- ## 5. 对外接口 [SRC-API-01:4.5] [SRC-CODE-01:module/training/controller/] | API 名称 | 方法 | 路径 | 权限 | |---------|------|------|------| | 培训计划列表(讲师) | GET | /api/training | LECTURER/ADMIN | | 创建培训计划 | POST | /api/training | LECTURER/ADMIN | | 修改培训计划 | PUT | /api/training/{id} | LECTURER/ADMIN | | 删除培训计划 | DELETE | /api/training/{id} | LECTURER/ADMIN | | 关联知识 | POST | /api/training/{id}/knowledge | LECTURER/ADMIN | | 关联考试 | POST | /api/training/{id}/exam | LECTURER/ADMIN | | 分配对象 | POST | /api/training/{id}/target | LECTURER/ADMIN | | 学员-培训列表 | GET | /api/student/training | STUDENT | | 学员-培训详情 | GET | /api/student/training/{id} | STUDENT | --- ## 6. 异常与边界处理 | 场景 | 处理方式 | |------|---------| | 培训计划结束后学员查看 | 状态=ENDED,页面展示历史进度和成绩,不可继续学习/考试 | | 学员尚未被分配到某计划就访问 | 403:无权访问该培训计划 | | 相关考试时间窗口已过,但培训计划仍进行中 | 考试显示"已结束",无法参加,该考试通过状态默认为"未通过"(评估影响进度) | | 同一学员多次被添加(部门+个人双重覆盖) | tr_plan_target 可能有重复记录,进度计算时去重(按 user_id 聚合) | | 培训计划关联的知识被删除 | tr_plan_knowledge 记录仍在,显示时标注"知识已下架" [ASSUMPTION] | | 总必修项为 0 的计划 | 进度直接返回 100%(calculateProgress 中已处理:totalRequired=0 return 100)[SRC-API-01:5.4] |