|
|
@@ -0,0 +1,187 @@
|
|
|
+package cn.iocoder.yudao.module.rd.service.researchProject;
|
|
|
+
|
|
|
+import cn.hutool.core.collection.CollUtil;
|
|
|
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
|
|
|
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
|
|
+import cn.iocoder.yudao.module.rd.controller.admin.researchProject.vo.ProjectTaskRespVO;
|
|
|
+import cn.iocoder.yudao.module.rd.controller.admin.researchProject.vo.ProjectTaskSaveReqVO;
|
|
|
+import cn.iocoder.yudao.module.rd.dal.dataobject.researchProject.ProjectDO;
|
|
|
+import cn.iocoder.yudao.module.rd.dal.dataobject.researchProject.ProjectTaskDO;
|
|
|
+import cn.iocoder.yudao.module.rd.dal.mysql.researchProject.ProjectMapper;
|
|
|
+import cn.iocoder.yudao.module.rd.dal.mysql.researchProject.ProjectTaskMapper;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.validation.annotation.Validated;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
|
|
+import static cn.iocoder.yudao.module.rd.enums.ErrorCodeConstant.*;
|
|
|
+
|
|
|
+@Service
|
|
|
+@Validated
|
|
|
+public class ProjectTaskServiceImpl implements ProjectTaskService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ProjectTaskMapper taskMapper;
|
|
|
+ @Resource
|
|
|
+ private ProjectMapper projectMapper;
|
|
|
+
|
|
|
+ /** 最大层级 */
|
|
|
+ private static final int MAX_LEVEL = 3;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public Long createTask(ProjectTaskSaveReqVO createReqVO) {
|
|
|
+ // 1. 校验项目存在
|
|
|
+ ProjectDO project = projectMapper.selectById(createReqVO.getProjectId());
|
|
|
+ if (project == null) {
|
|
|
+ throw exception(PROJECT_NOT_EXISTS);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 校验父任务 + 计算层级
|
|
|
+ int level = 1;
|
|
|
+ Long parentId = createReqVO.getParentId();
|
|
|
+ if (parentId != null && parentId != 0) {
|
|
|
+ ProjectTaskDO parent = taskMapper.selectById(parentId);
|
|
|
+ if (parent == null) {
|
|
|
+ throw exception(TASK_PARENT_NOT_EXISTS);
|
|
|
+ }
|
|
|
+ // ⭐ 硬性校验:不能超过 3 级
|
|
|
+ if (parent.getLevel() >= MAX_LEVEL) {
|
|
|
+ throw exception(TASK_LEVEL_EXCEED);
|
|
|
+ }
|
|
|
+ level = parent.getLevel() + 1;
|
|
|
+ } else {
|
|
|
+ parentId = 0L;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 插入
|
|
|
+ ProjectTaskDO task = BeanUtils.toBean(createReqVO, ProjectTaskDO.class);
|
|
|
+ task.setParentId(parentId);
|
|
|
+ task.setLevel(level);
|
|
|
+ task.setStatus(0); // 未开始
|
|
|
+ task.setProgress(0);
|
|
|
+ task.setSort(task.getSort() == null ? 0 : task.getSort());
|
|
|
+ taskMapper.insert(task);
|
|
|
+
|
|
|
+ // 4. 更新项目任务统计
|
|
|
+ refreshProjectStat(createReqVO.getProjectId());
|
|
|
+
|
|
|
+ return task.getId();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void updateTask(ProjectTaskSaveReqVO updateReqVO) {
|
|
|
+ ProjectTaskDO exist = taskMapper.selectById(updateReqVO.getId());
|
|
|
+ if (exist == null) {
|
|
|
+ throw exception(TASK_NOT_EXISTS);
|
|
|
+ }
|
|
|
+ ProjectTaskDO updateObj = BeanUtils.toBean(updateReqVO, ProjectTaskDO.class);
|
|
|
+ // ⚠️ 不允许改父任务、层级、状态、进度(由进度录入改)
|
|
|
+ updateObj.setParentId(null);
|
|
|
+ updateObj.setLevel(null);
|
|
|
+ updateObj.setStatus(null);
|
|
|
+ updateObj.setProgress(null);
|
|
|
+ taskMapper.updateById(updateObj);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void deleteTask(Long id) {
|
|
|
+ ProjectTaskDO task = taskMapper.selectById(id);
|
|
|
+ if (task == null) {
|
|
|
+ throw exception(TASK_NOT_EXISTS);
|
|
|
+ }
|
|
|
+ // 递归删除子任务
|
|
|
+ deleteRecursively(id);
|
|
|
+ refreshProjectStat(task.getProjectId());
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 递归删除 */
|
|
|
+ private void deleteRecursively(Long taskId) {
|
|
|
+ List<ProjectTaskDO> children = taskMapper.selectListByParentId(taskId);
|
|
|
+ for (ProjectTaskDO child : children) {
|
|
|
+ deleteRecursively(child.getId());
|
|
|
+ }
|
|
|
+ taskMapper.deleteById(taskId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ProjectTaskRespVO getTask(Long id) {
|
|
|
+ ProjectTaskDO task = taskMapper.selectById(id);
|
|
|
+ return BeanUtils.toBean(task, ProjectTaskRespVO.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<ProjectTaskRespVO> getTaskListByProjectId(Long projectId) {
|
|
|
+ List<ProjectTaskDO> list = taskMapper.selectListByProjectId(projectId);
|
|
|
+ return BeanUtils.toBean(list, ProjectTaskRespVO.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<ProjectTaskRespVO> getTaskTreeByProjectId(Long projectId) {
|
|
|
+ List<ProjectTaskDO> list = taskMapper.selectListByProjectId(projectId);
|
|
|
+ List<ProjectTaskRespVO> voList = BeanUtils.toBean(list, ProjectTaskRespVO.class);
|
|
|
+ return buildTree(voList);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 把扁平列表组装成树
|
|
|
+ */
|
|
|
+ private List<ProjectTaskRespVO> buildTree(List<ProjectTaskRespVO> list) {
|
|
|
+ if (CollUtil.isEmpty(list)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
+ Map<Long, ProjectTaskRespVO> map = list.stream()
|
|
|
+ .collect(Collectors.toMap(ProjectTaskRespVO::getId, v -> v));
|
|
|
+ List<ProjectTaskRespVO> roots = new ArrayList<>();
|
|
|
+ for (ProjectTaskRespVO vo : list) {
|
|
|
+ if (vo.getParentId() == null || vo.getParentId() == 0) {
|
|
|
+ roots.add(vo);
|
|
|
+ } else {
|
|
|
+ ProjectTaskRespVO parent = map.get(vo.getParentId());
|
|
|
+ if (parent != null) {
|
|
|
+ if (parent.getChildren() == null) {
|
|
|
+ parent.setChildren(new ArrayList<>());
|
|
|
+ }
|
|
|
+ parent.getChildren().add(vo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return roots;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 刷新项目任务统计(任务总数、已完成数、整体进度)
|
|
|
+ */
|
|
|
+ public void refreshProjectStat(Long projectId) {
|
|
|
+ List<ProjectTaskDO> tasks = taskMapper.selectListByProjectId(projectId);
|
|
|
+ if (CollUtil.isEmpty(tasks)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ int total = tasks.size();
|
|
|
+ long completed = tasks.stream().filter(t -> t.getStatus() != null && t.getStatus() == 2).count();
|
|
|
+ // 整体进度 = 所有任务进度的平均(只算叶子任务)
|
|
|
+ Set<Long> parentIds = tasks.stream()
|
|
|
+ .map(ProjectTaskDO::getParentId)
|
|
|
+ .filter(pid -> pid != null && pid != 0)
|
|
|
+ .collect(Collectors.toSet());
|
|
|
+ List<ProjectTaskDO> leafTasks = tasks.stream()
|
|
|
+ .filter(t -> !parentIds.contains(t.getId()))
|
|
|
+ .collect(Collectors.toList());
|
|
|
+ double avgProgress = leafTasks.isEmpty() ? 0 :
|
|
|
+ leafTasks.stream().mapToInt(t -> t.getProgress() == null ? 0 : t.getProgress()).average().orElse(0);
|
|
|
+
|
|
|
+ ProjectDO update = new ProjectDO();
|
|
|
+ update.setId(projectId);
|
|
|
+ update.setTaskTotal(total);
|
|
|
+ update.setTaskCompleted((int) completed);
|
|
|
+ update.setProgressRate(java.math.BigDecimal.valueOf(avgProgress).setScale(2, java.math.RoundingMode.HALF_UP));
|
|
|
+ projectMapper.updateById(update);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|