diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassPlanController.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassPlanController.java deleted file mode 100644 index c3de051b5..000000000 --- a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassPlanController.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.ruoyi.web.controller.train; - -import java.util.List; -import javax.servlet.http.HttpServletResponse; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import com.ruoyi.common.annotation.Log; -import com.ruoyi.common.core.controller.BaseController; -import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.enums.BusinessType; -import com.ruoyi.train.domain.TrainClassPlan; -import com.ruoyi.train.service.ITrainClassPlanService; -import com.ruoyi.common.utils.poi.ExcelUtil; -import com.ruoyi.common.core.page.TableDataInfo; - -/** - * 开班计划Controller - * - * @author ruoyi - * @date 2026-09-08 - */ -@RestController -@RequestMapping("/train/plan") -public class TrainClassPlanController extends BaseController -{ - @Autowired - private ITrainClassPlanService trainClassPlanService; - - /** - * 查询开班计划列表 - */ - @PreAuthorize("@ss.hasPermi('system:plan:list')") - @GetMapping("/list") - public TableDataInfo list(TrainClassPlan trainClassPlan) - { - startPage(); - List list = trainClassPlanService.selectTrainClassPlanList(trainClassPlan); - return getDataTable(list); - } - - /** - * 导出开班计划列表 - */ - @PreAuthorize("@ss.hasPermi('system:plan:export')") - @Log(title = "开班计划", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(HttpServletResponse response, TrainClassPlan trainClassPlan) - { - List list = trainClassPlanService.selectTrainClassPlanList(trainClassPlan); - ExcelUtil util = new ExcelUtil(TrainClassPlan.class); - util.exportExcel(response, list, "开班计划数据"); - } - - /** - * 获取开班计划详细信息 - */ - @PreAuthorize("@ss.hasPermi('system:plan:query')") - @GetMapping(value = "/{planId}") - public AjaxResult getInfo(@PathVariable("planId") String planId) - { - return success(trainClassPlanService.selectTrainClassPlanByPlanId(planId)); - } - - /** - * 新增开班计划 - */ - @PreAuthorize("@ss.hasPermi('system:plan:add')") - @Log(title = "开班计划", businessType = BusinessType.INSERT) - @PostMapping - public AjaxResult add(@RequestBody TrainClassPlan trainClassPlan) - { - return toAjax(trainClassPlanService.insertTrainClassPlan(trainClassPlan)); - } - - /** - * 修改开班计划 - */ - @PreAuthorize("@ss.hasPermi('system:plan:edit')") - @Log(title = "开班计划", businessType = BusinessType.UPDATE) - @PutMapping - public AjaxResult edit(@RequestBody TrainClassPlan trainClassPlan) - { - return toAjax(trainClassPlanService.updateTrainClassPlan(trainClassPlan)); - } - - /** - * 删除开班计划 - */ - @PreAuthorize("@ss.hasPermi('system:plan:remove')") - @Log(title = "开班计划", businessType = BusinessType.DELETE) - @DeleteMapping("/{planIds}") - public AjaxResult remove(@PathVariable String[] planIds) - { - return toAjax(trainClassPlanService.deleteTrainClassPlanByPlanIds(planIds)); - } -} diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassroomInfoController.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassroomInfoController.java index 380f64147..85593bb82 100644 --- a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassroomInfoController.java +++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainClassroomInfoController.java @@ -1,34 +1,33 @@ package com.ruoyi.web.controller.train; import java.util.List; -import javax.servlet.http.HttpServletResponse; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.train.domain.TrainClassroomInfo; import com.ruoyi.train.service.ITrainClassroomInfoService; -import com.ruoyi.common.utils.poi.ExcelUtil; -import com.ruoyi.common.core.page.TableDataInfo; /** * 教室信息Controller - * + * * @author ruoyi * @date 2026-09-08 */ @RestController -@RequestMapping({"/train/classroominfo", "/train/classroom", "/train/info"}) +@RequestMapping("/train/classroom") public class TrainClassroomInfoController extends BaseController { @Autowired @@ -37,7 +36,7 @@ public class TrainClassroomInfoController extends BaseController /** * 查询教室信息列表 */ - @PreAuthorize("@ss.hasPermi('system:info:list')") + @PreAuthorize("@ss.hasPermi('train:classroom:list')") @GetMapping("/list") public TableDataInfo list(TrainClassroomInfo trainClassroomInfo) { @@ -47,58 +46,37 @@ public class TrainClassroomInfoController extends BaseController } /** - * 导出教室信息列表 + * 获取教室信息详细信息(回显详情) */ - @PreAuthorize("@ss.hasPermi('system:info:export')") - @Log(title = "教室信息", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(HttpServletResponse response, TrainClassroomInfo trainClassroomInfo) - { - List list = trainClassroomInfoService.selectTrainClassroomInfoList(trainClassroomInfo); - ExcelUtil util = new ExcelUtil(TrainClassroomInfo.class); - util.exportExcel(response, list, "教室信息数据"); - } - - /** - * 获取教室信息详细信息 - */ - @PreAuthorize("@ss.hasPermi('system:info:query')") + @PreAuthorize("@ss.hasPermi('train:classroom:list')") @GetMapping(value = "/{classRoomId}") public AjaxResult getInfo(@PathVariable("classRoomId") String classRoomId) { - return success(trainClassroomInfoService.selectTrainClassroomInfoByClassRoomId(classRoomId)); + return success(trainClassroomInfoService.selectClassroomDetailByClassRoomId(classRoomId)); } /** * 新增教室信息 */ - @PreAuthorize("@ss.hasPermi('system:info:add')") + @PreAuthorize("@ss.hasPermi('train:classroom:edit')") @Log(title = "教室信息", businessType = BusinessType.INSERT) @PostMapping - public AjaxResult add(@RequestBody TrainClassroomInfo trainClassroomInfo) + public AjaxResult add(@Validated @RequestBody TrainClassroomInfo trainClassroomInfo) { + trainClassroomInfo.setCreateBy(SecurityUtils.getUsername()); return toAjax(trainClassroomInfoService.insertTrainClassroomInfo(trainClassroomInfo)); } /** - * 修改教室信息 + * 修改教室信息 (RESTful PUT /train/classroom/{classRoomId}) */ - @PreAuthorize("@ss.hasPermi('system:info:edit')") + @PreAuthorize("@ss.hasPermi('train:classroom:edit')") @Log(title = "教室信息", businessType = BusinessType.UPDATE) - @PutMapping - public AjaxResult edit(@RequestBody TrainClassroomInfo trainClassroomInfo) + @PutMapping("/{classRoomId}") + public AjaxResult edit(@PathVariable("classRoomId") String classRoomId, @Validated @RequestBody TrainClassroomInfo trainClassroomInfo) { + trainClassroomInfo.setClassRoomId(classRoomId); + trainClassroomInfo.setUpdateBy(SecurityUtils.getUsername()); return toAjax(trainClassroomInfoService.updateTrainClassroomInfo(trainClassroomInfo)); } - - /** - * 删除教室信息 - */ - @PreAuthorize("@ss.hasPermi('system:info:remove')") - @Log(title = "教室信息", businessType = BusinessType.DELETE) - @DeleteMapping("/{classRoomIds}") - public AjaxResult remove(@PathVariable String[] classRoomIds) - { - return toAjax(trainClassroomInfoService.deleteTrainClassroomInfoByClassRoomIds(classRoomIds)); - } } diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainCourseController.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainCourseController.java index 0beccbc13..e27f40ccc 100644 --- a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainCourseController.java +++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainCourseController.java @@ -1,13 +1,11 @@ package com.ruoyi.web.controller.train; import java.util.List; -import javax.servlet.http.HttpServletResponse; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -16,16 +14,17 @@ import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.StringUtils; import com.ruoyi.train.domain.TrainCourse; +import com.ruoyi.train.domain.vo.CourseDetailVO; import com.ruoyi.train.service.ITrainCourseService; -import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; /** * 课程信息Controller - * + * * @author ruoyi - * @date 2026-09-08 + * @date 2026-09-14 */ @RestController @RequestMapping("/train/course") @@ -35,9 +34,9 @@ public class TrainCourseController extends BaseController private ITrainCourseService trainCourseService; /** - * 查询课程信息列表 + * 查询课程信息列表 【P0】 */ - @PreAuthorize("@ss.hasPermi('system:course:list')") + @PreAuthorize("@ss.hasPermi('train:course:list')") @GetMapping("/list") public TableDataInfo list(TrainCourse trainCourse) { @@ -47,58 +46,61 @@ public class TrainCourseController extends BaseController } /** - * 导出课程信息列表 + * 获取课程信息详细信息 【P0】 + * 返回结构:CourseDetailVO (包含课程基础字段与契约预留resources列表) */ - @PreAuthorize("@ss.hasPermi('system:course:export')") - @Log(title = "课程信息", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(HttpServletResponse response, TrainCourse trainCourse) - { - List list = trainCourseService.selectTrainCourseList(trainCourse); - ExcelUtil util = new ExcelUtil(TrainCourse.class); - util.exportExcel(response, list, "课程信息数据"); - } - - /** - * 获取课程信息详细信息 - */ - @PreAuthorize("@ss.hasPermi('system:course:query')") + @PreAuthorize("@ss.hasPermi('train:course:query')") @GetMapping(value = "/{cId}") public AjaxResult getInfo(@PathVariable("cId") String cId) { - return success(trainCourseService.selectTrainCourseByCId(cId)); + CourseDetailVO detailVO = trainCourseService.selectCourseDetailByCId(cId); + if (detailVO == null) + { + return AjaxResult.error(404, "课程不存在"); + } + return success(detailVO); } /** - * 新增课程信息 + * 新增课程信息 【P0】 */ - @PreAuthorize("@ss.hasPermi('system:course:add')") + @PreAuthorize("@ss.hasPermi('train:course:add')") @Log(title = "课程信息", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody TrainCourse trainCourse) { + if (StringUtils.isEmpty(trainCourse.getcName())) + { + return AjaxResult.error(400, "课程名称不能为空"); + } + if (StringUtils.isNotEmpty(trainCourse.getcCode()) && !trainCourseService.checkCCodeUnique(trainCourse)) + { + return AjaxResult.error(40003, "课程编码已存在"); + } + if (trainCourse.getcDuration() != null && trainCourse.getcDuration() <= 0) + { + return AjaxResult.error(400, "课程学时必须为正整数"); + } return toAjax(trainCourseService.insertTrainCourse(trainCourse)); } /** - * 修改课程信息 + * 修改课程信息 (API Contract 3.4 【P1】: PUT /train/course/{cId}) */ - @PreAuthorize("@ss.hasPermi('system:course:edit')") + @PreAuthorize("@ss.hasPermi('train:course:edit')") @Log(title = "课程信息", businessType = BusinessType.UPDATE) - @PutMapping - public AjaxResult edit(@RequestBody TrainCourse trainCourse) + @PutMapping("/{cId}") + public AjaxResult edit(@PathVariable("cId") String cId, @RequestBody TrainCourse trainCourse) { + trainCourse.setcId(cId); + if (StringUtils.isNotEmpty(trainCourse.getcCode()) && !trainCourseService.checkCCodeUnique(trainCourse)) + { + return AjaxResult.error(40003, "课程编码已存在"); + } + if (trainCourse.getcDuration() != null && trainCourse.getcDuration() <= 0) + { + return AjaxResult.error(400, "课程学时必须为正整数"); + } return toAjax(trainCourseService.updateTrainCourse(trainCourse)); } - - /** - * 删除课程信息 - */ - @PreAuthorize("@ss.hasPermi('system:course:remove')") - @Log(title = "课程信息", businessType = BusinessType.DELETE) - @DeleteMapping("/{cIds}") - public AjaxResult remove(@PathVariable String[] cIds) - { - return toAjax(trainCourseService.deleteTrainCourseByCIds(cIds)); - } } diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainPlanController.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainPlanController.java new file mode 100644 index 000000000..df69df8c6 --- /dev/null +++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainPlanController.java @@ -0,0 +1,85 @@ +package com.ruoyi.web.controller.train; + +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.train.domain.dto.PlanCreateDTO; +import com.ruoyi.train.domain.dto.PlanUpdateDTO; +import com.ruoyi.train.domain.query.PlanQuery; +import com.ruoyi.train.domain.vo.PlanItemVO; +import com.ruoyi.train.service.PlanAppService; + +/** 培训计划生命周期 API。 */ +@RestController +@RequestMapping("/train/plans") +public class TrainPlanController extends BaseController +{ + private final PlanAppService planAppService; + + @Autowired + public TrainPlanController(PlanAppService planAppService) + { + this.planAppService = planAppService; + } + + /** 分页查询培训计划列表。 */ + @PreAuthorize("@ss.hasPermi('train:plan:list')") + @GetMapping + public TableDataInfo list(PlanQuery query) + { + startPage(); + List list = planAppService.listPlans(query); + return getDataTable(list); + } + + /** 获取培训计划基本信息。 */ + @PreAuthorize("@ss.hasPermi('train:plan:query')") + @GetMapping("/{planId}") + public AjaxResult getInfo(@PathVariable String planId) + { + return success(planAppService.getPlan(planId)); + } + + /** 创建培训计划。 */ + @PreAuthorize("@ss.hasPermi('train:plan:add')") + @Log(title = "培训计划", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody PlanCreateDTO dto) + { + return success(planAppService.createPlan(dto, SecurityUtils.getUsername())); + } + + /** 编辑培训计划。 */ + @PreAuthorize("@ss.hasPermi('train:plan:edit')") + @Log(title = "培训计划", businessType = BusinessType.UPDATE) + @PutMapping("/{planId}") + public AjaxResult edit(@PathVariable String planId, @Validated @RequestBody PlanUpdateDTO dto) + { + planAppService.updatePlan(planId, dto, SecurityUtils.getUsername()); + return success(); + } + + /** 结束培训计划。 */ + @PreAuthorize("@ss.hasPermi('train:plan:complete')") + @Log(title = "培训计划结训", businessType = BusinessType.UPDATE) + @PostMapping("/{planId}/complete") + public AjaxResult complete(@PathVariable String planId) + { + planAppService.completePlan(planId, SecurityUtils.getUsername()); + return success(); + } +} diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTeacherController.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTeacherController.java index e1ef3dc2c..842f89551 100644 --- a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTeacherController.java +++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTeacherController.java @@ -1,13 +1,12 @@ package com.ruoyi.web.controller.train; import java.util.List; -import javax.servlet.http.HttpServletResponse; +import java.util.regex.Pattern; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -15,90 +14,108 @@ import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.page.TableSupport; import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; import com.ruoyi.train.domain.TrainTeacher; import com.ruoyi.train.service.ITrainTeacherService; -import com.ruoyi.common.utils.poi.ExcelUtil; -import com.ruoyi.common.core.page.TableDataInfo; /** * 教师信息Controller - * + * * @author ruoyi - * @date 2026-09-08 + * @date 2026-09-14 */ @RestController @RequestMapping("/train/teacher") public class TrainTeacherController extends BaseController { + private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$"); + @Autowired private ITrainTeacherService trainTeacherService; /** - * 查询教师信息列表 + * 查询教师信息列表 (GET /train/teacher/list) */ - @PreAuthorize("@ss.hasPermi('system:teacher:list')") + @PreAuthorize("@ss.hasPermi('train:teacher:list')") @GetMapping("/list") public TableDataInfo list(TrainTeacher trainTeacher) { + PageDomain pageDomain = TableSupport.buildPageRequest(); + Integer pageSize = pageDomain.getPageSize(); + if (pageSize != null && pageSize > 100) + { + throw new ServiceException("分页大小不能超过100"); + } startPage(); List list = trainTeacherService.selectTrainTeacherList(trainTeacher); return getDataTable(list); } /** - * 导出教师信息列表 + * 获取教师信息详细信息 (GET /train/teacher/{tId}) */ - @PreAuthorize("@ss.hasPermi('system:teacher:export')") - @Log(title = "教师信息", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(HttpServletResponse response, TrainTeacher trainTeacher) - { - List list = trainTeacherService.selectTrainTeacherList(trainTeacher); - ExcelUtil util = new ExcelUtil(TrainTeacher.class); - util.exportExcel(response, list, "教师信息数据"); - } - - /** - * 获取教师信息详细信息 - */ - @PreAuthorize("@ss.hasPermi('system:teacher:query')") + @PreAuthorize("@ss.hasPermi('train:teacher:query')") @GetMapping(value = "/{tId}") public AjaxResult getInfo(@PathVariable("tId") String tId) { - return success(trainTeacherService.selectTrainTeacherByTId(tId)); + TrainTeacher teacher = trainTeacherService.selectTrainTeacherByTId(tId); + if (teacher == null) + { + return AjaxResult.error(40401, "教师不存在"); + } + return success(teacher); } /** - * 新增教师信息 + * 新增教师信息 (POST /train/teacher) */ - @PreAuthorize("@ss.hasPermi('system:teacher:add')") - @Log(title = "教师信息", businessType = BusinessType.INSERT) + @PreAuthorize("@ss.hasPermi('train:teacher:add')") + @Log(title = "教师管理", businessType = BusinessType.INSERT) @PostMapping public AjaxResult add(@RequestBody TrainTeacher trainTeacher) { + if (StringUtils.isEmpty(trainTeacher.gettName())) + { + return AjaxResult.error("教师姓名不能为空"); + } + if (trainTeacher.gettName().trim().length() > 50) + { + return AjaxResult.error("教师姓名长度不能超过50个字符"); + } + if (StringUtils.isNotEmpty(trainTeacher.gettPhone()) && !isValidPhone(trainTeacher.gettPhone())) + { + return AjaxResult.error("手机号码格式不正确"); + } return toAjax(trainTeacherService.insertTrainTeacher(trainTeacher)); } /** - * 修改教师信息 + * 修改教师信息 (PUT /train/teacher/{tId}) */ - @PreAuthorize("@ss.hasPermi('system:teacher:edit')") - @Log(title = "教师信息", businessType = BusinessType.UPDATE) - @PutMapping - public AjaxResult edit(@RequestBody TrainTeacher trainTeacher) + @PreAuthorize("@ss.hasPermi('train:teacher:edit')") + @Log(title = "教师管理", businessType = BusinessType.UPDATE) + @PutMapping(value = "/{tId}") + public AjaxResult edit(@PathVariable("tId") String tId, @RequestBody TrainTeacher trainTeacher) { + trainTeacher.settId(tId); + if (StringUtils.isNotEmpty(trainTeacher.gettName()) && trainTeacher.gettName().trim().length() > 50) + { + return AjaxResult.error("教师姓名长度不能超过50个字符"); + } + if (StringUtils.isNotEmpty(trainTeacher.gettPhone()) && !isValidPhone(trainTeacher.gettPhone())) + { + return AjaxResult.error("手机号码格式不正确"); + } return toAjax(trainTeacherService.updateTrainTeacher(trainTeacher)); } - /** - * 删除教师信息 - */ - @PreAuthorize("@ss.hasPermi('system:teacher:remove')") - @Log(title = "教师信息", businessType = BusinessType.DELETE) - @DeleteMapping("/{tIds}") - public AjaxResult remove(@PathVariable String[] tIds) + private boolean isValidPhone(String phone) { - return toAjax(trainTeacherService.deleteTrainTeacherByTIds(tIds)); + return PHONE_PATTERN.matcher(phone).matches(); } } diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTrainingSiteController.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTrainingSiteController.java index cb6e81e50..a88fe16cd 100644 --- a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTrainingSiteController.java +++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainTrainingSiteController.java @@ -1,34 +1,33 @@ package com.ruoyi.web.controller.train; import java.util.List; -import javax.servlet.http.HttpServletResponse; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.train.domain.TrainTrainingSite; import com.ruoyi.train.service.ITrainTrainingSiteService; -import com.ruoyi.common.utils.poi.ExcelUtil; -import com.ruoyi.common.core.page.TableDataInfo; /** * 实训场地Controller - * + * * @author ruoyi * @date 2026-09-08 */ @RestController -@RequestMapping("/train/site") +@RequestMapping("/train/training-site") public class TrainTrainingSiteController extends BaseController { @Autowired @@ -37,7 +36,7 @@ public class TrainTrainingSiteController extends BaseController /** * 查询实训场地列表 */ - @PreAuthorize("@ss.hasPermi('system:site:list')") + @PreAuthorize("@ss.hasPermi('train:site:list')") @GetMapping("/list") public TableDataInfo list(TrainTrainingSite trainTrainingSite) { @@ -46,23 +45,10 @@ public class TrainTrainingSiteController extends BaseController return getDataTable(list); } - /** - * 导出实训场地列表 - */ - @PreAuthorize("@ss.hasPermi('system:site:export')") - @Log(title = "实训场地", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(HttpServletResponse response, TrainTrainingSite trainTrainingSite) - { - List list = trainTrainingSiteService.selectTrainTrainingSiteList(trainTrainingSite); - ExcelUtil util = new ExcelUtil(TrainTrainingSite.class); - util.exportExcel(response, list, "实训场地数据"); - } - /** * 获取实训场地详细信息 */ - @PreAuthorize("@ss.hasPermi('system:site:query')") + @PreAuthorize("@ss.hasPermi('train:site:list')") @GetMapping(value = "/{siteId}") public AjaxResult getInfo(@PathVariable("siteId") String siteId) { @@ -72,33 +58,25 @@ public class TrainTrainingSiteController extends BaseController /** * 新增实训场地 */ - @PreAuthorize("@ss.hasPermi('system:site:add')") + @PreAuthorize("@ss.hasPermi('train:site:edit')") @Log(title = "实训场地", businessType = BusinessType.INSERT) @PostMapping - public AjaxResult add(@RequestBody TrainTrainingSite trainTrainingSite) + public AjaxResult add(@Validated @RequestBody TrainTrainingSite trainTrainingSite) { + trainTrainingSite.setCreateBy(SecurityUtils.getUsername()); return toAjax(trainTrainingSiteService.insertTrainTrainingSite(trainTrainingSite)); } /** - * 修改实训场地 + * 修改实训场地 (RESTful PUT /train/training-site/{siteId}) */ - @PreAuthorize("@ss.hasPermi('system:site:edit')") + @PreAuthorize("@ss.hasPermi('train:site:edit')") @Log(title = "实训场地", businessType = BusinessType.UPDATE) - @PutMapping - public AjaxResult edit(@RequestBody TrainTrainingSite trainTrainingSite) + @PutMapping("/{siteId}") + public AjaxResult edit(@PathVariable("siteId") String siteId, @Validated @RequestBody TrainTrainingSite trainTrainingSite) { + trainTrainingSite.setSiteId(siteId); + trainTrainingSite.setUpdateBy(SecurityUtils.getUsername()); return toAjax(trainTrainingSiteService.updateTrainTrainingSite(trainTrainingSite)); } - - /** - * 删除实训场地 - */ - @PreAuthorize("@ss.hasPermi('system:site:remove')") - @Log(title = "实训场地", businessType = BusinessType.DELETE) - @DeleteMapping("/{siteIds}") - public AjaxResult remove(@PathVariable String[] siteIds) - { - return toAjax(trainTrainingSiteService.deleteTrainTrainingSiteBySiteIds(siteIds)); - } } diff --git a/RuoYi-Vue/ruoyi-train/pom.xml b/RuoYi-Vue/ruoyi-train/pom.xml index dc4985cb5..4debe3374 100644 --- a/RuoYi-Vue/ruoyi-train/pom.xml +++ b/RuoYi-Vue/ruoyi-train/pom.xml @@ -23,6 +23,13 @@ ruoyi-common + + junit + junit + 4.13.2 + test + + - \ No newline at end of file + diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/constant/PlanErrorCode.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/constant/PlanErrorCode.java new file mode 100644 index 000000000..5eeb4dde6 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/constant/PlanErrorCode.java @@ -0,0 +1,23 @@ +package com.ruoyi.train.constant; + +/** 培训计划正式业务错误码(API Contract V1.0.1)。 */ +public final class PlanErrorCode +{ + public static final int PLAN_TIME_INVALID = 40010; + public static final int PLAN_NAME_DUPLICATED = 40011; + public static final int PLAN_STATUS_LOCKED = 40012; + public static final int PLAN_LESSON_OUT_OF_BOUNDS = 40013; + public static final int PLAN_ALREADY_COMPLETED = 40014; + public static final int PLAN_NOT_FOUND = 40410; + + public static final String PLAN_TIME_INVALID_MESSAGE = "培训计划起止时间不合法 (结束时间必须晚于开始时间)"; + public static final String PLAN_NAME_DUPLICATED_MESSAGE = "同名培训计划已存在"; + public static final String PLAN_STATUS_LOCKED_MESSAGE = "当前培训计划已结训或取消,不允许修改"; + public static final String PLAN_LESSON_OUT_OF_BOUNDS_MESSAGE = "调整培训时间导致现有已排课次超出计划周期"; + public static final String PLAN_ALREADY_COMPLETED_MESSAGE = "培训计划已经结训,不可重复执行结训"; + public static final String PLAN_NOT_FOUND_MESSAGE = "培训计划不存在"; + + private PlanErrorCode() + { + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainClassPlan.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainClassPlan.java index 7de1acb25..b2f86d5d2 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainClassPlan.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainClassPlan.java @@ -24,28 +24,25 @@ public class TrainClassPlan extends BaseEntity @Excel(name = "开班名称") private String planName; - /** 所属课程编号,关联train_course.c_id */ - @Excel(name = "所属课程编号,关联train_course.c_id") - private String cId; - /** 开班时间 */ - @JsonFormat(pattern = "yyyy-MM-dd") - @Excel(name = "开班时间", width = 30, dateFormat = "yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "开班时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date startTime; /** 结束时间 */ - @JsonFormat(pattern = "yyyy-MM-dd") - @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") private Date endTime; + /** 报到截止时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @Excel(name = "报到截止时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + private Date reportDeadline; + /** 培训地点 */ @Excel(name = "培训地点") private String trainAddress; - /** 授课教师工号,关联train_teacher.t_id */ - @Excel(name = "授课教师工号,关联train_teacher.t_id") - private String tId; - /** 招生人数 */ @Excel(name = "招生人数") private Long recruitNum; @@ -58,9 +55,8 @@ public class TrainClassPlan extends BaseEntity @Excel(name = "0 未开始 1 进行中 2 已结束 3 已取消") private Integer planStatus; - /** 创建管理员账号 */ - @Excel(name = "创建管理员账号") - private String createUser; + /** 删除标志(0代表存在 2代表删除) */ + private String delFlag; public void setPlanId(String planId) { @@ -82,16 +78,6 @@ public class TrainClassPlan extends BaseEntity return planName; } - public void setcId(String cId) - { - this.cId = cId; - } - - public String getcId() - { - return cId; - } - public void setStartTime(Date startTime) { this.startTime = startTime; @@ -112,26 +98,26 @@ public class TrainClassPlan extends BaseEntity return endTime; } - public void setTrainAddress(String trainAddress) + public void setReportDeadline(Date reportDeadline) + { + this.reportDeadline = reportDeadline; + } + + public Date getReportDeadline() + { + return reportDeadline; + } + + public void setTrainAddress(String trainAddress) { this.trainAddress = trainAddress; } - public String getTrainAddress() + public String getTrainAddress() { return trainAddress; } - public void settId(String tId) - { - this.tId = tId; - } - - public String gettId() - { - return tId; - } - public void setRecruitNum(Long recruitNum) { this.recruitNum = recruitNum; @@ -162,14 +148,14 @@ public class TrainClassPlan extends BaseEntity return planStatus; } - public void setCreateUser(String createUser) + public void setDelFlag(String delFlag) { - this.createUser = createUser; + this.delFlag = delFlag; } - public String getCreateUser() + public String getDelFlag() { - return createUser; + return delFlag; } @Override @@ -177,16 +163,17 @@ public class TrainClassPlan extends BaseEntity return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("planId", getPlanId()) .append("planName", getPlanName()) - .append("cId", getcId()) .append("startTime", getStartTime()) .append("endTime", getEndTime()) + .append("reportDeadline", getReportDeadline()) .append("trainAddress", getTrainAddress()) - .append("tId", gettId()) .append("recruitNum", getRecruitNum()) .append("applyCondition", getApplyCondition()) .append("planStatus", getPlanStatus()) - .append("createUser", getCreateUser()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainClassroomInfo.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainClassroomInfo.java index 273905f48..1733539bc 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainClassroomInfo.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainClassroomInfo.java @@ -1,5 +1,9 @@ package com.ruoyi.train.domain; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; @@ -7,7 +11,7 @@ import com.ruoyi.common.core.domain.BaseEntity; /** * 教室信息对象 train_classroom_info - * + * * @author ruoyi * @date 2026-09-08 */ @@ -19,83 +23,87 @@ public class TrainClassroomInfo extends BaseEntity private String classRoomId; /** 教室名称 */ + @NotBlank(message = "教室名称不能为空") + @Size(max = 60, message = "教室名称长度不能超过60个字符") @Excel(name = "教室名称") private String roomName; /** 教室位置 */ + @Size(max = 100, message = "教室位置长度不能超过100个字符") @Excel(name = "教室位置") private String roomLocation; /** 容纳人数 */ + @NotNull(message = "容纳人数不能为空") + @Min(value = 1, message = "容纳人数必须大于0") @Excel(name = "容纳人数") private Long capacity; /** 0 正在使用 1 可用,空闲 2 其他 */ - @Excel(name = "0 正在使用 1 可用,空闲 2 其他") + @Excel(name = "教室状态", readConverterExp = "0=正在使用,1=可用空闲,2=其他") private Integer roomStatus; - /** 创建管理员账号 */ - @Excel(name = "创建管理员账号") - private String createUser; + /** 删除标志 (0代表存在 2代表删除) */ + private String delFlag; - public void setClassRoomId(String classRoomId) + public void setClassRoomId(String classRoomId) { this.classRoomId = classRoomId; } - public String getClassRoomId() + public String getClassRoomId() { return classRoomId; } - public void setRoomName(String roomName) + public void setRoomName(String roomName) { this.roomName = roomName; } - public String getRoomName() + public String getRoomName() { return roomName; } - public void setRoomLocation(String roomLocation) + public void setRoomLocation(String roomLocation) { this.roomLocation = roomLocation; } - public String getRoomLocation() + public String getRoomLocation() { return roomLocation; } - public void setCapacity(Long capacity) + public void setCapacity(Long capacity) { this.capacity = capacity; } - public Long getCapacity() + public Long getCapacity() { return capacity; } - public void setRoomStatus(Integer roomStatus) + public void setRoomStatus(Integer roomStatus) { this.roomStatus = roomStatus; } - public Integer getRoomStatus() + public Integer getRoomStatus() { return roomStatus; } - public void setCreateUser(String createUser) + public void setDelFlag(String delFlag) { - this.createUser = createUser; + this.delFlag = delFlag; } - public String getCreateUser() + public String getDelFlag() { - return createUser; + return delFlag; } @Override @@ -106,8 +114,10 @@ public class TrainClassroomInfo extends BaseEntity .append("roomLocation", getRoomLocation()) .append("capacity", getCapacity()) .append("roomStatus", getRoomStatus()) - .append("createUser", getCreateUser()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainCourse.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainCourse.java index 08a6c22cf..e2d379227 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainCourse.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainCourse.java @@ -2,14 +2,15 @@ package com.ruoyi.train.domain; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; +import com.fasterxml.jackson.annotation.JsonProperty; import com.ruoyi.common.annotation.Excel; import com.ruoyi.common.core.domain.BaseEntity; /** * 课程信息对象 train_course - * + * * @author ruoyi - * @date 2026-09-08 + * @date 2026-09-14 */ public class TrainCourse extends BaseEntity { @@ -18,6 +19,10 @@ public class TrainCourse extends BaseEntity /** 课程编号主键 */ private String cId; + /** 课程统一业务编码 */ + @Excel(name = "课程统一业务编码") + private String cCode; + /** 课程名称 */ @Excel(name = "课程名称") private String cName; @@ -26,163 +31,190 @@ public class TrainCourse extends BaseEntity @Excel(name = "课程类型") private String cType; - /** 课程时长 (分钟) */ - @Excel(name = "课程时长 (分钟)") - private Long cDuration; + /** 课程学时 */ + @Excel(name = "课程学时") + private Integer cDuration; /** 教学目标 */ @Excel(name = "教学目标") private String teachingObjective; - /** 课程内容 */ + /** 课程内容/摘要 */ @Excel(name = "课程内容") private String courseContent; - /** 配套文档,多个逗号分隔 */ - @Excel(name = "配套文档,多个逗号分隔") - private String attachDoc; + /** 课程状态 (1:正常, 0:停用) */ + @Excel(name = "课程状态", readConverterExp = "1=正常,0=停用") + private Integer status; - /** 配套视频地址 */ - @Excel(name = "配套视频地址") - private String attachVideo; + /** 删除标志 (0:存在, 2:删除) */ + private String delFlag; - /** 配套模型链接 */ - @Excel(name = "配套模型链接") - private String attachModelLink; - - /** 关联3D设备拆解模型、实训场景资源ID,多ID逗号分隔 */ - @Excel(name = "关联3D设备拆解模型、实训场景资源ID,多ID逗号分隔") - private String modelResourceIds; - - /** 创建人 (管理员 / 教师) */ - @Excel(name = "创建人 (管理员 / 教师)") - private String createUser; - - public void setcId(String cId) + @JsonProperty("cId") + public void setcId(String cId) { this.cId = cId; } - public String getcId() + @JsonProperty("cId") + public String getcId() { return cId; } - public void setcName(String cName) + public void setCId(String cId) + { + this.cId = cId; + } + + public String getCId() + { + return cId; + } + + @JsonProperty("cCode") + public void setcCode(String cCode) + { + this.cCode = cCode; + } + + @JsonProperty("cCode") + public String getcCode() + { + return cCode; + } + + public void setCCode(String cCode) + { + this.cCode = cCode; + } + + public String getCCode() + { + return cCode; + } + + @JsonProperty("cName") + public void setcName(String cName) { this.cName = cName; } - public String getcName() + @JsonProperty("cName") + public String getcName() { return cName; } - public void setcType(String cType) + public void setCName(String cName) + { + this.cName = cName; + } + + public String getCName() + { + return cName; + } + + @JsonProperty("cType") + public void setcType(String cType) { this.cType = cType; } - public String getcType() + @JsonProperty("cType") + public String getcType() { return cType; } - public void setcDuration(Long cDuration) + public void setCType(String cType) + { + this.cType = cType; + } + + public String getCType() + { + return cType; + } + + @JsonProperty("cDuration") + public void setcDuration(Integer cDuration) { this.cDuration = cDuration; } - public Long getcDuration() + @JsonProperty("cDuration") + public Integer getcDuration() { return cDuration; } - public void setTeachingObjective(String teachingObjective) + public void setCDuration(Integer cDuration) + { + this.cDuration = cDuration; + } + + public Integer getCDuration() + { + return cDuration; + } + + public void setTeachingObjective(String teachingObjective) { this.teachingObjective = teachingObjective; } - public String getTeachingObjective() + public String getTeachingObjective() { return teachingObjective; } - public void setCourseContent(String courseContent) + public void setCourseContent(String courseContent) { this.courseContent = courseContent; } - public String getCourseContent() + public String getCourseContent() { return courseContent; } - public void setAttachDoc(String attachDoc) + public void setStatus(Integer status) { - this.attachDoc = attachDoc; + this.status = status; } - public String getAttachDoc() + public Integer getStatus() { - return attachDoc; + return status; } - public void setAttachVideo(String attachVideo) + public void setDelFlag(String delFlag) { - this.attachVideo = attachVideo; + this.delFlag = delFlag; } - public String getAttachVideo() + public String getDelFlag() { - return attachVideo; - } - - public void setAttachModelLink(String attachModelLink) - { - this.attachModelLink = attachModelLink; - } - - public String getAttachModelLink() - { - return attachModelLink; - } - - public void setModelResourceIds(String modelResourceIds) - { - this.modelResourceIds = modelResourceIds; - } - - public String getModelResourceIds() - { - return modelResourceIds; - } - - public void setCreateUser(String createUser) - { - this.createUser = createUser; - } - - public String getCreateUser() - { - return createUser; + return delFlag; } @Override public String toString() { return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) .append("cId", getcId()) + .append("cCode", getcCode()) .append("cName", getcName()) .append("cType", getcType()) .append("cDuration", getcDuration()) .append("teachingObjective", getTeachingObjective()) .append("courseContent", getCourseContent()) - .append("attachDoc", getAttachDoc()) - .append("attachVideo", getAttachVideo()) - .append("attachModelLink", getAttachModelLink()) - .append("modelResourceIds", getModelResourceIds()) - .append("createUser", getCreateUser()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTeacher.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTeacher.java index bf513ecc9..e9c0a48a9 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTeacher.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTeacher.java @@ -9,15 +9,15 @@ import com.ruoyi.common.core.domain.BaseEntity; /** * 教师信息对象 train_teacher - * + * * @author ruoyi - * @date 2026-09-08 + * @date 2026-09-14 */ public class TrainTeacher extends BaseEntity { private static final long serialVersionUID = 1L; - /** 教师工号 */ + /** 教师工号/标识 */ private String tId; /** 教师姓名 */ @@ -25,7 +25,7 @@ public class TrainTeacher extends BaseEntity private String tName; /** 性别:男 / 女 */ - @Excel(name = "性别:男 / 女") + @Excel(name = "性别") private String tGender; /** 出生日期 */ @@ -33,13 +33,16 @@ public class TrainTeacher extends BaseEntity @Excel(name = "出生日期", width = 30, dateFormat = "yyyy-MM-dd") private Date tBirth; - /** 所属院系 */ - @Excel(name = "所属院系") - private String tDepartment; + /** sys_user登录账号逻辑关联ID */ + private Long userId; - /** 职称:讲师 / 副教授 / 教授 */ - @Excel(name = "职称:讲师 / 副教授 / 教授") - private String tTitle; + /** 所属单位/部门名称 (由旧 t_department 迁移) */ + @Excel(name = "所属单位") + private String orgName; + + /** 职务/职称 (由旧 t_title 迁移) */ + @Excel(name = "职务/职称") + private String jobTitle; /** 联系电话 */ @Excel(name = "联系电话") @@ -49,98 +52,121 @@ public class TrainTeacher extends BaseEntity @Excel(name = "邮箱") private String tEmail; - /** 创建人 (管理员 / 教师) */ - @Excel(name = "创建人 (管理员 / 教师)") - private String createUser; + /** 状态 (1:正常, 0:停用) */ + @Excel(name = "状态", readConverterExp = "1=正常,0=停用") + private Integer status; - public void settId(String tId) + /** 删除标志 (0:存在, 2:删除) */ + private String delFlag; + + public void settId(String tId) { this.tId = tId; } - public String gettId() + public String gettId() { return tId; } - public void settName(String tName) + public void settName(String tName) { this.tName = tName; } - public String gettName() + public String gettName() { return tName; } - public void settGender(String tGender) + public void settGender(String tGender) { this.tGender = tGender; } - public String gettGender() + public String gettGender() { return tGender; } - public void settBirth(Date tBirth) + public void settBirth(Date tBirth) { this.tBirth = tBirth; } - public Date gettBirth() + public Date gettBirth() { return tBirth; } - public void settDepartment(String tDepartment) + public void setUserId(Long userId) { - this.tDepartment = tDepartment; + this.userId = userId; } - public String gettDepartment() + public Long getUserId() { - return tDepartment; + return userId; } - public void settTitle(String tTitle) + public void setOrgName(String orgName) { - this.tTitle = tTitle; + this.orgName = orgName; } - public String gettTitle() + public String getOrgName() { - return tTitle; + return orgName; } - public void settPhone(String tPhone) + public void setJobTitle(String jobTitle) + { + this.jobTitle = jobTitle; + } + + public String getJobTitle() + { + return jobTitle; + } + + public void settPhone(String tPhone) { this.tPhone = tPhone; } - public String gettPhone() + public String gettPhone() { return tPhone; } - public void settEmail(String tEmail) + public void settEmail(String tEmail) { this.tEmail = tEmail; } - public String gettEmail() + public String gettEmail() { return tEmail; } - public void setCreateUser(String createUser) + public void setStatus(Integer status) { - this.createUser = createUser; + this.status = status; } - public String getCreateUser() + public Integer getStatus() { - return createUser; + return status; + } + + public void setDelFlag(String delFlag) + { + this.delFlag = delFlag; + } + + public String getDelFlag() + { + return delFlag; } @Override @@ -150,12 +176,16 @@ public class TrainTeacher extends BaseEntity .append("tName", gettName()) .append("tGender", gettGender()) .append("tBirth", gettBirth()) - .append("tDepartment", gettDepartment()) - .append("tTitle", gettTitle()) + .append("userId", getUserId()) + .append("orgName", getOrgName()) + .append("jobTitle", getJobTitle()) .append("tPhone", gettPhone()) .append("tEmail", gettEmail()) - .append("createUser", getCreateUser()) + .append("status", getStatus()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTrainingSite.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTrainingSite.java index 021c5ead9..fd705060f 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTrainingSite.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainTrainingSite.java @@ -1,5 +1,9 @@ package com.ruoyi.train.domain; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import com.ruoyi.common.annotation.Excel; @@ -7,7 +11,7 @@ import com.ruoyi.common.core.domain.BaseEntity; /** * 实训场地对象 train_training_site - * + * * @author ruoyi * @date 2026-09-08 */ @@ -19,14 +23,19 @@ public class TrainTrainingSite extends BaseEntity private String siteId; /** 实训场地名称 */ + @NotBlank(message = "实训场地名称不能为空") + @Size(max = 80, message = "实训场地名称长度不能超过80个字符") @Excel(name = "实训场地名称") private String siteName; /** 场地位置 */ + @Size(max = 100, message = "场地位置长度不能超过100个字符") @Excel(name = "场地位置") private String siteLocation; /** 最大容纳人数 */ + @NotNull(message = "最大容纳人数不能为空") + @Min(value = 1, message = "最大容纳人数必须大于0") @Excel(name = "最大容纳人数") private Long capacity; @@ -35,81 +44,80 @@ public class TrainTrainingSite extends BaseEntity private String siteDevice; /** 0 停用 1 可用 */ - @Excel(name = "0 停用 1 可用") + @Excel(name = "场地状态", readConverterExp = "0=停用,1=可用") private Integer siteStatus; - /** 创建管理员账号 */ - @Excel(name = "创建管理员账号") - private String createUser; + /** 删除标志 (0代表存在 2代表删除) */ + private String delFlag; - public void setSiteId(String siteId) + public void setSiteId(String siteId) { this.siteId = siteId; } - public String getSiteId() + public String getSiteId() { return siteId; } - public void setSiteName(String siteName) + public void setSiteName(String siteName) { this.siteName = siteName; } - public String getSiteName() + public String getSiteName() { return siteName; } - public void setSiteLocation(String siteLocation) + public void setSiteLocation(String siteLocation) { this.siteLocation = siteLocation; } - public String getSiteLocation() + public String getSiteLocation() { return siteLocation; } - public void setCapacity(Long capacity) + public void setCapacity(Long capacity) { this.capacity = capacity; } - public Long getCapacity() + public Long getCapacity() { return capacity; } - public void setSiteDevice(String siteDevice) + public void setSiteDevice(String siteDevice) { this.siteDevice = siteDevice; } - public String getSiteDevice() + public String getSiteDevice() { return siteDevice; } - public void setSiteStatus(Integer siteStatus) + public void setSiteStatus(Integer siteStatus) { this.siteStatus = siteStatus; } - public Integer getSiteStatus() + public Integer getSiteStatus() { return siteStatus; } - public void setCreateUser(String createUser) + public void setDelFlag(String delFlag) { - this.createUser = createUser; + this.delFlag = delFlag; } - public String getCreateUser() + public String getDelFlag() { - return createUser; + return delFlag; } @Override @@ -121,8 +129,10 @@ public class TrainTrainingSite extends BaseEntity .append("capacity", getCapacity()) .append("siteDevice", getSiteDevice()) .append("siteStatus", getSiteStatus()) - .append("createUser", getCreateUser()) + .append("delFlag", getDelFlag()) + .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) + .append("updateBy", getUpdateBy()) .append("updateTime", getUpdateTime()) .append("remark", getRemark()) .toString(); diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/ClassroomCreateDTO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/ClassroomCreateDTO.java new file mode 100644 index 000000000..8e1758ed7 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/ClassroomCreateDTO.java @@ -0,0 +1,88 @@ +package com.ruoyi.train.domain.dto; + +import java.io.Serializable; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 教室创建 DTO (ISSUE-01-04 / API Contract V1.0.1) + * + * @author ruoyi + */ +public class ClassroomCreateDTO implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 教室名称 */ + @NotBlank(message = "教室名称不能为空") + @Size(max = 60, message = "教室名称长度不能超过60个字符") + private String roomName; + + /** 教室位置 */ + @Size(max = 100, message = "教室位置长度不能超过100个字符") + private String roomLocation; + + /** 核定容纳人数 */ + @NotNull(message = "容纳人数不能为空") + @Min(value = 1, message = "容纳人数必须大于0") + private Long capacity; + + /** 教室状态 (0: 正在使用, 1: 可用空闲, 2: 维护中) */ + private Integer roomStatus; + + /** 备注 */ + @Size(max = 500, message = "备注长度不能超过500个字符") + private String remark; + + public String getRoomName() + { + return roomName; + } + + public void setRoomName(String roomName) + { + this.roomName = roomName; + } + + public String getRoomLocation() + { + return roomLocation; + } + + public void setRoomLocation(String roomLocation) + { + this.roomLocation = roomLocation; + } + + public Long getCapacity() + { + return capacity; + } + + public void setCapacity(Long capacity) + { + this.capacity = capacity; + } + + public Integer getRoomStatus() + { + return roomStatus; + } + + public void setRoomStatus(Integer roomStatus) + { + this.roomStatus = roomStatus; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/PlanCreateDTO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/PlanCreateDTO.java new file mode 100644 index 000000000..c807fe7bd --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/PlanCreateDTO.java @@ -0,0 +1,57 @@ +package com.ruoyi.train.domain.dto; + +import java.util.Date; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** 创建培训计划请求。 */ +public class PlanCreateDTO +{ + @NotBlank(message = "培训班名称不能为空") + @Size(max = 150, message = "培训班名称不能超过150个字符") + private String planName; + + @NotNull(message = "开班时间不能为空") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + + @NotNull(message = "结束时间不能为空") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + + @NotNull(message = "报到截止时间不能为空") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date reportDeadline; + + @NotNull(message = "招生人数不能为空") + @Min(value = 1, message = "招生人数必须大于0") + private Long recruitNum; + + @Size(max = 200, message = "培训地点不能超过200个字符") + private String trainAddress; + + private String applyCondition; + + @Size(max = 500, message = "备注不能超过500个字符") + private String remark; + + public String getPlanName() { return planName; } + public void setPlanName(String planName) { this.planName = planName; } + public Date getStartTime() { return startTime; } + public void setStartTime(Date startTime) { this.startTime = startTime; } + public Date getEndTime() { return endTime; } + public void setEndTime(Date endTime) { this.endTime = endTime; } + public Date getReportDeadline() { return reportDeadline; } + public void setReportDeadline(Date reportDeadline) { this.reportDeadline = reportDeadline; } + public Long getRecruitNum() { return recruitNum; } + public void setRecruitNum(Long recruitNum) { this.recruitNum = recruitNum; } + public String getTrainAddress() { return trainAddress; } + public void setTrainAddress(String trainAddress) { this.trainAddress = trainAddress; } + public String getApplyCondition() { return applyCondition; } + public void setApplyCondition(String applyCondition) { this.applyCondition = applyCondition; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/PlanUpdateDTO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/PlanUpdateDTO.java new file mode 100644 index 000000000..5f94d1057 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/PlanUpdateDTO.java @@ -0,0 +1,57 @@ +package com.ruoyi.train.domain.dto; + +import java.util.Date; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** 编辑培训计划请求。生命周期状态不接受客户端直接修改。 */ +public class PlanUpdateDTO +{ + @NotBlank(message = "培训班名称不能为空") + @Size(max = 150, message = "培训班名称不能超过150个字符") + private String planName; + + @NotNull(message = "开班时间不能为空") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + + @NotNull(message = "结束时间不能为空") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + + @NotNull(message = "报到截止时间不能为空") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date reportDeadline; + + @NotNull(message = "招生人数不能为空") + @Min(value = 1, message = "招生人数必须大于0") + private Long recruitNum; + + @Size(max = 200, message = "培训地点不能超过200个字符") + private String trainAddress; + + private String applyCondition; + + @Size(max = 500, message = "备注不能超过500个字符") + private String remark; + + public String getPlanName() { return planName; } + public void setPlanName(String planName) { this.planName = planName; } + public Date getStartTime() { return startTime; } + public void setStartTime(Date startTime) { this.startTime = startTime; } + public Date getEndTime() { return endTime; } + public void setEndTime(Date endTime) { this.endTime = endTime; } + public Date getReportDeadline() { return reportDeadline; } + public void setReportDeadline(Date reportDeadline) { this.reportDeadline = reportDeadline; } + public Long getRecruitNum() { return recruitNum; } + public void setRecruitNum(Long recruitNum) { this.recruitNum = recruitNum; } + public String getTrainAddress() { return trainAddress; } + public void setTrainAddress(String trainAddress) { this.trainAddress = trainAddress; } + public String getApplyCondition() { return applyCondition; } + public void setApplyCondition(String applyCondition) { this.applyCondition = applyCondition; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/SiteCreateDTO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/SiteCreateDTO.java new file mode 100644 index 000000000..6744760d5 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/dto/SiteCreateDTO.java @@ -0,0 +1,101 @@ +package com.ruoyi.train.domain.dto; + +import java.io.Serializable; +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 实训场地创建 DTO (ISSUE-01-04 / API Contract V1.0.1) + * + * @author ruoyi + */ +public class SiteCreateDTO implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 实训场地名称 */ + @NotBlank(message = "实训场地名称不能为空") + @Size(max = 80, message = "实训场地名称长度不能超过80个字符") + private String siteName; + + /** 场地位置 */ + @Size(max = 100, message = "场地位置长度不能超过100个字符") + private String siteLocation; + + /** 最大容纳人数 */ + @NotNull(message = "最大容纳人数不能为空") + @Min(value = 1, message = "最大容纳人数必须大于0") + private Long capacity; + + /** 场地配套设备:VR、3D拆解、3D巡检设备等 */ + private String siteDevice; + + /** 场地状态 (0: 停用, 1: 可用) */ + private Integer siteStatus; + + /** 备注 */ + @Size(max = 500, message = "备注长度不能超过500个字符") + private String remark; + + public String getSiteName() + { + return siteName; + } + + public void setSiteName(String siteName) + { + this.siteName = siteName; + } + + public String getSiteLocation() + { + return siteLocation; + } + + public void setSiteLocation(String siteLocation) + { + this.siteLocation = siteLocation; + } + + public Long getCapacity() + { + return capacity; + } + + public void setCapacity(Long capacity) + { + this.capacity = capacity; + } + + public String getSiteDevice() + { + return siteDevice; + } + + public void setSiteDevice(String siteDevice) + { + this.siteDevice = siteDevice; + } + + public Integer getSiteStatus() + { + return siteStatus; + } + + public void setSiteStatus(Integer siteStatus) + { + this.siteStatus = siteStatus; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/query/PlanQuery.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/query/PlanQuery.java new file mode 100644 index 000000000..ab3ddf3f3 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/query/PlanQuery.java @@ -0,0 +1,26 @@ +package com.ruoyi.train.domain.query; + +import java.util.Date; +import org.springframework.format.annotation.DateTimeFormat; + +/** 培训计划分页筛选条件。分页参数由 RuoYi BaseController 接管。 */ +public class PlanQuery +{ + private String planName; + private Integer planStatus; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTimeBegin; + + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTimeEnd; + + public String getPlanName() { return planName; } + public void setPlanName(String planName) { this.planName = planName; } + public Integer getPlanStatus() { return planStatus; } + public void setPlanStatus(Integer planStatus) { this.planStatus = planStatus; } + public Date getStartTimeBegin() { return startTimeBegin; } + public void setStartTimeBegin(Date startTimeBegin) { this.startTimeBegin = startTimeBegin; } + public Date getStartTimeEnd() { return startTimeEnd; } + public void setStartTimeEnd(Date startTimeEnd) { this.startTimeEnd = startTimeEnd; } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/ClassroomBoardSummaryVO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/ClassroomBoardSummaryVO.java new file mode 100644 index 000000000..7a02b7b49 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/ClassroomBoardSummaryVO.java @@ -0,0 +1,63 @@ +package com.ruoyi.train.domain.vo; + +import java.io.Serializable; + +/** + * 教室绑定的电子班牌轻量摘要视图 (API Contract V1.0.2 / CG-05 闭环) + * + * @author ruoyi + */ +public class ClassroomBoardSummaryVO implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 班牌设备唯一标识 */ + private String boardId; + + /** 设备硬件序列号 */ + private String boardSn; + + /** 班牌业务状态 (0: 离线, 1: 在线正常) */ + private Integer boardStatus; + + public ClassroomBoardSummaryVO() + { + } + + public ClassroomBoardSummaryVO(String boardId, String boardSn, Integer boardStatus) + { + this.boardId = boardId; + this.boardSn = boardSn; + this.boardStatus = boardStatus; + } + + public String getBoardId() + { + return boardId; + } + + public void setBoardId(String boardId) + { + this.boardId = boardId; + } + + public String getBoardSn() + { + return boardSn; + } + + public void setBoardSn(String boardSn) + { + this.boardSn = boardSn; + } + + public Integer getBoardStatus() + { + return boardStatus; + } + + public void setBoardStatus(Integer boardStatus) + { + this.boardStatus = boardStatus; + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/ClassroomDetailVO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/ClassroomDetailVO.java new file mode 100644 index 000000000..32e35ce66 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/ClassroomDetailVO.java @@ -0,0 +1,122 @@ +package com.ruoyi.train.domain.vo; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 教室详情 VO (API Contract V1.0.2 / CG-05 闭环) + * + * @author ruoyi + */ +public class ClassroomDetailVO implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 教室ID主键 */ + private String classRoomId; + + /** 教室名称 */ + private String roomName; + + /** 教室位置 */ + private String roomLocation; + + /** 核定容纳人数 */ + private Long capacity; + + /** 教室状态 (0: 正在使用, 1: 可用空闲, 2: 维护中) */ + private Integer roomStatus; + + /** 绑定的班牌摘要列表。无绑定班牌时返回 [],不得返回 null */ + private List boards = new ArrayList<>(); + + /** 备注 */ + private String remark; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + public String getClassRoomId() + { + return classRoomId; + } + + public void setClassRoomId(String classRoomId) + { + this.classRoomId = classRoomId; + } + + public String getRoomName() + { + return roomName; + } + + public void setRoomName(String roomName) + { + this.roomName = roomName; + } + + public String getRoomLocation() + { + return roomLocation; + } + + public void setRoomLocation(String roomLocation) + { + this.roomLocation = roomLocation; + } + + public Long getCapacity() + { + return capacity; + } + + public void setCapacity(Long capacity) + { + this.capacity = capacity; + } + + public Integer getRoomStatus() + { + return roomStatus; + } + + public void setRoomStatus(Integer roomStatus) + { + this.roomStatus = roomStatus; + } + + public List getBoards() + { + return boards == null ? new ArrayList<>() : boards; + } + + public void setBoards(List boards) + { + this.boards = (boards == null) ? new ArrayList<>() : boards; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/CourseDetailVO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/CourseDetailVO.java new file mode 100644 index 000000000..c6c04d6bf --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/CourseDetailVO.java @@ -0,0 +1,298 @@ +package com.ruoyi.train.domain.vo; + +import java.io.Serializable; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.ruoyi.train.domain.TrainCourse; + +/** + * 课程详情视图对象 (API Contract: CourseDetailVO) + * 聚合返回课程基础信息及已绑定的教学资源列表 + * + * @author ruoyi + * @date 2026-09-14 + */ +public class CourseDetailVO implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 课程编号主键 */ + private String cId; + + /** 课程统一业务编码 */ + private String cCode; + + /** 课程名称 */ + private String cName; + + /** 课程类型 */ + private String cType; + + /** 课程学时 */ + private Integer cDuration; + + /** 教学目标 */ + private String teachingObjective; + + /** 课程内容/摘要 */ + private String courseContent; + + /** 课程状态 (1:正常, 0:停用) */ + private Integer status; + + /** 备注 */ + private String remark; + + /** 创建者 */ + private String createBy; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** 更新者 */ + private String updateBy; + + /** 更新时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + /** + * 已关联的教学资源列表 + * 注:当前 ISSUE-01-03 未实现真实 Course<->Resource 查询; + * 绑定/解绑及真实关联能力属于 P1。为保证集合字段结构稳定,默认返回空列表 (empty list)。 + */ + private List resources; + + public CourseDetailVO() + { + } + + public static CourseDetailVO from(TrainCourse course) + { + if (course == null) + { + return null; + } + CourseDetailVO vo = new CourseDetailVO(); + vo.setcId(course.getcId()); + vo.setcCode(course.getcCode()); + vo.setcName(course.getcName()); + vo.setcType(course.getcType()); + vo.setcDuration(course.getcDuration()); + vo.setTeachingObjective(course.getTeachingObjective()); + vo.setCourseContent(course.getCourseContent()); + vo.setStatus(course.getStatus()); + vo.setRemark(course.getRemark()); + vo.setCreateBy(course.getCreateBy()); + vo.setCreateTime(course.getCreateTime()); + vo.setUpdateBy(course.getUpdateBy()); + vo.setUpdateTime(course.getUpdateTime()); + // 资源绑定/解绑能力属于 P1,当前返回空列表保持响应结构稳定 + vo.setResources(Collections.emptyList()); + return vo; + } + + @JsonProperty("cId") + public String getcId() + { + return cId; + } + + @JsonProperty("cId") + public void setcId(String cId) + { + this.cId = cId; + } + + public String getCId() + { + return cId; + } + + public void setCId(String cId) + { + this.cId = cId; + } + + @JsonProperty("cCode") + public String getcCode() + { + return cCode; + } + + @JsonProperty("cCode") + public void setcCode(String cCode) + { + this.cCode = cCode; + } + + public String getCCode() + { + return cCode; + } + + public void setCCode(String cCode) + { + this.cCode = cCode; + } + + @JsonProperty("cName") + public String getcName() + { + return cName; + } + + @JsonProperty("cName") + public void setcName(String cName) + { + this.cName = cName; + } + + public String getCName() + { + return cName; + } + + public void setCName(String cName) + { + this.cName = cName; + } + + @JsonProperty("cType") + public String getcType() + { + return cType; + } + + @JsonProperty("cType") + public void setcType(String cType) + { + this.cType = cType; + } + + public String getCType() + { + return cType; + } + + public void setCType(String cType) + { + this.cType = cType; + } + + @JsonProperty("cDuration") + public Integer getcDuration() + { + return cDuration; + } + + @JsonProperty("cDuration") + public void setcDuration(Integer cDuration) + { + this.cDuration = cDuration; + } + + public Integer getCDuration() + { + return cDuration; + } + + public void setCDuration(Integer cDuration) + { + this.cDuration = cDuration; + } + + public String getTeachingObjective() + { + return teachingObjective; + } + + public void setTeachingObjective(String teachingObjective) + { + this.teachingObjective = teachingObjective; + } + + public String getCourseContent() + { + return courseContent; + } + + public void setCourseContent(String courseContent) + { + this.courseContent = courseContent; + } + + public Integer getStatus() + { + return status; + } + + public void setStatus(Integer status) + { + this.status = status; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public String getCreateBy() + { + return createBy; + } + + public void setCreateBy(String createBy) + { + this.createBy = createBy; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } + + public String getUpdateBy() + { + return updateBy; + } + + public void setUpdateBy(String updateBy) + { + this.updateBy = updateBy; + } + + public Date getUpdateTime() + { + return updateTime; + } + + public void setUpdateTime(Date updateTime) + { + this.updateTime = updateTime; + } + + public List getResources() + { + return resources; + } + + public void setResources(List resources) + { + this.resources = resources; + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/PlanDetailVO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/PlanDetailVO.java new file mode 100644 index 000000000..53dfd3ccf --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/PlanDetailVO.java @@ -0,0 +1,57 @@ +package com.ruoyi.train.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** 培训计划基本信息视图。 */ +public class PlanDetailVO +{ + private String planId; + private String planName; + private Integer planStatus; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date reportDeadline; + private String trainAddress; + private Long recruitNum; + private String applyCondition; + private String remark; + private String createBy; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + private String updateBy; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; + + public String getPlanId() { return planId; } + public void setPlanId(String planId) { this.planId = planId; } + public String getPlanName() { return planName; } + public void setPlanName(String planName) { this.planName = planName; } + public Integer getPlanStatus() { return planStatus; } + public void setPlanStatus(Integer planStatus) { this.planStatus = planStatus; } + public Date getStartTime() { return startTime; } + public void setStartTime(Date startTime) { this.startTime = startTime; } + public Date getEndTime() { return endTime; } + public void setEndTime(Date endTime) { this.endTime = endTime; } + public Date getReportDeadline() { return reportDeadline; } + public void setReportDeadline(Date reportDeadline) { this.reportDeadline = reportDeadline; } + public String getTrainAddress() { return trainAddress; } + public void setTrainAddress(String trainAddress) { this.trainAddress = trainAddress; } + public Long getRecruitNum() { return recruitNum; } + public void setRecruitNum(Long recruitNum) { this.recruitNum = recruitNum; } + public String getApplyCondition() { return applyCondition; } + public void setApplyCondition(String applyCondition) { this.applyCondition = applyCondition; } + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } + public String getCreateBy() { return createBy; } + public void setCreateBy(String createBy) { this.createBy = createBy; } + public Date getCreateTime() { return createTime; } + public void setCreateTime(Date createTime) { this.createTime = createTime; } + public String getUpdateBy() { return updateBy; } + public void setUpdateBy(String updateBy) { this.updateBy = updateBy; } + public Date getUpdateTime() { return updateTime; } + public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/PlanItemVO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/PlanItemVO.java new file mode 100644 index 000000000..b9ab2ae20 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/PlanItemVO.java @@ -0,0 +1,43 @@ +package com.ruoyi.train.domain.vo; + +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** 培训计划列表项。 */ +public class PlanItemVO +{ + private String planId; + private String planName; + private Integer planStatus; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date startTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date endTime; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date reportDeadline; + private String trainAddress; + private Long recruitNum; + private Long courseCount; + private Long enrolledCount; + + public String getPlanId() { return planId; } + public void setPlanId(String planId) { this.planId = planId; } + public String getPlanName() { return planName; } + public void setPlanName(String planName) { this.planName = planName; } + public Integer getPlanStatus() { return planStatus; } + public void setPlanStatus(Integer planStatus) { this.planStatus = planStatus; } + public Date getStartTime() { return startTime; } + public void setStartTime(Date startTime) { this.startTime = startTime; } + public Date getEndTime() { return endTime; } + public void setEndTime(Date endTime) { this.endTime = endTime; } + public Date getReportDeadline() { return reportDeadline; } + public void setReportDeadline(Date reportDeadline) { this.reportDeadline = reportDeadline; } + public String getTrainAddress() { return trainAddress; } + public void setTrainAddress(String trainAddress) { this.trainAddress = trainAddress; } + public Long getRecruitNum() { return recruitNum; } + public void setRecruitNum(Long recruitNum) { this.recruitNum = recruitNum; } + public Long getCourseCount() { return courseCount; } + public void setCourseCount(Long courseCount) { this.courseCount = courseCount; } + public Long getEnrolledCount() { return enrolledCount; } + public void setEnrolledCount(Long enrolledCount) { this.enrolledCount = enrolledCount; } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/SiteDetailVO.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/SiteDetailVO.java new file mode 100644 index 000000000..082eb7b49 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/vo/SiteDetailVO.java @@ -0,0 +1,120 @@ +package com.ruoyi.train.domain.vo; + +import java.io.Serializable; +import java.util.Date; +import com.fasterxml.jackson.annotation.JsonFormat; + +/** + * 实训场地详情 VO (ISSUE-01-04 / API Contract V1.0.1) + * + * @author ruoyi + */ +public class SiteDetailVO implements Serializable +{ + private static final long serialVersionUID = 1L; + + /** 实训场地ID主键 */ + private String siteId; + + /** 实训场地名称 */ + private String siteName; + + /** 场地位置 */ + private String siteLocation; + + /** 最大容纳人数 */ + private Long capacity; + + /** 场地配套设备:VR、3D拆解、3D巡检设备等 */ + private String siteDevice; + + /** 场地状态 (0: 停用, 1: 可用) */ + private Integer siteStatus; + + /** 备注 */ + private String remark; + + /** 创建时间 */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + public String getSiteId() + { + return siteId; + } + + public void setSiteId(String siteId) + { + this.siteId = siteId; + } + + public String getSiteName() + { + return siteName; + } + + public void setSiteName(String siteName) + { + this.siteName = siteName; + } + + public String getSiteLocation() + { + return siteLocation; + } + + public void setSiteLocation(String siteLocation) + { + this.siteLocation = siteLocation; + } + + public Long getCapacity() + { + return capacity; + } + + public void setCapacity(Long capacity) + { + this.capacity = capacity; + } + + public String getSiteDevice() + { + return siteDevice; + } + + public void setSiteDevice(String siteDevice) + { + this.siteDevice = siteDevice; + } + + public Integer getSiteStatus() + { + return siteStatus; + } + + public void setSiteStatus(Integer siteStatus) + { + this.siteStatus = siteStatus; + } + + public String getRemark() + { + return remark; + } + + public void setRemark(String remark) + { + this.remark = remark; + } + + public Date getCreateTime() + { + return createTime; + } + + public void setCreateTime(Date createTime) + { + this.createTime = createTime; + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainClassPlanMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainClassPlanMapper.java index 25541f69c..c04172216 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainClassPlanMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainClassPlanMapper.java @@ -2,6 +2,9 @@ package com.ruoyi.train.mapper; import java.util.List; import com.ruoyi.train.domain.TrainClassPlan; +import com.ruoyi.train.domain.query.PlanQuery; +import com.ruoyi.train.domain.vo.PlanItemVO; +import org.apache.ibatis.annotations.Param; /** * 开班计划Mapper接口 @@ -19,13 +22,24 @@ public interface TrainClassPlanMapper */ public TrainClassPlan selectTrainClassPlanByPlanId(String planId); + /** + * 查询并锁定培训计划,供生命周期动作使用。 + */ + public TrainClassPlan selectTrainClassPlanByPlanIdForUpdate(String planId); + /** * 查询开班计划列表 * - * @param trainClassPlan 开班计划 + * @param query 培训计划筛选条件 * @return 开班计划集合 */ - public List selectTrainClassPlanList(TrainClassPlan trainClassPlan); + public List selectTrainClassPlanList(PlanQuery query); + + /** + * 按规范化名称查询重复计划。 + */ + public int countTrainClassPlanByName(@Param("planName") String planName, + @Param("excludePlanId") String excludePlanId); /** * 新增开班计划 @@ -44,18 +58,7 @@ public interface TrainClassPlanMapper public int updateTrainClassPlan(TrainClassPlan trainClassPlan); /** - * 删除开班计划 - * - * @param planId 开班计划主键 - * @return 结果 + * 更新生命周期状态。 */ - public int deleteTrainClassPlanByPlanId(String planId); - - /** - * 批量删除开班计划 - * - * @param planIds 需要删除的数据主键集合 - * @return 结果 - */ - public int deleteTrainClassPlanByPlanIds(String[] planIds); + public int updateTrainClassPlanStatus(TrainClassPlan trainClassPlan); } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainClassroomInfoMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainClassroomInfoMapper.java index b840b6fb3..76bcb4821 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainClassroomInfoMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainClassroomInfoMapper.java @@ -1,19 +1,20 @@ package com.ruoyi.train.mapper; import java.util.List; +import org.apache.ibatis.annotations.Param; import com.ruoyi.train.domain.TrainClassroomInfo; /** * 教室信息Mapper接口 - * + * * @author ruoyi * @date 2026-09-08 */ -public interface TrainClassroomInfoMapper +public interface TrainClassroomInfoMapper { /** * 查询教室信息 - * + * * @param classRoomId 教室信息主键 * @return 教室信息 */ @@ -21,15 +22,24 @@ public interface TrainClassroomInfoMapper /** * 查询教室信息列表 - * + * * @param trainClassroomInfo 教室信息 * @return 教室信息集合 */ public List selectTrainClassroomInfoList(TrainClassroomInfo trainClassroomInfo); + /** + * 校验教室名称是否唯一 + * + * @param roomName 教室名称 + * @param excludeClassRoomId 排除的教室ID (修改时传,新增时传null) + * @return 教室信息 + */ + public TrainClassroomInfo checkRoomNameUnique(@Param("roomName") String roomName, @Param("excludeClassRoomId") String excludeClassRoomId); + /** * 新增教室信息 - * + * * @param trainClassroomInfo 教室信息 * @return 结果 */ @@ -37,7 +47,7 @@ public interface TrainClassroomInfoMapper /** * 修改教室信息 - * + * * @param trainClassroomInfo 教室信息 * @return 结果 */ @@ -45,7 +55,7 @@ public interface TrainClassroomInfoMapper /** * 删除教室信息 - * + * * @param classRoomId 教室信息主键 * @return 结果 */ @@ -53,7 +63,7 @@ public interface TrainClassroomInfoMapper /** * 批量删除教室信息 - * + * * @param classRoomIds 需要删除的数据主键集合 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainCourseMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainCourseMapper.java index 4d941bf04..59dbf0ad1 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainCourseMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainCourseMapper.java @@ -5,15 +5,15 @@ import com.ruoyi.train.domain.TrainCourse; /** * 课程信息Mapper接口 - * + * * @author ruoyi * @date 2026-09-08 */ -public interface TrainCourseMapper +public interface TrainCourseMapper { /** * 查询课程信息 - * + * * @param cId 课程信息主键 * @return 课程信息 */ @@ -21,7 +21,7 @@ public interface TrainCourseMapper /** * 查询课程信息列表 - * + * * @param trainCourse 课程信息 * @return 课程信息集合 */ @@ -29,7 +29,7 @@ public interface TrainCourseMapper /** * 新增课程信息 - * + * * @param trainCourse 课程信息 * @return 结果 */ @@ -37,7 +37,7 @@ public interface TrainCourseMapper /** * 修改课程信息 - * + * * @param trainCourse 课程信息 * @return 结果 */ @@ -45,15 +45,31 @@ public interface TrainCourseMapper /** * 删除课程信息 - * + * * @param cId 课程信息主键 * @return 结果 */ public int deleteTrainCourseByCId(String cId); + /** + * 校验课程统一业务编码是否唯一 + * + * @param cCode 课程统一业务编码 + * @return 课程信息 + */ + public TrainCourse checkCCodeUnique(String cCode); + + /** + * 根据课程统一业务编码查询课程信息 + * + * @param cCode 课程统一业务编码 + * @return 课程信息 + */ + public TrainCourse selectTrainCourseByCCode(String cCode); + /** * 批量删除课程信息 - * + * * @param cIds 需要删除的数据主键集合 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTeacherMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTeacherMapper.java index c422d71b0..718e3513a 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTeacherMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTeacherMapper.java @@ -5,15 +5,15 @@ import com.ruoyi.train.domain.TrainTeacher; /** * 教师信息Mapper接口 - * + * * @author ruoyi * @date 2026-09-08 */ -public interface TrainTeacherMapper +public interface TrainTeacherMapper { /** * 查询教师信息 - * + * * @param tId 教师信息主键 * @return 教师信息 */ @@ -21,7 +21,7 @@ public interface TrainTeacherMapper /** * 查询教师信息列表 - * + * * @param trainTeacher 教师信息 * @return 教师信息集合 */ @@ -29,7 +29,7 @@ public interface TrainTeacherMapper /** * 新增教师信息 - * + * * @param trainTeacher 教师信息 * @return 结果 */ @@ -37,15 +37,23 @@ public interface TrainTeacherMapper /** * 修改教师信息 - * + * * @param trainTeacher 教师信息 * @return 结果 */ public int updateTrainTeacher(TrainTeacher trainTeacher); + /** + * 根据手机号码查询教师(用于全局查重) + * + * @param tPhone 手机号码 + * @return 教师信息 + */ + public TrainTeacher checkPhoneUnique(String tPhone); + /** * 删除教师信息 - * + * * @param tId 教师信息主键 * @return 结果 */ @@ -53,7 +61,7 @@ public interface TrainTeacherMapper /** * 批量删除教师信息 - * + * * @param tIds 需要删除的数据主键集合 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTrainingSiteMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTrainingSiteMapper.java index 2d7d7e044..376bd0cb2 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTrainingSiteMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainTrainingSiteMapper.java @@ -1,19 +1,20 @@ package com.ruoyi.train.mapper; import java.util.List; +import org.apache.ibatis.annotations.Param; import com.ruoyi.train.domain.TrainTrainingSite; /** * 实训场地Mapper接口 - * + * * @author ruoyi * @date 2026-09-08 */ -public interface TrainTrainingSiteMapper +public interface TrainTrainingSiteMapper { /** * 查询实训场地 - * + * * @param siteId 实训场地主键 * @return 实训场地 */ @@ -21,15 +22,24 @@ public interface TrainTrainingSiteMapper /** * 查询实训场地列表 - * + * * @param trainTrainingSite 实训场地 * @return 实训场地集合 */ public List selectTrainTrainingSiteList(TrainTrainingSite trainTrainingSite); + /** + * 校验实训场地名称是否唯一 + * + * @param siteName 实训场地名称 + * @param excludeSiteId 排除的场地ID (修改时传,新增时传null) + * @return 实训场地信息 + */ + public TrainTrainingSite checkSiteNameUnique(@Param("siteName") String siteName, @Param("excludeSiteId") String excludeSiteId); + /** * 新增实训场地 - * + * * @param trainTrainingSite 实训场地 * @return 结果 */ @@ -37,7 +47,7 @@ public interface TrainTrainingSiteMapper /** * 修改实训场地 - * + * * @param trainTrainingSite 实训场地 * @return 结果 */ @@ -45,7 +55,7 @@ public interface TrainTrainingSiteMapper /** * 删除实训场地 - * + * * @param siteId 实训场地主键 * @return 结果 */ @@ -53,7 +63,7 @@ public interface TrainTrainingSiteMapper /** * 批量删除实训场地 - * + * * @param siteIds 需要删除的数据主键集合 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainClassPlanService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainClassPlanService.java index 14cd9429d..f47cb8bad 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainClassPlanService.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainClassPlanService.java @@ -2,6 +2,8 @@ package com.ruoyi.train.service; import java.util.List; import com.ruoyi.train.domain.TrainClassPlan; +import com.ruoyi.train.domain.query.PlanQuery; +import com.ruoyi.train.domain.vo.PlanItemVO; /** * 开班计划Service接口 @@ -19,13 +21,17 @@ public interface ITrainClassPlanService */ public TrainClassPlan selectTrainClassPlanByPlanId(String planId); + public TrainClassPlan selectTrainClassPlanByPlanIdForUpdate(String planId); + /** * 查询开班计划列表 * - * @param trainClassPlan 开班计划 + * @param query 培训计划筛选条件 * @return 开班计划集合 */ - public List selectTrainClassPlanList(TrainClassPlan trainClassPlan); + public List selectTrainClassPlanList(PlanQuery query); + + public boolean existsByPlanName(String planName, String excludePlanId); /** * 新增开班计划 @@ -43,19 +49,5 @@ public interface ITrainClassPlanService */ public int updateTrainClassPlan(TrainClassPlan trainClassPlan); - /** - * 批量删除开班计划 - * - * @param planIds 需要删除的开班计划主键集合 - * @return 结果 - */ - public int deleteTrainClassPlanByPlanIds(String[] planIds); - - /** - * 删除开班计划信息 - * - * @param planId 开班计划主键 - * @return 结果 - */ - public int deleteTrainClassPlanByPlanId(String planId); + public int updateTrainClassPlanStatus(TrainClassPlan trainClassPlan); } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainClassroomInfoService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainClassroomInfoService.java index 1eac3c578..2f573de45 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainClassroomInfoService.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainClassroomInfoService.java @@ -2,18 +2,27 @@ package com.ruoyi.train.service; import java.util.List; import com.ruoyi.train.domain.TrainClassroomInfo; +import com.ruoyi.train.domain.vo.ClassroomDetailVO; /** * 教室信息Service接口 - * + * * @author ruoyi * @date 2026-09-08 */ -public interface ITrainClassroomInfoService +public interface ITrainClassroomInfoService { /** - * 查询教室信息 - * + * 查询教室详情(聚合电子班牌信息,供API详情接口使用) + * + * @param classRoomId 教室信息主键 + * @return 教室详情VO + */ + public ClassroomDetailVO selectClassroomDetailByClassRoomId(String classRoomId); + + /** + * 查询教室基础信息 + * * @param classRoomId 教室信息主键 * @return 教室信息 */ @@ -21,15 +30,23 @@ public interface ITrainClassroomInfoService /** * 查询教室信息列表 - * + * * @param trainClassroomInfo 教室信息 * @return 教室信息集合 */ public List selectTrainClassroomInfoList(TrainClassroomInfo trainClassroomInfo); + /** + * 校验教室名称是否唯一 + * + * @param trainClassroomInfo 教室信息 + * @return 结果 (true 唯一, false 不唯一) + */ + public boolean checkRoomNameUnique(TrainClassroomInfo trainClassroomInfo); + /** * 新增教室信息 - * + * * @param trainClassroomInfo 教室信息 * @return 结果 */ @@ -37,7 +54,7 @@ public interface ITrainClassroomInfoService /** * 修改教室信息 - * + * * @param trainClassroomInfo 教室信息 * @return 结果 */ @@ -45,7 +62,7 @@ public interface ITrainClassroomInfoService /** * 批量删除教室信息 - * + * * @param classRoomIds 需要删除的教室信息主键集合 * @return 结果 */ @@ -53,7 +70,7 @@ public interface ITrainClassroomInfoService /** * 删除教室信息信息 - * + * * @param classRoomId 教室信息主键 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainCourseService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainCourseService.java index b5dc4979b..f01ec5893 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainCourseService.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainCourseService.java @@ -2,18 +2,27 @@ package com.ruoyi.train.service; import java.util.List; import com.ruoyi.train.domain.TrainCourse; +import com.ruoyi.train.domain.vo.CourseDetailVO; /** * 课程信息Service接口 - * + * * @author ruoyi * @date 2026-09-08 */ -public interface ITrainCourseService +public interface ITrainCourseService { + /** + * 查询课程详情视图 (CourseDetailVO) + * + * @param cId 课程信息主键 + * @return 课程详情VO + */ + public CourseDetailVO selectCourseDetailByCId(String cId); + /** * 查询课程信息 - * + * * @param cId 课程信息主键 * @return 课程信息 */ @@ -21,7 +30,7 @@ public interface ITrainCourseService /** * 查询课程信息列表 - * + * * @param trainCourse 课程信息 * @return 课程信息集合 */ @@ -29,7 +38,7 @@ public interface ITrainCourseService /** * 新增课程信息 - * + * * @param trainCourse 课程信息 * @return 结果 */ @@ -37,7 +46,7 @@ public interface ITrainCourseService /** * 修改课程信息 - * + * * @param trainCourse 课程信息 * @return 结果 */ @@ -45,15 +54,31 @@ public interface ITrainCourseService /** * 批量删除课程信息 - * + * * @param cIds 需要删除的课程信息主键集合 * @return 结果 */ public int deleteTrainCourseByCIds(String[] cIds); + /** + * 校验课程统一业务编码是否唯一 + * + * @param trainCourse 课程信息 + * @return 结果 + */ + public boolean checkCCodeUnique(TrainCourse trainCourse); + + /** + * 根据课程统一业务编码查询课程信息 + * + * @param cCode 课程统一业务编码 + * @return 课程信息 + */ + public TrainCourse selectTrainCourseByCCode(String cCode); + /** * 删除课程信息信息 - * + * * @param cId 课程信息主键 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTeacherService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTeacherService.java index 934034230..49af34583 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTeacherService.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTeacherService.java @@ -5,15 +5,15 @@ import com.ruoyi.train.domain.TrainTeacher; /** * 教师信息Service接口 - * + * * @author ruoyi * @date 2026-09-08 */ -public interface ITrainTeacherService +public interface ITrainTeacherService { /** * 查询教师信息 - * + * * @param tId 教师信息主键 * @return 教师信息 */ @@ -21,7 +21,7 @@ public interface ITrainTeacherService /** * 查询教师信息列表 - * + * * @param trainTeacher 教师信息 * @return 教师信息集合 */ @@ -29,7 +29,7 @@ public interface ITrainTeacherService /** * 新增教师信息 - * + * * @param trainTeacher 教师信息 * @return 结果 */ @@ -37,15 +37,23 @@ public interface ITrainTeacherService /** * 修改教师信息 - * + * * @param trainTeacher 教师信息 * @return 结果 */ public int updateTrainTeacher(TrainTeacher trainTeacher); + /** + * 校验教师手机号码是否唯一 + * + * @param trainTeacher 教师信息 + * @return 结果 + */ + public boolean checkPhoneUnique(TrainTeacher trainTeacher); + /** * 批量删除教师信息 - * + * * @param tIds 需要删除的教师信息主键集合 * @return 结果 */ @@ -53,7 +61,7 @@ public interface ITrainTeacherService /** * 删除教师信息信息 - * + * * @param tId 教师信息主键 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTrainingSiteService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTrainingSiteService.java index 53b34842d..19286b56b 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTrainingSiteService.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainTrainingSiteService.java @@ -5,15 +5,15 @@ import com.ruoyi.train.domain.TrainTrainingSite; /** * 实训场地Service接口 - * + * * @author ruoyi * @date 2026-09-08 */ -public interface ITrainTrainingSiteService +public interface ITrainTrainingSiteService { /** * 查询实训场地 - * + * * @param siteId 实训场地主键 * @return 实训场地 */ @@ -21,15 +21,23 @@ public interface ITrainTrainingSiteService /** * 查询实训场地列表 - * + * * @param trainTrainingSite 实训场地 * @return 实训场地集合 */ public List selectTrainTrainingSiteList(TrainTrainingSite trainTrainingSite); + /** + * 校验实训场地名称是否唯一 + * + * @param trainTrainingSite 实训场地 + * @return 结果 (true 唯一, false 不唯一) + */ + public boolean checkSiteNameUnique(TrainTrainingSite trainTrainingSite); + /** * 新增实训场地 - * + * * @param trainTrainingSite 实训场地 * @return 结果 */ @@ -37,7 +45,7 @@ public interface ITrainTrainingSiteService /** * 修改实训场地 - * + * * @param trainTrainingSite 实训场地 * @return 结果 */ @@ -45,7 +53,7 @@ public interface ITrainTrainingSiteService /** * 批量删除实训场地 - * + * * @param siteIds 需要删除的实训场地主键集合 * @return 结果 */ @@ -53,7 +61,7 @@ public interface ITrainTrainingSiteService /** * 删除实训场地信息 - * + * * @param siteId 实训场地主键 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/PlanAppService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/PlanAppService.java new file mode 100644 index 000000000..4f305bcbf --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/PlanAppService.java @@ -0,0 +1,22 @@ +package com.ruoyi.train.service; + +import java.util.List; +import com.ruoyi.train.domain.dto.PlanCreateDTO; +import com.ruoyi.train.domain.dto.PlanUpdateDTO; +import com.ruoyi.train.domain.query.PlanQuery; +import com.ruoyi.train.domain.vo.PlanDetailVO; +import com.ruoyi.train.domain.vo.PlanItemVO; + +/** 培训计划生命周期应用服务。 */ +public interface PlanAppService +{ + List listPlans(PlanQuery query); + + PlanDetailVO getPlan(String planId); + + String createPlan(PlanCreateDTO dto, String operator); + + void updatePlan(String planId, PlanUpdateDTO dto, String operator); + + void completePlan(String planId, String operator); +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/PlanAppServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/PlanAppServiceImpl.java new file mode 100644 index 000000000..a7a9760ca --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/PlanAppServiceImpl.java @@ -0,0 +1,230 @@ +package com.ruoyi.train.service.impl; + +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; +import com.ruoyi.train.constant.PlanErrorCode; +import com.ruoyi.train.domain.TrainClassPlan; +import com.ruoyi.train.domain.dto.PlanCreateDTO; +import com.ruoyi.train.domain.dto.PlanUpdateDTO; +import com.ruoyi.train.domain.query.PlanQuery; +import com.ruoyi.train.domain.vo.PlanDetailVO; +import com.ruoyi.train.domain.vo.PlanItemVO; +import com.ruoyi.train.service.ITrainClassPlanService; +import com.ruoyi.train.service.PlanAppService; +import com.ruoyi.train.service.port.PlanEnrollmentCompletionPort; + +/** 培训计划生命周期应用服务实现。 */ +@Service +public class PlanAppServiceImpl implements PlanAppService +{ + private static final int STATUS_NOT_STARTED = 0; + private static final int STATUS_COMPLETED = 2; + private static final int STATUS_CANCELLED = 3; + + private final ITrainClassPlanService planService; + private final List enrollmentCompletionPorts; + + @Autowired + public PlanAppServiceImpl(ITrainClassPlanService planService, + ObjectProvider enrollmentCompletionPortProvider) + { + this(planService, enrollmentCompletionPortProvider.orderedStream() + .collect(java.util.stream.Collectors.toList())); + } + + /** Test seam; production has no fake/no-op Enrollment adapter before ISSUE-03-01. */ + PlanAppServiceImpl(ITrainClassPlanService planService, + List enrollmentCompletionPorts) + { + this.planService = planService; + this.enrollmentCompletionPorts = enrollmentCompletionPorts == null + ? Collections.emptyList() : enrollmentCompletionPorts; + } + + @Override + public List listPlans(PlanQuery query) + { + return planService.selectTrainClassPlanList(query == null ? new PlanQuery() : query); + } + + @Override + public PlanDetailVO getPlan(String planId) + { + TrainClassPlan plan = requirePlan(planService.selectTrainClassPlanByPlanId(planId)); + return toDetailVO(plan); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String createPlan(PlanCreateDTO dto, String operator) + { + validatePlan(dto.getStartTime(), dto.getEndTime(), dto.getReportDeadline(), dto.getRecruitNum()); + String planName = normalizePlanName(dto.getPlanName()); + ensureUniqueName(planName, null); + + Date now = DateUtils.getNowDate(); + TrainClassPlan plan = new TrainClassPlan(); + plan.setPlanId(IdUtils.fastSimpleUUID()); + plan.setPlanName(planName); + plan.setStartTime(dto.getStartTime()); + plan.setEndTime(dto.getEndTime()); + plan.setReportDeadline(dto.getReportDeadline()); + plan.setTrainAddress(dto.getTrainAddress()); + plan.setRecruitNum(dto.getRecruitNum()); + plan.setApplyCondition(dto.getApplyCondition()); + plan.setPlanStatus(STATUS_NOT_STARTED); + plan.setDelFlag("0"); + plan.setCreateBy(operator); + plan.setCreateTime(now); + plan.setRemark(dto.getRemark()); + planService.insertTrainClassPlan(plan); + return plan.getPlanId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updatePlan(String planId, PlanUpdateDTO dto, String operator) + { + TrainClassPlan existing = requirePlan(planService.selectTrainClassPlanByPlanIdForUpdate(planId)); + if (isLocked(existing.getPlanStatus())) + { + throw error(PlanErrorCode.PLAN_STATUS_LOCKED_MESSAGE, PlanErrorCode.PLAN_STATUS_LOCKED); + } + validatePlan(dto.getStartTime(), dto.getEndTime(), dto.getReportDeadline(), dto.getRecruitNum()); + String planName = normalizePlanName(dto.getPlanName()); + ensureUniqueName(planName, planId); + + TrainClassPlan plan = new TrainClassPlan(); + plan.setPlanId(planId); + plan.setPlanName(planName); + plan.setStartTime(dto.getStartTime()); + plan.setEndTime(dto.getEndTime()); + plan.setReportDeadline(dto.getReportDeadline()); + plan.setTrainAddress(dto.getTrainAddress()); + plan.setRecruitNum(dto.getRecruitNum()); + plan.setApplyCondition(dto.getApplyCondition()); + plan.setUpdateBy(operator); + plan.setUpdateTime(DateUtils.getNowDate()); + plan.setRemark(dto.getRemark()); + planService.updateTrainClassPlan(plan); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void completePlan(String planId, String operator) + { + List completionPorts = requireEnrollmentCompletionPorts(); + TrainClassPlan plan = requirePlan(planService.selectTrainClassPlanByPlanIdForUpdate(planId)); + if (Integer.valueOf(STATUS_COMPLETED).equals(plan.getPlanStatus())) + { + throw error(PlanErrorCode.PLAN_ALREADY_COMPLETED_MESSAGE, + PlanErrorCode.PLAN_ALREADY_COMPLETED); + } + if (Integer.valueOf(STATUS_CANCELLED).equals(plan.getPlanStatus())) + { + throw error(PlanErrorCode.PLAN_STATUS_LOCKED_MESSAGE, PlanErrorCode.PLAN_STATUS_LOCKED); + } + + Date completeTime = DateUtils.getNowDate(); + TrainClassPlan statusUpdate = new TrainClassPlan(); + statusUpdate.setPlanId(planId); + statusUpdate.setPlanStatus(STATUS_COMPLETED); + statusUpdate.setUpdateBy(operator); + statusUpdate.setUpdateTime(completeTime); + planService.updateTrainClassPlanStatus(statusUpdate); + + for (PlanEnrollmentCompletionPort port : completionPorts) + { + port.completeEnrollments(planId, operator, completeTime); + } + } + + private List requireEnrollmentCompletionPorts() + { + if (enrollmentCompletionPorts.isEmpty()) + { + throw new IllegalStateException( + "PlanEnrollmentCompletionPort is not configured; plan completion is disabled"); + } + return enrollmentCompletionPorts; + } + + private TrainClassPlan requirePlan(TrainClassPlan plan) + { + if (plan == null) + { + throw error(PlanErrorCode.PLAN_NOT_FOUND_MESSAGE, PlanErrorCode.PLAN_NOT_FOUND); + } + return plan; + } + + private void ensureUniqueName(String planName, String excludePlanId) + { + if (planService.existsByPlanName(planName, excludePlanId)) + { + throw error(PlanErrorCode.PLAN_NAME_DUPLICATED_MESSAGE, + PlanErrorCode.PLAN_NAME_DUPLICATED); + } + } + + private String normalizePlanName(String planName) + { + return StringUtils.trim(planName); + } + + private void validatePlan(Date startTime, Date endTime, Date reportDeadline, Long recruitNum) + { + if (startTime == null || endTime == null || !startTime.before(endTime)) + { + throw error(PlanErrorCode.PLAN_TIME_INVALID_MESSAGE, PlanErrorCode.PLAN_TIME_INVALID); + } + if (reportDeadline == null || reportDeadline.after(startTime)) + { + throw error("报到截止时间不得晚于开班时间", PlanErrorCode.PLAN_TIME_INVALID); + } + if (recruitNum == null || recruitNum.longValue() <= 0) + { + throw new ServiceException("计划招生人数必须大于0"); + } + } + + private boolean isLocked(Integer status) + { + return Integer.valueOf(STATUS_COMPLETED).equals(status) + || Integer.valueOf(STATUS_CANCELLED).equals(status); + } + + private PlanDetailVO toDetailVO(TrainClassPlan plan) + { + PlanDetailVO vo = new PlanDetailVO(); + vo.setPlanId(plan.getPlanId()); + vo.setPlanName(plan.getPlanName()); + vo.setPlanStatus(plan.getPlanStatus()); + vo.setStartTime(plan.getStartTime()); + vo.setEndTime(plan.getEndTime()); + vo.setReportDeadline(plan.getReportDeadline()); + vo.setTrainAddress(plan.getTrainAddress()); + vo.setRecruitNum(plan.getRecruitNum()); + vo.setApplyCondition(plan.getApplyCondition()); + vo.setRemark(plan.getRemark()); + vo.setCreateBy(plan.getCreateBy()); + vo.setCreateTime(plan.getCreateTime()); + vo.setUpdateBy(plan.getUpdateBy()); + vo.setUpdateTime(plan.getUpdateTime()); + return vo; + } + + private ServiceException error(String message, int code) + { + return new ServiceException(message, code); + } +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainClassPlanServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainClassPlanServiceImpl.java index 3775e56f8..6012479c6 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainClassPlanServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainClassPlanServiceImpl.java @@ -6,6 +6,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.train.mapper.TrainClassPlanMapper; import com.ruoyi.train.domain.TrainClassPlan; +import com.ruoyi.train.domain.query.PlanQuery; +import com.ruoyi.train.domain.vo.PlanItemVO; import com.ruoyi.train.service.ITrainClassPlanService; /** @@ -32,16 +34,28 @@ public class TrainClassPlanServiceImpl implements ITrainClassPlanService return trainClassPlanMapper.selectTrainClassPlanByPlanId(planId); } + @Override + public TrainClassPlan selectTrainClassPlanByPlanIdForUpdate(String planId) + { + return trainClassPlanMapper.selectTrainClassPlanByPlanIdForUpdate(planId); + } + /** * 查询开班计划列表 * - * @param trainClassPlan 开班计划 + * @param query 培训计划筛选条件 * @return 开班计划 */ @Override - public List selectTrainClassPlanList(TrainClassPlan trainClassPlan) + public List selectTrainClassPlanList(PlanQuery query) { - return trainClassPlanMapper.selectTrainClassPlanList(trainClassPlan); + return trainClassPlanMapper.selectTrainClassPlanList(query); + } + + @Override + public boolean existsByPlanName(String planName, String excludePlanId) + { + return trainClassPlanMapper.countTrainClassPlanByName(planName, excludePlanId) > 0; } /** @@ -70,27 +84,9 @@ public class TrainClassPlanServiceImpl implements ITrainClassPlanService return trainClassPlanMapper.updateTrainClassPlan(trainClassPlan); } - /** - * 批量删除开班计划 - * - * @param planIds 需要删除的开班计划主键 - * @return 结果 - */ @Override - public int deleteTrainClassPlanByPlanIds(String[] planIds) + public int updateTrainClassPlanStatus(TrainClassPlan trainClassPlan) { - return trainClassPlanMapper.deleteTrainClassPlanByPlanIds(planIds); - } - - /** - * 删除开班计划信息 - * - * @param planId 开班计划主键 - * @return 结果 - */ - @Override - public int deleteTrainClassPlanByPlanId(String planId) - { - return trainClassPlanMapper.deleteTrainClassPlanByPlanId(planId); + return trainClassPlanMapper.updateTrainClassPlanStatus(trainClassPlan); } } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainClassroomInfoServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainClassroomInfoServiceImpl.java index acc541e32..988a49b2b 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainClassroomInfoServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainClassroomInfoServiceImpl.java @@ -1,28 +1,82 @@ package com.ruoyi.train.service.impl; +import java.util.ArrayList; import java.util.List; -import com.ruoyi.common.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import com.ruoyi.train.mapper.TrainClassroomInfoMapper; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.train.domain.TrainClassroomInfo; +import com.ruoyi.train.domain.TrainElectronicBoard; +import com.ruoyi.train.domain.vo.ClassroomBoardSummaryVO; +import com.ruoyi.train.domain.vo.ClassroomDetailVO; +import com.ruoyi.train.mapper.TrainClassroomInfoMapper; +import com.ruoyi.train.mapper.TrainElectronicBoardMapper; import com.ruoyi.train.service.ITrainClassroomInfoService; /** * 教室信息Service业务层处理 - * + * * @author ruoyi * @date 2026-09-08 */ @Service -public class TrainClassroomInfoServiceImpl implements ITrainClassroomInfoService +public class TrainClassroomInfoServiceImpl implements ITrainClassroomInfoService { @Autowired private TrainClassroomInfoMapper trainClassroomInfoMapper; + @Autowired + private TrainElectronicBoardMapper trainElectronicBoardMapper; + /** - * 查询教室信息 - * + * 查询教室详情(供API详情接口使用,聚合班牌轻量摘要列表,0..N语义) + * + * @param classRoomId 教室信息主键 + * @return 教室详情VO + */ + @Override + public ClassroomDetailVO selectClassroomDetailByClassRoomId(String classRoomId) + { + TrainClassroomInfo classroom = trainClassroomInfoMapper.selectTrainClassroomInfoByClassRoomId(classRoomId); + if (classroom == null) + { + return null; + } + ClassroomDetailVO vo = new ClassroomDetailVO(); + vo.setClassRoomId(classroom.getClassRoomId()); + vo.setRoomName(classroom.getRoomName()); + vo.setRoomLocation(classroom.getRoomLocation()); + vo.setCapacity(classroom.getCapacity()); + vo.setRoomStatus(classroom.getRoomStatus()); + vo.setRemark(classroom.getRemark()); + vo.setCreateTime(classroom.getCreateTime()); + + // 按 bind_classroom_id 查询绑定的电子班牌(0..N),映射为轻量摘要 VO,无绑定时返回空列表 [] + TrainElectronicBoard boardQuery = new TrainElectronicBoard(); + boardQuery.setBindClassroomId(classRoomId); + List boardList = trainElectronicBoardMapper.selectTrainElectronicBoardList(boardQuery); + List summaryList = new ArrayList<>(); + if (boardList != null && !boardList.isEmpty()) + { + for (TrainElectronicBoard board : boardList) + { + summaryList.add(new ClassroomBoardSummaryVO( + board.getBoardId(), + board.getBoardSn(), + board.getBoardStatus() + )); + } + } + vo.setBoards(summaryList); + return vo; + } + + /** + * 查询教室基础信息 (仅持久化Domain) + * * @param classRoomId 教室信息主键 * @return 教室信息 */ @@ -34,7 +88,7 @@ public class TrainClassroomInfoServiceImpl implements ITrainClassroomInfoService /** * 查询教室信息列表 - * + * * @param trainClassroomInfo 教室信息 * @return 教室信息 */ @@ -44,35 +98,86 @@ public class TrainClassroomInfoServiceImpl implements ITrainClassroomInfoService return trainClassroomInfoMapper.selectTrainClassroomInfoList(trainClassroomInfo); } + /** + * 校验教室名称是否唯一 + * + * @param trainClassroomInfo 教室信息 + * @return 结果 (true 唯一, false 不唯一) + */ + @Override + public boolean checkRoomNameUnique(TrainClassroomInfo trainClassroomInfo) + { + if (StringUtils.isEmpty(trainClassroomInfo.getRoomName())) + { + return true; + } + TrainClassroomInfo info = trainClassroomInfoMapper.checkRoomNameUnique( + trainClassroomInfo.getRoomName(), + trainClassroomInfo.getClassRoomId() + ); + return info == null; + } + /** * 新增教室信息 - * + * * @param trainClassroomInfo 教室信息 * @return 结果 */ @Override public int insertTrainClassroomInfo(TrainClassroomInfo trainClassroomInfo) { + // 1. 容纳人数必须大于0 + if (trainClassroomInfo.getCapacity() == null || trainClassroomInfo.getCapacity() <= 0) + { + throw new ServiceException("教室容纳人数必须大于0"); + } + // 2. 教室名称全局/基地内唯一校验 + if (!checkRoomNameUnique(trainClassroomInfo)) + { + throw new ServiceException("教室名称已存在", 40008); + } + // 3. 自动生成32位主键UUID(若未指定) + if (StringUtils.isEmpty(trainClassroomInfo.getClassRoomId())) + { + trainClassroomInfo.setClassRoomId(IdUtils.fastSimpleUUID()); + } + // 4. 默认值处理 + if (trainClassroomInfo.getRoomStatus() == null) + { + trainClassroomInfo.setRoomStatus(1); // 默认可用空闲 + } + trainClassroomInfo.setDelFlag("0"); trainClassroomInfo.setCreateTime(DateUtils.getNowDate()); return trainClassroomInfoMapper.insertTrainClassroomInfo(trainClassroomInfo); } /** * 修改教室信息 - * + * * @param trainClassroomInfo 教室信息 * @return 结果 */ @Override public int updateTrainClassroomInfo(TrainClassroomInfo trainClassroomInfo) { + // 1. 容纳人数校验 + if (trainClassroomInfo.getCapacity() != null && trainClassroomInfo.getCapacity() <= 0) + { + throw new ServiceException("教室容纳人数必须大于0"); + } + // 2. 教室名称唯一性校验 + if (!checkRoomNameUnique(trainClassroomInfo)) + { + throw new ServiceException("教室名称已存在", 40008); + } trainClassroomInfo.setUpdateTime(DateUtils.getNowDate()); return trainClassroomInfoMapper.updateTrainClassroomInfo(trainClassroomInfo); } /** * 批量删除教室信息 - * + * * @param classRoomIds 需要删除的教室信息主键 * @return 结果 */ @@ -83,8 +188,8 @@ public class TrainClassroomInfoServiceImpl implements ITrainClassroomInfoService } /** - * 删除教室信息信息 - * + * 删除教室信息 + * * @param classRoomId 教室信息主键 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainCourseServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainCourseServiceImpl.java index d8c9c32f6..dc4622212 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainCourseServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainCourseServiceImpl.java @@ -1,28 +1,46 @@ package com.ruoyi.train.service.impl; import java.util.List; -import com.ruoyi.common.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.train.mapper.TrainCourseMapper; import com.ruoyi.train.domain.TrainCourse; +import com.ruoyi.train.domain.vo.CourseDetailVO; import com.ruoyi.train.service.ITrainCourseService; /** * 课程信息Service业务层处理 - * + * * @author ruoyi - * @date 2026-09-08 + * @date 2026-09-14 */ @Service -public class TrainCourseServiceImpl implements ITrainCourseService +public class TrainCourseServiceImpl implements ITrainCourseService { @Autowired private TrainCourseMapper trainCourseMapper; + /** + * 查询课程详情视图 (CourseDetailVO) + * + * @param cId 课程信息主键 + * @return 课程详情VO + */ + @Override + public CourseDetailVO selectCourseDetailByCId(String cId) + { + TrainCourse course = trainCourseMapper.selectTrainCourseByCId(cId); + return CourseDetailVO.from(course); + } + /** * 查询课程信息 - * + * * @param cId 课程信息主键 * @return 课程信息 */ @@ -34,7 +52,7 @@ public class TrainCourseServiceImpl implements ITrainCourseService /** * 查询课程信息列表 - * + * * @param trainCourse 课程信息 * @return 课程信息 */ @@ -46,33 +64,150 @@ public class TrainCourseServiceImpl implements ITrainCourseService /** * 新增课程信息 - * + * * @param trainCourse 课程信息 * @return 结果 */ @Override public int insertTrainCourse(TrainCourse trainCourse) { + // 1. 课程名称校验 + if (StringUtils.isEmpty(trainCourse.getcName())) + { + throw new ServiceException("课程名称不能为空", 400); + } + if (trainCourse.getcName().length() > 100) + { + throw new ServiceException("课程名称长度不能超过100个字符", 400); + } + + // 2. 课程统一编码唯一性校验 + if (StringUtils.isNotEmpty(trainCourse.getcCode())) + { + if (trainCourse.getcCode().length() > 50) + { + throw new ServiceException("课程编码长度不能超过50个字符", 400); + } + if (!checkCCodeUnique(trainCourse)) + { + throw new ServiceException("课程编码已存在", 40003); + } + } + + // 3. 课程学时正整数校验 + if (trainCourse.getcDuration() != null && trainCourse.getcDuration() <= 0) + { + throw new ServiceException("课程学时必须为正整数", 400); + } + + // 4. 生成 32 位无短横线 UUID 主键 + if (StringUtils.isEmpty(trainCourse.getcId())) + { + trainCourse.setcId(IdUtils.fastSimpleUUID()); + } + + // 5. 初始默认状态与逻辑删除标志 + if (trainCourse.getStatus() == null) + { + trainCourse.setStatus(1); + } + trainCourse.setDelFlag("0"); trainCourse.setCreateTime(DateUtils.getNowDate()); + + try + { + trainCourse.setCreateBy(SecurityUtils.getUsername()); + } + catch (Exception e) + { + // 兜底处理 (如无上下文或测试环境) + } + return trainCourseMapper.insertTrainCourse(trainCourse); } /** * 修改课程信息 - * + * * @param trainCourse 课程信息 * @return 结果 */ @Override public int updateTrainCourse(TrainCourse trainCourse) { + // 1. 课程名称校验 + if (StringUtils.isNotEmpty(trainCourse.getcName()) && trainCourse.getcName().length() > 100) + { + throw new ServiceException("课程名称长度不能超过100个字符", 400); + } + + // 2. 课程统一编码唯一性校验 + if (StringUtils.isNotEmpty(trainCourse.getcCode())) + { + if (trainCourse.getcCode().length() > 50) + { + throw new ServiceException("课程编码长度不能超过50个字符", 400); + } + if (!checkCCodeUnique(trainCourse)) + { + throw new ServiceException("课程编码已存在", 40003); + } + } + + // 3. 课程学时正整数校验 + if (trainCourse.getcDuration() != null && trainCourse.getcDuration() <= 0) + { + throw new ServiceException("课程学时必须为正整数", 400); + } + trainCourse.setUpdateTime(DateUtils.getNowDate()); + try + { + trainCourse.setUpdateBy(SecurityUtils.getUsername()); + } + catch (Exception e) + { + // 兜底处理 + } return trainCourseMapper.updateTrainCourse(trainCourse); } + /** + * 校验课程统一业务编码是否唯一 + * + * @param trainCourse 课程信息 + * @return 结果 (true: 唯一可用, false: 已存在重复) + */ + @Override + public boolean checkCCodeUnique(TrainCourse trainCourse) + { + if (StringUtils.isEmpty(trainCourse.getcCode())) + { + return true; + } + TrainCourse info = trainCourseMapper.checkCCodeUnique(trainCourse.getcCode()); + if (info != null && (StringUtils.isEmpty(trainCourse.getcId()) || !info.getcId().equals(trainCourse.getcId()))) + { + return false; + } + return true; + } + + /** + * 根据课程统一业务编码查询课程信息 + * + * @param cCode 课程统一业务编码 + * @return 课程信息 + */ + @Override + public TrainCourse selectTrainCourseByCCode(String cCode) + { + return trainCourseMapper.selectTrainCourseByCCode(cCode); + } + /** * 批量删除课程信息 - * + * * @param cIds 需要删除的课程信息主键 * @return 结果 */ @@ -84,7 +219,7 @@ public class TrainCourseServiceImpl implements ITrainCourseService /** * 删除课程信息信息 - * + * * @param cId 课程信息主键 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTeacherServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTeacherServiceImpl.java index 6c7107de8..5fbad20af 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTeacherServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTeacherServiceImpl.java @@ -1,7 +1,11 @@ package com.ruoyi.train.service.impl; import java.util.List; +import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.ruoyi.train.mapper.TrainTeacherMapper; @@ -10,19 +14,19 @@ import com.ruoyi.train.service.ITrainTeacherService; /** * 教师信息Service业务层处理 - * + * * @author ruoyi - * @date 2026-09-08 + * @date 2026-09-14 */ @Service -public class TrainTeacherServiceImpl implements ITrainTeacherService +public class TrainTeacherServiceImpl implements ITrainTeacherService { @Autowired private TrainTeacherMapper trainTeacherMapper; /** * 查询教师信息 - * + * * @param tId 教师信息主键 * @return 教师信息 */ @@ -34,7 +38,7 @@ public class TrainTeacherServiceImpl implements ITrainTeacherService /** * 查询教师信息列表 - * + * * @param trainTeacher 教师信息 * @return 教师信息 */ @@ -44,35 +48,125 @@ public class TrainTeacherServiceImpl implements ITrainTeacherService return trainTeacherMapper.selectTrainTeacherList(trainTeacher); } + /** + * 校验教师手机号码是否唯一 + * + * @param trainTeacher 教师信息 + * @return true 唯一,false 重复 + */ + @Override + public boolean checkPhoneUnique(TrainTeacher trainTeacher) + { + if (StringUtils.isEmpty(trainTeacher.gettPhone())) + { + return true; + } + TrainTeacher info = trainTeacherMapper.checkPhoneUnique(trainTeacher.gettPhone()); + if (StringUtils.isNotNull(info) && !StringUtils.equals(info.gettId(), trainTeacher.gettId())) + { + return false; + } + return true; + } + /** * 新增教师信息 - * + * * @param trainTeacher 教师信息 * @return 结果 */ @Override public int insertTrainTeacher(TrainTeacher trainTeacher) { + // 1. 姓名校验 + if (StringUtils.isEmpty(trainTeacher.gettName())) + { + throw new ServiceException("教师姓名不能为空"); + } + if (trainTeacher.gettName().trim().length() > 50) + { + throw new ServiceException("教师姓名长度不能超过50个字符"); + } + + // 2. 手机号系统全局查重 (AC-02: 40001 TEACHER_PHONE_DUPLICATED) + if (StringUtils.isNotEmpty(trainTeacher.gettPhone()) && !checkPhoneUnique(trainTeacher)) + { + throw new ServiceException("教师手机号码已存在", 40001); + } + + // 3. 主键生成:支持自定义工号或自动生成32位UUID + if (StringUtils.isEmpty(trainTeacher.gettId())) + { + trainTeacher.settId(IdUtils.fastSimpleUUID()); + } + + // 4. 默认值与审计字段 + if (trainTeacher.getStatus() == null) + { + trainTeacher.setStatus(1); + } + trainTeacher.setDelFlag("0"); trainTeacher.setCreateTime(DateUtils.getNowDate()); + try + { + trainTeacher.setCreateBy(SecurityUtils.getUsername()); + } + catch (Exception e) + { + // 非登录上下文防崩溃 + } + return trainTeacherMapper.insertTrainTeacher(trainTeacher); } /** * 修改教师信息 - * + * * @param trainTeacher 教师信息 * @return 结果 */ @Override public int updateTrainTeacher(TrainTeacher trainTeacher) { + // 1. 检查教师是否存在 (40401 TEACHER_NOT_FOUND) + if (StringUtils.isEmpty(trainTeacher.gettId())) + { + throw new ServiceException("教师工号不能为空"); + } + TrainTeacher existing = trainTeacherMapper.selectTrainTeacherByTId(trainTeacher.gettId()); + if (existing == null) + { + throw new ServiceException("教师不存在", 40401); + } + + // 2. 姓名校验 + if (StringUtils.isNotEmpty(trainTeacher.gettName()) && trainTeacher.gettName().trim().length() > 50) + { + throw new ServiceException("教师姓名长度不能超过50个字符"); + } + + // 3. 手机号查重 (AC-02: 40001 TEACHER_PHONE_DUPLICATED) + if (StringUtils.isNotEmpty(trainTeacher.gettPhone()) && !checkPhoneUnique(trainTeacher)) + { + throw new ServiceException("教师手机号码已存在", 40001); + } + trainTeacher.setUpdateTime(DateUtils.getNowDate()); + try + { + trainTeacher.setUpdateBy(SecurityUtils.getUsername()); + } + catch (Exception e) + { + // 非登录上下文防崩溃 + } + return trainTeacherMapper.updateTrainTeacher(trainTeacher); } /** - * 批量删除教师信息 - * + * 批量删除教师信息(逻辑删除) + * * @param tIds 需要删除的教师信息主键 * @return 结果 */ @@ -83,8 +177,8 @@ public class TrainTeacherServiceImpl implements ITrainTeacherService } /** - * 删除教师信息信息 - * + * 删除教师信息(逻辑删除) + * * @param tId 教师信息主键 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTrainingSiteServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTrainingSiteServiceImpl.java index 2499c2830..627d35a07 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTrainingSiteServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainTrainingSiteServiceImpl.java @@ -1,28 +1,31 @@ package com.ruoyi.train.service.impl; import java.util.List; -import com.ruoyi.common.utils.DateUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import com.ruoyi.train.mapper.TrainTrainingSiteMapper; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.DateUtils; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.train.domain.TrainTrainingSite; +import com.ruoyi.train.mapper.TrainTrainingSiteMapper; import com.ruoyi.train.service.ITrainTrainingSiteService; /** * 实训场地Service业务层处理 - * + * * @author ruoyi * @date 2026-09-08 */ @Service -public class TrainTrainingSiteServiceImpl implements ITrainTrainingSiteService +public class TrainTrainingSiteServiceImpl implements ITrainTrainingSiteService { @Autowired private TrainTrainingSiteMapper trainTrainingSiteMapper; /** * 查询实训场地 - * + * * @param siteId 实训场地主键 * @return 实训场地 */ @@ -34,7 +37,7 @@ public class TrainTrainingSiteServiceImpl implements ITrainTrainingSiteService /** * 查询实训场地列表 - * + * * @param trainTrainingSite 实训场地 * @return 实训场地 */ @@ -44,35 +47,86 @@ public class TrainTrainingSiteServiceImpl implements ITrainTrainingSiteService return trainTrainingSiteMapper.selectTrainTrainingSiteList(trainTrainingSite); } + /** + * 校验实训场地名称是否唯一 + * + * @param trainTrainingSite 实训场地 + * @return 结果 (true 唯一, false 不唯一) + */ + @Override + public boolean checkSiteNameUnique(TrainTrainingSite trainTrainingSite) + { + if (StringUtils.isEmpty(trainTrainingSite.getSiteName())) + { + return true; + } + TrainTrainingSite site = trainTrainingSiteMapper.checkSiteNameUnique( + trainTrainingSite.getSiteName(), + trainTrainingSite.getSiteId() + ); + return site == null; + } + /** * 新增实训场地 - * + * * @param trainTrainingSite 实训场地 * @return 结果 */ @Override public int insertTrainTrainingSite(TrainTrainingSite trainTrainingSite) { + // 1. 容纳人数必须大于0 + if (trainTrainingSite.getCapacity() == null || trainTrainingSite.getCapacity() <= 0) + { + throw new ServiceException("场地最大容纳人数必须大于0"); + } + // 2. 场地名称唯一性校验 + if (!checkSiteNameUnique(trainTrainingSite)) + { + throw new ServiceException("实训场地名称已存在", 40009); + } + // 3. 自动生成32位主键UUID(若未指定) + if (StringUtils.isEmpty(trainTrainingSite.getSiteId())) + { + trainTrainingSite.setSiteId(IdUtils.fastSimpleUUID()); + } + // 4. 默认值处理 + if (trainTrainingSite.getSiteStatus() == null) + { + trainTrainingSite.setSiteStatus(1); // 默认可用 + } + trainTrainingSite.setDelFlag("0"); trainTrainingSite.setCreateTime(DateUtils.getNowDate()); return trainTrainingSiteMapper.insertTrainTrainingSite(trainTrainingSite); } /** * 修改实训场地 - * + * * @param trainTrainingSite 实训场地 * @return 结果 */ @Override public int updateTrainTrainingSite(TrainTrainingSite trainTrainingSite) { + // 1. 容纳人数校验 + if (trainTrainingSite.getCapacity() != null && trainTrainingSite.getCapacity() <= 0) + { + throw new ServiceException("场地最大容纳人数必须大于0"); + } + // 2. 场地名称唯一性校验 + if (!checkSiteNameUnique(trainTrainingSite)) + { + throw new ServiceException("实训场地名称已存在", 40009); + } trainTrainingSite.setUpdateTime(DateUtils.getNowDate()); return trainTrainingSiteMapper.updateTrainTrainingSite(trainTrainingSite); } /** * 批量删除实训场地 - * + * * @param siteIds 需要删除的实训场地主键 * @return 结果 */ @@ -84,7 +138,7 @@ public class TrainTrainingSiteServiceImpl implements ITrainTrainingSiteService /** * 删除实训场地信息 - * + * * @param siteId 实训场地主键 * @return 结果 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/port/PlanEnrollmentCompletionPort.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/port/PlanEnrollmentCompletionPort.java new file mode 100644 index 000000000..5c81c4689 --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/port/PlanEnrollmentCompletionPort.java @@ -0,0 +1,12 @@ +package com.ruoyi.train.service.port; + +import java.util.Date; + +/** + * 计划结训时的参训档案归档边界。 + * ISSUE-03-01 实现该端口后,调用将与计划状态变更处于同一事务。 + */ +public interface PlanEnrollmentCompletionPort +{ + void completeEnrollments(String planId, String operator, Date completeTime); +} diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainClassPlanMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainClassPlanMapper.xml index 1299ba3cc..edffddff1 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainClassPlanMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainClassPlanMapper.xml @@ -7,43 +7,101 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + - - + + + - - select plan_id, plan_name, c_id, start_time, end_time, train_address, t_id, recruit_num, apply_condition, plan_status, create_user, create_time, update_time, remark from train_class_plan + + + + + + + + + + + + + + + + case when plan_status = 0 and start_time <= CURRENT_TIMESTAMP then 1 + else plan_status end - + select p.plan_id, + p.plan_name, + p.start_time, + p.end_time, + p.report_deadline, + p.train_address, + p.recruit_num, + p.plan_status as effective_plan_status, + (select count(*) from train_plan_course pc where pc.plan_id = p.plan_id) as course_count, + (select count(*) from train_enrollment e + where e.plan_id = p.plan_id and e.enrollment_status != 3) as enrolled_count + from ( + + ) p + + p.del_flag = '0' + + and p.plan_name like concat('%', #{planName}, '%') + + + and p.plan_status = #{planStatus} + + + and p.start_time >= #{startTimeBegin} + + + and p.start_time <= #{startTimeEnd} + + order by p.start_time desc, p.create_time desc + + + + @@ -51,32 +109,34 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" plan_id, plan_name, - c_id, start_time, end_time, + report_deadline, train_address, - t_id, recruit_num, apply_condition, plan_status, - create_user, + del_flag, + create_by, create_time, + update_by, update_time, remark, #{planId}, #{planName}, - #{cId}, #{startTime}, #{endTime}, + #{reportDeadline}, #{trainAddress}, - #{tId}, #{recruitNum}, #{applyCondition}, #{planStatus}, - #{createUser}, + #{delFlag}, + #{createBy}, #{createTime}, + #{updateBy}, #{updateTime}, #{remark}, @@ -84,32 +144,24 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" update train_class_plan - - plan_name = #{planName}, - c_id = #{cId}, - start_time = #{startTime}, - end_time = #{endTime}, - train_address = #{trainAddress}, - t_id = #{tId}, - recruit_num = #{recruitNum}, - apply_condition = #{applyCondition}, - plan_status = #{planStatus}, - create_user = #{createUser}, - create_time = #{createTime}, - update_time = #{updateTime}, - remark = #{remark}, - - where plan_id = #{planId} + set plan_name = #{planName}, + start_time = #{startTime}, + end_time = #{endTime}, + report_deadline = #{reportDeadline}, + train_address = #{trainAddress}, + recruit_num = #{recruitNum}, + apply_condition = #{applyCondition}, + update_by = #{updateBy}, + update_time = #{updateTime}, + remark = #{remark} + where plan_id = #{planId} and del_flag = '0' - - delete from train_class_plan where plan_id = #{planId} - - - - delete from train_class_plan where plan_id in - - #{planId} - - - \ No newline at end of file + + update train_class_plan + set plan_status = #{planStatus}, + update_by = #{updateBy}, + update_time = #{updateTime} + where plan_id = #{planId} and del_flag = '0' + + diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainClassroomInfoMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainClassroomInfoMapper.xml index 206ade585..4dcfe990f 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainClassroomInfoMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainClassroomInfoMapper.xml @@ -3,37 +3,48 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + - + + + - select class_room_id, room_name, room_location, capacity, room_status, create_user, create_time, update_time, remark from train_classroom_info + select class_room_id, room_name, room_location, capacity, room_status, del_flag, create_by, create_time, update_by, update_time, remark from train_classroom_info - + + + @@ -44,8 +55,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" room_location, capacity, room_status, - create_user, + del_flag, + create_by, create_time, + update_by, update_time, remark, @@ -55,8 +68,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{roomLocation}, #{capacity}, #{roomStatus}, - #{createUser}, + #{delFlag}, + #{createBy}, #{createTime}, + #{updateBy}, #{updateTime}, #{remark}, @@ -69,22 +84,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" room_location = #{roomLocation}, capacity = #{capacity}, room_status = #{roomStatus}, - create_user = #{createUser}, - create_time = #{createTime}, + del_flag = #{delFlag}, + update_by = #{updateBy}, update_time = #{updateTime}, remark = #{remark}, where class_room_id = #{classRoomId} - - delete from train_classroom_info where class_room_id = #{classRoomId} - + + update train_classroom_info set del_flag = '2' where class_room_id = #{classRoomId} + - - delete from train_classroom_info where class_room_id in + + update train_classroom_info set del_flag = '2' where class_room_id in #{classRoomId} - - \ No newline at end of file + + diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainCourseMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainCourseMapper.xml index cd789806a..5a0db4dce 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainCourseMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainCourseMapper.xml @@ -3,99 +3,103 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + + - - - - - + + + + - select c_id, c_name, c_type, c_duration, teaching_objective, course_content, attach_doc, attach_video, attach_model_link, model_resource_ids, create_user, create_time, update_time, remark from train_course + select c_id, c_code, c_name, c_type, c_duration, teaching_objective, course_content, status, del_flag, create_by, create_time, update_by, update_time, remark from train_course - + + + + + insert into train_course - c_id, + c_id, + c_code, c_name, - c_type, + c_type, c_duration, - teaching_objective, - course_content, - attach_doc, - attach_video, - attach_model_link, - model_resource_ids, - create_user, + teaching_objective, + course_content, + status, + del_flag, + create_by, create_time, + update_by, update_time, - remark, + remark, - #{cId}, + #{cId}, + #{cCode}, #{cName}, - #{cType}, + #{cType}, #{cDuration}, - #{teachingObjective}, - #{courseContent}, - #{attachDoc}, - #{attachVideo}, - #{attachModelLink}, - #{modelResourceIds}, - #{createUser}, + #{teachingObjective}, + #{courseContent}, + #{status}, + #{delFlag}, + #{createBy}, #{createTime}, + #{updateBy}, #{updateTime}, - #{remark}, + #{remark}, update train_course + c_code = #{cCode}, c_name = #{cName}, c_type = #{cType}, c_duration = #{cDuration}, teaching_objective = #{teachingObjective}, course_content = #{courseContent}, - attach_doc = #{attachDoc}, - attach_video = #{attachVideo}, - attach_model_link = #{attachModelLink}, - model_resource_ids = #{modelResourceIds}, - create_user = #{createUser}, - create_time = #{createTime}, + status = #{status}, + update_by = #{updateBy}, update_time = #{updateTime}, remark = #{remark}, @@ -103,13 +107,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - delete from train_course where c_id = #{cId} + update train_course set del_flag = '2' where c_id = #{cId} - delete from train_course where c_id in + update train_course set del_flag = '2' where c_id in #{cId} - \ No newline at end of file + diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTeacherMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTeacherMapper.xml index 5fda2c520..5752be0df 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTeacherMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTeacherMapper.xml @@ -3,43 +3,54 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + - - + + + - + + + + - select t_id, t_name, t_gender, t_birth, t_department, t_title, t_phone, t_email, create_user, create_time, update_time, remark from train_teacher + select t_id, t_name, t_gender, t_birth, user_id, org_name, job_title, t_phone, t_email, status, del_flag, create_by, create_time, update_by, update_time, remark from train_teacher - + + + @@ -49,12 +60,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" t_name, t_gender, t_birth, - t_department, - t_title, - t_phone, + user_id, + org_name, + job_title, + t_phone, t_email, - create_user, + status, + del_flag, + create_by, create_time, + update_by, update_time, remark, @@ -63,12 +78,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{tName}, #{tGender}, #{tBirth}, - #{tDepartment}, - #{tTitle}, - #{tPhone}, + #{userId}, + #{orgName}, + #{jobTitle}, + #{tPhone}, #{tEmail}, - #{createUser}, + #{status}, + #{delFlag}, + #{createBy}, #{createTime}, + #{updateBy}, #{updateTime}, #{remark}, @@ -80,26 +99,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" t_name = #{tName}, t_gender = #{tGender}, t_birth = #{tBirth}, - t_department = #{tDepartment}, - t_title = #{tTitle}, + user_id = #{userId}, + org_name = #{orgName}, + job_title = #{jobTitle}, t_phone = #{tPhone}, t_email = #{tEmail}, - create_user = #{createUser}, - create_time = #{createTime}, + status = #{status}, + update_by = #{updateBy}, update_time = #{updateTime}, remark = #{remark}, - where t_id = #{tId} + where t_id = #{tId} and del_flag = '0' - - delete from train_teacher where t_id = #{tId} - + + + update train_teacher set del_flag = '2' where t_id = #{tId} + - - delete from train_teacher where t_id in + + + update train_teacher set del_flag = '2' where t_id in #{tId} - - \ No newline at end of file + + diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTrainingSiteMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTrainingSiteMapper.xml index 243b11751..12a52116c 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTrainingSiteMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainTrainingSiteMapper.xml @@ -3,7 +3,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - + @@ -11,31 +11,42 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - + + + - select site_id, site_name, site_location, capacity, site_device, site_status, create_user, create_time, update_time, remark from train_training_site + select site_id, site_name, site_location, capacity, site_device, site_status, del_flag, create_by, create_time, update_by, update_time, remark from train_training_site - + + + @@ -47,8 +58,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" capacity, site_device, site_status, - create_user, + del_flag, + create_by, create_time, + update_by, update_time, remark, @@ -59,8 +72,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" #{capacity}, #{siteDevice}, #{siteStatus}, - #{createUser}, + #{delFlag}, + #{createBy}, #{createTime}, + #{updateBy}, #{updateTime}, #{remark}, @@ -74,22 +89,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" capacity = #{capacity}, site_device = #{siteDevice}, site_status = #{siteStatus}, - create_user = #{createUser}, - create_time = #{createTime}, + del_flag = #{delFlag}, + update_by = #{updateBy}, update_time = #{updateTime}, remark = #{remark}, where site_id = #{siteId} - - delete from train_training_site where site_id = #{siteId} - + + update train_training_site set del_flag = '2' where site_id = #{siteId} + - - delete from train_training_site where site_id in + + update train_training_site set del_flag = '2' where site_id in #{siteId} - - \ No newline at end of file + + diff --git a/RuoYi-Vue/ruoyi-train/src/test/java/com/ruoyi/train/service/impl/PlanAppServiceImplTest.java b/RuoYi-Vue/ruoyi-train/src/test/java/com/ruoyi/train/service/impl/PlanAppServiceImplTest.java new file mode 100644 index 000000000..f529ead1c --- /dev/null +++ b/RuoYi-Vue/ruoyi-train/src/test/java/com/ruoyi/train/service/impl/PlanAppServiceImplTest.java @@ -0,0 +1,429 @@ +package com.ruoyi.train.service.impl; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.lang.reflect.Field; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.springframework.transaction.annotation.Transactional; +import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.train.constant.PlanErrorCode; +import com.ruoyi.train.domain.TrainClassPlan; +import com.ruoyi.train.domain.dto.PlanCreateDTO; +import com.ruoyi.train.domain.dto.PlanUpdateDTO; +import com.ruoyi.train.domain.query.PlanQuery; +import com.ruoyi.train.domain.vo.PlanDetailVO; +import com.ruoyi.train.domain.vo.PlanItemVO; +import com.ruoyi.train.service.ITrainClassPlanService; +import com.ruoyi.train.service.port.PlanEnrollmentCompletionPort; + +/** ISSUE-02-01 培训计划生命周期单元测试。 */ +public class PlanAppServiceImplTest +{ + private InMemoryPlanService repository; + private RecordingEnrollmentPort enrollmentPort; + private PlanAppServiceImpl appService; + + @Before + public void setUp() + { + repository = new InMemoryPlanService(); + enrollmentPort = new RecordingEnrollmentPort(); + appService = new PlanAppServiceImpl(repository, Collections.singletonList(enrollmentPort)); + } + + @Test + public void createPlanShouldGenerateIdAndForceInitialStatus() + { + String planId = appService.createPlan(validCreate(" 2026年信号培训班 "), "admin"); + + TrainClassPlan saved = repository.selectTrainClassPlanByPlanId(planId); + assertNotNull(planId); + assertEquals(32, planId.length()); + assertEquals("2026年信号培训班", saved.getPlanName()); + assertEquals(Integer.valueOf(0), saved.getPlanStatus()); + assertEquals("admin", saved.getCreateBy()); + for (Field field : TrainClassPlan.class.getDeclaredFields()) + { + assertTrue("Plan Domain 不得恢复单课程/单教师字段", + !"cId".equals(field.getName()) && !"tId".equals(field.getName())); + } + } + + @Test + public void createPlanShouldRejectReversedOrEqualTime() + { + PlanCreateDTO reversed = validCreate("倒序计划"); + reversed.setStartTime(date("2026-09-15 08:00:00")); + reversed.setEndTime(date("2026-09-10 08:00:00")); + assertError(PlanErrorCode.PLAN_TIME_INVALID, () -> appService.createPlan(reversed, "admin")); + + PlanCreateDTO equal = validCreate("同一时刻计划"); + equal.setEndTime(equal.getStartTime()); + assertError(PlanErrorCode.PLAN_TIME_INVALID, () -> appService.createPlan(equal, "admin")); + } + + @Test + public void createPlanShouldRejectReportDeadlineAfterStart() + { + PlanCreateDTO dto = validCreate("超时报到计划"); + dto.setReportDeadline(date("2030-10-10 09:00:00")); + assertError(PlanErrorCode.PLAN_TIME_INVALID, () -> appService.createPlan(dto, "admin")); + } + + @Test + public void createPlanShouldRejectDuplicatedName() + { + appService.createPlan(validCreate("重复计划"), "admin"); + assertError(PlanErrorCode.PLAN_NAME_DUPLICATED, + () -> appService.createPlan(validCreate(" 重复计划 "), "admin")); + } + + @Test + public void getPlanShouldReturnDetailAndRejectMissingPlan() + { + String planId = appService.createPlan(validCreate("查询计划"), "admin"); + PlanDetailVO detail = appService.getPlan(planId); + assertEquals(planId, detail.getPlanId()); + assertEquals("查询计划", detail.getPlanName()); + + assertError(PlanErrorCode.PLAN_NOT_FOUND, () -> appService.getPlan("missing")); + } + + @Test + public void getPlanShouldExposeInProgressStatusAfterStartTime() + { + PlanCreateDTO dto = validCreate("已开班计划"); + dto.setReportDeadline(date("2020-01-01 08:00:00")); + dto.setStartTime(date("2020-01-02 08:00:00")); + dto.setEndTime(date("2030-01-03 08:00:00")); + String planId = appService.createPlan(dto, "admin"); + + assertEquals(Integer.valueOf(1), appService.getPlan(planId).getPlanStatus()); + assertEquals("有效状态查询不得回写持久化状态", Integer.valueOf(0), + repository.store.get(planId).getPlanStatus()); + } + + @Test + public void updatePlanShouldUpdateEditablePlan() + { + String planId = appService.createPlan(validCreate("原计划"), "admin"); + PlanUpdateDTO update = validUpdate("新计划"); + update.setTrainAddress("新基地"); + + appService.updatePlan(planId, update, "editor"); + + TrainClassPlan saved = repository.selectTrainClassPlanByPlanId(planId); + assertEquals("新计划", saved.getPlanName()); + assertEquals("新基地", saved.getTrainAddress()); + assertEquals("editor", saved.getUpdateBy()); + } + + @Test + public void updatePlanShouldRejectCompletedOrCancelledPlan() + { + String completedId = appService.createPlan(validCreate("已结束计划"), "admin"); + repository.store.get(completedId).setPlanStatus(2); + assertError(PlanErrorCode.PLAN_STATUS_LOCKED, + () -> appService.updatePlan(completedId, validUpdate("修改已结束"), "admin")); + + String cancelledId = appService.createPlan(validCreate("已取消计划"), "admin"); + repository.store.get(cancelledId).setPlanStatus(3); + assertError(PlanErrorCode.PLAN_STATUS_LOCKED, + () -> appService.updatePlan(cancelledId, validUpdate("修改已取消"), "admin")); + } + + @Test + public void completePlanShouldUpdateStatusAndInvokeEnrollmentBoundary() throws NoSuchMethodException + { + String planId = appService.createPlan(validCreate("待结训计划"), "admin"); + + appService.completePlan(planId, "operator"); + + assertEquals(Integer.valueOf(2), repository.store.get(planId).getPlanStatus()); + assertEquals(1, enrollmentPort.invocationCount); + assertEquals(planId, enrollmentPort.lastPlanId); + assertEquals("operator", enrollmentPort.lastOperator); + assertNotNull(enrollmentPort.lastCompleteTime); + Transactional transaction = PlanAppServiceImpl.class + .getMethod("completePlan", String.class, String.class) + .getAnnotation(Transactional.class); + assertNotNull(transaction); + assertEquals(Exception.class, transaction.rollbackFor()[0]); + } + + @Test + public void completePlanShouldPropagateEnrollmentPortFailure() + { + InMemoryPlanService failingRepository = new InMemoryPlanService(); + RuntimeException boundaryFailure = new IllegalStateException("Enrollment completion failed"); + PlanEnrollmentCompletionPort failingPort = (id, user, time) -> + { + throw boundaryFailure; + }; + PlanAppServiceImpl failingService = new PlanAppServiceImpl(failingRepository, + Collections.singletonList(failingPort)); + String failingPlanId = failingService.createPlan(validCreate("事务异常计划"), "admin"); + try + { + failingService.completePlan(failingPlanId, "operator"); + fail("Enrollment 端口异常必须向事务代理传播"); + } + catch (RuntimeException e) + { + assertSame(boundaryFailure, e); + } + assertEquals(1, failingRepository.statusUpdateInvocationCount); + } + + @Test + public void completePlanShouldFailClosedWhenEnrollmentPortMissing() + { + InMemoryPlanService noPortRepository = new InMemoryPlanService(); + PlanAppServiceImpl noPortService = new PlanAppServiceImpl(noPortRepository, + Collections.emptyList()); + String planId = noPortService.createPlan(validCreate("缺少联动适配器计划"), "admin"); + + try + { + noPortService.completePlan(planId, "operator"); + fail("缺少 Enrollment Completion Port 时必须 fail closed"); + } + catch (IllegalStateException e) + { + assertTrue(e.getMessage().contains("PlanEnrollmentCompletionPort")); + } + assertEquals(0, noPortRepository.statusUpdateInvocationCount); + assertEquals(Integer.valueOf(0), noPortRepository.store.get(planId).getPlanStatus()); + } + + @Test + public void completePlanShouldRejectRepeatedCompletion() + { + String planId = appService.createPlan(validCreate("幂等计划"), "admin"); + appService.completePlan(planId, "admin"); + + assertError(PlanErrorCode.PLAN_ALREADY_COMPLETED, + () -> appService.completePlan(planId, "admin")); + assertEquals(1, enrollmentPort.invocationCount); + } + + @Test + public void completePlanShouldRejectMissingOrCancelledPlan() + { + assertError(PlanErrorCode.PLAN_NOT_FOUND, + () -> appService.completePlan("missing", "admin")); + + String planId = appService.createPlan(validCreate("取消计划"), "admin"); + repository.store.get(planId).setPlanStatus(3); + assertError(PlanErrorCode.PLAN_STATUS_LOCKED, + () -> appService.completePlan(planId, "admin")); + } + + @Test + public void listPlansShouldDelegatePlanQuery() + { + PlanItemVO item = new PlanItemVO(); + item.setPlanId("P001"); + repository.listResult = Collections.singletonList(item); + + List result = appService.listPlans(new PlanQuery()); + + assertEquals(1, result.size()); + assertEquals("P001", result.get(0).getPlanId()); + } + + private PlanCreateDTO validCreate(String name) + { + PlanCreateDTO dto = new PlanCreateDTO(); + dto.setPlanName(name); + dto.setReportDeadline(date("2030-10-09 18:00:00")); + dto.setStartTime(date("2030-10-10 08:30:00")); + dto.setEndTime(date("2030-10-24 17:30:00")); + dto.setTrainAddress("培训基地"); + dto.setRecruitNum(40L); + dto.setApplyCondition("在岗职工"); + dto.setRemark("测试计划"); + return dto; + } + + private PlanUpdateDTO validUpdate(String name) + { + PlanUpdateDTO dto = new PlanUpdateDTO(); + dto.setPlanName(name); + dto.setReportDeadline(date("2030-10-09 18:00:00")); + dto.setStartTime(date("2030-10-10 08:30:00")); + dto.setEndTime(date("2030-10-24 17:30:00")); + dto.setTrainAddress("培训基地"); + dto.setRecruitNum(40L); + dto.setApplyCondition("在岗职工"); + dto.setRemark("更新测试"); + return dto; + } + + private Date date(String value) + { + try + { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(value); + } + catch (ParseException e) + { + throw new IllegalArgumentException(e); + } + } + + private void assertError(int code, ThrowingAction action) + { + try + { + action.run(); + fail("Expected ServiceException code " + code); + } + catch (ServiceException e) + { + assertEquals(Integer.valueOf(code), e.getCode()); + assertTrue(e.getMessage() != null && !e.getMessage().isEmpty()); + } + } + + private interface ThrowingAction + { + void run(); + } + + private static class RecordingEnrollmentPort implements PlanEnrollmentCompletionPort + { + private int invocationCount; + private String lastPlanId; + private String lastOperator; + private Date lastCompleteTime; + + @Override + public void completeEnrollments(String planId, String operator, Date completeTime) + { + invocationCount++; + lastPlanId = planId; + lastOperator = operator; + lastCompleteTime = completeTime; + } + } + + private static class InMemoryPlanService implements ITrainClassPlanService + { + private final Map store = new LinkedHashMap<>(); + private List listResult = new ArrayList<>(); + private int statusUpdateInvocationCount; + + @Override + public TrainClassPlan selectTrainClassPlanByPlanId(String planId) + { + return effectiveView(store.get(planId)); + } + + @Override + public TrainClassPlan selectTrainClassPlanByPlanIdForUpdate(String planId) + { + return effectiveView(store.get(planId)); + } + + @Override + public List selectTrainClassPlanList(PlanQuery query) + { + return listResult; + } + + @Override + public boolean existsByPlanName(String planName, String excludePlanId) + { + for (TrainClassPlan plan : store.values()) + { + if (plan.getPlanName().trim().equalsIgnoreCase(planName.trim()) + && !plan.getPlanId().equals(excludePlanId)) + { + return true; + } + } + return false; + } + + @Override + public int insertTrainClassPlan(TrainClassPlan plan) + { + store.put(plan.getPlanId(), plan); + return 1; + } + + @Override + public int updateTrainClassPlan(TrainClassPlan update) + { + TrainClassPlan existing = store.get(update.getPlanId()); + existing.setPlanName(update.getPlanName()); + existing.setStartTime(update.getStartTime()); + existing.setEndTime(update.getEndTime()); + existing.setReportDeadline(update.getReportDeadline()); + existing.setTrainAddress(update.getTrainAddress()); + existing.setRecruitNum(update.getRecruitNum()); + existing.setApplyCondition(update.getApplyCondition()); + existing.setUpdateBy(update.getUpdateBy()); + existing.setUpdateTime(update.getUpdateTime()); + existing.setRemark(update.getRemark()); + return 1; + } + + @Override + public int updateTrainClassPlanStatus(TrainClassPlan update) + { + statusUpdateInvocationCount++; + TrainClassPlan existing = store.get(update.getPlanId()); + existing.setPlanStatus(update.getPlanStatus()); + existing.setUpdateBy(update.getUpdateBy()); + existing.setUpdateTime(update.getUpdateTime()); + return 1; + } + + /** Mirrors the mapper's read-only effectivePlanStatusPolicy for this JVM fake. */ + private TrainClassPlan effectiveView(TrainClassPlan source) + { + if (source == null) + { + return null; + } + TrainClassPlan view = new TrainClassPlan(); + view.setPlanId(source.getPlanId()); + view.setPlanName(source.getPlanName()); + view.setStartTime(source.getStartTime()); + view.setEndTime(source.getEndTime()); + view.setReportDeadline(source.getReportDeadline()); + view.setTrainAddress(source.getTrainAddress()); + view.setRecruitNum(source.getRecruitNum()); + view.setApplyCondition(source.getApplyCondition()); + view.setPlanStatus(source.getPlanStatus()); + view.setDelFlag(source.getDelFlag()); + view.setCreateBy(source.getCreateBy()); + view.setCreateTime(source.getCreateTime()); + view.setUpdateBy(source.getUpdateBy()); + view.setUpdateTime(source.getUpdateTime()); + view.setRemark(source.getRemark()); + if (Integer.valueOf(0).equals(source.getPlanStatus()) + && source.getStartTime() != null + && !source.getStartTime().after(new Date())) + { + view.setPlanStatus(1); + } + return view; + } + } +} diff --git a/RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql b/RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql new file mode 100644 index 000000000..cb61e7720 --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/001_generator_identity_precheck.sql @@ -0,0 +1,91 @@ +-- ============================================================================== +-- 001_generator_identity_precheck.sql +-- Generator Kingbase Identity 预检只读脚本 (增强版: 支持 Partial 状态与数据量探测) +-- +-- 功能:只读探测 gen_table.table_id 与 gen_table_column.column_id 的 Identity 状态 +-- 判定准则: +-- 1. CASE_1_BOTH_NEED_PATCH: 两列均缺失 Identity,需依次执行 002a 和 002b +-- 2. CASE_2_BOTH_ALREADY_PATCHED: 两列均已具备 Identity,禁止重复执行任何 Patch +-- 3. CASE_3_ONLY_COLUMN_NEEDS_PATCH: 仅 column_id 缺失,严禁执行 002a,仅执行 002b +-- 4. CASE_4_ONLY_TABLE_NEEDS_PATCH: 仅 table_id 缺失,严禁执行 002b,仅执行 002a +-- 安全说明:纯只读查询,无任何 DDL / DML / 序列修改副作用 +-- ============================================================================== + +-- 1. 列级别 Identity 详细属性检查 +SELECT + c.relname AS table_name, + a.attname AS column_name, + t.typname AS data_type, + a.attnotnull AS is_not_null, + COALESCE(a.attidentity, '') AS attidentity, + CASE a.attidentity + WHEN 'd' THEN 'ALREADY_PATCHED (GENERATED BY DEFAULT AS IDENTITY)' + WHEN 'a' THEN 'ALREADY_PATCHED (GENERATED ALWAYS AS IDENTITY)' + ELSE 'NEED_PATCH (MISSING_IDENTITY)' + END AS patch_status +FROM sys_class c +JOIN sys_attribute a ON a.attrelid = c.oid +JOIN sys_type t ON a.atttypid = t.oid +JOIN sys_namespace n ON n.oid = c.relnamespace +WHERE n.nspname = 'public' + AND ((c.relname = 'gen_table' AND a.attname = 'table_id') + OR (c.relname = 'gen_table_column' AND a.attname = 'column_id')) +ORDER BY c.relname, a.attname; + +-- 2. 检查关联 Sequence 存在性 +SELECT + c.relname AS sequence_name, + c.relkind AS relkind, + CASE c.relkind + WHEN 'S' THEN 'SEQUENCE_EXISTS' + ELSE 'UNKNOWN' + END AS seq_status +FROM sys_class c +JOIN sys_namespace n ON n.oid = c.relnamespace +WHERE n.nspname = 'public' + AND c.relname IN ('gen_table_table_id_seq', 'gen_table_column_column_id_seq') +ORDER BY c.relname; + +-- 3. 现有数据行数与最大 ID 统计 (区分空表与已有数据环境) +SELECT + 'gen_table' AS table_name, + COUNT(*) AS row_count, + COALESCE(MAX(table_id), 0) AS max_id, + CASE WHEN COUNT(*) = 0 THEN 'EMPTY_METADATA_ENV (默认起始序列即可安全使用)' + ELSE 'EXISTING_METADATA_ENV (补丁后需重点核验 Sequence 对齐)' END AS env_type +FROM gen_table +UNION ALL +SELECT + 'gen_table_column' AS table_name, + COUNT(*) AS row_count, + COALESCE(MAX(column_id), 0) AS max_id, + CASE WHEN COUNT(*) = 0 THEN 'EMPTY_METADATA_ENV (默认起始序列即可安全使用)' + ELSE 'EXISTING_METADATA_ENV (补丁后需重点核验 Sequence 对齐)' END AS env_type +FROM gen_table_column; + +-- 4. 全局 Partial / Full 状态判定与执行建议 +WITH status_data AS ( + SELECT + MAX(CASE WHEN c.relname = 'gen_table' AND a.attname = 'table_id' THEN COALESCE(a.attidentity, '') END) AS table_id_id, + MAX(CASE WHEN c.relname = 'gen_table_column' AND a.attname = 'column_id' THEN COALESCE(a.attidentity, '') END) AS column_id_id + FROM sys_class c + JOIN sys_attribute a ON a.attrelid = c.oid + JOIN sys_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = 'public' + AND ((c.relname = 'gen_table' AND a.attname = 'table_id') + OR (c.relname = 'gen_table_column' AND a.attname = 'column_id')) +) +SELECT + CASE + WHEN table_id_id IN ('d', 'a') AND column_id_id IN ('d', 'a') THEN + 'CASE_2_BOTH_ALREADY_PATCHED: 两列均已配置 Identity,禁止执行任何 Patch,直接运行 003_generator_identity_verify.sql' + WHEN table_id_id NOT IN ('d', 'a') AND column_id_id NOT IN ('d', 'a') THEN + 'CASE_1_BOTH_NEED_PATCH: 两列均缺失 Identity,需依次执行 002a_gen_table_identity_patch.sql 与 002b_gen_table_column_identity_patch.sql' + WHEN table_id_id IN ('d', 'a') AND column_id_id NOT IN ('d', 'a') THEN + 'CASE_3_ONLY_COLUMN_NEEDS_PATCH: 仅 column_id 缺失,严禁执行 002a,仅可执行 002b_gen_table_column_identity_patch.sql' + WHEN table_id_id NOT IN ('d', 'a') AND column_id_id IN ('d', 'a') THEN + 'CASE_4_ONLY_TABLE_NEEDS_PATCH: 仅 table_id 缺失,严禁执行 002b,仅可执行 002a_gen_table_identity_patch.sql' + ELSE + 'CASE_UNKNOWN: 异常状态,请联系管理员核实' + END AS precheck_scenario_verdict +FROM status_data; diff --git a/RuoYi-Vue/sql/generator/kingbase/002a_gen_table_identity_patch.sql b/RuoYi-Vue/sql/generator/kingbase/002a_gen_table_identity_patch.sql new file mode 100644 index 000000000..36ca07a53 --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/002a_gen_table_identity_patch.sql @@ -0,0 +1,22 @@ +-- ============================================================================== +-- 002a_gen_table_identity_patch.sql +-- Generator Kingbase Identity 独立补丁 A: gen_table.table_id +-- +-- 变更目标: +-- 为 public.gen_table.table_id 添加 GENERATED BY DEFAULT AS IDENTITY +-- +-- 执行准入前置条件(强制要求): +-- 1. 必须先执行 001_generator_identity_precheck.sql; +-- 2. 仅当 Precheck 判定结果为 CASE_1_BOTH_NEED_PATCH 或 CASE_4_ONLY_TABLE_NEEDS_PATCH 时方可执行; +-- 3. 若 table_id 已为 ALREADY_PATCHED,严禁执行本脚本(Kingbase 将抛出 already an identity column 错误)。 +-- +-- 环境数据安全约束: +-- - 本脚本仅添加 Identity 特性,默认从 1 开始; +-- - 若当前 gen_table 为空表 (row_count = 0),执行后直接安全; +-- - 若当前 gen_table 已存在历史元数据 (row_count > 0),执行后必须通过 003 脚本核对 Sequence 对齐状态; +-- - 严禁 DROP 表、重建表或清空 Generator 元数据。 +-- ============================================================================== + +ALTER TABLE gen_table +ALTER COLUMN table_id +ADD GENERATED BY DEFAULT AS IDENTITY; diff --git a/RuoYi-Vue/sql/generator/kingbase/002b_gen_table_column_identity_patch.sql b/RuoYi-Vue/sql/generator/kingbase/002b_gen_table_column_identity_patch.sql new file mode 100644 index 000000000..7ea4c9f7c --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/002b_gen_table_column_identity_patch.sql @@ -0,0 +1,22 @@ +-- ============================================================================== +-- 002b_gen_table_column_identity_patch.sql +-- Generator Kingbase Identity 独立补丁 B: gen_table_column.column_id +-- +-- 变更目标: +-- 为 public.gen_table_column.column_id 添加 GENERATED BY DEFAULT AS IDENTITY +-- +-- 执行准入前置条件(强制要求): +-- 1. 必须先执行 001_generator_identity_precheck.sql; +-- 2. 仅当 Precheck 判定结果为 CASE_1_BOTH_NEED_PATCH 或 CASE_3_ONLY_COLUMN_NEEDS_PATCH 时方可执行; +-- 3. 若 column_id 已为 ALREADY_PATCHED,严禁执行本脚本(Kingbase 将抛出 already an identity column 错误)。 +-- +-- 环境数据安全约束: +-- - 本脚本仅添加 Identity 特性,默认从 1 开始; +-- - 若当前 gen_table_column 为空表 (row_count = 0),执行后直接安全; +-- - 若当前 gen_table_column 已存在历史元数据 (row_count > 0),执行后必须通过 003 脚本核对 Sequence 对齐状态; +-- - 严禁 DROP 表、重建表或清空 Generator 元数据。 +-- ============================================================================== + +ALTER TABLE gen_table_column +ALTER COLUMN column_id +ADD GENERATED BY DEFAULT AS IDENTITY; diff --git a/RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql b/RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql new file mode 100644 index 000000000..1f57a16db --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/003_generator_identity_verify.sql @@ -0,0 +1,146 @@ +-- ============================================================================== +-- 003_generator_identity_verify.sql +-- Generator Kingbase Identity 验证只读脚本 (增强版: 含 Sequence 对齐安全核验) +-- +-- 功能:只读验证 gen_table 与 gen_table_column 的 Identity 状态及 Sequence 安全性 +-- 验证维度: +-- 1. 列 Identity 属性配置:attidentity 精确等于 'd' +-- 2. Sequence 存在性:gen_table_table_id_seq / gen_table_column_column_id_seq 均为 'S' +-- 3. Sequence 依赖关系:sys_depend 中记录类型为 'i' (internal identity dependency) +-- 4. Sequence 对齐安全位:下一个分配 ID 必须严格大于现有 MAX(id) (或空表 SAFE) +-- 安全说明:纯只读查询,无任何 DDL / DML / nextval / setval 副作用 +-- ============================================================================== + +-- 1. 验证列级别 Identity 配置 +SELECT + c.relname AS table_name, + a.attname AS column_name, + a.attidentity AS attidentity, + CASE + WHEN a.attidentity = 'd' THEN 'PASS (GENERATED BY DEFAULT AS IDENTITY)' + ELSE 'FAIL (ATTIDENTITY_NOT_D)' + END AS identity_check +FROM sys_class c +JOIN sys_attribute a ON a.attrelid = c.oid +JOIN sys_namespace n ON n.oid = c.relnamespace +WHERE n.nspname = 'public' + AND ((c.relname = 'gen_table' AND a.attname = 'table_id') + OR (c.relname = 'gen_table_column' AND a.attname = 'column_id')) +ORDER BY c.relname, a.attname; + +-- 2. 验证 Sequence 存在性与类别 +SELECT + c.relname AS sequence_name, + c.relkind AS relkind, + CASE + WHEN c.relkind = 'S' THEN 'PASS (SEQUENCE_EXISTS)' + ELSE 'FAIL (NOT_A_SEQUENCE)' + END AS sequence_check +FROM sys_class c +JOIN sys_namespace n ON n.oid = c.relnamespace +WHERE n.nspname = 'public' + AND c.relname IN ('gen_table_table_id_seq', 'gen_table_column_column_id_seq') +ORDER BY c.relname; + +-- 3. 验证 Sequence 依赖关系 (deptype = 'i') +SELECT + s.relname AS sequence_name, + t.relname AS target_table, + a.attname AS target_column, + d.deptype AS dependency_type, + CASE + WHEN d.deptype = 'i' THEN 'PASS (INTERNAL_IDENTITY_DEPENDENCY)' + ELSE 'FAIL (INVALID_DEPENDENCY_TYPE)' + END AS dependency_check +FROM sys_depend d +JOIN sys_class s ON s.oid = d.objid +JOIN sys_class t ON t.oid = d.refobjid +JOIN sys_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid +JOIN sys_namespace n ON n.oid = t.relnamespace +WHERE n.nspname = 'public' + AND t.relname IN ('gen_table', 'gen_table_column') + AND a.attname IN ('table_id', 'column_id') +ORDER BY t.relname, a.attname; + +-- 4. 验证 Sequence 当前位置与已有 MAX(id) 对齐安全性 (只读) +SELECT + 'gen_table' AS table_name, + 'table_id' AS column_name, + t.row_count, + t.max_id AS existing_max_id, + s.last_value::bigint AS sequence_last_value, + s.is_called, + CASE + WHEN t.row_count = 0 THEN 'EMPTY_TABLE_SAFE' + WHEN (CASE WHEN s.is_called THEN s.last_value::bigint + 1 ELSE s.last_value::bigint END) > t.max_id THEN 'SAFE' + ELSE 'UNSAFE (SEQUENCE_BEHIND_OR_EQUAL_MAX_ID)' + END AS seq_alignment_status +FROM (SELECT COUNT(*) AS row_count, COALESCE(MAX(table_id), 0) AS max_id FROM gen_table) t, + gen_table_table_id_seq s +UNION ALL +SELECT + 'gen_table_column' AS table_name, + 'column_id' AS column_name, + c.row_count, + c.max_id AS existing_max_id, + s.last_value::bigint AS sequence_last_value, + s.is_called, + CASE + WHEN c.row_count = 0 THEN 'EMPTY_TABLE_SAFE' + WHEN (CASE WHEN s.is_called THEN s.last_value::bigint + 1 ELSE s.last_value::bigint END) > c.max_id THEN 'SAFE' + ELSE 'UNSAFE (SEQUENCE_BEHIND_OR_EQUAL_MAX_ID)' + END AS seq_alignment_status +FROM (SELECT COUNT(*) AS row_count, COALESCE(MAX(column_id), 0) AS max_id FROM gen_table_column) c, + gen_table_column_column_id_seq s; + +-- 5. 总体综合判定 (包含属性配置、序列存在、依赖关系及 Sequence 对齐安全) +WITH identity_summary AS ( + SELECT count(*) AS id_cnt + FROM sys_class c + JOIN sys_attribute a ON a.attrelid = c.oid + JOIN sys_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = 'public' + AND ((c.relname = 'gen_table' AND a.attname = 'table_id') + OR (c.relname = 'gen_table_column' AND a.attname = 'column_id')) + AND a.attidentity = 'd' +), +seq_summary AS ( + SELECT count(*) AS seq_cnt + FROM sys_class c + JOIN sys_namespace n ON n.oid = c.relnamespace + WHERE n.nspname = 'public' + AND c.relname IN ('gen_table_table_id_seq', 'gen_table_column_column_id_seq') + AND c.relkind = 'S' +), +dep_summary AS ( + SELECT count(*) AS dep_cnt + FROM sys_depend d + JOIN sys_class s ON s.oid = d.objid + JOIN sys_class t ON t.oid = d.refobjid + JOIN sys_attribute a ON a.attrelid = d.refobjid AND a.attnum = d.refobjsubid + JOIN sys_namespace n ON n.oid = t.relnamespace + WHERE n.nspname = 'public' + AND t.relname IN ('gen_table', 'gen_table_column') + AND a.attname IN ('table_id', 'column_id') + AND d.deptype = 'i' +), +seq_align AS ( + SELECT + sum(CASE WHEN t.row_count = 0 OR (CASE WHEN s.is_called THEN s.last_value::bigint + 1 ELSE s.last_value::bigint END) > t.max_id THEN 1 ELSE 0 END) AS safe_cnt + FROM ( + SELECT 'gen_table' AS tbl, COUNT(*) AS row_count, COALESCE(MAX(table_id), 0) AS max_id FROM gen_table + ) t, gen_table_table_id_seq s + UNION ALL + SELECT + sum(CASE WHEN c.row_count = 0 OR (CASE WHEN s.is_called THEN s.last_value::bigint + 1 ELSE s.last_value::bigint END) > c.max_id THEN 1 ELSE 0 END) AS safe_cnt + FROM ( + SELECT 'gen_table_column' AS tbl, COUNT(*) AS row_count, COALESCE(MAX(column_id), 0) AS max_id FROM gen_table_column + ) c, gen_table_column_column_id_seq s +) +SELECT + CASE + WHEN i.id_cnt = 2 AND s.seq_cnt = 2 AND d.dep_cnt = 2 AND (SELECT sum(safe_cnt) FROM seq_align) = 2 + THEN 'VERIFY: PASS (所有 Identity、Sequence、依赖关系与序列对齐安全核验一致通过)' + ELSE 'VERIFY: FAIL (存在核验未达标或 Sequence 落后风险,详情见分项输出)' + END AS overall_verify_result +FROM identity_summary i, seq_summary s, dep_summary d; diff --git a/RuoYi-Vue/sql/generator/kingbase/README.md b/RuoYi-Vue/sql/generator/kingbase/README.md new file mode 100644 index 000000000..01998d32d --- /dev/null +++ b/RuoYi-Vue/sql/generator/kingbase/README.md @@ -0,0 +1,79 @@ +# Kingbase Generator Identity 基础设施补丁 + +## 1. 补丁目标 +解决 RuoYi 代码生成器(Generator)在 KingbaseES 数据库下执行表结构导入(`importGenTable`)时,因 `gen_table.table_id` 和 `gen_table_column.column_id` 缺失自增/Identity 属性,导致 `useGeneratedKeys="true"` 回填失败并引发主键冲突(Duplicate key violation)的运行时兼容性问题。 + +## 2. 补丁范围与安全红线 +- **涉及对象**:严格仅限 `public.gen_table.table_id` 与 `public.gen_table_column.column_id`。 +- **安全红线**: + - 严禁修改 21 张 `train_*` 业务表; + - 严禁修改 `sys_*` 系统/权限/字典表; + - 严禁 DROP 表、重建表或清空/截断 Generator 历史元数据; + - 严禁在未做 Precheck 判定前盲目全量执行 Patch。 + +## 3. 文件清单 + +| 文件名 | 类型 | 说明 | +| :--- | :---: | :--- | +| `001_generator_identity_precheck.sql` | 只读探测 | 检查两列 Identity 配置、现有数据量及全局 Case 场景。 | +| `002a_gen_table_identity_patch.sql` | DDL 补丁 | 仅为 `gen_table.table_id` 添加 `GENERATED BY DEFAULT AS IDENTITY`。 | +| `002b_gen_table_column_identity_patch.sql` | DDL 补丁 | 仅为 `gen_table_column.column_id` 添加 `GENERATED BY DEFAULT AS IDENTITY`。 | +| `003_generator_identity_verify.sql` | 只读验证 | 综合校验列属性、Sequence 存在、依赖关系及 Sequence 对齐安全性。 | + +--- + +## 4. 四种 Partial / Full 执行决策矩阵 + +根据 `001_generator_identity_precheck.sql` 查询 4 返回的 `precheck_scenario_verdict`,严格按以下矩阵执行: + +| 场景编号 | Precheck 判定结果 | 执行动作 | +| :--- | :--- | :--- | +| **Scenario A** | `CASE_1_BOTH_NEED_PATCH`
(两列均缺失 Identity) | 1. 执行 `002a_gen_table_identity_patch.sql`
2. 执行 `002b_gen_table_column_identity_patch.sql`
3. 执行 `003_generator_identity_verify.sql` | +| **Scenario B** | `CASE_2_BOTH_ALREADY_PATCHED`
(两列均已配置 Identity) | **严禁执行任何 Patch!**
直接执行 `003_generator_identity_verify.sql` 复核。 | +| **Scenario C** | `CASE_3_ONLY_COLUMN_NEEDS_PATCH`
(仅 `column_id` 缺失) | **严禁执行 002a!**
1. 仅执行 `002b_gen_table_column_identity_patch.sql`
2. 执行 `003_generator_identity_verify.sql` | +| **Scenario D** | `CASE_4_ONLY_TABLE_NEEDS_PATCH`
(仅 `table_id` 缺失) | **严禁执行 002b!**
1. 仅执行 `002a_gen_table_identity_patch.sql`
2. 执行 `003_generator_identity_verify.sql` | + +--- + +## 5. 数据环境分类与 Sequence 对齐要求 + +### 5.1 Case A:Generator 元数据为空(0 rows,全新环境) +- `001` 预检中 `env_type` 为 `EMPTY_METADATA_ENV`; +- 执行 Patch 后,Kingbase 自动生成的 Sequence 从 1 开始分配; +- 执行 `003` 核验,Sequence 对齐状态将显示 `EMPTY_TABLE_SAFE`,可直接投入使用。 + +### 5.2 Case B:Generator 元数据已存在(已有历史导入数据) +- `001` 预检中 `env_type` 为 `EXISTING_METADATA_ENV`; +- **核心风险**:Kingbase `ADD GENERATED BY DEFAULT AS IDENTITY` 在已有数据环境下默认从 1 开始分配,**不宣称自动对齐已有数据 MAX(id)**! +- **操作要求**: + 1. 执行对应补丁后,**必须立即执行 `003_generator_identity_verify.sql`**; + 2. 观察验证输出中的 `seq_alignment_status`: + - 若为 `SAFE`:表明 sequence 位置已超前于现有数据(例如已有试点导入推进了序列),可直接使用; + - 若为 `UNSAFE`:表明 sequence 当前位置落后或等于现有主键,**绝对禁止直接继续 Generator Import**! + 3. **Sequence 对齐专项步骤(需 DBA 确认并在维护窗口执行)**: + 针对落后的序列,使用 Kingbase 兼容的管理语法调整序列游标: + ```sql + -- 示例:对齐 gen_table sequence + SELECT setval('gen_table_table_id_seq', (SELECT COALESCE(MAX(table_id), 0) + 1 FROM gen_table), false); + -- 示例:对齐 gen_table_column sequence + SELECT setval('gen_table_column_column_id_seq', (SELECT COALESCE(MAX(column_id), 0) + 1 FROM gen_table_column), false); + ``` + 4. 重新执行 `003_generator_identity_verify.sql`,确认全部输出 `SAFE` 且最终结果为 `VERIFY: PASS` 后,方可开放表导入功能。 + +--- + +## 6. 执行命令示例(无真实凭据) + +```bash +# 步骤 1: 预检并确认 Scenario +ksql -h -p -U -d -f 001_generator_identity_precheck.sql + +# 步骤 2: 根据 Precheck 决策矩阵有选择地执行对应补丁 +# 仅当命中 Scenario A 或 D 时: +ksql -h -p -U -d -f 002a_gen_table_identity_patch.sql +# 仅当命中 Scenario A 或 C 时: +ksql -h -p -U -d -f 002b_gen_table_column_identity_patch.sql + +# 步骤 3: 严格复核验证 +ksql -h -p -U -d -f 003_generator_identity_verify.sql +``` diff --git a/build_ui/src/api/train/classroom.js b/build_ui/src/api/train/classroom.js index 406641e97..e031e89a5 100644 --- a/build_ui/src/api/train/classroom.js +++ b/build_ui/src/api/train/classroom.js @@ -3,7 +3,7 @@ import request from '@/utils/request' // 查询教室信息列表 export function listClassroom(query) { return request({ - url: '/train/classroominfo/list', + url: '/train/classroom/list', method: 'get', params: query }) @@ -12,7 +12,7 @@ export function listClassroom(query) { // 查询教室信息详细 export function getClassroom(classRoomId) { return request({ - url: '/train/classroominfo/' + classRoomId, + url: '/train/classroom/' + classRoomId, method: 'get' }) } @@ -20,7 +20,7 @@ export function getClassroom(classRoomId) { // 新增教室信息 export function addClassroom(data) { return request({ - url: '/train/classroominfo', + url: '/train/classroom', method: 'post', data: data }) @@ -29,25 +29,8 @@ export function addClassroom(data) { // 修改教室信息 export function updateClassroom(data) { return request({ - url: '/train/classroominfo', + url: '/train/classroom/' + data.classRoomId, method: 'put', data: data }) } - -// 删除教室信息 -export function delClassroom(classRoomId) { - return request({ - url: '/train/classroominfo/' + classRoomId, - method: 'delete' - }) -} - -// 导出教室信息 -export function exportClassroom(query) { - return request({ - url: '/train/classroominfo/export', - method: 'post', - params: query - }) -} diff --git a/build_ui/src/api/train/course.js b/build_ui/src/api/train/course.js index 593fa3670..11b422c17 100644 --- a/build_ui/src/api/train/course.js +++ b/build_ui/src/api/train/course.js @@ -26,19 +26,11 @@ export function addCourse(data) { }) } -// 修改课程信息 +// 修改课程信息 (API Contract 3.4: PUT /train/course/{cId}) export function updateCourse(data) { return request({ - url: '/train/course', + url: '/train/course/' + data.cId, method: 'put', data: data }) } - -// 删除课程信息 -export function delCourse(cId) { - return request({ - url: '/train/course/' + cId, - method: 'delete' - }) -} diff --git a/build_ui/src/api/train/plan.js b/build_ui/src/api/train/plan.js index a415646a1..4aca4cd70 100644 --- a/build_ui/src/api/train/plan.js +++ b/build_ui/src/api/train/plan.js @@ -3,7 +3,7 @@ import request from '@/utils/request' // 查询开班计划列表 export function listPlan(query) { return request({ - url: '/train/plan/list', + url: '/train/plans', method: 'get', params: query }) @@ -12,7 +12,7 @@ export function listPlan(query) { // 查询开班计划详细 export function getPlan(planId) { return request({ - url: '/train/plan/' + planId, + url: '/train/plans/' + planId, method: 'get' }) } @@ -20,25 +20,25 @@ export function getPlan(planId) { // 新增开班计划 export function addPlan(data) { return request({ - url: '/train/plan', + url: '/train/plans', method: 'post', data: data }) } // 修改开班计划 -export function updatePlan(data) { +export function updatePlan(planId, data) { return request({ - url: '/train/plan', + url: '/train/plans/' + planId, method: 'put', data: data }) } -// 删除开班计划 -export function delPlan(planId) { +// 结束培训计划 +export function completePlan(planId) { return request({ - url: '/train/plan/' + planId, - method: 'delete' + url: '/train/plans/' + planId + '/complete', + method: 'post' }) } diff --git a/build_ui/src/api/train/site.js b/build_ui/src/api/train/site.js index 4d1701d98..e2e38964e 100644 --- a/build_ui/src/api/train/site.js +++ b/build_ui/src/api/train/site.js @@ -3,7 +3,7 @@ import request from '@/utils/request' // 查询实训场地列表 export function listSite(query) { return request({ - url: '/train/site/list', + url: '/train/training-site/list', method: 'get', params: query }) @@ -12,7 +12,7 @@ export function listSite(query) { // 查询实训场地详细 export function getSite(siteId) { return request({ - url: '/train/site/' + siteId, + url: '/train/training-site/' + siteId, method: 'get' }) } @@ -20,7 +20,7 @@ export function getSite(siteId) { // 新增实训场地 export function addSite(data) { return request({ - url: '/train/site', + url: '/train/training-site', method: 'post', data: data }) @@ -29,16 +29,8 @@ export function addSite(data) { // 修改实训场地 export function updateSite(data) { return request({ - url: '/train/site', + url: '/train/training-site/' + data.siteId, method: 'put', data: data }) } - -// 删除实训场地 -export function delSite(siteId) { - return request({ - url: '/train/site/' + siteId, - method: 'delete' - }) -} diff --git a/build_ui/src/api/train/teacher.js b/build_ui/src/api/train/teacher.js index c48e9620e..885282467 100644 --- a/build_ui/src/api/train/teacher.js +++ b/build_ui/src/api/train/teacher.js @@ -29,16 +29,8 @@ export function addTeacher(data) { // 修改教师信息 export function updateTeacher(data) { return request({ - url: '/train/teacher', + url: '/train/teacher/' + (data.tId || ''), method: 'put', data: data }) } - -// 删除教师信息 -export function delTeacher(tId) { - return request({ - url: '/train/teacher/' + tId, - method: 'delete' - }) -} diff --git a/build_ui/src/views/train/classroom/index.vue b/build_ui/src/views/train/classroom/index.vue index 2a575210b..55466358c 100644 --- a/build_ui/src/views/train/classroom/index.vue +++ b/build_ui/src/views/train/classroom/index.vue @@ -1,6 +1,6 @@