| | |
| | | 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)); |
| | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void startProject(Long projectId) { |
| | | requireProject(projectId); |
| | | String currentPhase = jdbc.queryForObject(""" |
| | | select current_phase from project_management_info |
| | | where id=:projectId and is_delete=0 for update |
| | | """, Map.of("projectId", projectId), String.class); |
| | | if (!"未开始".equals(currentPhase)) { |
| | | throw new ServiceException("只有未开始的项目可以开始,当前阶段为“" + currentPhase + "”"); |
| | | } |
| | | Integer basicReady = jdbc.queryForObject(""" |
| | | select count(*) from project_management_info |
| | | where id=:projectId and is_delete=0 and project_classification is not null |
| | | and project_classification<>'' and plan_start_time is not null and plan_end_time is not null |
| | | where id=:projectId and is_delete=0 and project_category_id is not null |
| | | and plan_start_time is not null and plan_end_time is not null |
| | | and plan_end_time>=plan_start_time |
| | | 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(""" |
| | |
| | | from project_management_module where project_id=:projectId and is_deleted=0 |
| | | """, Map.of("projectId", projectId)); |
| | | if (modules.isEmpty()) throw new ServiceException("项目开始前请至少添加一个项目模块"); |
| | | Integer invalidModuleDates = jdbc.queryForObject(""" |
| | | select count(*) from project_management_module m |
| | | join project_management_info p on p.id=m.project_id and p.is_delete=0 |
| | | where m.project_id=:projectId and m.is_deleted=0 |
| | | and (m.planned_start_date<p.plan_start_time or m.planned_start_date>p.plan_end_time) |
| | | """, Map.of("projectId", projectId), Integer.class); |
| | | if (invalidModuleDates != null && invalidModuleDates > 0) { |
| | | throw new ServiceException("模块预计开始时间必须在项目计划开始和结束日期之间"); |
| | | } |
| | | BigDecimal total = BigDecimal.ZERO; |
| | | for (Map<String, Object> module : modules) { |
| | | if (isBlank(Objects.toString(module.get("moduleName"), "")) || module.get("plannedStartDate") == null |
| | |
| | | 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()); |
| | | String current = jdbc.queryForObject("select current_phase from project_management_info where id=:id and is_delete=0", |
| | | ensurePhases(request.getProjectId()); |
| | | String current = jdbc.queryForObject("select current_phase from project_management_info where id=:id and is_delete=0 for update", |
| | | Map.of("id", request.getProjectId()), String.class); |
| | | if ("立项".equals(current)) current = "开发"; |
| | | Map<String, String> nextStages = Map.of( |
| | | "开发", "申报", |
| | | "申报", "申报结果", |
| | |
| | | if (expected == null || !expected.equals(request.getTargetStage())) { |
| | | throw new ServiceException("项目阶段只能按开发、申报、申报结果、验收、已完成的顺序推进"); |
| | | } |
| | | validateTransitionForm(current, request.getFormData()); |
| | | if ("开发".equals(current)) { |
| | | Integer unfinished = jdbc.queryForObject(""" |
| | | select count(*) from project_management_module |
| | |
| | | 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) |
| | |
| | | int index = PHASE_CODES.indexOf(Objects.toString(request.getPhaseCode(), "")); |
| | | if (index < 0) throw new ServiceException("项目阶段不正确"); |
| | | String status = Objects.toString(request.getStatus(), "IN_PROGRESS").toUpperCase(); |
| | | if (!List.of("NOT_STARTED", "IN_PROGRESS", "COMPLETED").contains(status)) { |
| | | throw new ServiceException("阶段状态不正确"); |
| | | if (!"IN_PROGRESS".equals(status)) throw new ServiceException("阶段补充办理只能保持进行中状态"); |
| | | String currentPhase = jdbc.queryForObject(""" |
| | | select current_phase from project_management_info |
| | | where id=:id and is_delete=0 for update |
| | | """, Map.of("id", request.getProjectId()), String.class); |
| | | String expectedCode = phaseCodeForName(currentPhase); |
| | | if (!Objects.equals(expectedCode, request.getPhaseCode())) { |
| | | throw new ServiceException("只能维护当前阶段“" + currentPhase + "”,不能跳转或倒退阶段"); |
| | | } |
| | | if (index > 0 && !"NOT_STARTED".equals(status)) { |
| | | Integer previousDone = jdbc.queryForObject(""" |
| | | select count(*) from project_management_phase |
| | | where project_id=:projectId and phase_code=:phaseCode and status='COMPLETED' and is_deleted=0 |
| | | """, 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) + "”"); |
| | | } |
| | | } |
| | | validateSupplementalPhase(currentPhase, request.getFormData()); |
| | | MapSqlParameterSource p = new MapSqlParameterSource() |
| | | .addValue("projectId", request.getProjectId()).addValue("phaseCode", request.getPhaseCode()) |
| | | .addValue("status", status).addValue("formData", jsonObject(request.getFormData())) |
| | |
| | | update_user_name=:userName,update_time=now() |
| | | where project_id=:projectId and phase_code=:phaseCode and is_deleted=0 |
| | | """, p); |
| | | 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())); |
| | | 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()); |
| | | 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) { |
| | | Integer dateInProjectRange = jdbc.queryForObject(""" |
| | | select count(*) from project_management_info |
| | | where id=:projectId and is_delete=0 and plan_start_time is not null and plan_end_time is not null |
| | | and :plannedStartDate between plan_start_time and plan_end_time |
| | | """, new MapSqlParameterSource().addValue("projectId", request.getProjectId()) |
| | | .addValue("plannedStartDate", request.getPlannedStartDate()), Integer.class); |
| | | if (dateInProjectRange == null || dateInProjectRange == 0) { |
| | | throw new ServiceException("模块预计开始时间必须在项目计划开始和结束日期之间"); |
| | | } |
| | | } |
| | | 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()); |
| | | 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())) { |
| | | throw new ServiceException("只有项目负责人可以填写项目跟进"); |
| | | } |
| | | ensureProjectMaintenanceEditable(request.getProjectId()); |
| | | ensureProjectLeader(request.getProjectId()); |
| | | if (request.getFollowDate() == null || isBlank(request.getDetail())) { |
| | | throw new ServiceException("请填写跟进日期和跟进详情"); |
| | | } |
| | | validateFollowWindow(request.getProjectId(), request.getFollowDate(), request.getId()); |
| | | if (request.getFollowDate().isAfter(LocalDate.now())) throw new ServiceException("跟进日期不能晚于今天"); |
| | | if (request.getId() == null) { |
| | | validateFollowWindow(request.getProjectId(), request.getFollowDate(), null); |
| | | } else { |
| | | LocalDate oldDate = jdbc.query(""" |
| | | select follow_date from project_management_follow_up |
| | | where id=:id and project_id=:projectId and is_deleted=0 |
| | | """, new MapSqlParameterSource().addValue("id", request.getId()) |
| | | .addValue("projectId", request.getProjectId()), |
| | | rs -> rs.next() ? rs.getDate(1).toLocalDate() : null); |
| | | if (oldDate == null) throw new ServiceException("跟进记录不存在"); |
| | | if (!oldDate.equals(request.getFollowDate())) throw new ServiceException("已有跟进记录不能修改跟进日期"); |
| | | } |
| | | MapSqlParameterSource p = new MapSqlParameterSource() |
| | | .addValue("id", request.getId()).addValue("projectId", request.getProjectId()) |
| | | .addValue("followDate", request.getFollowDate()).addValue("detail", request.getDetail()) |
| | |
| | | """, p, keys, new String[]{"id"}); |
| | | return Objects.requireNonNull(keys.getKey()).longValue(); |
| | | } |
| | | jdbc.update(""" |
| | | int updated = jdbc.update(""" |
| | | update project_management_follow_up set follow_date=:followDate,detail=:detail, |
| | | attachments=:attachments,update_time=now() where id=:id and project_id=:projectId and is_deleted=0 |
| | | """, p); |
| | | if (updated == 0) throw new ServiceException("跟进记录不存在"); |
| | | return request.getId(); |
| | | } |
| | | |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public void deleteFollowUp(Long id) { |
| | | Long projectId = jdbc.query(""" |
| | | select project_id from project_management_follow_up 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); |
| | | ensureProjectLeader(projectId); |
| | | jdbc.update("update project_management_follow_up set is_deleted=1,update_time=now() where id=:id", Map.of("id", id)); |
| | | } |
| | | |
| | | @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(), "模块进度"); |
| | | if (request.getReportDate().isAfter(LocalDate.now())) { |
| | | throw new ServiceException("汇报时间不能晚于今天"); |
| | | } |
| | | 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 |
| | | for update |
| | | """, 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(""" |
| | |
| | | if (reporterShares.stream().anyMatch(member -> !moduleMemberIds.contains(member.getUserId()))) { |
| | | throw new ServiceException("汇报人员只能选择该模块执行人"); |
| | | } |
| | | if (reporterShares.stream().noneMatch(member -> Objects.equals(member.getUserId(), SecurityUtils.getUserId()))) { |
| | | throw new ServiceException("当前办理人必须包含在本次汇报人员中"); |
| | | } |
| | | 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) { |
| | | BigDecimal finalProgress = current.add(increment); |
| | | if (finalProgress.compareTo(BigDecimal.valueOf(100)) > 0) { |
| | | throw new ServiceException("模块当前进度为" + current.stripTrailingZeros().toPlainString() |
| | | + "% ,本次进度最多可填写" + BigDecimal.valueOf(100).subtract(current).stripTrailingZeros().toPlainString() + "%"); |
| | | } |
| | | progress = previousReportProgress(request.getModuleId(), request.getReportDate(), Long.MAX_VALUE).add(increment); |
| | | } else { |
| | | Map<String, Object> oldReport = query(""" |
| | | select id,module_id as moduleId,report_date as reportDate,progress,reporter_ids as reporterIds |
| | | 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("已有进度汇报不能更换项目模块"); |
| | | } |
| | | if (!parseLongList(oldReport.get("reporterIds")).contains(SecurityUtils.getUserId())) { |
| | | 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(""" |
| | |
| | | values (:projectId,:moduleId,:reporterIds,:reporterNames,:reporterShares,:progress,:reportDate,:remark,:attachments,0,now(),now()) |
| | | """, p, keys, new String[]{"id"}); |
| | | reportId = Objects.requireNonNull(keys.getKey()).longValue(); |
| | | jdbc.update(""" |
| | | update project_management_progress_report set progress=progress+:increment,update_time=now() |
| | | where module_id=:moduleId and is_deleted=0 and report_date>:reportDate |
| | | """, new MapSqlParameterSource().addValue("increment", increment) |
| | | .addValue("moduleId", request.getModuleId()).addValue("reportDate", request.getReportDate())); |
| | | } else { |
| | | jdbc.update(""" |
| | | update project_management_progress_report set module_id=:moduleId,reporter_ids=:reporterIds, |
| | |
| | | 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, |
| | | reporter_ids as reporterIds |
| | | 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")); |
| | | if (!parseLongList(report.get("reporterIds")).contains(SecurityUtils.getUserId())) { |
| | | throw new ServiceException("只有原汇报办理人可以删除该进度汇报"); |
| | | } |
| | | BigDecimal lockedProgress = jdbc.queryForObject(""" |
| | | select current_progress from project_management_module where id=:moduleId and is_deleted=0 for update |
| | | """, Map.of("moduleId", moduleId), BigDecimal.class); |
| | | if (lockedProgress == null) 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("请输入资料名称"); |
| | | if (request.getAttachments() == null || request.getAttachments().isEmpty()) { |
| | | throw new ServiceException("请上传至少一个项目资料文件"); |
| | | } |
| | | MapSqlParameterSource p = new MapSqlParameterSource() |
| | | .addValue("id", request.getId()).addValue("projectId", request.getProjectId()) |
| | | .addValue("materialName", request.getMaterialName()).addValue("materialType", request.getMaterialType()) |
| | |
| | | """, p, keys, new String[]{"id"}); |
| | | return Objects.requireNonNull(keys.getKey()).longValue(); |
| | | } |
| | | jdbc.update(""" |
| | | int updated = jdbc.update(""" |
| | | update project_management_material set material_name=:materialName,material_type=:materialType, |
| | | remark=:remark,attachments=:attachments,update_time=now() |
| | | where id=:id and project_id=:projectId and is_deleted=0 |
| | | """, p); |
| | | if (updated == 0) throw new ServiceException("项目资料不存在"); |
| | | return request.getId(); |
| | | } |
| | | |
| | |
| | | if (!List.of("project_management_follow_up", "project_management_progress_report", "project_management_material", "government_policy_file").contains(table)) { |
| | | throw new ServiceException("不支持的删除类型"); |
| | | } |
| | | jdbc.getJdbcTemplate().update("update " + table + " set is_deleted=1,update_time=now() where id=?", id); |
| | | 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); |
| | | } |
| | | int updated = jdbc.getJdbcTemplate().update("update " + table + " set is_deleted=1,update_time=now() where id=? and is_deleted=0", id); |
| | | if (updated == 0) throw new ServiceException("记录不存在"); |
| | | } |
| | | |
| | | public List<Map<String, Object>> listCategories() { |
| | |
| | | if (isBlank(request.getCategoryName())) throw new ServiceException("请输入分类名称"); |
| | | Long parentId = request.getParentId() == null ? 0L : request.getParentId(); |
| | | if (request.getId() != null && request.getId().equals(parentId)) throw new ServiceException("上级分类不能选择自己"); |
| | | validateCategoryParent(request.getId(), parentId); |
| | | Integer duplicate = jdbc.queryForObject(""" |
| | | select count(*) from project_management_category |
| | | where coalesce(parent_id,0)=:parentId and category_name=:categoryName and is_deleted=0 |
| | | and (:id is null or id<>:id) |
| | | """, new MapSqlParameterSource().addValue("parentId", parentId) |
| | | .addValue("categoryName", request.getCategoryName().trim()).addValue("id", request.getId()), Integer.class); |
| | | if (duplicate != null && duplicate > 0) throw new ServiceException("同一上级下已存在同名分类"); |
| | | MapSqlParameterSource p = new MapSqlParameterSource().addValue("id", request.getId()) |
| | | .addValue("parentId", parentId).addValue("categoryName", request.getCategoryName().trim()) |
| | | .addValue("sort", request.getSort() == null ? 0 : request.getSort()).addValue("remark", request.getRemark()); |
| | |
| | | """, p, keys, new String[]{"id"}); |
| | | return Objects.requireNonNull(keys.getKey()).longValue(); |
| | | } |
| | | jdbc.update(""" |
| | | int updated = jdbc.update(""" |
| | | update project_management_category set parent_id=:parentId,category_name=:categoryName, |
| | | sort=:sort,remark=:remark,update_time=now() where id=:id and is_deleted=0 |
| | | """, p); |
| | | if (updated == 0) throw new ServiceException("项目分类不存在或已删除"); |
| | | return request.getId(); |
| | | } |
| | | |
| | | public void deleteCategory(Long id) { |
| | | int children = jdbc.queryForObject("select count(*) from project_management_category where parent_id=:id and is_deleted=0", Map.of("id", id), Integer.class); |
| | | if (children > 0) throw new ServiceException("请先删除下级分类"); |
| | | String categoryName = jdbc.query("select category_name from project_management_category where id=:id and is_deleted=0", |
| | | Map.of("id", id), rs -> rs.next() ? rs.getString(1) : null); |
| | | if (categoryName == null) throw new ServiceException("项目分类不存在"); |
| | | Integer used = jdbc.queryForObject(""" |
| | | select count(*) from project_management_info |
| | | where is_delete=0 and (project_category_id=:id or |
| | | (project_category_id is null and project_classification=:categoryName)) |
| | | """, new MapSqlParameterSource().addValue("id", id).addValue("categoryName", categoryName), Integer.class); |
| | | if (used != null && used > 0) throw new ServiceException("该分类已被项目使用,不能删除"); |
| | | jdbc.update("update project_management_category set is_deleted=1,update_time=now() where id=:id", Map.of("id", id)); |
| | | } |
| | | |
| | | private void validateCategoryParent(Long categoryId, Long parentId) { |
| | | if (parentId == null || parentId == 0L) return; |
| | | Set<Long> visited = new HashSet<>(); |
| | | Long current = parentId; |
| | | while (current != null && current != 0L) { |
| | | if (Objects.equals(current, categoryId)) throw new ServiceException("上级分类不能选择当前分类或其下级分类"); |
| | | if (!visited.add(current)) throw new ServiceException("分类数据已存在循环,请先修复分类关系"); |
| | | current = jdbc.query(""" |
| | | select parent_id from project_management_category where id=:id and is_deleted=0 |
| | | """, Map.of("id", current), rs -> rs.next() ? rs.getLong(1) : null); |
| | | if (current == null) throw new ServiceException("上级分类不存在或已删除"); |
| | | } |
| | | } |
| | | |
| | | public List<Map<String, Object>> listPolicies(String keyword) { |
| | |
| | | """, p, keys, new String[]{"id"}); |
| | | return Objects.requireNonNull(keys.getKey()).longValue(); |
| | | } |
| | | jdbc.update(""" |
| | | int updated = jdbc.update(""" |
| | | update government_policy_file set policy_name=:policyName,publish_date=:publishDate, |
| | | policy_url=:policyUrl,attachments=:attachments,maintainer_name=:maintainer, |
| | | remark=:remark,update_time=now() where id=:id and is_deleted=0 |
| | | """, p); |
| | | if (updated == 0) throw new ServiceException("政策文件不存在或已删除"); |
| | | return request.getId(); |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | 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) { |
| | |
| | | return val.setScale(2, RoundingMode.HALF_UP); |
| | | } |
| | | |
| | | private void validateTransitionForm(String currentPhase, Map<String, Object> formData) { |
| | | switch (currentPhase) { |
| | | case "开发" -> { |
| | | requireDate(formData, "applicationDate", "申报日期"); |
| | | requireText(formData, "applicationType", "申报类型"); |
| | | requireText(formData, "applicationName", "申报名称"); |
| | | requireText(formData, "description", "申报说明"); |
| | | } |
| | | case "申报" -> { |
| | | requireDate(formData, "resultDate", "结果日期"); |
| | | requireValue(formData, "resultStatus", "PASS", "只有申报通过后才能进入申报结果阶段"); |
| | | requireText(formData, "resultDescription", "结果说明"); |
| | | } |
| | | case "申报结果" -> { |
| | | requireDate(formData, "acceptancePlanDate", "计划验收日期"); |
| | | requireText(formData, "acceptor", "验收人/客户"); |
| | | requireText(formData, "acceptanceStandard", "验收标准"); |
| | | } |
| | | case "验收" -> { |
| | | requireDate(formData, "acceptanceDate", "验收日期"); |
| | | requireText(formData, "acceptor", "验收人/客户"); |
| | | requireValue(formData, "acceptanceResult", "PASS", "只有验收通过后才能完成项目"); |
| | | requireText(formData, "acceptanceDescription", "验收说明"); |
| | | } |
| | | default -> throw new ServiceException("当前阶段不能办理流转"); |
| | | } |
| | | } |
| | | |
| | | private void validateSupplementalPhase(String currentPhase, Map<String, Object> formData) { |
| | | if ("申报".equals(currentPhase)) { |
| | | requireDate(formData, "resultDate", "结果日期"); |
| | | String result = requireText(formData, "resultStatus", "申报结果"); |
| | | if (!List.of("SUPPLEMENT", "REJECTED").contains(result)) { |
| | | throw new ServiceException("申报通过必须使用阶段推进操作"); |
| | | } |
| | | requireText(formData, "resultDescription", "结果说明"); |
| | | return; |
| | | } |
| | | if ("验收".equals(currentPhase)) { |
| | | requireDate(formData, "acceptanceDate", "验收日期"); |
| | | requireText(formData, "acceptor", "验收人/客户"); |
| | | requireValue(formData, "acceptanceResult", "RECTIFY", "验收通过必须使用完成项目操作"); |
| | | requireText(formData, "acceptanceDescription", "验收说明"); |
| | | return; |
| | | } |
| | | throw new ServiceException("当前阶段不支持补充办理,只能按流程推进"); |
| | | } |
| | | |
| | | private String phaseCodeForName(String phaseName) { |
| | | return switch (Objects.toString(phaseName, "")) { |
| | | case "立项" -> "INITIATION"; |
| | | case "开发" -> "DEVELOPMENT"; |
| | | case "申报" -> "APPLICATION"; |
| | | case "申报结果" -> "RESULT"; |
| | | case "验收" -> "ACCEPTANCE"; |
| | | default -> null; |
| | | }; |
| | | } |
| | | |
| | | private String requireText(Map<String, Object> formData, String key, String label) { |
| | | String value = Objects.toString(formData == null ? null : formData.get(key), "").trim(); |
| | | if (value.isEmpty()) throw new ServiceException("请填写" + label); |
| | | return value; |
| | | } |
| | | |
| | | private void requireDate(Map<String, Object> formData, String key, String label) { |
| | | String value = requireText(formData, key, label); |
| | | try { |
| | | LocalDate.parse(value); |
| | | } catch (Exception e) { |
| | | throw new ServiceException(label + "格式不正确"); |
| | | } |
| | | } |
| | | |
| | | private void requireValue(Map<String, Object> formData, String key, String expected, String message) { |
| | | if (!expected.equals(requireText(formData, key, key))) throw new ServiceException(message); |
| | | } |
| | | |
| | | private void requireProject(Long projectId) { |
| | | if (projectId == null) throw new ServiceException("项目不能为空"); |
| | | Integer count = jdbc.queryForObject("select count(*) from project_management_info where id=:id and is_delete=0", Map.of("id", projectId), Integer.class); |
| | | 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 void ensureProjectLeader(Long projectId) { |
| | | Long leaderId = jdbc.query("select project_leader_id from project_management_info where id=:id and is_delete=0", |
| | | Map.of("id", projectId), rs -> rs.next() ? rs.getLong(1) : null); |
| | | if (leaderId == null || !leaderId.equals(SecurityUtils.getUserId())) { |
| | | throw new ServiceException("只有项目负责人可以维护项目跟进"); |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> query(String sql, Map<String, ?> params) { |
| | | return jdbc.queryForList(sql, params); |
| | | } |