diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectScheduleStepController.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectScheduleStepController.java index 9b49bc3..9f4f9a0 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectScheduleStepController.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectScheduleStepController.java @@ -7,6 +7,7 @@ import java.util.concurrent.TimeUnit; import com.ruoyi.common.core.AjaxResult; import com.ruoyi.oa.domain.bo.BatchBo; +import com.ruoyi.oa.domain.bo.BatchDelayBo; import com.ruoyi.oa.domain.dto.PersonalReportDTO; import lombok.RequiredArgsConstructor; @@ -117,6 +118,15 @@ public class OaProjectScheduleStepController extends BaseController { return toAjax(iOaProjectScheduleStepService.changeBatch(batchBo)); } + /** + * 批量延期步骤结束时间 + */ + @RepeatSubmit() + @PutMapping("/batch-delay") + public R batchDelay(@Validated(EditGroup.class) @RequestBody BatchDelayBo batchDelayBo) { + return toAjax(iOaProjectScheduleStepService.batchDelay(batchDelayBo)); + } + /** * 删除项目进度步骤跟踪 * diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/BatchDelayBo.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/BatchDelayBo.java new file mode 100644 index 0000000..c8bfaa2 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/BatchDelayBo.java @@ -0,0 +1,15 @@ +package com.ruoyi.oa.domain.bo; + +import lombok.Data; + +import java.util.List; + +@Data +public class BatchDelayBo { + + private List trackIds; + + private Integer delayValue; + + private String delayUnit; +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectScheduleStepMapper.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectScheduleStepMapper.java index 753d3c8..ef0403e 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectScheduleStepMapper.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectScheduleStepMapper.java @@ -55,4 +55,12 @@ public interface OaProjectScheduleStepMapper extends BaseMapperPlus trackIds, @Param("delayMinutes") Long delayMinutes); } diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectScheduleStepService.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectScheduleStepService.java index 2744e12..40a14f0 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectScheduleStepService.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectScheduleStepService.java @@ -82,4 +82,11 @@ public interface IOaProjectScheduleStepService{ PersonalReportDTO personalReport(Long poolId, String nickName); TableDataInfo queryPageListMyPage(OaProjectScheduleStepBo bo, PageQuery pageQuery); + + /** + * 批量延期步骤结束时间 + * @param bo 批量延期参数 + * @return 是否成功 + */ + Boolean batchDelay(com.ruoyi.oa.domain.bo.BatchDelayBo bo); } diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleStepServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleStepServiceImpl.java index a95a215..5100633 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleStepServiceImpl.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleStepServiceImpl.java @@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.ruoyi.oa.domain.*; import com.ruoyi.oa.domain.bo.BatchBo; +import com.ruoyi.oa.domain.bo.BatchDelayBo; import com.ruoyi.oa.domain.dto.NodeDTO; import com.ruoyi.oa.domain.dto.PersonalReportDTO; import com.ruoyi.oa.domain.vo.OaExpressVo; @@ -71,15 +72,15 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS private final OaProjectScheduleStepMapper projectScheduleStepMapper; private final ISysOaTaskService sysOaTaskService; - + private final OaProjectReportMapper projectReportMapper; - + private final SysOaTaskMapper taskMapper; - + private final OaUserActiveMapper userActiveMapper; - + private final OaExpressQuestionMapper expressQuestionMapper; - + private final OaRequirementsMapper requirementsMapper; private final IOaExpressService oaExpressService; @@ -606,49 +607,49 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS return count != null ? count.intValue() : 0; } - + /** * 统计报工信息 */ private PersonalReportDTO.WorkReportStats getWorkReportStats(Long userId, Date startDate, Date endDate) { PersonalReportDTO.WorkReportStats stats = new PersonalReportDTO.WorkReportStats(); - + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .eq(OaProjectReport::getDelFlag, 0) .eq(OaProjectReport::getUserId, userId); - + // 添加时间范围条件 if (startDate != null && endDate != null) { wrapper.between(OaProjectReport::getCreateTime, startDate, endDate); } - + List reports = projectReportMapper.selectList(wrapper); - + // 计算报工天数(这里简化处理,实际可能需要按日期去重) stats.setTotalWorkDays(BigDecimal.valueOf(reports.size())); stats.setValidWorkDays(BigDecimal.valueOf(reports.size())); - + // 统计涉及的项目数量 Set projectIds = reports.stream() .map(OaProjectReport::getProjectId) .filter(Objects::nonNull) .collect(Collectors.toSet()); stats.setReportProjectCount(projectIds.size()); - + return stats; } - + /** * 统计出差信息 */ private PersonalReportDTO.BusinessTripStats getBusinessTripStats(Long userId, Date startDate, Date endDate) { PersonalReportDTO.BusinessTripStats stats = new PersonalReportDTO.BusinessTripStats(); - + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .eq(OaProjectReport::getDelFlag, 0) .eq(OaProjectReport::getIsTrip, 1) // 出差标记 .eq(OaProjectReport::getUserId, userId); - + // 添加时间范围条件 if (startDate != null && endDate != null) { wrapper.between(OaProjectReport::getCreateTime, startDate, endDate); @@ -673,7 +674,7 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS // 3. 国内出差天数 = 总出差天数 - 国外出差天数 stats.setDomesticTripDays(BigDecimal.valueOf(totalTripCount - foreignTripCount)); - + // 统计出差项目数量 Set tripProjectIds = projectReportMapper.selectObjs(wrapper .select(OaProjectReport::getProjectId)) // 只查询project_id字段 @@ -682,10 +683,10 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS .map(obj -> (Long) obj) .collect(Collectors.toSet()); stats.setTripProjectCount(tripProjectIds.size()); - + return stats; } - + /** * 统计项目信息 */ @@ -765,45 +766,45 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS stats.setProjectList(projectList); return stats; } - - + + /** * 统计任务信息 */ private PersonalReportDTO.TaskStats getTaskStats(Long userId, Date startDate, Date endDate, List projectIds) { PersonalReportDTO.TaskStats stats = new PersonalReportDTO.TaskStats(); - + // 查询发放的任务(创建的任务) LambdaQueryWrapper assignedWrapper = Wrappers.lambdaQuery() .eq(SysOaTask::getCreateUserId, userId); - + if (startDate != null && endDate != null) { assignedWrapper.between(SysOaTask::getBeginTime, startDate, endDate); } if (projectIds != null && !projectIds.isEmpty()) { assignedWrapper.in(SysOaTask::getProjectId, projectIds); } - + List assignedTasks = taskMapper.selectList(assignedWrapper); - + // 查询承担的任务(被分配的任务) LambdaQueryWrapper undertakenWrapper = Wrappers.lambdaQuery() .eq(SysOaTask::getWorkerId, userId); - + if (startDate != null && endDate != null) { undertakenWrapper.between(SysOaTask::getBeginTime, startDate, endDate); } if (projectIds != null && !projectIds.isEmpty()) { undertakenWrapper.in(SysOaTask::getProjectId, projectIds); } - + List undertakenTasks = taskMapper.selectList(undertakenWrapper); - + // 统计数量 stats.setAssignedTasks(assignedTasks.size()); stats.setUndertakenTasks(undertakenTasks.size()); - + // 统计已完成任务数量(状态为完成) long completedAssigned = assignedTasks.stream().filter(task -> Long.valueOf(2).equals(task.getState())).count(); long completedUndertaken = undertakenTasks.stream().filter(task -> Long.valueOf(2).equals(task.getState())).count(); @@ -817,27 +818,27 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS long unfinishedAssigned = assignedTasks.stream().filter(task -> Long.valueOf(0).equals(task.getState())).count(); long unfinishedUndertaken = undertakenTasks.stream().filter(task -> Long.valueOf(0).equals(task.getState())).count(); stats.setUnfinishedTasks((int) (unfinishedAssigned + unfinishedUndertaken)); - + // 统计延期任务数量 long delayedAssigned = assignedTasks.stream().filter(task -> Long.valueOf(15).equals(task.getState())).count(); long delayedUndertaken = undertakenTasks.stream().filter(task -> Long.valueOf(15).equals(task.getState())).count(); stats.setDelayedTasks((int) (delayedAssigned + delayedUndertaken)); - + // 构建任务清单 List assignedTaskList = assignedTasks.stream() .map(this::buildTaskSummary) .collect(Collectors.toList()); - + List undertakenTaskList = undertakenTasks.stream() .map(this::buildTaskSummary) .collect(Collectors.toList()); - + stats.setAssignedTaskList(assignedTaskList); stats.setUndertakenTaskList(undertakenTaskList); - + return stats; } - + /** * 构建任务摘要 */ @@ -848,7 +849,7 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS summary.setState(task.getState()); summary.setIsDelayed(task.getPostponements() != null && task.getPostponements() > 0); summary.setPostponements(task.getPostponements()); - + // 获取项目名称 if (task.getProjectId() != null) { SysOaProject project = projectMapper.selectById(task.getProjectId()); @@ -856,17 +857,17 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS summary.setProjectName(project.getProjectName()); } } - + return summary; } - + /** * 统计工程异常信息(基于oa_express_question表) */ private PersonalReportDTO.EngineeringExceptionStats getExceptionStats(String nickName, Date startDate, Date endDate) { PersonalReportDTO.EngineeringExceptionStats stats = new PersonalReportDTO.EngineeringExceptionStats(); - + // 查询快递问题表,通过汇报人匹配 LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .eq(OaExpressQuestion::getReportBy, nickName) @@ -876,18 +877,18 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS if (startDate != null && endDate != null) { wrapper.between(OaExpressQuestion::getReportTime, startDate, endDate); } - + List questions = expressQuestionMapper.selectList(wrapper); - + stats.setTotalExceptions(questions.size()); - + // 统计已解决和未解决的异常 long resolvedCount = questions.stream().filter(q -> Long.valueOf(1).equals(q.getStatus())).count(); long unresolvedCount = questions.stream().filter(q -> Long.valueOf(0).equals(q.getStatus())).count(); - + stats.setResolvedExceptions((int) resolvedCount); stats.setDelayedExceptions((int) unresolvedCount); // 未解决的视为延期异常 - + // 构建异常清单 List exceptionList = questions.stream() .map(question -> { @@ -896,49 +897,49 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS summary.setDescription(question.getDescription()); summary.setStatus(Long.valueOf(1).equals(question.getStatus()) ? "已解决" : "未解决"); summary.setIsDelayed(Long.valueOf(0).equals(question.getStatus())); // 未解决的视为延期 - + // 这里可以根据expressId关联获取项目信息 summary.setProjectName(oaExpressService.queryById(question.getExpressId()).getProjectName()); - + return summary; }) .collect(Collectors.toList()); - + stats.setExceptionList(exceptionList); return stats; } - + /** * 统计关键采购任务信息(基于oa_requirements表) */ private PersonalReportDTO.ProcurementTaskStats getProcurementStats(Long userId, Date startDate, Date endDate) { PersonalReportDTO.ProcurementTaskStats stats = new PersonalReportDTO.ProcurementTaskStats(); - + // 查询需求表,通过负责人ID匹配 LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .eq(OaRequirements::getOwnerId, userId) .eq(OaRequirements::getDelFlag, 0); - + // 添加时间范围条件 if (startDate != null && endDate != null) { wrapper.between(OaRequirements::getCreateTime, startDate, endDate); } - + List requirements = requirementsMapper.selectList(wrapper); - + stats.setTotalProcurementTasks(requirements.size()); - + // 统计已完成和延期的需求 long completedCount = requirements.stream().filter(req -> Integer.valueOf(1).equals(req.getStatus())).count(); stats.setCompletedProcurementTasks((int) completedCount); - + // 计算延期需求(截止日期已过但未完成的) long delayedCount = requirements.stream() .filter(req -> Integer.valueOf(0).equals(req.getStatus())) // 未完成 .filter(req -> req.getDeadline() != null && req.getDeadline().before(new Date())) // 已过期 .count(); stats.setDelayedProcurementTasks((int) delayedCount); - + // 构建采购任务清单 List procurementList = requirements.stream() .map(req -> { @@ -946,12 +947,12 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS summary.setProcurementId(req.getRequirementId()); summary.setProcurementContent(req.getTitle()); summary.setStatus(Integer.valueOf(1).equals(req.getStatus()) ? "已完成" : "进行中"); - + // 判断是否延期 - boolean isDelayed = Integer.valueOf(0).equals(req.getStatus()) && + boolean isDelayed = Integer.valueOf(0).equals(req.getStatus()) && req.getDeadline() != null && req.getDeadline().before(new Date()); summary.setIsDelayed(isDelayed); - + // 获取项目名称 if (req.getProjectId() != null) { SysOaProject project = projectMapper.selectById(req.getProjectId()); @@ -961,11 +962,11 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS } else { summary.setProjectName("无关联项目"); } - + return summary; }) .collect(Collectors.toList()); - + stats.setProcurementTaskList(procurementList); return stats; } @@ -1035,5 +1036,40 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS // return stats; // } + @Override + @Transactional(rollbackFor = Exception.class) + public Boolean batchDelay(BatchDelayBo bo) { + if (bo.getTrackIds() == null || bo.getTrackIds().isEmpty()) { + return false; + } + + // 延期前检查:查询所有待延期的步骤,判断是否已完成 + List steps = baseMapper.selectList( + new LambdaQueryWrapper() + .in(OaProjectScheduleStep::getTrackId, bo.getTrackIds()) + ); + + // 检查是否有已完成的步骤(status=2) + String completedTrack = steps.stream() + .filter(step -> step.getStatus() != null && step.getStatus() == 2) + .map(OaProjectScheduleStep::getSecondLevelNode) + .collect(Collectors.joining(",")); + + if (!completedTrack.isEmpty()) { + throw new RuntimeException("以下步骤已完成,不允许延期:" + completedTrack); + } + + Long delayMinutes; + if ("day".equals(bo.getDelayUnit())) { + delayMinutes = bo.getDelayValue() * 24L * 60L; + } else if ("hour".equals(bo.getDelayUnit())) { + delayMinutes = bo.getDelayValue() * 60L; + } else { + return false; + } + int updated = baseMapper.batchDelayPlanEnd(bo.getTrackIds(), delayMinutes); + return updated > 0; + } + } diff --git a/ruoyi-oa/src/main/resources/mapper/oa/OaProjectScheduleStepMapper.xml b/ruoyi-oa/src/main/resources/mapper/oa/OaProjectScheduleStepMapper.xml index 8d48c40..5c34a75 100644 --- a/ruoyi-oa/src/main/resources/mapper/oa/OaProjectScheduleStepMapper.xml +++ b/ruoyi-oa/src/main/resources/mapper/oa/OaProjectScheduleStepMapper.xml @@ -306,8 +306,19 @@ WHERE schedule_id = #{scheduleId} AND del_flag = '0' AND use_flag = '1' - AND node_header IS NOT NULL - AND node_header != '' + AND (node_header IS NULL OR node_header = '') + + + + UPDATE oa_project_schedule_step + SET plan_end = DATE_ADD(plan_end, INTERVAL #{delayMinutes} MINUTE) + WHERE track_id IN + + #{id} + + AND del_flag = '0' + AND use_flag = '1' + AND status IN (0, 1) diff --git a/ruoyi-ui/src/api/oa/projectScheduleStep.js b/ruoyi-ui/src/api/oa/projectScheduleStep.js index cd336a9..e6376da 100644 --- a/ruoyi-ui/src/api/oa/projectScheduleStep.js +++ b/ruoyi-ui/src/api/oa/projectScheduleStep.js @@ -67,3 +67,12 @@ export function listMyPage (query) { params: query }) } + +// 批量延期步骤结束时间 +export function batchDelayStep (data) { + return request({ + url: '/oa/projectScheduleStep/batch-delay', + method: 'put', + data: data + }) +} diff --git a/ruoyi-ui/src/views/oa/project/pace/components/StepTable.vue b/ruoyi-ui/src/views/oa/project/pace/components/StepTable.vue index c288dd0..f43c63a 100644 --- a/ruoyi-ui/src/views/oa/project/pace/components/StepTable.vue +++ b/ruoyi-ui/src/views/oa/project/pace/components/StepTable.vue @@ -4,12 +4,15 @@ 新增 + 批量延期 + :row-config="{ 'isCurrent': true, 'isHover': true }" :column-config="{ 'isCurrent': true }" :sort-config="sortConfig" + @checkbox-change="handleCheckboxChange" @checkbox-all="handleCheckboxAll"> + @@ -166,6 +169,25 @@ 确认申请 + + + + + + + + + + 小时 + + + +
+ 取消 + 确认延期 +
+
+ @@ -229,7 +251,7 @@