feat(oa): 实现项目进度延期申请与审批功能

- 新增申请延期接口,自动填充申请人与申请时间
- 新增审批延期接口,自动填充审批人与审批时间
- 完善延期记录查询逻辑,支持多字段筛选
- 补充延期记录列表展示字段,关联步骤信息
- 优化延期申请逻辑,自动补全原计划结束时间
- 更新Mapper XML,完善延期记录联合查询SQL
- VO类新增步骤相关字段,用于前端展示
- Controller层增加申请与审批接口路由
- Service层实现申请与审批核心业务逻辑
- BO类新增申请与审批方法签名定义
This commit is contained in:
2025-12-10 17:28:55 +08:00
parent d99c8593b1
commit 6bcbdf6bca
5 changed files with 153 additions and 2 deletions

View File

@@ -98,4 +98,26 @@ public class OaProjectScheduleDelayController extends BaseController {
@PathVariable Long[] delayIds) {
return toAjax(iOaProjectScheduleDelayService.deleteWithValidByIds(Arrays.asList(delayIds), true));
}
/**
* 申请延期:申请人取当前登录昵称
* 前端需传trackId, expectEndTime, applyReason, originalEndTime(可选)
*/
@Log(title = "项目进度步骤延期记录-申请延期", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/apply")
public R<Void> apply(@Validated(AddGroup.class) @RequestBody OaProjectScheduleDelayBo bo) {
return toAjax(iOaProjectScheduleDelayService.applyDelay(bo));
}
/**
* 审批延期:审批人取当前登录昵称
* 前端需传delayId, approveResult(1通过/2驳回), approveRemark(可选)
*/
@Log(title = "项目进度步骤延期记录-审批", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PostMapping("/approve")
public R<Void> approve(@Validated(EditGroup.class) @RequestBody OaProjectScheduleDelayBo bo) {
return toAjax(iOaProjectScheduleDelayService.approveDelay(bo));
}
}

View File

@@ -103,4 +103,28 @@ public class OaProjectScheduleDelayVo {
private String remark;
/**
* 步骤名称(来自步骤表)
*/
@ExcelProperty(value = "步骤名称")
private String stepName;
/**
* 节点负责人(来自步骤表)
*/
@ExcelProperty(value = "节点负责人")
private String nodeHeader;
/**
* 步骤状态(来自步骤表)
*/
@ExcelProperty(value = "步骤状态")
private Long stepStatus;
/**
* 所属进度ID来自步骤表
*/
@ExcelProperty(value = "所属进度ID")
private Long scheduleId;
}

View File

@@ -46,4 +46,14 @@ public interface IOaProjectScheduleDelayService {
* 校验并批量删除项目进度步骤延期记录信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 申请延期:自动填充申请人与申请时间
*/
Boolean applyDelay(OaProjectScheduleDelayBo bo);
/**
* 审批延期:自动填充审批人与审批时间,并更新状态
*/
Boolean approveDelay(OaProjectScheduleDelayBo bo);
}

View File

@@ -15,10 +15,16 @@ import com.ruoyi.oa.domain.vo.OaProjectScheduleDelayVo;
import com.ruoyi.oa.domain.OaProjectScheduleDelay;
import com.ruoyi.oa.mapper.OaProjectScheduleDelayMapper;
import com.ruoyi.oa.service.IOaProjectScheduleDelayService;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.oa.mapper.OaProjectScheduleStepMapper;
import com.ruoyi.oa.domain.OaProjectScheduleStep;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.util.Date;
import java.time.LocalDateTime;
import java.time.ZoneId;
/**
* 项目进度步骤延期记录Service业务层处理
@@ -31,6 +37,7 @@ import java.util.Collection;
public class OaProjectScheduleDelayServiceImpl implements IOaProjectScheduleDelayService {
private final OaProjectScheduleDelayMapper baseMapper;
private final OaProjectScheduleStepMapper stepMapper;
/**
* 查询项目进度步骤延期记录
@@ -51,7 +58,22 @@ public class OaProjectScheduleDelayServiceImpl implements IOaProjectScheduleDela
}
private QueryWrapper<OaProjectScheduleDelay> buildQueryWrapperPlus(OaProjectScheduleDelayBo bo) {
return null;
QueryWrapper<OaProjectScheduleDelay> lqw = Wrappers.query();
// 使用别名与XML中保持一致
lqw.eq("opsd.del_flag", 0);
lqw.eq(bo.getTrackId() != null, "opsd.track_id", bo.getTrackId());
lqw.like(StringUtils.isNotBlank(bo.getApplyUserName()), "opsd.apply_user_name", bo.getApplyUserName());
lqw.eq(bo.getApplyTime() != null, "opsd.apply_time", bo.getApplyTime());
lqw.like(StringUtils.isNotBlank(bo.getApplyReason()), "opsd.apply_reason", bo.getApplyReason());
lqw.eq(bo.getOriginalEndTime() != null, "opsd.original_end_time", bo.getOriginalEndTime());
lqw.eq(bo.getExpectEndTime() != null, "opsd.expect_end_time", bo.getExpectEndTime());
lqw.like(StringUtils.isNotBlank(bo.getApproveUserName()), "opsd.approve_user_name", bo.getApproveUserName());
lqw.eq(bo.getApproveTime() != null, "opsd.approve_time", bo.getApproveTime());
lqw.eq(bo.getApproveResult() != null, "opsd.approve_result", bo.getApproveResult());
lqw.like(StringUtils.isNotBlank(bo.getApproveRemark()), "opsd.approve_remark", bo.getApproveRemark());
lqw.eq(bo.getDelayStatus() != null, "opsd.delay_status", bo.getDelayStatus());
lqw.orderByDesc("opsd.create_time");
return lqw;
}
/**
@@ -122,4 +144,51 @@ public class OaProjectScheduleDelayServiceImpl implements IOaProjectScheduleDela
}
return baseMapper.deleteBatchIds(ids) > 0;
}
@Override
public Boolean applyDelay(OaProjectScheduleDelayBo bo) {
// 自动填充申请人和时间,初始化状态
bo.setApplyUserName(LoginHelper.getNickName());
bo.setApplyTime(new Date());
if (bo.getDelayStatus() == null) {
bo.setDelayStatus(0L);
}
if (bo.getApproveResult() == null) {
bo.setApproveResult(0L);
}
// 如果未传原计划结束时间,则从步骤表补齐
if (bo.getOriginalEndTime() == null && bo.getTrackId() != null) {
OaProjectScheduleStep step = stepMapper.selectById(bo.getTrackId());
if (step != null && step.getOriginalEndTime() != null) {
LocalDateTime ldt = step.getOriginalEndTime();
bo.setOriginalEndTime(Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()));
}
}
return insertByBo(bo);
}
@Override
public Boolean approveDelay(OaProjectScheduleDelayBo bo) {
// 审批人与时间
bo.setApproveUserName(LoginHelper.getNickName());
bo.setApproveTime(new Date());
// 状态联动1 通过2 驳回
if (bo.getApproveResult() != null) {
bo.setDelayStatus(bo.getApproveResult() == 1L ? 1L : 2L);
}
boolean ok = updateByBo(bo);
// // 审批通过后,同步更新步骤的结束时间为申请的预计结束时间
// if (ok && bo.getApproveResult() != null && bo.getApproveResult() == 1L) {
// OaProjectScheduleDelay current = baseMapper.selectById(bo.getDelayId());
// if (current != null && current.getTrackId() != null && current.getExpectEndTime() != null) {
// OaProjectScheduleStep step = stepMapper.selectById(current.getTrackId());
// if (step != null) {
// LocalDateTime newEnd = LocalDateTime.ofInstant(current.getExpectEndTime().toInstant(), ZoneId.systemDefault());
// step.setEndTime(newEnd);
// stepMapper.updateById(step);
// }
// }
// }
return ok;
}
}

View File

@@ -24,7 +24,33 @@
<result property="delFlag" column="del_flag"/>
<result property="remark" column="remark"/>
</resultMap>
<select id="selectVoPagePlus" resultType="com.ruoyi.oa.domain.vo.OaProjectScheduleDelayVo"></select>
<select id="selectVoPagePlus" resultType="com.ruoyi.oa.domain.vo.OaProjectScheduleDelayVo">
SELECT opsd.delay_id AS delayId,
opsd.track_id AS trackId,
opsd.apply_user_name AS applyUserName,
opsd.apply_time AS applyTime,
opsd.apply_reason AS applyReason,
opsd.original_end_time AS originalEndTime,
opsd.expect_end_time AS expectEndTime,
opsd.approve_user_name AS approveUserName,
opsd.approve_time AS approveTime,
opsd.approve_result AS approveResult,
opsd.approve_remark AS approveRemark,
opsd.delay_status AS delayStatus,
opsd.remark AS remark,
opss.step_name AS stepName,
opss.node_header AS nodeHeader,
opss.status AS stepStatus,
opss.schedule_id AS scheduleId,
opss.tab_node AS tabNode,
opss.first_level_node AS firstLevelNode,
opss.second_level_node AS secondLevelNode,
opss.start_time AS startTime,
opss.specification AS specification
FROM oa_project_schedule_delay opsd
LEFT JOIN oa_project_schedule_step opss ON opss.track_id = opsd.track_id
${ew.customSqlSegment}
</select>
</mapper>