feat(oa): 添加项目进度步骤批量延期功能

- 在服务层接口添加批量延期方法定义
- 实现批量延期业务逻辑,支持按天或小时延期
- 添加数据库批量延期SQL映射
- 控制器增加批量延期API端点
- 前端组件添加批量延期按钮和对话框
- 集成前端批量延期API调用逻辑
- 添加批量延期数据传输对象定义
This commit is contained in:
2026-05-16 15:30:19 +08:00
parent 305d8524d1
commit 81e529a2dd
8 changed files with 226 additions and 63 deletions

View File

@@ -7,6 +7,7 @@ import java.util.concurrent.TimeUnit;
import com.ruoyi.common.core.AjaxResult; import com.ruoyi.common.core.AjaxResult;
import com.ruoyi.oa.domain.bo.BatchBo; import com.ruoyi.oa.domain.bo.BatchBo;
import com.ruoyi.oa.domain.bo.BatchDelayBo;
import com.ruoyi.oa.domain.dto.PersonalReportDTO; import com.ruoyi.oa.domain.dto.PersonalReportDTO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -117,6 +118,15 @@ public class OaProjectScheduleStepController extends BaseController {
return toAjax(iOaProjectScheduleStepService.changeBatch(batchBo)); return toAjax(iOaProjectScheduleStepService.changeBatch(batchBo));
} }
/**
* 批量延期步骤结束时间
*/
@RepeatSubmit()
@PutMapping("/batch-delay")
public R<Void> batchDelay(@Validated(EditGroup.class) @RequestBody BatchDelayBo batchDelayBo) {
return toAjax(iOaProjectScheduleStepService.batchDelay(batchDelayBo));
}
/** /**
* 删除项目进度步骤跟踪 * 删除项目进度步骤跟踪
* *

View File

@@ -0,0 +1,15 @@
package com.ruoyi.oa.domain.bo;
import lombok.Data;
import java.util.List;
@Data
public class BatchDelayBo {
private List<Long> trackIds;
private Integer delayValue;
private String delayUnit;
}

View File

@@ -55,4 +55,12 @@ public interface OaProjectScheduleStepMapper extends BaseMapperPlus<OaProjectSch
* 根据 schedule_id 批量更新节点负责人(仅更新 node_header 不为空的记录) * 根据 schedule_id 批量更新节点负责人(仅更新 node_header 不为空的记录)
*/ */
int updateNodeHeaderByScheduleId(@Param("scheduleId") Long scheduleId, @Param("nodeHeader") String nodeHeader); int updateNodeHeaderByScheduleId(@Param("scheduleId") Long scheduleId, @Param("nodeHeader") String nodeHeader);
/**
* 批量延期:根据 trackId 列表更新 plan_end 时间
* @param trackIds trackId 列表
* @param delayMinutes 延期分钟数
* @return 更新记录数
*/
int batchDelayPlanEnd(@Param("trackIds") List<Long> trackIds, @Param("delayMinutes") Long delayMinutes);
} }

View File

@@ -82,4 +82,11 @@ public interface IOaProjectScheduleStepService{
PersonalReportDTO personalReport(Long poolId, String nickName); PersonalReportDTO personalReport(Long poolId, String nickName);
TableDataInfo<OaProjectScheduleStepVo> queryPageListMyPage(OaProjectScheduleStepBo bo, PageQuery pageQuery); TableDataInfo<OaProjectScheduleStepVo> queryPageListMyPage(OaProjectScheduleStepBo bo, PageQuery pageQuery);
/**
* 批量延期步骤结束时间
* @param bo 批量延期参数
* @return 是否成功
*/
Boolean batchDelay(com.ruoyi.oa.domain.bo.BatchDelayBo bo);
} }

View File

@@ -13,6 +13,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.oa.domain.*; import com.ruoyi.oa.domain.*;
import com.ruoyi.oa.domain.bo.BatchBo; 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.NodeDTO;
import com.ruoyi.oa.domain.dto.PersonalReportDTO; import com.ruoyi.oa.domain.dto.PersonalReportDTO;
import com.ruoyi.oa.domain.vo.OaExpressVo; import com.ruoyi.oa.domain.vo.OaExpressVo;
@@ -71,15 +72,15 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
private final OaProjectScheduleStepMapper projectScheduleStepMapper; private final OaProjectScheduleStepMapper projectScheduleStepMapper;
private final ISysOaTaskService sysOaTaskService; private final ISysOaTaskService sysOaTaskService;
private final OaProjectReportMapper projectReportMapper; private final OaProjectReportMapper projectReportMapper;
private final SysOaTaskMapper taskMapper; private final SysOaTaskMapper taskMapper;
private final OaUserActiveMapper userActiveMapper; private final OaUserActiveMapper userActiveMapper;
private final OaExpressQuestionMapper expressQuestionMapper; private final OaExpressQuestionMapper expressQuestionMapper;
private final OaRequirementsMapper requirementsMapper; private final OaRequirementsMapper requirementsMapper;
private final IOaExpressService oaExpressService; private final IOaExpressService oaExpressService;
@@ -606,49 +607,49 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
return count != null ? count.intValue() : 0; return count != null ? count.intValue() : 0;
} }
/** /**
* 统计报工信息 * 统计报工信息
*/ */
private PersonalReportDTO.WorkReportStats getWorkReportStats(Long userId, Date startDate, Date endDate) { private PersonalReportDTO.WorkReportStats getWorkReportStats(Long userId, Date startDate, Date endDate) {
PersonalReportDTO.WorkReportStats stats = new PersonalReportDTO.WorkReportStats(); PersonalReportDTO.WorkReportStats stats = new PersonalReportDTO.WorkReportStats();
LambdaQueryWrapper<OaProjectReport> wrapper = Wrappers.<OaProjectReport>lambdaQuery() LambdaQueryWrapper<OaProjectReport> wrapper = Wrappers.<OaProjectReport>lambdaQuery()
.eq(OaProjectReport::getDelFlag, 0) .eq(OaProjectReport::getDelFlag, 0)
.eq(OaProjectReport::getUserId, userId); .eq(OaProjectReport::getUserId, userId);
// 添加时间范围条件 // 添加时间范围条件
if (startDate != null && endDate != null) { if (startDate != null && endDate != null) {
wrapper.between(OaProjectReport::getCreateTime, startDate, endDate); wrapper.between(OaProjectReport::getCreateTime, startDate, endDate);
} }
List<OaProjectReport> reports = projectReportMapper.selectList(wrapper); List<OaProjectReport> reports = projectReportMapper.selectList(wrapper);
// 计算报工天数(这里简化处理,实际可能需要按日期去重) // 计算报工天数(这里简化处理,实际可能需要按日期去重)
stats.setTotalWorkDays(BigDecimal.valueOf(reports.size())); stats.setTotalWorkDays(BigDecimal.valueOf(reports.size()));
stats.setValidWorkDays(BigDecimal.valueOf(reports.size())); stats.setValidWorkDays(BigDecimal.valueOf(reports.size()));
// 统计涉及的项目数量 // 统计涉及的项目数量
Set<Long> projectIds = reports.stream() Set<Long> projectIds = reports.stream()
.map(OaProjectReport::getProjectId) .map(OaProjectReport::getProjectId)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
stats.setReportProjectCount(projectIds.size()); stats.setReportProjectCount(projectIds.size());
return stats; return stats;
} }
/** /**
* 统计出差信息 * 统计出差信息
*/ */
private PersonalReportDTO.BusinessTripStats getBusinessTripStats(Long userId, Date startDate, Date endDate) { private PersonalReportDTO.BusinessTripStats getBusinessTripStats(Long userId, Date startDate, Date endDate) {
PersonalReportDTO.BusinessTripStats stats = new PersonalReportDTO.BusinessTripStats(); PersonalReportDTO.BusinessTripStats stats = new PersonalReportDTO.BusinessTripStats();
LambdaQueryWrapper<OaProjectReport> wrapper = Wrappers.<OaProjectReport>lambdaQuery() LambdaQueryWrapper<OaProjectReport> wrapper = Wrappers.<OaProjectReport>lambdaQuery()
.eq(OaProjectReport::getDelFlag, 0) .eq(OaProjectReport::getDelFlag, 0)
.eq(OaProjectReport::getIsTrip, 1) // 出差标记 .eq(OaProjectReport::getIsTrip, 1) // 出差标记
.eq(OaProjectReport::getUserId, userId); .eq(OaProjectReport::getUserId, userId);
// 添加时间范围条件 // 添加时间范围条件
if (startDate != null && endDate != null) { if (startDate != null && endDate != null) {
wrapper.between(OaProjectReport::getCreateTime, startDate, endDate); wrapper.between(OaProjectReport::getCreateTime, startDate, endDate);
@@ -673,7 +674,7 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
// 3. 国内出差天数 = 总出差天数 - 国外出差天数 // 3. 国内出差天数 = 总出差天数 - 国外出差天数
stats.setDomesticTripDays(BigDecimal.valueOf(totalTripCount - foreignTripCount)); stats.setDomesticTripDays(BigDecimal.valueOf(totalTripCount - foreignTripCount));
// 统计出差项目数量 // 统计出差项目数量
Set<Long> tripProjectIds = projectReportMapper.selectObjs(wrapper Set<Long> tripProjectIds = projectReportMapper.selectObjs(wrapper
.select(OaProjectReport::getProjectId)) // 只查询project_id字段 .select(OaProjectReport::getProjectId)) // 只查询project_id字段
@@ -682,10 +683,10 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
.map(obj -> (Long) obj) .map(obj -> (Long) obj)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
stats.setTripProjectCount(tripProjectIds.size()); stats.setTripProjectCount(tripProjectIds.size());
return stats; return stats;
} }
/** /**
* 统计项目信息 * 统计项目信息
*/ */
@@ -765,45 +766,45 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
stats.setProjectList(projectList); stats.setProjectList(projectList);
return stats; return stats;
} }
/** /**
* 统计任务信息 * 统计任务信息
*/ */
private PersonalReportDTO.TaskStats getTaskStats(Long userId, Date startDate, Date endDate, List<Long> projectIds) { private PersonalReportDTO.TaskStats getTaskStats(Long userId, Date startDate, Date endDate, List<Long> projectIds) {
PersonalReportDTO.TaskStats stats = new PersonalReportDTO.TaskStats(); PersonalReportDTO.TaskStats stats = new PersonalReportDTO.TaskStats();
// 查询发放的任务(创建的任务) // 查询发放的任务(创建的任务)
LambdaQueryWrapper<SysOaTask> assignedWrapper = Wrappers.<SysOaTask>lambdaQuery() LambdaQueryWrapper<SysOaTask> assignedWrapper = Wrappers.<SysOaTask>lambdaQuery()
.eq(SysOaTask::getCreateUserId, userId); .eq(SysOaTask::getCreateUserId, userId);
if (startDate != null && endDate != null) { if (startDate != null && endDate != null) {
assignedWrapper.between(SysOaTask::getBeginTime, startDate, endDate); assignedWrapper.between(SysOaTask::getBeginTime, startDate, endDate);
} }
if (projectIds != null && !projectIds.isEmpty()) { if (projectIds != null && !projectIds.isEmpty()) {
assignedWrapper.in(SysOaTask::getProjectId, projectIds); assignedWrapper.in(SysOaTask::getProjectId, projectIds);
} }
List<SysOaTask> assignedTasks = taskMapper.selectList(assignedWrapper); List<SysOaTask> assignedTasks = taskMapper.selectList(assignedWrapper);
// 查询承担的任务(被分配的任务) // 查询承担的任务(被分配的任务)
LambdaQueryWrapper<SysOaTask> undertakenWrapper = Wrappers.<SysOaTask>lambdaQuery() LambdaQueryWrapper<SysOaTask> undertakenWrapper = Wrappers.<SysOaTask>lambdaQuery()
.eq(SysOaTask::getWorkerId, userId); .eq(SysOaTask::getWorkerId, userId);
if (startDate != null && endDate != null) { if (startDate != null && endDate != null) {
undertakenWrapper.between(SysOaTask::getBeginTime, startDate, endDate); undertakenWrapper.between(SysOaTask::getBeginTime, startDate, endDate);
} }
if (projectIds != null && !projectIds.isEmpty()) { if (projectIds != null && !projectIds.isEmpty()) {
undertakenWrapper.in(SysOaTask::getProjectId, projectIds); undertakenWrapper.in(SysOaTask::getProjectId, projectIds);
} }
List<SysOaTask> undertakenTasks = taskMapper.selectList(undertakenWrapper); List<SysOaTask> undertakenTasks = taskMapper.selectList(undertakenWrapper);
// 统计数量 // 统计数量
stats.setAssignedTasks(assignedTasks.size()); stats.setAssignedTasks(assignedTasks.size());
stats.setUndertakenTasks(undertakenTasks.size()); stats.setUndertakenTasks(undertakenTasks.size());
// 统计已完成任务数量(状态为完成) // 统计已完成任务数量(状态为完成)
long completedAssigned = assignedTasks.stream().filter(task -> Long.valueOf(2).equals(task.getState())).count(); 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(); 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 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(); long unfinishedUndertaken = undertakenTasks.stream().filter(task -> Long.valueOf(0).equals(task.getState())).count();
stats.setUnfinishedTasks((int) (unfinishedAssigned + unfinishedUndertaken)); stats.setUnfinishedTasks((int) (unfinishedAssigned + unfinishedUndertaken));
// 统计延期任务数量 // 统计延期任务数量
long delayedAssigned = assignedTasks.stream().filter(task -> Long.valueOf(15).equals(task.getState())).count(); 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(); long delayedUndertaken = undertakenTasks.stream().filter(task -> Long.valueOf(15).equals(task.getState())).count();
stats.setDelayedTasks((int) (delayedAssigned + delayedUndertaken)); stats.setDelayedTasks((int) (delayedAssigned + delayedUndertaken));
// 构建任务清单 // 构建任务清单
List<PersonalReportDTO.TaskSummary> assignedTaskList = assignedTasks.stream() List<PersonalReportDTO.TaskSummary> assignedTaskList = assignedTasks.stream()
.map(this::buildTaskSummary) .map(this::buildTaskSummary)
.collect(Collectors.toList()); .collect(Collectors.toList());
List<PersonalReportDTO.TaskSummary> undertakenTaskList = undertakenTasks.stream() List<PersonalReportDTO.TaskSummary> undertakenTaskList = undertakenTasks.stream()
.map(this::buildTaskSummary) .map(this::buildTaskSummary)
.collect(Collectors.toList()); .collect(Collectors.toList());
stats.setAssignedTaskList(assignedTaskList); stats.setAssignedTaskList(assignedTaskList);
stats.setUndertakenTaskList(undertakenTaskList); stats.setUndertakenTaskList(undertakenTaskList);
return stats; return stats;
} }
/** /**
* 构建任务摘要 * 构建任务摘要
*/ */
@@ -848,7 +849,7 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
summary.setState(task.getState()); summary.setState(task.getState());
summary.setIsDelayed(task.getPostponements() != null && task.getPostponements() > 0); summary.setIsDelayed(task.getPostponements() != null && task.getPostponements() > 0);
summary.setPostponements(task.getPostponements()); summary.setPostponements(task.getPostponements());
// 获取项目名称 // 获取项目名称
if (task.getProjectId() != null) { if (task.getProjectId() != null) {
SysOaProject project = projectMapper.selectById(task.getProjectId()); SysOaProject project = projectMapper.selectById(task.getProjectId());
@@ -856,17 +857,17 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
summary.setProjectName(project.getProjectName()); summary.setProjectName(project.getProjectName());
} }
} }
return summary; return summary;
} }
/** /**
* 统计工程异常信息基于oa_express_question表 * 统计工程异常信息基于oa_express_question表
*/ */
private PersonalReportDTO.EngineeringExceptionStats getExceptionStats(String nickName, Date startDate, Date endDate) { private PersonalReportDTO.EngineeringExceptionStats getExceptionStats(String nickName, Date startDate, Date endDate) {
PersonalReportDTO.EngineeringExceptionStats stats = new PersonalReportDTO.EngineeringExceptionStats(); PersonalReportDTO.EngineeringExceptionStats stats = new PersonalReportDTO.EngineeringExceptionStats();
// 查询快递问题表,通过汇报人匹配 // 查询快递问题表,通过汇报人匹配
LambdaQueryWrapper<OaExpressQuestion> wrapper = Wrappers.<OaExpressQuestion>lambdaQuery() LambdaQueryWrapper<OaExpressQuestion> wrapper = Wrappers.<OaExpressQuestion>lambdaQuery()
.eq(OaExpressQuestion::getReportBy, nickName) .eq(OaExpressQuestion::getReportBy, nickName)
@@ -876,18 +877,18 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
if (startDate != null && endDate != null) { if (startDate != null && endDate != null) {
wrapper.between(OaExpressQuestion::getReportTime, startDate, endDate); wrapper.between(OaExpressQuestion::getReportTime, startDate, endDate);
} }
List<OaExpressQuestion> questions = expressQuestionMapper.selectList(wrapper); List<OaExpressQuestion> questions = expressQuestionMapper.selectList(wrapper);
stats.setTotalExceptions(questions.size()); stats.setTotalExceptions(questions.size());
// 统计已解决和未解决的异常 // 统计已解决和未解决的异常
long resolvedCount = questions.stream().filter(q -> Long.valueOf(1).equals(q.getStatus())).count(); 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(); long unresolvedCount = questions.stream().filter(q -> Long.valueOf(0).equals(q.getStatus())).count();
stats.setResolvedExceptions((int) resolvedCount); stats.setResolvedExceptions((int) resolvedCount);
stats.setDelayedExceptions((int) unresolvedCount); // 未解决的视为延期异常 stats.setDelayedExceptions((int) unresolvedCount); // 未解决的视为延期异常
// 构建异常清单 // 构建异常清单
List<PersonalReportDTO.ExceptionSummary> exceptionList = questions.stream() List<PersonalReportDTO.ExceptionSummary> exceptionList = questions.stream()
.map(question -> { .map(question -> {
@@ -896,49 +897,49 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
summary.setDescription(question.getDescription()); summary.setDescription(question.getDescription());
summary.setStatus(Long.valueOf(1).equals(question.getStatus()) ? "已解决" : "未解决"); summary.setStatus(Long.valueOf(1).equals(question.getStatus()) ? "已解决" : "未解决");
summary.setIsDelayed(Long.valueOf(0).equals(question.getStatus())); // 未解决的视为延期 summary.setIsDelayed(Long.valueOf(0).equals(question.getStatus())); // 未解决的视为延期
// 这里可以根据expressId关联获取项目信息 // 这里可以根据expressId关联获取项目信息
summary.setProjectName(oaExpressService.queryById(question.getExpressId()).getProjectName()); summary.setProjectName(oaExpressService.queryById(question.getExpressId()).getProjectName());
return summary; return summary;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
stats.setExceptionList(exceptionList); stats.setExceptionList(exceptionList);
return stats; return stats;
} }
/** /**
* 统计关键采购任务信息基于oa_requirements表 * 统计关键采购任务信息基于oa_requirements表
*/ */
private PersonalReportDTO.ProcurementTaskStats getProcurementStats(Long userId, Date startDate, Date endDate) { private PersonalReportDTO.ProcurementTaskStats getProcurementStats(Long userId, Date startDate, Date endDate) {
PersonalReportDTO.ProcurementTaskStats stats = new PersonalReportDTO.ProcurementTaskStats(); PersonalReportDTO.ProcurementTaskStats stats = new PersonalReportDTO.ProcurementTaskStats();
// 查询需求表通过负责人ID匹配 // 查询需求表通过负责人ID匹配
LambdaQueryWrapper<OaRequirements> wrapper = Wrappers.<OaRequirements>lambdaQuery() LambdaQueryWrapper<OaRequirements> wrapper = Wrappers.<OaRequirements>lambdaQuery()
.eq(OaRequirements::getOwnerId, userId) .eq(OaRequirements::getOwnerId, userId)
.eq(OaRequirements::getDelFlag, 0); .eq(OaRequirements::getDelFlag, 0);
// 添加时间范围条件 // 添加时间范围条件
if (startDate != null && endDate != null) { if (startDate != null && endDate != null) {
wrapper.between(OaRequirements::getCreateTime, startDate, endDate); wrapper.between(OaRequirements::getCreateTime, startDate, endDate);
} }
List<OaRequirements> requirements = requirementsMapper.selectList(wrapper); List<OaRequirements> requirements = requirementsMapper.selectList(wrapper);
stats.setTotalProcurementTasks(requirements.size()); stats.setTotalProcurementTasks(requirements.size());
// 统计已完成和延期的需求 // 统计已完成和延期的需求
long completedCount = requirements.stream().filter(req -> Integer.valueOf(1).equals(req.getStatus())).count(); long completedCount = requirements.stream().filter(req -> Integer.valueOf(1).equals(req.getStatus())).count();
stats.setCompletedProcurementTasks((int) completedCount); stats.setCompletedProcurementTasks((int) completedCount);
// 计算延期需求(截止日期已过但未完成的) // 计算延期需求(截止日期已过但未完成的)
long delayedCount = requirements.stream() long delayedCount = requirements.stream()
.filter(req -> Integer.valueOf(0).equals(req.getStatus())) // 未完成 .filter(req -> Integer.valueOf(0).equals(req.getStatus())) // 未完成
.filter(req -> req.getDeadline() != null && req.getDeadline().before(new Date())) // 已过期 .filter(req -> req.getDeadline() != null && req.getDeadline().before(new Date())) // 已过期
.count(); .count();
stats.setDelayedProcurementTasks((int) delayedCount); stats.setDelayedProcurementTasks((int) delayedCount);
// 构建采购任务清单 // 构建采购任务清单
List<PersonalReportDTO.ProcurementTaskSummary> procurementList = requirements.stream() List<PersonalReportDTO.ProcurementTaskSummary> procurementList = requirements.stream()
.map(req -> { .map(req -> {
@@ -946,12 +947,12 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
summary.setProcurementId(req.getRequirementId()); summary.setProcurementId(req.getRequirementId());
summary.setProcurementContent(req.getTitle()); summary.setProcurementContent(req.getTitle());
summary.setStatus(Integer.valueOf(1).equals(req.getStatus()) ? "已完成" : "进行中"); 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()); req.getDeadline() != null && req.getDeadline().before(new Date());
summary.setIsDelayed(isDelayed); summary.setIsDelayed(isDelayed);
// 获取项目名称 // 获取项目名称
if (req.getProjectId() != null) { if (req.getProjectId() != null) {
SysOaProject project = projectMapper.selectById(req.getProjectId()); SysOaProject project = projectMapper.selectById(req.getProjectId());
@@ -961,11 +962,11 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
} else { } else {
summary.setProjectName("无关联项目"); summary.setProjectName("无关联项目");
} }
return summary; return summary;
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());
stats.setProcurementTaskList(procurementList); stats.setProcurementTaskList(procurementList);
return stats; return stats;
} }
@@ -1035,5 +1036,40 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS
// return stats; // return stats;
// } // }
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchDelay(BatchDelayBo bo) {
if (bo.getTrackIds() == null || bo.getTrackIds().isEmpty()) {
return false;
}
// 延期前检查:查询所有待延期的步骤,判断是否已完成
List<OaProjectScheduleStep> steps = baseMapper.selectList(
new LambdaQueryWrapper<OaProjectScheduleStep>()
.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;
}
} }

View File

@@ -306,8 +306,19 @@
WHERE schedule_id = #{scheduleId} WHERE schedule_id = #{scheduleId}
AND del_flag = '0' AND del_flag = '0'
AND use_flag = '1' AND use_flag = '1'
AND node_header IS NOT NULL AND (node_header IS NULL OR node_header = '')
AND node_header != '' </update>
<update id="batchDelayPlanEnd">
UPDATE oa_project_schedule_step
SET plan_end = DATE_ADD(plan_end, INTERVAL #{delayMinutes} MINUTE)
WHERE track_id IN
<foreach collection="trackIds" item="id" open="(" separator="," close=")">
#{id}
</foreach>
AND del_flag = '0'
AND use_flag = '1'
AND status IN (0, 1)
</update> </update>
</mapper> </mapper>

View File

@@ -67,3 +67,12 @@ export function listMyPage (query) {
params: query params: query
}) })
} }
// 批量延期步骤结束时间
export function batchDelayStep (data) {
return request({
url: '/oa/projectScheduleStep/batch-delay',
method: 'put',
data: data
})
}

View File

@@ -4,12 +4,15 @@
<el-col> <el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="addInnerData">新增</el-button> <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="addInnerData">新增</el-button>
<el-button type="warning" plain icon="el-icon-time" size="mini" @click="handleBatchDelay" :disabled="selectedRows.length === 0">批量延期</el-button>
<slot name="extra-buttons"></slot> <slot name="extra-buttons"></slot>
</el-col> </el-col>
</el-col> </el-col>
</el-row> </el-row>
<vxe-table size="mini" height="500" ref="tableRef" border show-overflow :edit-config="editConfig" :data="innerData" <vxe-table size="mini" height="500" ref="tableRef" border show-overflow :edit-config="editConfig" :data="innerData"
:row-config="{ 'isCurrent': true }" :column-config="{ 'isCurrent': true }" :sort-config="sortConfig"> :row-config="{ 'isCurrent': true, 'isHover': true }" :column-config="{ 'isCurrent': true }" :sort-config="sortConfig"
@checkbox-change="handleCheckboxChange" @checkbox-all="handleCheckboxAll">
<vxe-column type="checkbox" width="50"></vxe-column>
<vxe-column field="sortNum" title="顺序" :edit-render="{ name: 'input' }" width="70" sortable></vxe-column> <vxe-column field="sortNum" title="顺序" :edit-render="{ name: 'input' }" width="70" sortable></vxe-column>
<vxe-column field="secondLevelNode" title="步骤名称" :edit-render="{ name: 'input' }"></vxe-column> <vxe-column field="secondLevelNode" title="步骤名称" :edit-render="{ name: 'input' }"></vxe-column>
<vxe-column field="tabNode" title="进度类别" :edit-render="{ name: 'input' }"></vxe-column> <vxe-column field="tabNode" title="进度类别" :edit-render="{ name: 'input' }"></vxe-column>
@@ -166,6 +169,25 @@
<el-button type="primary" @click="submitApplyDelay">确认申请</el-button> <el-button type="primary" @click="submitApplyDelay">确认申请</el-button>
</el-dialog> </el-dialog>
<!-- 批量延期对话框 -->
<el-dialog :visible.sync="dialogBatchDelayVisible" title="批量延期" append-to-body width="400px">
<el-form :model="dialogBatchDelayForm" label-width="100px">
<el-form-item label="延期数值">
<el-input-number v-model="dialogBatchDelayForm.delayValue" :min="1" :max="365" style="width: 100%;"></el-input-number>
</el-form-item>
<el-form-item label="延期单位">
<el-radio-group v-model="dialogBatchDelayForm.delayUnit">
<el-radio label="day"></el-radio>
<el-radio label="hour">小时</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<div slot="footer">
<el-button @click="dialogBatchDelayVisible = false">取消</el-button>
<el-button type="primary" @click="submitBatchDelay">确认延期</el-button>
</div>
</el-dialog>
<el-dialog :visible.sync="addDialogVisible" title="新增进度" append-to-body> <el-dialog :visible.sync="addDialogVisible" title="新增进度" append-to-body>
<el-form :model="dialogAddForm" ref="formRef" label-width="120px"> <el-form :model="dialogAddForm" ref="formRef" label-width="120px">
<el-form-item label="进度类别" prop="tabNode"> <el-form-item label="进度类别" prop="tabNode">
@@ -229,7 +251,7 @@
<script> <script>
import { addFileOperationRecord } from '@/api/oa/fileOperationRecord'; import { addFileOperationRecord } from '@/api/oa/fileOperationRecord';
import { applyProjectScheduleDelay } from "@/api/oa/projectScheduleDelay"; import { applyProjectScheduleDelay } from "@/api/oa/projectScheduleDelay";
import { updateProjectScheduleStep } from "@/api/oa/projectScheduleStep"; import { updateProjectScheduleStep, batchDelayStep } from "@/api/oa/projectScheduleStep";
import { listSupplier } from "@/api/oa/supplier"; import { listSupplier } from "@/api/oa/supplier";
import { listUser } from "@/api/system/user"; import { listUser } from "@/api/system/user";
@@ -280,6 +302,13 @@ export default {
delayTo: '', delayTo: '',
applyReason: '', applyReason: '',
}, },
// 批量延期对话框控制
dialogBatchDelayVisible: false,
dialogBatchDelayForm: {
delayValue: 1,
delayUnit: 'day',
},
selectedRows: [],
dialogMoreVisible: false, dialogMoreVisible: false,
// 新增对话框控制 // 新增对话框控制
addDialogVisible: false, addDialogVisible: false,
@@ -699,6 +728,44 @@ export default {
handleDelete (row) { handleDelete (row) {
this.$emit('delete', row.trackId); this.$emit('delete', row.trackId);
}, },
handleCheckboxChange ({ records }) {
this.selectedRows = records;
},
handleCheckboxAll ({ records }) {
this.selectedRows = records;
},
handleBatchDelay () {
if (this.selectedRows.length === 0) {
this.$modal.msgWarning("请先选择要延期的步骤");
return;
}
this.dialogBatchDelayForm = {
delayValue: 1,
delayUnit: 'day',
};
this.dialogBatchDelayVisible = true;
},
submitBatchDelay () {
const trackIds = this.selectedRows.map(row => row.trackId).filter(id => id);
if (trackIds.length === 0) {
this.$modal.msgWarning("请选择有效的步骤");
return;
}
this.buttonLoading = true;
batchDelayStep({
trackIds: trackIds,
delayValue: this.dialogBatchDelayForm.delayValue,
delayUnit: this.dialogBatchDelayForm.delayUnit,
}).then(res => {
this.$modal.msgSuccess("批量延期成功");
this.dialogBatchDelayVisible = false;
this.$emit("refresh", this.innerData);
}).catch(() => {
this.$modal.msgError("批量延期失败");
}).finally(() => {
this.buttonLoading = false;
});
},
}, },
} }
</script> </script>