diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainResource.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainResource.java index 0efe22cf6..0a4e3c278 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainResource.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/domain/TrainResource.java @@ -61,6 +61,9 @@ public class TrainResource extends BaseEntity /** 自增编号(数据库递增序号)。 */ private Long id; + /** 课程引用数(派生字段,非数据库列,仅详情查询时回填)。 */ + private Integer courseReferenceCount; + public void setResourceId(String resourceId) { this.resourceId = resourceId; @@ -139,6 +142,14 @@ public class TrainResource extends BaseEntity this.id = id; } + public Integer getCourseReferenceCount() { + return courseReferenceCount; + } + + public void setCourseReferenceCount(Integer courseReferenceCount) { + this.courseReferenceCount = courseReferenceCount; + } + @Override public String toString() { @@ -151,6 +162,7 @@ public class TrainResource extends BaseEntity .append("status", getStatus()) .append("delFlag", getDelFlag()) .append("id", getId()) + .append("courseReferenceCount", getCourseReferenceCount()) .append("createBy", getCreateBy()) .append("createTime", getCreateTime()) .append("updateBy", getUpdateBy()) 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 index d61536b7d..430190f81 100644 --- 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 @@ -71,6 +71,9 @@ public class CourseDetailVO implements Serializable */ private List resources; + /** 未完成状态开班计划的引用数(派生字段,供课程停用校验使用)。 */ + private Integer unfinishedPlanRefCount; + public CourseDetailVO() { } @@ -317,4 +320,14 @@ public class CourseDetailVO implements Serializable { this.resources = resources; } + + public Integer getUnfinishedPlanRefCount() + { + return unfinishedPlanRefCount; + } + + public void setUnfinishedPlanRefCount(Integer unfinishedPlanRefCount) + { + this.unfinishedPlanRefCount = unfinishedPlanRefCount; + } } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainPlanCourseMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainPlanCourseMapper.java index 97612ad02..ccfc7a462 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainPlanCourseMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainPlanCourseMapper.java @@ -91,6 +91,16 @@ public interface TrainPlanCourseMapper */ int countPlanCoursesByCId(@Param("cId") String cId); + /** + * 统计指定公共课程被「未完成」状态开班计划引用的数量。 + * 未完成 = plan_status in (0 未开始, 1 进行中) 且计划未删除; + * 已结束(2)、已取消(3)及已移除的本期课程配置不计入。 + * + * @param cId 公共课程ID + * @return 未完成计划引用数 + */ + int countUnfinishedPlanCoursesByCId(@Param("cId") String cId); + /** * 统计指定公共课程关联的当前有效V2成绩明细数(train_student_score + train_score_item 下游引用)。 * 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 c5f6ac631..29d4821da 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 @@ -49,6 +49,8 @@ public class TrainCourseServiceImpl implements ITrainCourseService { List resources = trainCourseResourceMapper.selectResourcesByCourseId(cId); detail.setResources(resources); + // 派生字段:供编辑窗口与表格停用开关判断,不参与新增/修改入库 + detail.setUnfinishedPlanRefCount(trainPlanCourseMapper.countUnfinishedPlanCoursesByCId(cId)); // 规范权威读取:优先以 train_course_resource 规范关联表首个资源派生兼容 resourceId; // 若关系表暂无关联但实体存在历史 resourceId,则保留历史 resourceId if (resources != null && !resources.isEmpty()) @@ -181,6 +183,9 @@ public class TrainCourseServiceImpl implements ITrainCourseService throw new ServiceException("课程学时必须为正整数", 400); } + // 4. 由正常改为停用时,校验未完成开班计划的引用 + assertUnfinishedPlanCourseNotDisabled(trainCourse); + trainCourse.setUpdateTime(DateUtils.getNowDate()); try { @@ -193,6 +198,34 @@ public class TrainCourseServiceImpl implements ITrainCourseService return trainCourseMapper.updateTrainCourse(trainCourse); } + /** + * 已被「未完成」状态开班计划引用的课程禁止由「正常」改为「停用」。 + * + *

触发条件:本次提交状态为停用(0)、库中原有状态为正常(1),且该课程仍被 + * 未开始(0)/进行中(1)且未删除的开班计划引用(train_plan_course)。 + * 计划已结束(2)、已取消(3)、或本期课程配置已被移除时不再拦截。 + * 作用范围:仅 PUT /train/course/{cId} 的状态变更;新增、状态未变、停用改正常、 + * 原本已停用、以及只修改其它字段均不受影响。 + * 预期行为:抛出 ServiceException,不执行更新,前端提示先等待班级完成或取消课程配置。

+ */ + private void assertUnfinishedPlanCourseNotDisabled(TrainCourse trainCourse) + { + Integer newStatus = trainCourse.getStatus(); + if (newStatus == null || newStatus != 0) + { + return; + } + TrainCourse existing = trainCourseMapper.selectTrainCourseByCId(trainCourse.getcId()); + if (existing == null || existing.getStatus() == null || existing.getStatus() != 1) + { + return; + } + if (trainPlanCourseMapper.countUnfinishedPlanCoursesByCId(trainCourse.getcId()) > 0) + { + throw new ServiceException("课程已被未完成状态的班级引用,不可停用;请等待班级完成、取消计划或移除本期课程配置后再试"); + } + } + /** * 校验课程统一业务编码是否唯一 * diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainResourceServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainResourceServiceImpl.java index 03104f25f..b7ca04906 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainResourceServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainResourceServiceImpl.java @@ -38,7 +38,13 @@ public class TrainResourceServiceImpl implements ITrainResourceService @Override public TrainResource selectTrainResourceByResourceId(String resourceId) { - return trainResourceMapper.selectTrainResourceByResourceId(resourceId); + TrainResource resource = trainResourceMapper.selectTrainResourceByResourceId(resourceId); + if (resource != null) + { + // 派生字段:供编辑窗口判断该资源是否允许停用,不参与新增/修改入库 + resource.setCourseReferenceCount(trainCourseResourceMapper.countCourseReferencesByResourceId(resourceId)); + } + return resource; } /** @@ -99,6 +105,7 @@ public class TrainResourceServiceImpl implements ITrainResourceService { validateStatus(trainResource.getStatus()); } + assertBoundResourceNotDisabled(trainResource); trainResource.setUpdateTime(DateUtils.getNowDate()); return trainResourceMapper.updateTrainResource(trainResource); } @@ -148,6 +155,33 @@ public class TrainResourceServiceImpl implements ITrainResourceService } } + /** + * 已绑定课程的教学资源禁止由「正常」改为「停用」。 + * + *

触发条件:本次提交状态为停用(0)、库中原有状态为正常(1),且 train_course_resource + * 中仍存在该资源的关联记录,三个条件同时满足才拦截。 + * 作用范围:仅 PUT /train/resource/{resourceId} 修改;新增资源、状态未变更、停用改正常、 + * 原本已停用、以及未被课程引用的资源均不受影响。 + * 预期行为:抛出 ServiceException,不执行更新,前端提示先解除课程关联。

+ */ + private void assertBoundResourceNotDisabled(TrainResource trainResource) + { + Integer newStatus = trainResource.getStatus(); + if (newStatus == null || newStatus != 0) + { + return; + } + TrainResource existing = trainResourceMapper.selectTrainResourceByResourceId(trainResource.getResourceId()); + if (existing == null || existing.getStatus() == null || existing.getStatus() != 1) + { + return; + } + if (trainCourseResourceMapper.countCourseReferencesByResourceId(trainResource.getResourceId()) > 0) + { + throw new ServiceException("教学资源已被课程引用,不可停用,请先解除课程关联"); + } + } + /** * 校验资源类型取值:1文档 2视频 33D模型 4VR资源 5外部教学链接 6其他 */ diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainPlanCourseMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainPlanCourseMapper.xml index 26332769e..f85bd3916 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainPlanCourseMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainPlanCourseMapper.xml @@ -122,6 +122,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select count(*) from train_plan_course where c_id = #{cId} + +