|
|
@@ -0,0 +1,561 @@
|
|
|
+package cn.iocoder.yudao.module.rd.service.researchProject;
|
|
|
+
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|
|
+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.*;
|
|
|
+import cn.iocoder.yudao.module.rd.dal.dataobject.researchProject.*;
|
|
|
+import cn.iocoder.yudao.module.rd.dal.mysql.researchProject.*;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+import org.springframework.validation.annotation.Validated;
|
|
|
+
|
|
|
+import javax.annotation.Resource;
|
|
|
+import java.time.LocalDateTime;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Objects;
|
|
|
+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.*;
|
|
|
+import static cn.iocoder.yudao.module.rd.enums.ProjectApprovalConstants.*;
|
|
|
+
|
|
|
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
|
|
|
+import cn.hutool.core.collection.CollUtil;
|
|
|
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
|
|
|
+
|
|
|
+@Service
|
|
|
+@Validated
|
|
|
+public class ResearchProjectServiceImpl implements ResearchProjectService {
|
|
|
+
|
|
|
+ @Resource
|
|
|
+ private ProjectMapper projectMapper;
|
|
|
+ @Resource
|
|
|
+ private ProjectScheduleMapper projectScheduleMapper;
|
|
|
+ @Resource
|
|
|
+ private ProjectBudgetMapper projectBudgetMapper;
|
|
|
+ @Resource
|
|
|
+ private ProjectApprovalRecordMapper approvalRecordMapper;
|
|
|
+ @Resource
|
|
|
+ private ApproverQueryMapper approverQueryMapper;
|
|
|
+ @Resource
|
|
|
+ private DeptApi deptApi;
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 单条:填充部门名(deptId 和 executingUnit 两个字段一起翻译)
|
|
|
+ */
|
|
|
+ private void fillDeptNames(ProjectRespVO vo) {
|
|
|
+ if (vo == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ fillDeptNames(Collections.singletonList(vo));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 批量:填充列表里所有项目的部门名
|
|
|
+ */
|
|
|
+ private void fillDeptNames(List<ProjectRespVO> list) {
|
|
|
+ if (CollUtil.isEmpty(list)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 1. 收集所有需要的部门 id
|
|
|
+ Set<Long> deptIds = new HashSet<>();
|
|
|
+ for (ProjectRespVO vo : list) {
|
|
|
+ if (vo.getDeptId() != null) deptIds.add(vo.getDeptId());
|
|
|
+ if (vo.getExecutingUnit() != null) deptIds.add(vo.getExecutingUnit());
|
|
|
+ }
|
|
|
+ if (deptIds.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 一次批量查
|
|
|
+ Map<Long, String> deptNameMap = getDeptNameMap(deptIds);
|
|
|
+
|
|
|
+ // 3. 回填
|
|
|
+ for (ProjectRespVO vo : list) {
|
|
|
+ if (vo.getDeptId() != null) {
|
|
|
+ vo.setDeptName(deptNameMap.get(vo.getDeptId()));
|
|
|
+ }
|
|
|
+ if (vo.getExecutingUnit() != null) {
|
|
|
+ vo.setExecutingUnitName(deptNameMap.get(vo.getExecutingUnit()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 调 DeptApi 批量查「id → name」
|
|
|
+ */
|
|
|
+ private Map<Long, String> getDeptNameMap(Set<Long> deptIds) {
|
|
|
+ if (CollUtil.isEmpty(deptIds)) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ List<DeptRespDTO> depts = deptApi.getDeptList(deptIds);
|
|
|
+ if (CollUtil.isEmpty(depts)) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ return depts.stream()
|
|
|
+ .collect(Collectors.toMap(DeptRespDTO::getId, DeptRespDTO::getName, (a, b) -> a));
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 新增 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public Long createProject(ProjectSaveReqVO createReqVO) {
|
|
|
+ ProjectDO project = BeanUtils.toBean(createReqVO, ProjectDO.class);
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ project.setSort(getMaxSort() + 1);
|
|
|
+
|
|
|
+ // ⭐ 关键:初始化审批状态为"草稿"
|
|
|
+ project.setPreAuditStatus(AUDIT_STATUS_DRAFT); // 0 预审未提交
|
|
|
+ project.setPreCurrentNode(null);
|
|
|
+ project.setPreCurrentApproverId(null);
|
|
|
+ project.setPreSubmitTime(null);
|
|
|
+
|
|
|
+ project.setFormalAuditStatus(AUDIT_STATUS_DRAFT); // 0 正式未提交
|
|
|
+ project.setFormalCurrentNode(null);
|
|
|
+ project.setFormalCurrentApproverId(null);
|
|
|
+ project.setFormalSubmitTime(null);
|
|
|
+
|
|
|
+ // 是否涉及预算:由前端传 or 后端根据预算合计判断
|
|
|
+ if (project.getInvolveBudget() == null) {
|
|
|
+ project.setInvolveBudget(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ projectMapper.insert(project);
|
|
|
+
|
|
|
+ // 子表
|
|
|
+ insertScheduleList(createReqVO.getScheduleList(), project.getId());
|
|
|
+ insertBudgetList(createReqVO.getBudgetList(), project.getId());
|
|
|
+
|
|
|
+ return project.getId();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void updateProject(ProjectSaveReqVO updateReqVO) {
|
|
|
+ ProjectDO exist = projectMapper.selectById(updateReqVO.getId());
|
|
|
+ if (exist == null) {
|
|
|
+ throw exception(PROJECT_NOT_EXISTS);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ⚠️ 已提交审批的项目不允许随意修改(否则审批内容会被篡改)
|
|
|
+ if (exist.getPreAuditStatus() != null && exist.getPreAuditStatus() == AUDIT_STATUS_PROCESSING) {
|
|
|
+ throw exception(PROJECT_IN_APPROVAL_CANNOT_UPDATE);
|
|
|
+ }
|
|
|
+ if (exist.getFormalAuditStatus() != null && exist.getFormalAuditStatus() == AUDIT_STATUS_PROCESSING) {
|
|
|
+ throw exception(PROJECT_IN_APPROVAL_CANNOT_UPDATE);
|
|
|
+ }
|
|
|
+
|
|
|
+ ProjectDO updateObj = BeanUtils.toBean(updateReqVO, ProjectDO.class);
|
|
|
+ // ⚠️ 不允许前端改审批字段(安全)
|
|
|
+ updateObj.setPreAuditStatus(null);
|
|
|
+ updateObj.setPreCurrentNode(null);
|
|
|
+ updateObj.setPreCurrentApproverId(null);
|
|
|
+ updateObj.setFormalAuditStatus(null);
|
|
|
+ updateObj.setFormalCurrentNode(null);
|
|
|
+ updateObj.setFormalCurrentApproverId(null);
|
|
|
+
|
|
|
+ projectMapper.updateById(updateObj);
|
|
|
+
|
|
|
+ // 子表全删全插
|
|
|
+ projectScheduleMapper.deleteByProjectId(updateReqVO.getId());
|
|
|
+ projectBudgetMapper.deleteByProjectId(updateReqVO.getId());
|
|
|
+ insertScheduleList(updateReqVO.getScheduleList(), updateReqVO.getId());
|
|
|
+ insertBudgetList(updateReqVO.getBudgetList(), updateReqVO.getId());
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void deleteProject(Long id) {
|
|
|
+ ProjectDO exist = projectMapper.selectById(id);
|
|
|
+ if (exist == null) {
|
|
|
+ throw exception(PROJECT_NOT_EXISTS);
|
|
|
+ }
|
|
|
+ // ⚠️ 已进入审批的项目禁止删除(安全)
|
|
|
+ if (exist.getPreAuditStatus() != null && exist.getPreAuditStatus() == AUDIT_STATUS_PROCESSING) {
|
|
|
+ throw exception(PROJECT_IN_APPROVAL_CANNOT_DELETE);
|
|
|
+ }
|
|
|
+ if (exist.getFormalAuditStatus() != null && exist.getFormalAuditStatus() == AUDIT_STATUS_PROCESSING) {
|
|
|
+ throw exception(PROJECT_IN_APPROVAL_CANNOT_DELETE);
|
|
|
+ }
|
|
|
+
|
|
|
+ projectMapper.deleteById(id);
|
|
|
+ projectScheduleMapper.deleteByProjectId(id);
|
|
|
+ projectBudgetMapper.deleteByProjectId(id);
|
|
|
+ // 审批记录保留(审计需求),如需级联删,放开下一行
|
|
|
+ // approvalRecordMapper.deleteByProjectId(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 详情 & 分页 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public ProjectRespVO getProject(Long id) {
|
|
|
+ ProjectDO project = projectMapper.selectById(id);
|
|
|
+ if (project == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ ProjectRespVO vo = BeanUtils.toBean(project, ProjectRespVO.class);
|
|
|
+
|
|
|
+ // 子表
|
|
|
+ vo.setScheduleList(BeanUtils.toBean(
|
|
|
+ projectScheduleMapper.selectListByProjectId(id), ProjectRespVO.ScheduleVO.class));
|
|
|
+ vo.setBudgetList(BeanUtils.toBean(
|
|
|
+ projectBudgetMapper.selectListByProjectId(id), ProjectRespVO.BudgetVO.class));
|
|
|
+
|
|
|
+ // 审批记录
|
|
|
+ vo.setApprovalRecords(BeanUtils.toBean(
|
|
|
+ approvalRecordMapper.selectListByProjectId(id), ProjectRespVO.ApprovalRecordVO.class));
|
|
|
+
|
|
|
+ return vo;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public PageResult<ProjectRespVO> getProjectPage(ProjectPageReqVO pageReqVO) {
|
|
|
+ PageResult<ProjectDO> pageResult = projectMapper.selectPage(pageReqVO);
|
|
|
+ PageResult<ProjectRespVO> voPage = BeanUtils.toBean(pageResult, ProjectRespVO.class);
|
|
|
+
|
|
|
+ // ⭐ 批量填部门名
|
|
|
+ fillDeptNames(voPage.getList());
|
|
|
+
|
|
|
+ return voPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<ProjectDO> getProjectList(ProjectPageReqVO reqVO) {
|
|
|
+ return projectMapper.selectList(reqVO);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 提交预审 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public PageResult<ProjectRespVO> getTodoPage(ProjectPageReqVO pageReqVO) {
|
|
|
+ Long userId = SecurityFrameworkUtils.getLoginUserId();
|
|
|
+ PageResult<ProjectDO> pageResult = projectMapper.selectTodoPage(pageReqVO, userId);
|
|
|
+ PageResult<ProjectRespVO> voPage = BeanUtils.toBean(pageResult, ProjectRespVO.class);
|
|
|
+ fillDeptNames(voPage.getList());
|
|
|
+ return voPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void submitPreApproval(Long projectId) {
|
|
|
+ ProjectDO project = getAndValidate(projectId);
|
|
|
+
|
|
|
+ // 只有"未提交"或"被驳回"可以提交
|
|
|
+ Integer status = project.getPreAuditStatus();
|
|
|
+ if (status != AUDIT_STATUS_DRAFT && status != AUDIT_STATUS_REJECTED) {
|
|
|
+ throw exception(PROJECT_PRE_STATUS_ILLEGAL);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ⭐ 校验必填项(比如附件必须上传),防止用户草稿直接提交
|
|
|
+ validateBeforeSubmitPre(project);
|
|
|
+
|
|
|
+ // 找一级审批人(预审只有二级)
|
|
|
+ Long approverId = findApprover(NODE_PRE_SECOND, project.getDeptId());
|
|
|
+ if (approverId == null) {
|
|
|
+ throw exception(PROJECT_APPROVER_NOT_FOUND);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 写一条"提交"记录
|
|
|
+ insertRecord(projectId, STAGE_PRE, NODE_PRE_SECOND, "提交预审", RESULT_SUBMIT, "提交预审材料");
|
|
|
+
|
|
|
+ // 更新主表
|
|
|
+ ProjectDO update = new ProjectDO();
|
|
|
+ update.setId(projectId);
|
|
|
+ update.setPreAuditStatus(AUDIT_STATUS_PROCESSING);
|
|
|
+ update.setPreCurrentNode(NODE_PRE_SECOND);
|
|
|
+ update.setPreCurrentApproverId(approverId);
|
|
|
+ update.setPreSubmitTime(LocalDateTime.now());
|
|
|
+ projectMapper.updateById(update);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 预审审批 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void approvePre(ProjectApproveReqVO reqVO) {
|
|
|
+ ProjectDO project = getAndValidate(reqVO.getProjectId());
|
|
|
+
|
|
|
+ if (project.getPreAuditStatus() != AUDIT_STATUS_PROCESSING) {
|
|
|
+ throw exception(PROJECT_NOT_IN_APPROVAL);
|
|
|
+ }
|
|
|
+ if (!Objects.equals(project.getPreCurrentApproverId(), SecurityFrameworkUtils.getLoginUserId())) {
|
|
|
+ throw exception(PROJECT_NOT_YOUR_TURN);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 写记录
|
|
|
+ insertRecord(project.getId(), STAGE_PRE, project.getPreCurrentNode(),
|
|
|
+ getNodeName(project.getPreCurrentNode()), reqVO.getResult(), reqVO.getOpinion());
|
|
|
+
|
|
|
+ // 更新主表
|
|
|
+ ProjectDO update = new ProjectDO();
|
|
|
+ update.setId(project.getId());
|
|
|
+ if (reqVO.getResult() == RESULT_REJECT) {
|
|
|
+ update.setPreAuditStatus(AUDIT_STATUS_REJECTED);
|
|
|
+ } else {
|
|
|
+ // 预审只有二级,通过就结束
|
|
|
+ update.setPreAuditStatus(AUDIT_STATUS_PASSED);
|
|
|
+ }
|
|
|
+ update.setPreCurrentNode(null);
|
|
|
+ update.setPreCurrentApproverId(null);
|
|
|
+ projectMapper.updateById(update);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 提交正式立项 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void submitFormalApproval(Long projectId) {
|
|
|
+ ProjectDO project = getAndValidate(projectId);
|
|
|
+
|
|
|
+ // ⭐ 必须先预审通过
|
|
|
+ if (project.getPreAuditStatus() == null || project.getPreAuditStatus() != AUDIT_STATUS_PASSED) {
|
|
|
+ throw exception(PROJECT_PRE_NOT_PASSED);
|
|
|
+ }
|
|
|
+ Integer status = project.getFormalAuditStatus();
|
|
|
+ if (status != AUDIT_STATUS_DRAFT && status != AUDIT_STATUS_REJECTED) {
|
|
|
+ throw exception(PROJECT_FORMAL_STATUS_ILLEGAL);
|
|
|
+ }
|
|
|
+
|
|
|
+ validateBeforeSubmitFormal(project);
|
|
|
+
|
|
|
+ Long approverId = findApprover(NODE_FORMAL_FIRST, project.getDeptId());
|
|
|
+ if (approverId == null) {
|
|
|
+ throw exception(PROJECT_APPROVER_NOT_FOUND);
|
|
|
+ }
|
|
|
+
|
|
|
+ insertRecord(projectId, STAGE_FORMAL, NODE_FORMAL_FIRST, "提交正式立项",
|
|
|
+ RESULT_SUBMIT, "提交立项汇报");
|
|
|
+
|
|
|
+ ProjectDO update = new ProjectDO();
|
|
|
+ update.setId(projectId);
|
|
|
+ update.setFormalAuditStatus(AUDIT_STATUS_PROCESSING);
|
|
|
+ update.setFormalCurrentNode(NODE_FORMAL_FIRST);
|
|
|
+ update.setFormalCurrentApproverId(approverId);
|
|
|
+ update.setFormalSubmitTime(LocalDateTime.now());
|
|
|
+ projectMapper.updateById(update);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 正式立项审批(含条件分支) ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void approveFormal(ProjectApproveReqVO reqVO) {
|
|
|
+ ProjectDO project = getAndValidate(reqVO.getProjectId());
|
|
|
+
|
|
|
+ if (project.getFormalAuditStatus() != AUDIT_STATUS_PROCESSING) {
|
|
|
+ throw exception(PROJECT_NOT_IN_APPROVAL);
|
|
|
+ }
|
|
|
+ if (!Objects.equals(project.getFormalCurrentApproverId(), SecurityFrameworkUtils.getLoginUserId())) {
|
|
|
+ throw exception(PROJECT_NOT_YOUR_TURN);
|
|
|
+ }
|
|
|
+
|
|
|
+ insertRecord(project.getId(), STAGE_FORMAL, project.getFormalCurrentNode(),
|
|
|
+ getNodeName(project.getFormalCurrentNode()), reqVO.getResult(), reqVO.getOpinion());
|
|
|
+
|
|
|
+ ProjectDO update = new ProjectDO();
|
|
|
+ update.setId(project.getId());
|
|
|
+
|
|
|
+ if (reqVO.getResult() == RESULT_REJECT) {
|
|
|
+ update.setFormalAuditStatus(AUDIT_STATUS_REJECTED);
|
|
|
+ update.setFormalCurrentNode(null);
|
|
|
+ update.setFormalCurrentApproverId(null);
|
|
|
+ } else {
|
|
|
+ Integer nextNode = getNextFormalNode(project);
|
|
|
+ if (nextNode == null) {
|
|
|
+ update.setFormalAuditStatus(AUDIT_STATUS_PASSED);
|
|
|
+ update.setFormalCurrentNode(null);
|
|
|
+ update.setFormalCurrentApproverId(null);
|
|
|
+ } else {
|
|
|
+ Long nextApproverId = findApprover(nextNode, project.getDeptId());
|
|
|
+ if (nextApproverId == null) {
|
|
|
+ throw exception(PROJECT_APPROVER_NOT_FOUND);
|
|
|
+ }
|
|
|
+ update.setFormalCurrentNode(nextNode);
|
|
|
+ update.setFormalCurrentApproverId(nextApproverId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ projectMapper.updateById(update);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 撤回 ====================
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
+ public void cancelApproval(Long projectId) {
|
|
|
+ ProjectDO project = getAndValidate(projectId);
|
|
|
+
|
|
|
+ // 判断在哪个阶段
|
|
|
+ if (project.getPreAuditStatus() != null && project.getPreAuditStatus() == AUDIT_STATUS_PROCESSING) {
|
|
|
+ insertRecord(projectId, STAGE_PRE, project.getPreCurrentNode(),
|
|
|
+ getNodeName(project.getPreCurrentNode()), RESULT_CANCEL, "撤回申请");
|
|
|
+ ProjectDO update = new ProjectDO();
|
|
|
+ update.setId(projectId);
|
|
|
+ update.setPreAuditStatus(AUDIT_STATUS_DRAFT);
|
|
|
+ update.setPreCurrentNode(null);
|
|
|
+ update.setPreCurrentApproverId(null);
|
|
|
+ projectMapper.updateById(update);
|
|
|
+ } else if (project.getFormalAuditStatus() != null && project.getFormalAuditStatus() == AUDIT_STATUS_PROCESSING) {
|
|
|
+ insertRecord(projectId, STAGE_FORMAL, project.getFormalCurrentNode(),
|
|
|
+ getNodeName(project.getFormalCurrentNode()), RESULT_CANCEL, "撤回申请");
|
|
|
+ ProjectDO update = new ProjectDO();
|
|
|
+ update.setId(projectId);
|
|
|
+ update.setFormalAuditStatus(AUDIT_STATUS_DRAFT);
|
|
|
+ update.setFormalCurrentNode(null);
|
|
|
+ update.setFormalCurrentApproverId(null);
|
|
|
+ projectMapper.updateById(update);
|
|
|
+ } else {
|
|
|
+ throw exception(PROJECT_NOT_IN_APPROVAL);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // ==================== 私有方法 ====================
|
|
|
+
|
|
|
+ private ProjectDO getAndValidate(Long id) {
|
|
|
+ ProjectDO project = projectMapper.selectById(id);
|
|
|
+ if (project == null) {
|
|
|
+ throw exception(PROJECT_NOT_EXISTS);
|
|
|
+ }
|
|
|
+ return project;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getMaxSort() {
|
|
|
+ Integer max = projectMapper.selectMaxSort();
|
|
|
+ return max == null ? 0 : max;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void insertScheduleList(List<ProjectSaveReqVO.ScheduleVO> list, Long projectId) {
|
|
|
+ if (list == null || list.isEmpty()) return;
|
|
|
+ List<ProjectScheduleDO> scheduleList = BeanUtils.toBean(list, ProjectScheduleDO.class);
|
|
|
+ scheduleList.forEach(item -> {
|
|
|
+ item.setProjectId(projectId);
|
|
|
+ item.setSort(item.getSort() == null ? 0 : item.getSort());
|
|
|
+ });
|
|
|
+ scheduleList.forEach(projectScheduleMapper::insert);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void insertBudgetList(List<ProjectSaveReqVO.BudgetVO> list, Long projectId) {
|
|
|
+ if (list == null || list.isEmpty()) return;
|
|
|
+ List<ProjectBudgetDO> budgetList = BeanUtils.toBean(list, ProjectBudgetDO.class);
|
|
|
+ budgetList.forEach(item -> {
|
|
|
+ item.setProjectId(projectId);
|
|
|
+ item.setSort(item.getSort() == null ? 0 : item.getSort());
|
|
|
+ });
|
|
|
+ budgetList.forEach(projectBudgetMapper::insert);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 写一条审批记录 */
|
|
|
+ private void insertRecord(Long projectId, Integer stage, Integer node, String nodeName,
|
|
|
+ Integer result, String opinion) {
|
|
|
+ ProjectApprovalRecordDO record = new ProjectApprovalRecordDO();
|
|
|
+ record.setProjectId(projectId);
|
|
|
+ record.setStage(stage);
|
|
|
+ record.setNode(node);
|
|
|
+ record.setNodeName(nodeName);
|
|
|
+ record.setApproverId(SecurityFrameworkUtils.getLoginUserId());
|
|
|
+ record.setApproverName(SecurityFrameworkUtils.getLoginUserNickname());
|
|
|
+ record.setResult(result);
|
|
|
+ record.setOpinion(opinion);
|
|
|
+ record.setApproveTime(LocalDateTime.now());
|
|
|
+ approvalRecordMapper.insert(record);
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 计算正式立项的下一节点(含"涉及预算"条件分支) */
|
|
|
+ private Integer getNextFormalNode(ProjectDO project) {
|
|
|
+ Integer currentNode = project.getFormalCurrentNode();
|
|
|
+ if (currentNode == null) return null;
|
|
|
+ switch (currentNode) {
|
|
|
+ case NODE_FORMAL_FIRST:
|
|
|
+ return NODE_FORMAL_SECOND;
|
|
|
+ case NODE_FORMAL_SECOND:
|
|
|
+ // ⭐ 条件分支:涉及预算 → 财务
|
|
|
+ if (project.getInvolveBudget() != null && project.getInvolveBudget() == 1) {
|
|
|
+ return NODE_FORMAL_FINANCE;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ case NODE_FORMAL_FINANCE:
|
|
|
+ return null;
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 按节点 + 部门查审批人
|
|
|
+ */
|
|
|
+ private Long findApprover(Integer node, Long deptId) {
|
|
|
+ if (node == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String roleCode = getRoleCodeByNode(node);
|
|
|
+ if (StrUtil.isBlank(roleCode)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // ⭐ 二级审批是全局角色,不按部门过滤
|
|
|
+ if (node == NODE_PRE_SECOND || node == NODE_FORMAL_SECOND) {
|
|
|
+ return approverQueryMapper.selectUserIdByRoleCode(roleCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // ⭐ 一级、财务审批按部门过滤
|
|
|
+ if (deptId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ return approverQueryMapper.selectUserIdByRoleCodeAndDept(roleCode, deptId);
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getRoleCodeByNode(Integer node) {
|
|
|
+ if (node == null) return null;
|
|
|
+ switch (node) {
|
|
|
+ case NODE_PRE_SECOND:
|
|
|
+ case NODE_FORMAL_SECOND:
|
|
|
+ return "科技发展部负责人";
|
|
|
+ case NODE_FORMAL_FIRST:
|
|
|
+ return "技术副总";
|
|
|
+ case NODE_FORMAL_FINANCE:
|
|
|
+ return "财务负责人";
|
|
|
+ default:
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private String getNodeName(Integer node) {
|
|
|
+ if (node == null) return "";
|
|
|
+ switch (node) {
|
|
|
+ case NODE_PRE_SECOND: return "预审-科技发展部二级审批";
|
|
|
+ case NODE_FORMAL_FIRST: return "正式-专业公司一级审批";
|
|
|
+ case NODE_FORMAL_SECOND: return "正式-科技发展部二级审批";
|
|
|
+ case NODE_FORMAL_FINANCE:return "正式-财务审批";
|
|
|
+ default: return "";
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 提交预审前的必填校验 */
|
|
|
+ private void validateBeforeSubmitPre(ProjectDO project) {
|
|
|
+ List<ProjectAttachmentVO> attachments = project.getAttachments();
|
|
|
+ if (CollUtil.isEmpty(attachments)) {
|
|
|
+ throw exception(PROJECT_APPLICATION_FILE_REQUIRED);
|
|
|
+ }
|
|
|
+ boolean hasApplication = attachments.stream()
|
|
|
+ .anyMatch(a -> "APPLICATION".equals(a.getType()));
|
|
|
+ if (!hasApplication) {
|
|
|
+ throw exception(PROJECT_APPLICATION_FILE_REQUIRED);
|
|
|
+ }
|
|
|
+ boolean hasOpeningReport = attachments.stream()
|
|
|
+ .anyMatch(a -> "OPENING_REPORT".equals(a.getType()));
|
|
|
+ if (!hasOpeningReport) {
|
|
|
+ throw exception(PROJECT_OPENING_REPORT_REQUIRED);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void validateBeforeSubmitFormal(ProjectDO project) {
|
|
|
+ // 例:正式立项前必须有预算
|
|
|
+ // 按需扩展
|
|
|
+ }
|
|
|
+
|
|
|
+}
|