| | |
| | | import com.ruoyi.common.exception.ServiceException; |
| | | import com.ruoyi.common.utils.SecurityUtils; |
| | | import com.ruoyi.projectManagement.dto.ProjectWorkflowDtos; |
| | | import jakarta.annotation.PostConstruct; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; |
| | | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; |
| | |
| | | import java.math.RoundingMode; |
| | | import java.time.LocalDate; |
| | | import java.util.ArrayList; |
| | | import java.util.Comparator; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Objects; |
| | | import java.util.Set; |
| | | |
| | | @Service |
| | | @RequiredArgsConstructor |
| | |
| | | |
| | | private final NamedParameterJdbcTemplate jdbc; |
| | | private final ObjectMapper objectMapper; |
| | | |
| | | @PostConstruct |
| | | public void ensurePhaseHistoryTable() { |
| | | jdbc.getJdbcTemplate().execute(""" |
| | | create table if not exists project_management_phase_history ( |
| | | id bigint not null auto_increment, |
| | | project_id bigint not null, |
| | | phase_code varchar(30) not null, |
| | | phase_name varchar(50) not null, |
| | | action_type varchar(30) not null, |
| | | action_label varchar(100) not null, |
| | | form_data json default null, |
| | | remark varchar(2000) default null, |
| | | attachments json default null, |
| | | operator_name varchar(100) default null, |
| | | action_time datetime not null default current_timestamp, |
| | | primary key (id), |
| | | key idx_phase_history_project (project_id,action_time), |
| | | key idx_phase_history_phase (project_id,phase_code) |
| | | ) engine=InnoDB default charset=utf8mb4 comment='项目阶段办理历史' |
| | | """); |
| | | } |
| | | |
| | | public Map<String, Object> getWorkbench(Long projectId) { |
| | | requireProject(projectId); |
| | |
| | | r.reporter_shares as reporterShares, r.remark, r.attachments, r.create_time as createTime |
| | | from project_management_progress_report r |
| | | left join project_management_module m on m.id=r.module_id |
| | | where r.project_id=:projectId and r.is_deleted=0 order by r.report_date desc,r.id desc |
| | | where r.project_id=:projectId and r.is_deleted=0 order by r.module_id,r.report_date,r.id |
| | | """, Map.of("projectId", projectId)); |
| | | parseAttachments(reports); |
| | | Map<Long, BigDecimal> previousProgress = new LinkedHashMap<>(); |
| | | for (Map<String, Object> row : reports) { |
| | | row.put("reporterIdList", parseLongList(row.get("reporterIds"))); |
| | | row.put("reporterNameList", parseStringList(row.get("reporterNames"))); |
| | | row.put("reporterShareList", parseFileList(row.get("reporterShares"))); |
| | | Long moduleId = asLong(row.get("moduleId")); |
| | | BigDecimal previous = previousProgress.getOrDefault(moduleId, BigDecimal.ZERO); |
| | | BigDecimal cumulative = nvl((BigDecimal) row.get("progress")); |
| | | row.put("previousProgress", previous); |
| | | row.put("incrementProgress", cumulative.subtract(previous).max(BigDecimal.ZERO)); |
| | | previousProgress.put(moduleId, cumulative.max(previous)); |
| | | } |
| | | reports.sort(Comparator.comparing((Map<String, Object> row) -> Objects.toString(row.get("reportDate"), "")) |
| | | .thenComparing(row -> asLong(row.get("id")), Comparator.nullsLast(Long::compareTo)).reversed()); |
| | | |
| | | List<Map<String, Object>> materials = query(""" |
| | | select id, project_id as projectId, material_name as materialName, |
| | |
| | | """, Map.of("projectId", projectId)); |
| | | parseAttachments(materials); |
| | | |
| | | List<Map<String, Object>> phases = query(""" |
| | | select id,phase_code as phaseCode,phase_name as phaseName,status,form_data as formData, |
| | | remark,attachments,completed_time as completedTime,update_user_name as operatorName, |
| | | update_time as actionTime |
| | | from project_management_phase where project_id=:projectId and is_deleted=0 order by phase_order |
| | | """, Map.of("projectId", projectId)); |
| | | parseAttachments(phases); |
| | | for (Map<String, Object> phase : phases) phase.put("formData", parseObjectMap(phase.get("formData"))); |
| | | |
| | | List<Map<String, Object>> phaseHistories = query(""" |
| | | select id,phase_code as phaseCode,phase_name as phaseName,action_type as actionType, |
| | | action_label as actionLabel,form_data as formData,remark,attachments, |
| | | operator_name as operatorName,action_time as actionTime |
| | | from project_management_phase_history where project_id=:projectId order by action_time desc,id desc |
| | | """, Map.of("projectId", projectId)); |
| | | parseAttachments(phaseHistories); |
| | | for (Map<String, Object> history : phaseHistories) history.put("formData", parseObjectMap(history.get("formData"))); |
| | | |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("modules", modules); |
| | | result.put("followUps", followUps); |
| | | result.put("reports", reports); |
| | | result.put("materials", materials); |
| | | result.put("phases", phases); |
| | | result.put("phaseHistories", phaseHistories); |
| | | result.put("sourceBusiness", getSourceBusiness(projectId)); |
| | | result.put("memberStatistics", memberStatistics(projectId)); |
| | | result.put("followWindow", getFollowWindow(projectId)); |
| | |
| | | and project_classification<>'' and plan_start_time is not null and plan_end_time is not null |
| | | and project_leader_id is not null and project_manager_ids is not null |
| | | and json_length(project_manager_ids)>0 and follow_cycle is not null and follow_cycle<>'' |
| | | and project_executor_ids is not null and json_length(project_executor_ids)>0 |
| | | """, Map.of("projectId", projectId), Integer.class); |
| | | if (basicReady == null || basicReady == 0) throw new ServiceException("请先完整填写项目基本情况"); |
| | | List<Map<String, Object>> modules = query(""" |
| | |
| | | if (total.setScale(2, RoundingMode.HALF_UP).compareTo(BigDecimal.valueOf(100).setScale(2)) != 0) { |
| | | throw new ServiceException("项目开始前,所有模块的项目工作量占比合计必须为100%"); |
| | | } |
| | | jdbc.update("update project_management_info set current_phase='开发',update_time=now() where id=:projectId", Map.of("projectId", projectId)); |
| | | jdbc.update(""" |
| | | update project_management_info set current_phase='开发', |
| | | actual_start_time=coalesce(actual_start_time,current_date),update_time=now() |
| | | where id=:projectId |
| | | """, Map.of("projectId", projectId)); |
| | | ensurePhases(projectId); |
| | | jdbc.update(""" |
| | | update project_management_phase set status='COMPLETED',completed_time=coalesce(completed_time,now()), |
| | | update_user_name=:userName,update_time=now() |
| | | where project_id=:projectId and phase_code='INITIATION' and is_deleted=0 |
| | | """, new MapSqlParameterSource().addValue("projectId", projectId).addValue("userName", currentUserName())); |
| | | jdbc.update(""" |
| | | update project_management_phase set status='IN_PROGRESS',completed_time=null, |
| | | update_user_name=:userName,update_time=now() |
| | | where project_id=:projectId and phase_code='DEVELOPMENT' and is_deleted=0 |
| | | """, new MapSqlParameterSource().addValue("projectId", projectId).addValue("userName", currentUserName())); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void transitionStage(ProjectWorkflowDtos.StageTransitionRequest request) { |
| | | requireProject(request.getProjectId()); |
| | | ensurePhases(request.getProjectId()); |
| | | String current = jdbc.queryForObject("select current_phase from project_management_info where id=:id and is_delete=0", |
| | | Map.of("id", request.getProjectId()), String.class); |
| | | if ("立项".equals(current)) current = "开发"; |
| | |
| | | throw new ServiceException("所有项目模块进度达到100%后才能进入申报阶段"); |
| | | } |
| | | } |
| | | jdbc.update("update project_management_info set current_phase=:stage,update_time=now() where id=:projectId", |
| | | new MapSqlParameterSource().addValue("stage", expected).addValue("projectId", request.getProjectId())); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void updateProjectExecutors(ProjectWorkflowDtos.ProjectExecutorsRequest request) { |
| | | requireProject(request.getProjectId()); |
| | | validateMembers(request.getExecutors()); |
| | | List<Map<String, Object>> rows = query(""" |
| | | select project_executor_ids as executorIds from project_management_info |
| | | where id=:projectId and is_delete=0 |
| | | """, Map.of("projectId", request.getProjectId())); |
| | | List<Long> oldIds = rows.isEmpty() ? List.of() : parseLongList(rows.getFirst().get("executorIds")); |
| | | List<Long> newIds = request.getExecutors().stream().map(ProjectWorkflowDtos.MemberRequest::getUserId).toList(); |
| | | for (Long removedId : oldIds.stream().filter(id -> !newIds.contains(id)).toList()) { |
| | | if (hasPositiveProjectShare(request.getProjectId(), removedId)) { |
| | | throw new ServiceException("该项目执行人已有项目汇报工作占比,不能删除"); |
| | | } |
| | | } |
| | | Map<String, String> phaseCodes = Map.of( |
| | | "开发", "DEVELOPMENT", |
| | | "申报", "APPLICATION", |
| | | "申报结果", "RESULT", |
| | | "验收", "ACCEPTANCE" |
| | | ); |
| | | MapSqlParameterSource phaseParams = new MapSqlParameterSource() |
| | | .addValue("projectId", request.getProjectId()) |
| | | .addValue("userName", currentUserName()) |
| | | .addValue("formData", jsonObject(request.getFormData())) |
| | | .addValue("remark", request.getRemark()) |
| | | .addValue("attachments", json(request.getAttachments())); |
| | | jdbc.update(""" |
| | | update project_management_info set project_executor_ids=:ids,project_executor_names=:names,update_time=now() |
| | | where id=:projectId and is_delete=0 |
| | | """, new MapSqlParameterSource().addValue("projectId", request.getProjectId()) |
| | | .addValue("ids", json(newIds)) |
| | | .addValue("names", json(request.getExecutors().stream().map(ProjectWorkflowDtos.MemberRequest::getUserName).toList()))); |
| | | update project_management_phase set status='COMPLETED',completed_time=coalesce(completed_time,now()), |
| | | form_data=case when :saveCurrent=1 then :formData else form_data end, |
| | | remark=case when :saveCurrent=1 then :remark else remark end, |
| | | attachments=case when :saveCurrent=1 then :attachments else attachments end, |
| | | update_user_name=:userName,update_time=now() |
| | | where project_id=:projectId and phase_code=:phaseCode and is_deleted=0 |
| | | """, phaseParams.addValue("phaseCode", phaseCodes.get(current)).addValue("saveCurrent", "已完成".equals(expected) ? 1 : 0)); |
| | | if (!"已完成".equals(expected)) { |
| | | jdbc.update(""" |
| | | update project_management_phase set status='IN_PROGRESS',completed_time=null, |
| | | form_data=:formData,remark=:remark,attachments=:attachments, |
| | | update_user_name=:userName,update_time=now() |
| | | where project_id=:projectId and phase_code=:phaseCode and is_deleted=0 |
| | | """, new MapSqlParameterSource() |
| | | .addValue("projectId", request.getProjectId()) |
| | | .addValue("userName", currentUserName()) |
| | | .addValue("formData", jsonObject(request.getFormData())) |
| | | .addValue("remark", request.getRemark()) |
| | | .addValue("attachments", json(request.getAttachments())) |
| | | .addValue("phaseCode", phaseCodes.get(expected))); |
| | | } |
| | | String historyPhase = "已完成".equals(expected) ? current : expected; |
| | | appendPhaseHistory(request.getProjectId(), phaseCodes.get(historyPhase), historyPhase, |
| | | stageHistoryAction(historyPhase, request.getFormData(), "已完成".equals(expected)), |
| | | stageHistoryLabel(historyPhase, request.getFormData(), "已完成".equals(expected)), |
| | | request.getFormData(), request.getRemark(), request.getAttachments()); |
| | | jdbc.update(""" |
| | | update project_management_info set current_phase=:stage, |
| | | actual_end_time=case when :stage='已完成' then coalesce(actual_end_time,current_date) else actual_end_time end, |
| | | update_time=now() where id=:projectId |
| | | """, new MapSqlParameterSource().addValue("stage", expected).addValue("projectId", request.getProjectId())); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | |
| | | """, new MapSqlParameterSource().addValue("projectId", request.getProjectId()) |
| | | .addValue("phaseCode", PHASE_CODES.get(index - 1)), Integer.class); |
| | | if (previousDone == null || previousDone == 0) { |
| | | throw new ServiceException("请先完成上一阶段“" + PHASE_NAMES.get(index - 1) + "”"); |
| | | String projectPhase = jdbc.queryForObject("select current_phase from project_management_info where id=:id and is_delete=0", |
| | | Map.of("id", request.getProjectId()), String.class); |
| | | if (lifecyclePhaseRank(projectPhase) < index) { |
| | | throw new ServiceException("请先完成上一阶段“" + PHASE_NAMES.get(index - 1) + "”"); |
| | | } |
| | | jdbc.update(""" |
| | | update project_management_phase set status='COMPLETED',completed_time=coalesce(completed_time,now()), |
| | | update_time=now() where project_id=:projectId and phase_code=:phaseCode and is_deleted=0 |
| | | """, new MapSqlParameterSource().addValue("projectId", request.getProjectId()) |
| | | .addValue("phaseCode", PHASE_CODES.get(index - 1))); |
| | | } |
| | | } |
| | | MapSqlParameterSource p = new MapSqlParameterSource() |
| | |
| | | update_user_name=:userName,update_time=now() |
| | | where project_id=:projectId and phase_code=:phaseCode and is_deleted=0 |
| | | """, p); |
| | | appendPhaseHistory(request.getProjectId(), request.getPhaseCode(), PHASE_NAMES.get(index), |
| | | stageHistoryAction(PHASE_NAMES.get(index), request.getFormData(), false), |
| | | stageHistoryLabel(PHASE_NAMES.get(index), request.getFormData(), false), |
| | | request.getFormData(), request.getRemark(), request.getAttachments()); |
| | | String currentPhase = "COMPLETED".equals(status) && index == PHASE_CODES.size() - 1 |
| | | ? "已完成" : PHASE_NAMES.get(index); |
| | | jdbc.update("update project_management_info set current_phase=:phase,update_time=now() where id=:projectId", |
| | | new MapSqlParameterSource().addValue("phase", currentPhase).addValue("projectId", request.getProjectId())); |
| | | jdbc.update(""" |
| | | update project_management_info set current_phase=:phase, |
| | | actual_end_time=case when :phase='已完成' then coalesce(actual_end_time,current_date) else actual_end_time end, |
| | | update_time=now() where id=:projectId |
| | | """, new MapSqlParameterSource().addValue("phase", currentPhase).addValue("projectId", request.getProjectId())); |
| | | return jdbc.queryForObject("select id from project_management_phase where project_id=:projectId and phase_code=:phaseCode and is_deleted=0", |
| | | p, Long.class); |
| | | } |
| | | |
| | | public Map<String, Object> getLifecycle(Long projectId) { |
| | | Map<String, Object> project = query(""" |
| | | select id,title,source,current_phase as currentPhase,establish_time as establishTime, |
| | | plan_start_time as planStartTime,plan_end_time as planEndTime, |
| | | actual_start_time as actualStartTime,actual_end_time as actualEndTime, |
| | | create_time as createTime,update_time as updateTime |
| | | from project_management_info where id=:projectId and is_delete=0 |
| | | """, Map.of("projectId", projectId)).stream() |
| | | .findFirst().orElseThrow(() -> new ServiceException("项目不存在")); |
| | | List<Map<String, Object>> phases = query(""" |
| | | select phase_code as phaseCode,phase_name as phaseName,status,completed_time as completedTime, |
| | | update_time as updateTime,update_user_name as updateUserName,remark |
| | | from project_management_phase |
| | | where project_id=:projectId and is_deleted=0 order by phase_order |
| | | """, Map.of("projectId", projectId)); |
| | | Map<String, Map<String, Object>> phaseMap = new LinkedHashMap<>(); |
| | | for (Map<String, Object> phase : phases) phaseMap.put(Objects.toString(phase.get("phaseCode"), ""), phase); |
| | | List<Map<String, Object>> phaseHistories = query(""" |
| | | select id,phase_code as phaseCode,phase_name as phaseName,action_type as actionType, |
| | | action_label as actionLabel,remark,operator_name as operatorName,action_time as actionTime |
| | | from project_management_phase_history where project_id=:projectId order by action_time,id |
| | | """, Map.of("projectId", projectId)); |
| | | |
| | | List<Map<String, Object>> nodes = new ArrayList<>(); |
| | | Object establishTime = project.get("establishTime") == null ? project.get("createTime") : project.get("establishTime"); |
| | | nodes.add(lifecycleNode("INITIATION", "项目立项", establishTime, "COMPLETED", |
| | | "项目已创建,来源:" + Objects.toString(project.get("source"), "手动创建"))); |
| | | nodes.add(lifecycleNode("PROJECT_START", "项目开始", project.get("actualStartTime"), |
| | | project.get("actualStartTime") == null ? currentNodeStatus(project.get("currentPhase"), "未开始") : "COMPLETED", |
| | | "计划开始:" + Objects.toString(project.get("planStartTime"), "未填写"))); |
| | | |
| | | addPhaseLifecycleNode(nodes, phaseMap.get("DEVELOPMENT"), "DEVELOPMENT", "项目开发", project.get("currentPhase"), project.get("updateTime")); |
| | | addPhaseHistoryNodes(nodes, phaseHistories, "DEVELOPMENT"); |
| | | addPhaseLifecycleNode(nodes, phaseMap.get("APPLICATION"), "APPLICATION", "项目申报", project.get("currentPhase"), project.get("updateTime")); |
| | | addPhaseHistoryNodes(nodes, phaseHistories, "APPLICATION"); |
| | | addPhaseLifecycleNode(nodes, phaseMap.get("RESULT"), "RESULT", "申报结果", project.get("currentPhase"), project.get("updateTime")); |
| | | addPhaseHistoryNodes(nodes, phaseHistories, "RESULT"); |
| | | addPhaseLifecycleNode(nodes, phaseMap.get("ACCEPTANCE"), "ACCEPTANCE", "项目验收", project.get("currentPhase"), project.get("updateTime")); |
| | | addPhaseHistoryNodes(nodes, phaseHistories, "ACCEPTANCE"); |
| | | nodes.add(lifecycleNode("PROJECT_END", "项目结束", project.get("actualEndTime"), |
| | | project.get("actualEndTime") == null ? ("已完成".equals(project.get("currentPhase")) ? "CURRENT" : "PENDING") : "COMPLETED", |
| | | "计划结束:" + Objects.toString(project.get("planEndTime"), "未填写"))); |
| | | |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("project", project); |
| | | result.put("nodes", nodes); |
| | | return result; |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long saveModule(ProjectWorkflowDtos.ModuleSaveRequest request) { |
| | | requireProject(request.getProjectId()); |
| | | ensureProjectMaintenanceEditable(request.getProjectId()); |
| | | if (isBlank(request.getModuleName())) { |
| | | throw new ServiceException("请输入模块名称"); |
| | | } |
| | |
| | | + "%,当前模块最多可设置" + available.stripTrailingZeros().toPlainString() + "%"); |
| | | } |
| | | validateMembers(request.getMembers()); |
| | | List<Long> projectExecutorIds = projectExecutorIds(request.getProjectId()); |
| | | if (request.getMembers().stream().anyMatch(member -> !projectExecutorIds.contains(member.getUserId()))) { |
| | | throw new ServiceException("模块执行人必须先加入项目执行人"); |
| | | } |
| | | |
| | | boolean started = isProjectStarted(request.getProjectId()); |
| | | if (started && request.getId() == null) { |
| | | throw new ServiceException("项目开始后不能新增模块,仅可管理模块执行人"); |
| | | } |
| | | if (request.getId() != null) { |
| | | Integer reportCount = jdbc.queryForObject("select count(*) from project_management_progress_report where module_id=:id and is_deleted=0", |
| | | Map.of("id", request.getId()), Integer.class); |
| | | if (reportCount != null && reportCount > 0) { |
| | | if (started || (reportCount != null && reportCount > 0)) { |
| | | Map<String, Object> old = query(""" |
| | | select module_name as moduleName,planned_start_date as plannedStartDate, |
| | | summary,project_weight as projectWeight from project_management_module |
| | |
| | | || !Objects.equals(Objects.toString(old.get("plannedStartDate"), ""), Objects.toString(request.getPlannedStartDate(), "")) |
| | | || !Objects.equals(Objects.toString(old.get("summary"), ""), Objects.toString(request.getSummary(), "")) |
| | | || nvl((BigDecimal) old.get("projectWeight")).compareTo(weight) != 0; |
| | | if (changed) throw new ServiceException("该模块已有进度汇报,模块基础信息和占比不能修改,仅可调整执行人"); |
| | | if (changed) throw new ServiceException(started |
| | | ? "项目开始后模块信息不能修改,仅可管理模块执行人" |
| | | : "该模块已有进度汇报,模块基础信息和占比不能修改,仅可调整模块执行人"); |
| | | } |
| | | List<Long> oldMemberIds = query(""" |
| | | select user_id as userId from project_management_module_member |
| | |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void saveModulesBatch(ProjectWorkflowDtos.ModulesBatchRequest request) { |
| | | requireProject(request.getProjectId()); |
| | | ensureProjectMaintenanceEditable(request.getProjectId()); |
| | | List<ProjectWorkflowDtos.ModuleSaveRequest> modules = request.getModules() == null |
| | | ? new ArrayList<>() : new ArrayList<>(request.getModules()); |
| | | if (modules.isEmpty()) throw new ServiceException("请至少添加一个项目模块"); |
| | | |
| | | BigDecimal total = BigDecimal.ZERO; |
| | | Set<Long> requestedIds = new HashSet<>(); |
| | | for (ProjectWorkflowDtos.ModuleSaveRequest module : modules) { |
| | | module.setProjectId(request.getProjectId()); |
| | | total = total.add(percent(module.getProjectWeight(), "模块在项目中的工作量占比")); |
| | | if (module.getId() != null && !requestedIds.add(module.getId())) { |
| | | throw new ServiceException("模块数据重复,请刷新后重试"); |
| | | } |
| | | } |
| | | if (total.compareTo(BigDecimal.valueOf(100)) != 0) { |
| | | throw new ServiceException("所有模块的项目工作量占比合计必须为100%"); |
| | | } |
| | | |
| | | List<Map<String, Object>> existing = query(""" |
| | | select id,project_weight as projectWeight from project_management_module |
| | | where project_id=:projectId and is_deleted=0 |
| | | """, Map.of("projectId", request.getProjectId())); |
| | | Map<Long, BigDecimal> oldWeights = new LinkedHashMap<>(); |
| | | for (Map<String, Object> row : existing) oldWeights.put(asLong(row.get("id")), nvl((BigDecimal) row.get("projectWeight"))); |
| | | for (Long id : requestedIds) { |
| | | if (!oldWeights.containsKey(id)) throw new ServiceException("项目模块不存在或不属于当前项目"); |
| | | } |
| | | if (isProjectStarted(request.getProjectId()) && (!requestedIds.equals(oldWeights.keySet()) || modules.size() != existing.size())) { |
| | | throw new ServiceException("项目开始后不能新增或删除模块,仅可管理模块执行人"); |
| | | } |
| | | for (Long oldId : oldWeights.keySet()) { |
| | | if (!requestedIds.contains(oldId)) deleteModule(oldId); |
| | | } |
| | | |
| | | // 先保存占比减少的模块,给后续新增或增加占比的模块释放空间。 |
| | | modules.sort(Comparator.comparing(module -> nvl(module.getProjectWeight()) |
| | | .subtract(oldWeights.getOrDefault(module.getId(), BigDecimal.ZERO)))); |
| | | for (ProjectWorkflowDtos.ModuleSaveRequest module : modules) saveModule(module); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteModule(Long id) { |
| | | Long projectId = jdbc.query("select project_id from project_management_module where id=:id and is_deleted=0", |
| | | Map.of("id", id), rs -> rs.next() ? rs.getLong(1) : null); |
| | | if (projectId == null) throw new ServiceException("项目模块不存在"); |
| | | ensureProjectMaintenanceEditable(projectId); |
| | | if (isProjectStarted(projectId)) throw new ServiceException("项目开始后不能删除模块"); |
| | | int count = jdbc.queryForObject("select count(*) from project_management_progress_report where module_id=:id and is_deleted=0", Map.of("id", id), Integer.class); |
| | | if (count > 0) throw new ServiceException("该模块已有进度汇报,不能删除"); |
| | | jdbc.update("update project_management_module set is_deleted=1,update_time=now() where id=:id", Map.of("id", id)); |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long saveFollowUp(ProjectWorkflowDtos.FollowUpRequest request) { |
| | | requireProject(request.getProjectId()); |
| | | ensureProjectMaintenanceEditable(request.getProjectId()); |
| | | Long leaderId = jdbc.queryForObject("select project_leader_id from project_management_info where id=:id", |
| | | Map.of("id", request.getProjectId()), Long.class); |
| | | if (leaderId == null || !leaderId.equals(SecurityUtils.getUserId())) { |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long saveReport(ProjectWorkflowDtos.ProgressReportRequest request) { |
| | | requireProject(request.getProjectId()); |
| | | ensureProjectMaintenanceEditable(request.getProjectId()); |
| | | if (request.getModuleId() == null || request.getReportDate() == null) { |
| | | throw new ServiceException("请选择项目模块和汇报时间"); |
| | | } |
| | | BigDecimal progress = percent(request.getProgress(), "模块进度"); |
| | | BigDecimal increment = percent(request.getIncrementProgress(), "本次进度"); |
| | | if (increment.compareTo(BigDecimal.ZERO) <= 0) throw new ServiceException("本次进度必须大于0"); |
| | | BigDecimal current = jdbc.queryForObject(""" |
| | | select current_progress from project_management_module |
| | | where id=:moduleId and project_id=:projectId and is_deleted=0 |
| | | """, new MapSqlParameterSource().addValue("moduleId", request.getModuleId()).addValue("projectId", request.getProjectId()), BigDecimal.class); |
| | | if (current == null) throw new ServiceException("项目模块不存在"); |
| | | if (progress.compareTo(current) < 0) throw new ServiceException("本次进度不能低于模块当前进度"); |
| | | List<ProjectWorkflowDtos.MemberRequest> reporterShares = normalizeReporterShares(request); |
| | | validateReporterShares(reporterShares); |
| | | List<Long> moduleMemberIds = query(""" |
| | |
| | | } |
| | | request.setReporterIds(reporterShares.stream().map(ProjectWorkflowDtos.MemberRequest::getUserId).toList()); |
| | | request.setReporterNames(reporterShares.stream().map(ProjectWorkflowDtos.MemberRequest::getUserName).toList()); |
| | | Long reportId = request.getId(); |
| | | BigDecimal progress; |
| | | BigDecimal progressAdjustment = BigDecimal.ZERO; |
| | | if (reportId == null) { |
| | | progress = current.add(increment); |
| | | if (progress.compareTo(BigDecimal.valueOf(100)) > 0) { |
| | | throw new ServiceException("模块当前进度为" + current.stripTrailingZeros().toPlainString() |
| | | + "% ,本次进度最多可填写" + BigDecimal.valueOf(100).subtract(current).stripTrailingZeros().toPlainString() + "%"); |
| | | } |
| | | } else { |
| | | Map<String, Object> oldReport = query(""" |
| | | select id,module_id as moduleId,report_date as reportDate,progress |
| | | from project_management_progress_report |
| | | where id=:id and project_id=:projectId and is_deleted=0 |
| | | """, new MapSqlParameterSource().addValue("id", reportId).addValue("projectId", request.getProjectId())) |
| | | .stream().findFirst().orElseThrow(() -> new ServiceException("进度汇报不存在")); |
| | | if (!Objects.equals(asLong(oldReport.get("moduleId")), request.getModuleId())) { |
| | | throw new ServiceException("已有进度汇报不能更换项目模块"); |
| | | } |
| | | LocalDate oldDate = oldReport.get("reportDate") instanceof java.sql.Date date ? date.toLocalDate() : request.getReportDate(); |
| | | if (!Objects.equals(oldDate, request.getReportDate())) throw new ServiceException("已有进度汇报不能修改汇报时间"); |
| | | BigDecimal previous = previousReportProgress(request.getModuleId(), request.getReportDate(), reportId); |
| | | BigDecimal oldProgress = nvl((BigDecimal) oldReport.get("progress")); |
| | | progress = previous.add(increment); |
| | | progressAdjustment = progress.subtract(oldProgress); |
| | | BigDecimal adjustedCurrent = current.add(progressAdjustment); |
| | | if (adjustedCurrent.compareTo(BigDecimal.ZERO) < 0 || adjustedCurrent.compareTo(BigDecimal.valueOf(100)) > 0) { |
| | | throw new ServiceException("修改后模块进度必须在0%至100%之间"); |
| | | } |
| | | } |
| | | MapSqlParameterSource p = new MapSqlParameterSource() |
| | | .addValue("id", request.getId()).addValue("projectId", request.getProjectId()) |
| | | .addValue("moduleId", request.getModuleId()) |
| | |
| | | .addValue("reporterShares", json(reporterShares)) |
| | | .addValue("progress", progress).addValue("reportDate", request.getReportDate()) |
| | | .addValue("remark", request.getRemark()).addValue("attachments", json(request.getAttachments())); |
| | | Long reportId = request.getId(); |
| | | if (reportId == null) { |
| | | GeneratedKeyHolder keys = new GeneratedKeyHolder(); |
| | | jdbc.update(""" |
| | |
| | | attachments=:attachments,update_time=now() |
| | | where id=:id and project_id=:projectId and is_deleted=0 |
| | | """, p); |
| | | if (progressAdjustment.compareTo(BigDecimal.ZERO) != 0) { |
| | | jdbc.update(""" |
| | | update project_management_progress_report set progress=progress+:adjustment,update_time=now() |
| | | where module_id=:moduleId and is_deleted=0 and |
| | | (report_date>:reportDate or (report_date=:reportDate and id>:id)) |
| | | """, new MapSqlParameterSource().addValue("adjustment", progressAdjustment) |
| | | .addValue("moduleId", request.getModuleId()).addValue("reportDate", request.getReportDate()).addValue("id", reportId)); |
| | | } |
| | | } |
| | | jdbc.update("update project_management_module set current_progress=:progress,update_time=now() where id=:moduleId", |
| | | new MapSqlParameterSource().addValue("progress", progress).addValue("moduleId", request.getModuleId())); |
| | | refreshModuleProgress(request.getModuleId()); |
| | | return reportId; |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteReport(Long id) { |
| | | Map<String, Object> report = query(""" |
| | | select id,project_id as projectId,module_id as moduleId,report_date as reportDate,progress |
| | | from project_management_progress_report where id=:id and is_deleted=0 |
| | | """, Map.of("id", id)).stream().findFirst().orElseThrow(() -> new ServiceException("进度汇报不存在")); |
| | | ensureProjectMaintenanceEditable(asLong(report.get("projectId"))); |
| | | Long moduleId = asLong(report.get("moduleId")); |
| | | List<Long> moduleMemberIds = query(""" |
| | | select user_id as userId from project_management_module_member |
| | | where module_id=:moduleId and is_deleted=0 |
| | | """, Map.of("moduleId", moduleId)).stream().map(row -> asLong(row.get("userId"))).toList(); |
| | | if (!moduleMemberIds.contains(SecurityUtils.getUserId())) throw new ServiceException("只有该模块执行人可以删除进度汇报"); |
| | | LocalDate reportDate = report.get("reportDate") instanceof java.sql.Date date ? date.toLocalDate() : null; |
| | | BigDecimal previous = previousReportProgress(moduleId, reportDate, id); |
| | | BigDecimal increment = nvl((BigDecimal) report.get("progress")).subtract(previous).max(BigDecimal.ZERO); |
| | | jdbc.update("update project_management_progress_report set is_deleted=1,update_time=now() where id=:id", Map.of("id", id)); |
| | | if (increment.compareTo(BigDecimal.ZERO) > 0) { |
| | | jdbc.update(""" |
| | | update project_management_progress_report set progress=greatest(0,progress-:increment),update_time=now() |
| | | where module_id=:moduleId and is_deleted=0 and |
| | | (report_date>:reportDate or (report_date=:reportDate and id>:id)) |
| | | """, new MapSqlParameterSource().addValue("increment", increment).addValue("moduleId", moduleId) |
| | | .addValue("reportDate", reportDate).addValue("id", id)); |
| | | } |
| | | refreshModuleProgress(moduleId); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public Long saveMaterial(ProjectWorkflowDtos.MaterialRequest request) { |
| | | requireProject(request.getProjectId()); |
| | | ensureProjectMaintenanceEditable(request.getProjectId()); |
| | | if (isBlank(request.getMaterialName())) throw new ServiceException("请输入资料名称"); |
| | | MapSqlParameterSource p = new MapSqlParameterSource() |
| | | .addValue("id", request.getId()).addValue("projectId", request.getProjectId()) |
| | |
| | | public void softDelete(String table, Long id) { |
| | | if (!List.of("project_management_follow_up", "project_management_progress_report", "project_management_material", "government_policy_file").contains(table)) { |
| | | throw new ServiceException("不支持的删除类型"); |
| | | } |
| | | if (!"government_policy_file".equals(table)) { |
| | | Long projectId = jdbc.query("select project_id from " + table + " where id=:id and is_deleted=0", |
| | | Map.of("id", id), rs -> rs.next() ? rs.getLong(1) : null); |
| | | if (projectId == null) throw new ServiceException("记录不存在"); |
| | | ensureProjectMaintenanceEditable(projectId); |
| | | } |
| | | jdbc.getJdbcTemplate().update("update " + table + " set is_deleted=1,update_time=now() where id=?", id); |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | private List<Long> projectExecutorIds(Long projectId) { |
| | | List<Map<String, Object>> rows = query(""" |
| | | select project_executor_ids as executorIds from project_management_info |
| | | where id=:projectId and is_delete=0 |
| | | """, Map.of("projectId", projectId)); |
| | | return rows.isEmpty() ? List.of() : parseLongList(rows.getFirst().get("executorIds")); |
| | | } |
| | | |
| | | private boolean hasPositiveProjectShare(Long projectId, Long userId) { |
| | | return hasPositiveShare(query(""" |
| | | select reporter_ids as reporterIds,reporter_names as reporterNames,reporter_shares as reporterShares |
| | | from project_management_progress_report where project_id=:projectId and is_deleted=0 |
| | | """, Map.of("projectId", projectId)), userId); |
| | | } |
| | | |
| | | private boolean hasPositiveModuleShare(Long moduleId, Long userId) { |
| | | return hasPositiveShare(query(""" |
| | | select reporter_ids as reporterIds,reporter_names as reporterNames,reporter_shares as reporterShares |
| | | from project_management_progress_report where module_id=:moduleId and is_deleted=0 |
| | | """, Map.of("moduleId", moduleId)), userId); |
| | | } |
| | | |
| | | private boolean isProjectStarted(Long projectId) { |
| | | String phase = jdbc.query("select current_phase from project_management_info where id=:id and is_delete=0", |
| | | Map.of("id", projectId), rs -> rs.next() ? rs.getString(1) : null); |
| | | return phase != null && !phase.isBlank() && !"未开始".equals(phase); |
| | | } |
| | | |
| | | private Map<String, Object> lifecycleNode(String code, String name, Object time, String status, String description) { |
| | | Map<String, Object> node = new LinkedHashMap<>(); |
| | | node.put("code", code); |
| | | node.put("name", name); |
| | | node.put("time", time); |
| | | node.put("status", status); |
| | | node.put("description", description); |
| | | return node; |
| | | } |
| | | |
| | | private void appendPhaseHistory(Long projectId, String phaseCode, String phaseName, |
| | | String actionType, String actionLabel, Map<String, Object> formData, |
| | | String remark, List<Map<String, Object>> attachments) { |
| | | jdbc.update(""" |
| | | insert into project_management_phase_history |
| | | (project_id,phase_code,phase_name,action_type,action_label,form_data,remark,attachments,operator_name,action_time) |
| | | values (:projectId,:phaseCode,:phaseName,:actionType,:actionLabel,:formData,:remark,:attachments,:operatorName,now()) |
| | | """, new MapSqlParameterSource() |
| | | .addValue("projectId", projectId).addValue("phaseCode", phaseCode).addValue("phaseName", phaseName) |
| | | .addValue("actionType", actionType).addValue("actionLabel", actionLabel) |
| | | .addValue("formData", jsonObject(formData)).addValue("remark", remark) |
| | | .addValue("attachments", json(attachments)).addValue("operatorName", currentUserName())); |
| | | } |
| | | |
| | | private String stageHistoryAction(String phaseName, Map<String, Object> formData, boolean completed) { |
| | | if ("验收".equals(phaseName)) { |
| | | return "RECTIFY".equals(Objects.toString(formData == null ? null : formData.get("acceptanceResult"), "")) |
| | | ? "RECTIFY" : completed ? "PASS" : "ENTER"; |
| | | } |
| | | if ("申报".equals(phaseName)) { |
| | | String result = Objects.toString(formData == null ? null : formData.get("resultStatus"), ""); |
| | | if ("SUPPLEMENT".equals(result)) return "SUPPLEMENT"; |
| | | if ("REJECTED".equals(result)) return "REJECTED"; |
| | | return "SUBMIT"; |
| | | } |
| | | if ("申报结果".equals(phaseName)) return "PASS"; |
| | | return completed ? "COMPLETE" : "SAVE"; |
| | | } |
| | | |
| | | private String stageHistoryLabel(String phaseName, Map<String, Object> formData, boolean completed) { |
| | | if ("验收".equals(phaseName)) { |
| | | String result = Objects.toString(formData == null ? null : formData.get("acceptanceResult"), ""); |
| | | if ("RECTIFY".equals(result)) return "需要整改"; |
| | | return completed || "PASS".equals(result) ? "验收通过" : "进入验收"; |
| | | } |
| | | if ("申报".equals(phaseName)) { |
| | | String result = Objects.toString(formData == null ? null : formData.get("resultStatus"), ""); |
| | | return switch (result) { |
| | | case "SUPPLEMENT" -> "需要补充"; |
| | | case "REJECTED" -> "申报未通过"; |
| | | default -> "提交申报"; |
| | | }; |
| | | } |
| | | if ("申报结果".equals(phaseName)) return "申报通过"; |
| | | return completed ? "阶段完成" : "阶段办理"; |
| | | } |
| | | |
| | | private void addPhaseHistoryNodes(List<Map<String, Object>> nodes, List<Map<String, Object>> histories, String phaseCode) { |
| | | histories.stream().filter(row -> phaseCode.equals(Objects.toString(row.get("phaseCode"), ""))).forEach(row -> { |
| | | String actionLabel = Objects.toString(row.get("actionLabel"), "办理记录"); |
| | | String description = Objects.toString(row.get("remark"), actionLabel); |
| | | if (!isBlank(Objects.toString(row.get("operatorName"), ""))) description += ";操作人:" + row.get("operatorName"); |
| | | Map<String, Object> node = lifecycleNode("HISTORY_" + row.get("id"), |
| | | row.get("phaseName") + " · " + actionLabel, row.get("actionTime"), "COMPLETED", description); |
| | | node.put("eventType", row.get("actionType")); |
| | | node.put("statusLabel", "办理记录"); |
| | | nodes.add(node); |
| | | }); |
| | | } |
| | | |
| | | private void addPhaseLifecycleNode(List<Map<String, Object>> nodes, Map<String, Object> phase, |
| | | String code, String name, Object currentPhase, Object currentPhaseTime) { |
| | | String status; |
| | | Object time = null; |
| | | String description = "尚未进入该流程"; |
| | | if (phase != null) { |
| | | String phaseStatus = Objects.toString(phase.get("status"), "NOT_STARTED"); |
| | | status = switch (phaseStatus) { |
| | | case "COMPLETED" -> "COMPLETED"; |
| | | case "IN_PROGRESS" -> "CURRENT"; |
| | | default -> "PENDING"; |
| | | }; |
| | | int currentRank = lifecyclePhaseRank(Objects.toString(currentPhase, "未开始")); |
| | | int nodeRank = lifecyclePhaseRank(name.replace("项目", "")); |
| | | if ("PENDING".equals(status) && currentRank == nodeRank) { |
| | | status = "CURRENT"; |
| | | time = currentPhaseTime; |
| | | description = "当前正在进行(根据项目阶段更新时间补录)"; |
| | | } else if ("PENDING".equals(status) && currentRank > nodeRank) { |
| | | status = "COMPLETED"; |
| | | description = "该流程已完成,历史时间未单独记录"; |
| | | } |
| | | if (!"PENDING".equals(status)) { |
| | | if (time == null) time = phase.get("completedTime") == null ? phase.get("updateTime") : phase.get("completedTime"); |
| | | if (!description.contains("补录")) description = "COMPLETED".equals(status) ? "该流程已完成" : "该流程正在进行"; |
| | | if (!isBlank(Objects.toString(phase.get("updateUserName"), ""))) { |
| | | description += ",维护人:" + phase.get("updateUserName"); |
| | | } |
| | | if (!isBlank(Objects.toString(phase.get("remark"), ""))) description += ";" + phase.get("remark"); |
| | | } |
| | | } else { |
| | | int currentRank = lifecyclePhaseRank(Objects.toString(currentPhase, "未开始")); |
| | | int nodeRank = lifecyclePhaseRank(name.replace("项目", "")); |
| | | status = currentRank > nodeRank ? "COMPLETED" : currentRank == nodeRank ? "CURRENT" : "PENDING"; |
| | | if ("CURRENT".equals(status)) description = "当前正在进行"; |
| | | else if ("COMPLETED".equals(status)) description = "该流程已完成,暂无独立时间记录"; |
| | | } |
| | | nodes.add(lifecycleNode(code, name, time, status, description)); |
| | | } |
| | | |
| | | private int lifecyclePhaseRank(String phase) { |
| | | return switch (phase) { |
| | | case "开发" -> 1; |
| | | case "申报" -> 2; |
| | | case "申报结果" -> 3; |
| | | case "验收" -> 4; |
| | | case "已完成" -> 5; |
| | | default -> 0; |
| | | }; |
| | | } |
| | | |
| | | private String currentNodeStatus(Object currentPhase, String pendingPhase) { |
| | | return pendingPhase.equals(Objects.toString(currentPhase, "未开始")) ? "PENDING" : "CURRENT"; |
| | | } |
| | | |
| | | private BigDecimal previousReportProgress(Long moduleId, LocalDate reportDate, Long reportId) { |
| | | BigDecimal previous = jdbc.query(""" |
| | | select progress from project_management_progress_report |
| | | where module_id=:moduleId and is_deleted=0 and |
| | | (report_date<:reportDate or (report_date=:reportDate and id<:id)) |
| | | order by report_date desc,id desc limit 1 |
| | | """, new MapSqlParameterSource().addValue("moduleId", moduleId) |
| | | .addValue("reportDate", reportDate).addValue("id", reportId), |
| | | rs -> rs.next() ? rs.getBigDecimal(1) : BigDecimal.ZERO); |
| | | return nvl(previous); |
| | | } |
| | | |
| | | private void refreshModuleProgress(Long moduleId) { |
| | | BigDecimal latest = jdbc.query(""" |
| | | select progress from project_management_progress_report |
| | | where module_id=:moduleId and is_deleted=0 order by report_date desc,id desc limit 1 |
| | | """, Map.of("moduleId", moduleId), rs -> rs.next() ? rs.getBigDecimal(1) : BigDecimal.ZERO); |
| | | jdbc.update("update project_management_module set current_progress=:progress,update_time=now() where id=:moduleId", |
| | | new MapSqlParameterSource().addValue("progress", nvl(latest)).addValue("moduleId", moduleId)); |
| | | } |
| | | |
| | | private boolean hasPositiveShare(List<Map<String, Object>> reports, Long userId) { |
| | |
| | | if (count == null || count == 0) throw new ServiceException("项目不存在"); |
| | | } |
| | | |
| | | private void ensureProjectMaintenanceEditable(Long projectId) { |
| | | String phase = jdbc.queryForObject("select current_phase from project_management_info where id=:id and is_delete=0", |
| | | Map.of("id", projectId), String.class); |
| | | if (List.of("申报", "申报结果", "验收", "已完成").contains(phase)) { |
| | | throw new ServiceException("项目进入申报阶段后,模块、跟进、汇报和项目资料只能查看,不能新增、编辑或删除"); |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> query(String sql, Map<String, ?> params) { |
| | | return jdbc.queryForList(sql, params); |
| | | } |