diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectOperationLogController.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectOperationLogController.java new file mode 100644 index 0000000..1185e64 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/controller/OaProjectOperationLogController.java @@ -0,0 +1,57 @@ +package com.ruoyi.oa.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.oa.domain.bo.OaProjectOperationLogBo; +import com.ruoyi.oa.domain.vo.OaProjectOperationLogVo; +import com.ruoyi.oa.service.IOaProjectOperationLogService; +import lombok.RequiredArgsConstructor; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.NotNull; +import java.util.List; + +/** + * 项目操作历史记录 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/oa/projectOperationLog") +public class OaProjectOperationLogController extends BaseController { + + private final IOaProjectOperationLogService iOaProjectOperationLogService; + + /** + * 分页查询操作历史 + */ + @GetMapping("/list") + public TableDataInfo list(OaProjectOperationLogBo bo, PageQuery pageQuery) { + return iOaProjectOperationLogService.queryPageList(bo, pageQuery); + } + + /** + * 导出操作历史 + */ + @Log(title = "项目操作历史", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(OaProjectOperationLogBo bo, HttpServletResponse response) { + List list = iOaProjectOperationLogService.queryList(bo); + ExcelUtil.exportExcel(list, "项目操作历史", OaProjectOperationLogVo.class, response); + } + + /** + * 查询单条详情 + */ + @GetMapping("/{logId}") + public R getInfo(@NotNull(message = "主键不能为空") @PathVariable Long logId) { + return R.ok(iOaProjectOperationLogService.queryById(logId)); + } +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaProjectOperationLog.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaProjectOperationLog.java new file mode 100644 index 0000000..708eda9 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/OaProjectOperationLog.java @@ -0,0 +1,61 @@ +package com.ruoyi.oa.domain; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableLogic; +import com.baomidou.mybatisplus.annotation.TableName; +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 项目操作历史记录对象 oa_project_operation_log + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("oa_project_operation_log") +public class OaProjectOperationLog extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @TableId(value = "log_id") + private Long logId; + + /** 所属项目ID */ + private Long projectId; + + /** + * 操作对象类型: 1-项目进度 2-进度步骤 3-任务 4-延期申请 + */ + private Integer targetType; + + /** 操作对象ID */ + private Long targetId; + + /** 操作对象名称 */ + private String targetName; + + /** + * 操作类型: 1-新增 2-修改 3-删除 4-状态变更 5-完成 6-申请延期 7-审批通过 8-审批驳回 + */ + private Integer operationType; + + /** 操作描述 */ + private String operationDesc; + + /** 操作前快照(JSON) */ + private String beforeValue; + + /** 操作后快照(JSON) */ + private String afterValue; + + /** 操作人昵称 */ + private String operator; + + /** 操作时间 */ + private Date operateTime; + + @TableLogic(value = "0", delval = "1") + private Integer delFlag; +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaProjectOperationLogBo.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaProjectOperationLogBo.java new file mode 100644 index 0000000..2dc48aa --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/bo/OaProjectOperationLogBo.java @@ -0,0 +1,51 @@ +package com.ruoyi.oa.domain.bo; + +import com.ruoyi.common.core.domain.BaseEntity; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.Date; + +/** + * 项目操作历史记录 业务对象 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class OaProjectOperationLogBo extends BaseEntity { + + /** 主键 */ + private Long logId; + + /** 所属项目ID */ + private Long projectId; + + /** 操作对象类型: 1-项目进度 2-进度步骤 3-任务 4-延期申请 */ + private Integer targetType; + + /** 操作对象ID */ + private Long targetId; + + /** 操作对象名称 */ + private String targetName; + + /** 操作类型: 1-新增 2-修改 3-删除 4-状态变更 5-完成 6-申请延期 7-审批通过 8-审批驳回 */ + private Integer operationType; + + /** 操作描述 */ + private String operationDesc; + + /** 操作前快照(JSON) */ + private String beforeValue; + + /** 操作后快照(JSON) */ + private String afterValue; + + /** 操作人昵称 */ + private String operator; + + /** 操作时间范围-开始 */ + private Date operateTimeStart; + + /** 操作时间范围-结束 */ + private Date operateTimeEnd; +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaProjectOperationLogVo.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaProjectOperationLogVo.java new file mode 100644 index 0000000..dc137a9 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/domain/vo/OaProjectOperationLogVo.java @@ -0,0 +1,59 @@ +package com.ruoyi.oa.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.util.Date; + +/** + * 项目操作历史记录 视图对象 + */ +@Data +@ExcelIgnoreUnannotated +public class OaProjectOperationLogVo { + + private static final long serialVersionUID = 1L; + + @ExcelProperty(value = "主键ID") + private Long logId; + + @ExcelProperty(value = "所属项目ID") + private Long projectId; + + @ExcelProperty(value = "项目名称") + private String projectName; + + @ExcelProperty(value = "项目编号") + private String projectNum; + + /** 操作对象类型: 1-项目进度 2-进度步骤 3-任务 4-延期申请 */ + @ExcelProperty(value = "操作对象类型") + private Integer targetType; + + @ExcelProperty(value = "操作对象ID") + private Long targetId; + + @ExcelProperty(value = "操作对象名称") + private String targetName; + + /** 操作类型: 1-新增 2-修改 3-删除 4-状态变更 5-完成 6-申请延期 7-审批通过 8-审批驳回 */ + @ExcelProperty(value = "操作类型") + private Integer operationType; + + @ExcelProperty(value = "操作描述") + private String operationDesc; + + private String beforeValue; + + private String afterValue; + + @ExcelProperty(value = "操作人") + private String operator; + + @ExcelProperty(value = "操作时间") + private Date operateTime; + + @ExcelProperty(value = "创建时间") + private Date createTime; +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectOperationLogMapper.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectOperationLogMapper.java new file mode 100644 index 0000000..4140a5f --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/mapper/OaProjectOperationLogMapper.java @@ -0,0 +1,17 @@ +package com.ruoyi.oa.mapper; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Constants; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.common.core.mapper.BaseMapperPlus; +import com.ruoyi.oa.domain.OaProjectOperationLog; +import com.ruoyi.oa.domain.vo.OaProjectOperationLogVo; +import org.apache.ibatis.annotations.Param; + +/** + * 项目操作历史记录 Mapper 接口 + */ +public interface OaProjectOperationLogMapper extends BaseMapperPlus { + + Page selectVoPagePlus(Page page, @Param(Constants.WRAPPER) QueryWrapper qw); +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectOperationLogService.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectOperationLogService.java new file mode 100644 index 0000000..55bc6cd --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/IOaProjectOperationLogService.java @@ -0,0 +1,39 @@ +package com.ruoyi.oa.service; + +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.oa.domain.bo.OaProjectOperationLogBo; +import com.ruoyi.oa.domain.vo.OaProjectOperationLogVo; + +import java.util.List; + +/** + * 项目操作历史记录 Service 接口 + */ +public interface IOaProjectOperationLogService { + + /** 分页查询 */ + TableDataInfo queryPageList(OaProjectOperationLogBo bo, PageQuery pageQuery); + + /** 列表查询(不分页) */ + List queryList(OaProjectOperationLogBo bo); + + /** 详情 */ + OaProjectOperationLogVo queryById(Long logId); + + /** + * 记录一条操作日志(供其他 Service 内部调用) + * + * @param projectId 项目ID + * @param targetType 对象类型: 1-进度 2-步骤 3-任务 4-延期 + * @param targetId 对象ID + * @param targetName 对象名称 + * @param operationType 操作类型: 1-新增 2-修改 3-删除 4-状态变更 5-完成 6-申请延期 7-审批通过 8-审批驳回 + * @param operationDesc 可读描述 + * @param beforeValue 操作前JSON(可为 null) + * @param afterValue 操作后JSON(可为 null) + */ + void recordLog(Long projectId, Integer targetType, Long targetId, String targetName, + Integer operationType, String operationDesc, + String beforeValue, String afterValue); +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectOperationLogServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectOperationLogServiceImpl.java new file mode 100644 index 0000000..a4d4193 --- /dev/null +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectOperationLogServiceImpl.java @@ -0,0 +1,106 @@ +package com.ruoyi.oa.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.helper.LoginHelper; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.oa.domain.OaProjectOperationLog; +import com.ruoyi.oa.domain.bo.OaProjectOperationLogBo; +import com.ruoyi.oa.domain.vo.OaProjectOperationLogVo; +import com.ruoyi.oa.mapper.OaProjectOperationLogMapper; +import com.ruoyi.oa.service.IOaProjectOperationLogService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; + +import java.util.Date; +import java.util.List; + +/** + * 项目操作历史记录 Service 实现 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class OaProjectOperationLogServiceImpl implements IOaProjectOperationLogService { + + private final OaProjectOperationLogMapper baseMapper; + + @Override + public TableDataInfo queryPageList(OaProjectOperationLogBo bo, PageQuery pageQuery) { + QueryWrapper qw = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPagePlus(pageQuery.build(), qw); + return TableDataInfo.build(result); + } + + @Override + public List queryList(OaProjectOperationLogBo bo) { + QueryWrapper qw = buildQueryWrapper(bo); + return baseMapper.selectVoList(qw); + } + + @Override + public OaProjectOperationLogVo queryById(Long logId) { + return baseMapper.selectVoById(logId); + } + + @Override + public void recordLog(Long projectId, Integer targetType, Long targetId, String targetName, + Integer operationType, String operationDesc, + String beforeValue, String afterValue) { + try { + OaProjectOperationLog entity = new OaProjectOperationLog(); + entity.setProjectId(projectId); + entity.setTargetType(targetType); + entity.setTargetId(targetId); + entity.setTargetName(StringUtils.defaultIfBlank(targetName, "")); + entity.setOperationType(operationType); + entity.setOperationDesc(StringUtils.defaultIfBlank(operationDesc, "")); + entity.setBeforeValue(beforeValue); + entity.setAfterValue(afterValue); + String operator = tryGetNickName(); + entity.setOperator(operator); + entity.setOperateTime(new Date()); + // @Async 线程中 Sa-Token 上下文已失效,MP 自动填充取不到登录用户,手动赋值 + entity.setCreateBy(operator); + entity.setUpdateBy(operator); + entity.setCreateTime(new Date()); + entity.setUpdateTime(new Date()); + baseMapper.insert(entity); + } catch (Exception e) { + log.error("记录操作日志失败: projectId={}, targetType={}, targetId={}", projectId, targetType, targetId, e); + } + } + + private String tryGetNickName() { + try { + String name = LoginHelper.getNickName(); + return StringUtils.isNotBlank(name) ? name : "system"; + } catch (Exception e) { + return "system"; + } + } + + private QueryWrapper buildQueryWrapper(OaProjectOperationLogBo bo) { + QueryWrapper qw = Wrappers.query(); + qw.eq("l.del_flag", 0); + qw.eq(bo.getProjectId() != null, "l.project_id", bo.getProjectId()); + qw.eq(bo.getTargetType() != null, "l.target_type", bo.getTargetType()); + qw.eq(bo.getTargetId() != null, "l.target_id", bo.getTargetId()); + qw.eq(bo.getOperationType() != null, "l.operation_type", bo.getOperationType()); + qw.like(StringUtils.isNotBlank(bo.getOperator()), "l.operator", bo.getOperator()); + qw.like(StringUtils.isNotBlank(bo.getOperationDesc()), "l.operation_desc", bo.getOperationDesc()); + if (bo.getOperateTimeStart() != null) { + qw.ge("l.operate_time", bo.getOperateTimeStart()); + } + if (bo.getOperateTimeEnd() != null) { + qw.le("l.operate_time", bo.getOperateTimeEnd()); + } + qw.orderByDesc("l.operate_time"); + return qw; + } +} diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleDelayServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleDelayServiceImpl.java index 9ea904a..ef02e15 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleDelayServiceImpl.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleDelayServiceImpl.java @@ -14,9 +14,12 @@ import com.ruoyi.oa.domain.bo.OaProjectScheduleDelayBo; import com.ruoyi.oa.domain.vo.OaProjectScheduleDelayVo; import com.ruoyi.oa.domain.OaProjectScheduleDelay; import com.ruoyi.oa.mapper.OaProjectScheduleDelayMapper; +import com.ruoyi.oa.service.IOaProjectOperationLogService; import com.ruoyi.oa.service.IOaProjectScheduleDelayService; import com.ruoyi.common.helper.LoginHelper; +import com.ruoyi.oa.mapper.OaProjectScheduleMapper; import com.ruoyi.oa.mapper.OaProjectScheduleStepMapper; +import com.ruoyi.oa.domain.OaProjectSchedule; import com.ruoyi.oa.domain.OaProjectScheduleStep; import java.util.List; @@ -38,6 +41,8 @@ public class OaProjectScheduleDelayServiceImpl implements IOaProjectScheduleDela private final OaProjectScheduleDelayMapper baseMapper; private final OaProjectScheduleStepMapper stepMapper; + private final OaProjectScheduleMapper scheduleMapper; + private final IOaProjectOperationLogService operationLogService; /** * 查询项目进度步骤延期记录 @@ -118,6 +123,9 @@ public class OaProjectScheduleDelayServiceImpl implements IOaProjectScheduleDela boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setDelayId(add.getDelayId()); + Long projectId = resolveProjectIdFromTrack(bo.getTrackId()); + operationLogService.recordLog(projectId, 4, add.getDelayId(), "延期申请", + 1, "新增延期申请", null, null); } return flag; } @@ -169,7 +177,13 @@ public class OaProjectScheduleDelayServiceImpl implements IOaProjectScheduleDela bo.setOriginalEndTime(Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant())); } } - return insertByBo(bo); + boolean ok = insertByBo(bo); + if (ok) { + Long projectId = resolveProjectIdFromTrack(bo.getTrackId()); + operationLogService.recordLog(projectId, 4, bo.getDelayId(), "延期申请", + 6, "申请延期,原因: " + bo.getApplyReason(), null, null); + } + return ok; } @Override @@ -182,6 +196,13 @@ public class OaProjectScheduleDelayServiceImpl implements IOaProjectScheduleDela bo.setDelayStatus(bo.getApproveResult() == 1L ? 1L : 2L); } boolean ok = updateByBo(bo); + if (ok) { + OaProjectScheduleDelay current = baseMapper.selectById(bo.getDelayId()); + Long projectId = current != null ? resolveProjectIdFromTrack(current.getTrackId()) : null; + int opType = (bo.getApproveResult() != null && bo.getApproveResult() == 1L) ? 7 : 8; + String desc = opType == 7 ? "审批通过延期申请" : "审批驳回延期申请"; + operationLogService.recordLog(projectId, 4, bo.getDelayId(), "延期申请", opType, desc, null, null); + } // // 审批通过后,同步更新步骤的结束时间为申请的预计结束时间 // if (ok && bo.getApproveResult() != null && bo.getApproveResult() == 1L) { // OaProjectScheduleDelay current = baseMapper.selectById(bo.getDelayId()); @@ -196,4 +217,12 @@ public class OaProjectScheduleDelayServiceImpl implements IOaProjectScheduleDela // } return ok; } + + private Long resolveProjectIdFromTrack(Long trackId) { + if (trackId == null) return null; + OaProjectScheduleStep step = stepMapper.selectById(trackId); + if (step == null || step.getScheduleId() == null) return null; + OaProjectSchedule schedule = scheduleMapper.selectById(step.getScheduleId()); + return schedule == null ? null : schedule.getProjectId(); + } } diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleServiceImpl.java index d00f90d..4c6f80c 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleServiceImpl.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/OaProjectScheduleServiceImpl.java @@ -28,7 +28,9 @@ import com.ruoyi.oa.domain.bo.OaProjectScheduleBo; import com.ruoyi.oa.domain.vo.OaProjectScheduleVo; import com.ruoyi.oa.domain.OaProjectSchedule; import com.ruoyi.oa.mapper.OaProjectScheduleMapper; +import com.ruoyi.oa.service.IOaProjectOperationLogService; import com.ruoyi.oa.service.IOaProjectScheduleService; +import org.springframework.context.annotation.Lazy; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; @@ -55,6 +57,10 @@ public class OaProjectScheduleServiceImpl implements IOaProjectScheduleService { private final IOaProjectScheduleStepService projectScheduleStepService; + @Lazy + @Resource + private IOaProjectOperationLogService operationLogService; + /** * 查询项目进度 */ @@ -188,6 +194,10 @@ public class OaProjectScheduleServiceImpl implements IOaProjectScheduleService { } projectScheduleStepService.insertByBo(step); } + if (flag) { + operationLogService.recordLog(bo.getProjectId(), 1, add.getScheduleId(), "项目进度", + 1, "绑定项目进度(模板: " + bo.getTemplateId() + ")", null, null); + } return flag; } @@ -210,7 +220,12 @@ public class OaProjectScheduleServiceImpl implements IOaProjectScheduleService { public Boolean updateByBo(OaProjectScheduleBo bo) { OaProjectSchedule update = BeanUtil.toBean(bo, OaProjectSchedule.class); validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; + boolean ok = baseMapper.updateById(update) > 0; + if (ok) { + operationLogService.recordLog(bo.getProjectId(), 1, bo.getScheduleId(), "项目进度", + 2, "修改项目进度", null, null); + } + return ok; } /** @@ -228,7 +243,14 @@ public class OaProjectScheduleServiceImpl implements IOaProjectScheduleService { if(isValid){ //TODO 做一些业务上的校验,判断是否需要校验 } - //同时在删除对应的项目进度步骤 + // 删前先记录日志(拿到 projectId) + for (Long scheduleId : ids) { + OaProjectScheduleVo vo = queryById(scheduleId); + if (vo != null) { + operationLogService.recordLog(vo.getProjectId(), 1, scheduleId, "项目进度", + 3, "删除项目进度", null, null); + } + } projectScheduleStepService.deleteByScheduleIds(ids, false); return baseMapper.deleteBatchIds(ids) > 0; } @@ -281,6 +303,8 @@ public class OaProjectScheduleServiceImpl implements IOaProjectScheduleService { throw new RuntimeException("未知的模板类型,无法插入项目进度节点"); } Log.info("项目进度节点批量插入成功"); + operationLogService.recordLog(bo.getProjectId(), 1, scheduleId, "项目进度", + 1, "绑定项目进度(模板类型: " + bo.getTemplateType() + ")", null, null); return true; } } 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 28e0572..a95a215 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 @@ -23,8 +23,12 @@ import com.ruoyi.oa.mapper.OaUserActiveMapper; import com.ruoyi.oa.mapper.OaExpressQuestionMapper; import com.ruoyi.oa.mapper.OaRequirementsMapper; import com.ruoyi.oa.service.IOaExpressService; +import com.ruoyi.oa.service.IOaProjectOperationLogService; import com.ruoyi.oa.service.ISysOaTaskService; import com.ruoyi.system.mapper.SysUserMapper; +import org.springframework.context.annotation.Lazy; + +import javax.annotation.Resource; import java.math.BigDecimal; import java.sql.Timestamp; @@ -80,6 +84,10 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS private final IOaExpressService oaExpressService; + @Lazy + @Resource + private IOaProjectOperationLogService operationLogService; + /** @@ -174,6 +182,10 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS boolean flag = baseMapper.insert(add) > 0; if (flag) { bo.setTrackId(add.getTrackId()); + Long projectId = resolveProjectId(bo.getScheduleId()); + String nodeName = buildNodeName(bo.getTabNode(), bo.getFirstLevelNode(), bo.getSecondLevelNode()); + operationLogService.recordLog(projectId, 2, add.getTrackId(), nodeName, + 1, "新增步骤节点: " + nodeName, null, null); } return flag; } @@ -206,7 +218,42 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS } // 4. 执行更新 - return baseMapper.updateById(update) > 0; + boolean ok = baseMapper.updateById(update) > 0; + if (ok) { + Long projectId = resolveProjectId(original.getScheduleId()); + String nodeName = buildNodeName(original.getTabNode(), original.getFirstLevelNode(), original.getSecondLevelNode()); + // 判断是否为状态变更 + Integer opType = (bo.getStatus() != null && !bo.getStatus().equals(original.getStatus())) ? 4 : 2; + String desc = opType == 4 + ? "步骤状态变更为: " + resolveStepStatus(bo.getStatus()) + : "修改步骤节点: " + nodeName; + operationLogService.recordLog(projectId, 2, trackId, nodeName, opType, desc, null, null); + } + return ok; + } + + private Long resolveProjectId(Long scheduleId) { + if (scheduleId == null) return null; + OaProjectSchedule schedule = scheduleMapper.selectById(scheduleId); + return schedule == null ? null : schedule.getProjectId(); + } + + private static String buildNodeName(String tabNode, String firstLevel, String secondLevel) { + StringBuilder sb = new StringBuilder(); + if (StringUtils.isNotBlank(tabNode)) sb.append(tabNode); + if (StringUtils.isNotBlank(firstLevel)) sb.append("/").append(firstLevel); + if (StringUtils.isNotBlank(secondLevel)) sb.append("/").append(secondLevel); + return sb.length() > 0 ? sb.toString() : "步骤节点"; + } + + private static String resolveStepStatus(Long status) { + if (status == null) return "未知"; + switch (status.intValue()) { + case 0: return "进行中"; + case 1: return "待验收"; + case 2: return "已完成"; + default: return String.valueOf(status); + } } @@ -225,6 +272,15 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS if(isValid){ //TODO 做一些业务上的校验,判断是否需要校验 } + for (Long trackId : ids) { + OaProjectScheduleStep step = baseMapper.selectById(trackId); + if (step != null) { + Long projectId = resolveProjectId(step.getScheduleId()); + String nodeName = buildNodeName(step.getTabNode(), step.getFirstLevelNode(), step.getSecondLevelNode()); + operationLogService.recordLog(projectId, 2, trackId, nodeName, + 3, "删除步骤节点: " + nodeName, null, null); + } + } return baseMapper.deleteBatchIds(ids) > 0; } @Override @@ -333,6 +389,9 @@ public class OaProjectScheduleStepServiceImpl implements IOaProjectScheduleStepS // 批量插入数据库 baseMapper.saveBatch(entities); + Long projectId = resolveProjectId(scheduleId); + operationLogService.recordLog(projectId, 2, scheduleId, "进度步骤", + 1, "批量新增步骤节点,共 " + entities.size() + " 条", null, null); } //查询进度负责人负责了多少进度以及完成度是多少0进行中 1待验收 2已完成 是否延期用比较end_time和original_end_time的大小 //如果前者大于后者则表示延期 diff --git a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/SysOaTaskServiceImpl.java b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/SysOaTaskServiceImpl.java index 8835b78..4b23fd8 100644 --- a/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/SysOaTaskServiceImpl.java +++ b/ruoyi-oa/src/main/java/com/ruoyi/oa/service/impl/SysOaTaskServiceImpl.java @@ -22,7 +22,11 @@ import org.springframework.stereotype.Service; import com.ruoyi.oa.domain.bo.SysOaTaskBo; import com.ruoyi.oa.domain.vo.SysOaTaskVo; import com.ruoyi.oa.mapper.SysOaTaskMapper; +import com.ruoyi.oa.service.IOaProjectOperationLogService; import com.ruoyi.oa.service.ISysOaTaskService; +import org.springframework.context.annotation.Lazy; + +import javax.annotation.Resource; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -52,6 +56,10 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService { private final SysUserMapper userMapper; + @Lazy + @Resource + private IOaProjectOperationLogService operationLogService; + /** * 查询任务管理 */ @@ -206,6 +214,10 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService { add.setOriginFinishTime(add.getFinishTime()); add.setWorkerId(workerId); flag = baseMapper.insert(add) > 0; + if (flag) { + operationLogService.recordLog(add.getProjectId(), 3, add.getTaskId(), + add.getTaskTitle(), 1, "新增任务: " + add.getTaskTitle(), null, null); + } // 判断是否为报工模式 if (bo.getStatus()==1L){ // 这里新增item数据,为单个条目 @@ -227,11 +239,24 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService { */ @Override public Boolean updateByBo(SysOaTaskBo bo) { - // 先删除所有的bo然后再次进行新增操作 SysOaTask update = BeanUtil.toBean(bo, SysOaTask.class); - // 再进行重新新增 validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; + boolean ok = baseMapper.updateById(update) > 0; + if (ok) { + // BO 可能只带了 taskId(如内部调用),从 DB 补全 projectId 和 title + Long projectId = bo.getProjectId(); + String taskTitle = bo.getTaskTitle(); + if (projectId == null || taskTitle == null) { + SysOaTask task = baseMapper.selectById(bo.getTaskId()); + if (task != null) { + if (projectId == null) projectId = task.getProjectId(); + if (taskTitle == null) taskTitle = task.getTaskTitle(); + } + } + operationLogService.recordLog(projectId, 3, bo.getTaskId(), + taskTitle, 2, "修改任务: " + taskTitle, null, null); + } + return ok; } /** @@ -249,6 +274,13 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService { if(isValid){ //TODO 做一些业务上的校验,判断是否需要校验 } + for (Long taskId : ids) { + SysOaTask task = baseMapper.selectById(taskId); + if (task != null) { + operationLogService.recordLog(task.getProjectId(), 3, taskId, + task.getTaskTitle(), 3, "删除任务: " + task.getTaskTitle(), null, null); + } + } return baseMapper.deleteBatchIds(ids) > 0; } @@ -259,7 +291,6 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService { */ @Override public Boolean postponeTask(SysOaTaskBo bo) { - // 防止截止日期被写入 新增对象 SysOaTaskBo sysOaTaskBo = new SysOaTaskBo(); sysOaTaskBo.setTaskId(bo.getTaskId()); sysOaTaskBo.setState(15L); @@ -270,7 +301,15 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService { sysOaTaskItemBo.setTempTime(bo.getTempTime()); taskItemService.updateByBo(sysOaTaskItemBo); } - return updateByBo(sysOaTaskBo); + boolean ok = updateByBo(sysOaTaskBo); + if (ok) { + SysOaTask task = baseMapper.selectById(bo.getTaskId()); + if (task != null) { + operationLogService.recordLog(task.getProjectId(), 3, bo.getTaskId(), + task.getTaskTitle(), 6, "任务申请延期: " + task.getTaskTitle(), null, null); + } + } + return ok; } /** @@ -290,7 +329,12 @@ public class SysOaTaskServiceImpl implements ISysOaTaskService { sysOaTaskItem.setEndTime(sysOaTaskItem.getTempTime()); taskItemMapper.updateById(sysOaTaskItem); } - return baseMapper.updateById(sysOaTask) > 0; + boolean ok = baseMapper.updateById(sysOaTask) > 0; + if (ok) { + operationLogService.recordLog(sysOaTask.getProjectId(), 3, sysOaTask.getTaskId(), + sysOaTask.getTaskTitle(), 7, "任务延期审批通过: " + sysOaTask.getTaskTitle(), null, null); + } + return ok; } diff --git a/ruoyi-oa/src/main/resources/mapper/oa/OaProjectOperationLogMapper.xml b/ruoyi-oa/src/main/resources/mapper/oa/OaProjectOperationLogMapper.xml new file mode 100644 index 0000000..4bf124a --- /dev/null +++ b/ruoyi-oa/src/main/resources/mapper/oa/OaProjectOperationLogMapper.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/ruoyi-ui/src/api/oa/projectOperationLog.js b/ruoyi-ui/src/api/oa/projectOperationLog.js new file mode 100644 index 0000000..06c16b5 --- /dev/null +++ b/ruoyi-ui/src/api/oa/projectOperationLog.js @@ -0,0 +1,28 @@ +import request from '@/utils/request' + +// 查询操作历史列表(分页) +export function listOperationLog(query) { + return request({ + url: '/oa/projectOperationLog/list', + method: 'get', + params: query + }) +} + +// 查询操作历史详情 +export function getOperationLog(logId) { + return request({ + url: '/oa/projectOperationLog/' + logId, + method: 'get' + }) +} + +// 导出操作历史 +export function exportOperationLog(query) { + return request({ + url: '/oa/projectOperationLog/export', + method: 'post', + params: query, + responseType: 'blob' + }) +} diff --git a/ruoyi-ui/src/views/oa/project/dispatch/index.vue b/ruoyi-ui/src/views/oa/project/dispatch/index.vue new file mode 100644 index 0000000..5d4ece4 --- /dev/null +++ b/ruoyi-ui/src/views/oa/project/dispatch/index.vue @@ -0,0 +1,1079 @@ + + + + + diff --git a/ruoyi-ui/src/views/oa/project/operationLog/OperationLogDrawer.vue b/ruoyi-ui/src/views/oa/project/operationLog/OperationLogDrawer.vue new file mode 100644 index 0000000..5018d82 --- /dev/null +++ b/ruoyi-ui/src/views/oa/project/operationLog/OperationLogDrawer.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/ruoyi-ui/src/views/oa/project/operationLog/index.vue b/ruoyi-ui/src/views/oa/project/operationLog/index.vue new file mode 100644 index 0000000..37af145 --- /dev/null +++ b/ruoyi-ui/src/views/oa/project/operationLog/index.vue @@ -0,0 +1,480 @@ + + + + + diff --git a/ruoyi-ui/src/views/oa/project/pace/index.vue b/ruoyi-ui/src/views/oa/project/pace/index.vue index 8930862..4c90caa 100644 --- a/ruoyi-ui/src/views/oa/project/pace/index.vue +++ b/ruoyi-ui/src/views/oa/project/pace/index.vue @@ -116,12 +116,14 @@ {{ parseTime(scope.row.startTime, '{y}-{m}-{d}') }} - +