fix(日志管理): 修复日志管理的一些问题。

fix(项目任务): 任务完成后需要依然能够修改工作日志,但是只能修改工作内容和上传附件;任务完成后,协办人的工作日志不应该能删除、所有任务里的成员不能新增工作日志。
This commit is contained in:
dk
2026-06-25 21:17:57 +08:00
parent 6d7e011b49
commit 69e9ea6b9f
6 changed files with 67 additions and 34 deletions

View File

@@ -184,8 +184,10 @@ public interface ErrorCodeConstants {
ErrorCode PROJECT_TASK_WORKLOG_DELETE_FORBIDDEN = new ErrorCode(1_008_006_006, "仅记录填报人或任务负责人可删除该工时记录"); ErrorCode PROJECT_TASK_WORKLOG_DELETE_FORBIDDEN = new ErrorCode(1_008_006_006, "仅记录填报人或任务负责人可删除该工时记录");
ErrorCode PROJECT_TASK_WORKLOG_DATE_RANGE_INVALID = new ErrorCode(1_008_006_007, "段起始日期不能晚于段结束日期"); ErrorCode PROJECT_TASK_WORKLOG_DATE_RANGE_INVALID = new ErrorCode(1_008_006_007, "段起始日期不能晚于段结束日期");
ErrorCode PROJECT_TASK_WORKLOG_DATE_OVERLAP = new ErrorCode(1_008_006_008, "日期范围与该任务下您已有的工时记录重叠"); ErrorCode PROJECT_TASK_WORKLOG_DATE_OVERLAP = new ErrorCode(1_008_006_008, "日期范围与该任务下您已有的工时记录重叠");
ErrorCode PROJECT_TASK_WORKLOG_DELETE_NOT_ALLOWED_BY_TASK_STATUS = new ErrorCode(1_008_006_009, "当前任务状态不允许删除工作日志");
ErrorCode PROJECT_TASK_WORKLOG_PROGRESS_NOT_MONOTONIC = new ErrorCode(1_008_006_010, "工时进度与日期顺序不一致:早段进度不得高于晚段、晚段进度不得低于早段"); ErrorCode PROJECT_TASK_WORKLOG_PROGRESS_NOT_MONOTONIC = new ErrorCode(1_008_006_010, "工时进度与日期顺序不一致:早段进度不得高于晚段、晚段进度不得低于早段");
ErrorCode PROJECT_TASK_WORKLOG_DIFFICULTY_INVALID = new ErrorCode(1_008_006_011, "完成难度不在字典范围内"); ErrorCode PROJECT_TASK_WORKLOG_DIFFICULTY_INVALID = new ErrorCode(1_008_006_011, "完成难度不在字典范围内");
ErrorCode PROJECT_TASK_WORKLOG_CREATE_NOT_ALLOWED_BY_TASK_STATUS = new ErrorCode(1_008_006_012, "当前任务状态不允许新增工作日志");
// ========== 任务 / 工时附件 1_008_010_xxx原 1_008_007 与下方项目需求段撞号,迁至独立号段;新增错误码域请从 1_008_011 起) ========== // ========== 任务 / 工时附件 1_008_010_xxx原 1_008_007 与下方项目需求段撞号,迁至独立号段;新增错误码域请从 1_008_011 起) ==========
ErrorCode PROJECT_TASK_ATTACHMENT_TOO_MANY = new ErrorCode(1_008_010_001, "附件数量不能超过 {} 个"); ErrorCode PROJECT_TASK_ATTACHMENT_TOO_MANY = new ErrorCode(1_008_010_001, "附件数量不能超过 {} 个");

View File

@@ -105,6 +105,7 @@ public class TaskWorklogServiceImpl implements TaskWorklogService {
permission = ProjectTaskConstants.PERMISSION_WORKLOG) permission = ProjectTaskConstants.PERMISSION_WORKLOG)
public Long createWorklog(Long projectId, Long executionId, Long taskId, TaskWorklogSaveReqVO reqVO) { public Long createWorklog(Long projectId, Long executionId, Long taskId, TaskWorklogSaveReqVO reqVO) {
ProjectTaskDO task = validateEditableContext(projectId, executionId, taskId); ProjectTaskDO task = validateEditableContext(projectId, executionId, taskId);
validateCreateAllowed(task);
validateLeafTask(taskId); validateLeafTask(taskId);
Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
validateFileWorklogPermission(taskId, task.getOwnerId(), loginUserId); validateFileWorklogPermission(taskId, task.getOwnerId(), loginUserId);
@@ -177,6 +178,7 @@ public class TaskWorklogServiceImpl implements TaskWorklogService {
permission = ProjectTaskConstants.PERMISSION_WORKLOG) permission = ProjectTaskConstants.PERMISSION_WORKLOG)
public void deleteWorklog(Long projectId, Long executionId, Long taskId, Long worklogId) { public void deleteWorklog(Long projectId, Long executionId, Long taskId, Long worklogId) {
ProjectTaskDO task = validateEditableContext(projectId, executionId, taskId); ProjectTaskDO task = validateEditableContext(projectId, executionId, taskId);
validateDeleteAllowed(task);
TaskWorklogDO worklog = loadWorklog(worklogId, taskId); TaskWorklogDO worklog = loadWorklog(worklogId, taskId);
Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
boolean isFiler = Objects.equals(worklog.getUserId(), loginUserId); boolean isFiler = Objects.equals(worklog.getUserId(), loginUserId);
@@ -240,24 +242,30 @@ public class TaskWorklogServiceImpl implements TaskWorklogService {
ProjectExecutionDO execution = validateExecutionExists(projectId, executionId); ProjectExecutionDO execution = validateExecutionExists(projectId, executionId);
validateAllowEdit(ProjectExecutionConstants.OBJECT_TYPE, execution.getStatusCode()); validateAllowEdit(ProjectExecutionConstants.OBJECT_TYPE, execution.getStatusCode());
ProjectTaskDO task = validateTaskExists(projectId, executionId, taskId); ProjectTaskDO task = validateTaskExists(projectId, executionId, taskId);
// 任务层放宽:completed 状态下非 owner即协办人允许继续维护自己的工时§4.2.4 矩阵) // 工作日志维护在任务 completed 状态下统一放行;新增/删除是否允许由各自接口单独判定
// 其他状态pending / active / paused / cancelled仍按 allow_edit 判定owner 在 completed 下被拦截, if (!isCompletedWorklogContext(task)) {
// 避免与"完成时硬置进度 100%"冲突。
if (!isCompletedAssigneeWorklogContext(task)) {
validateAllowEdit(ProjectTaskConstants.OBJECT_TYPE, task.getStatusCode()); validateAllowEdit(ProjectTaskConstants.OBJECT_TYPE, task.getStatusCode());
} }
return task; return task;
} }
/** /**
* 是否处于"任务已完成、协办人维护工时"的放行场景。 * 是否处于任务已完成”的工作日志维护场景。
*/ */
private boolean isCompletedAssigneeWorklogContext(ProjectTaskDO task) { private boolean isCompletedWorklogContext(ProjectTaskDO task) {
if (!"completed".equals(task.getStatusCode())) { return "completed".equals(task.getStatusCode());
return false; }
private void validateCreateAllowed(ProjectTaskDO task) {
if (isCompletedWorklogContext(task)) {
throw exception(ErrorCodeConstants.PROJECT_TASK_WORKLOG_CREATE_NOT_ALLOWED_BY_TASK_STATUS);
}
}
private void validateDeleteAllowed(ProjectTaskDO task) {
if (isCompletedWorklogContext(task)) {
throw exception(ErrorCodeConstants.PROJECT_TASK_WORKLOG_DELETE_NOT_ALLOWED_BY_TASK_STATUS);
} }
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
return loginUserId != null && !Objects.equals(loginUserId, task.getOwnerId());
} }
private void validateExecutionAndTaskExists(Long projectId, Long executionId, Long taskId) { private void validateExecutionAndTaskExists(Long projectId, Long executionId, Long taskId) {

View File

@@ -4,14 +4,15 @@ import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog;
import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageParam; import com.njcn.rdms.framework.common.pojo.PageParam;
import com.njcn.rdms.framework.common.pojo.PageResult; import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.framework.common.util.collection.CollectionUtils;
import com.njcn.rdms.framework.common.util.object.BeanUtils; import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.excel.core.util.ExcelUtils; import com.njcn.rdms.framework.excel.core.util.ExcelUtils;
import com.njcn.rdms.framework.translate.core.TranslateUtils;
import com.njcn.rdms.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO; import com.njcn.rdms.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;
import com.njcn.rdms.module.system.controller.admin.logger.vo.operatelog.OperateLogRespVO; import com.njcn.rdms.module.system.controller.admin.logger.vo.operatelog.OperateLogRespVO;
import com.njcn.rdms.module.system.dal.dataobject.logger.OperateLogDO; import com.njcn.rdms.module.system.dal.dataobject.logger.OperateLogDO;
import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO;
import com.njcn.rdms.module.system.service.logger.OperateLogService; import com.njcn.rdms.module.system.service.logger.OperateLogService;
import com.fhs.core.trans.anno.TransMethodResult; import com.njcn.rdms.module.system.service.user.AdminUserService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
@@ -26,7 +27,9 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.io.IOException; import java.io.IOException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map;
import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT; import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.njcn.rdms.framework.common.pojo.CommonResult.success; import static com.njcn.rdms.framework.common.pojo.CommonResult.success;
@@ -40,34 +43,53 @@ public class OperateLogController {
@Resource @Resource
private OperateLogService operateLogService; private OperateLogService operateLogService;
@Resource
private AdminUserService adminUserService;
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "查看操作日志") @Operation(summary = "查看操作日志")
@Parameter(name = "id", description = "编号", required = true, example = "1024") @Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:operate-log:query')") @PreAuthorize("@ss.hasPermission('system:operate-log:query')")
public CommonResult<OperateLogRespVO> getOperateLog(@RequestParam("id") Long id) { public CommonResult<OperateLogRespVO> getOperateLog(@RequestParam("id") Long id) {
OperateLogDO operateLog = operateLogService.getOperateLog(id); OperateLogRespVO respVO = BeanUtils.toBean(operateLogService.getOperateLog(id), OperateLogRespVO.class);
return success(BeanUtils.toBean(operateLog, OperateLogRespVO.class)); fillUserNames(Collections.singletonList(respVO));
return success(respVO);
} }
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "查看操作日志分页列表") @Operation(summary = "查看操作日志分页列表")
@PreAuthorize("@ss.hasPermission('system:operate-log:query')") @PreAuthorize("@ss.hasPermission('system:operate-log:query')")
@TransMethodResult
public CommonResult<PageResult<OperateLogRespVO>> pageOperateLog(@Valid OperateLogPageReqVO pageReqVO) { public CommonResult<PageResult<OperateLogRespVO>> pageOperateLog(@Valid OperateLogPageReqVO pageReqVO) {
PageResult<OperateLogDO> pageResult = operateLogService.getOperateLogPage(pageReqVO); PageResult<OperateLogDO> pageResult = operateLogService.getOperateLogPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, OperateLogRespVO.class)); PageResult<OperateLogRespVO> respPageResult = BeanUtils.toBean(pageResult, OperateLogRespVO.class);
fillUserNames(respPageResult.getList());
return success(respPageResult);
} }
@Operation(summary = "导出操作日志") @Operation(summary = "导出操作日志")
@GetMapping("/export-excel") @GetMapping("/export-excel")
@PreAuthorize("@ss.hasPermission('system:operate-log:export')") @PreAuthorize("@ss.hasPermission('system:operate-log:export')")
@TransMethodResult
@ApiAccessLog(operateType = EXPORT) @ApiAccessLog(operateType = EXPORT)
public void exportOperateLog(HttpServletResponse response, @Valid OperateLogPageReqVO exportReqVO) throws IOException { public void exportOperateLog(HttpServletResponse response, @Valid OperateLogPageReqVO exportReqVO) throws IOException {
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<OperateLogDO> list = operateLogService.getOperateLogPage(exportReqVO).getList(); List<OperateLogRespVO> respVOList = BeanUtils.toBean(
ExcelUtils.write(response, "操作日志.xls", "数据列表", OperateLogRespVO.class, operateLogService.getOperateLogPage(exportReqVO).getList(), OperateLogRespVO.class);
TranslateUtils.translate(BeanUtils.toBean(list, OperateLogRespVO.class))); fillUserNames(respVOList);
ExcelUtils.write(response, "操作日志.xls", "数据列表", OperateLogRespVO.class, respVOList);
}
private void fillUserNames(List<OperateLogRespVO> respVOList) {
if (respVOList == null || respVOList.isEmpty()) {
return;
}
Map<Long, AdminUserDO> userMap = adminUserService.getUserMap(
CollectionUtils.convertSet(respVOList, OperateLogRespVO::getUserId));
respVOList.forEach(respVO -> {
AdminUserDO user = userMap.get(respVO.getUserId());
if (user != null) {
respVO.setUserName(user.getNickname());
}
});
} }
} }

View File

@@ -13,21 +13,24 @@ import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MON
@Data @Data
public class OperateLogPageReqVO extends PageParam { public class OperateLogPageReqVO extends PageParam {
@Schema(description = "用户编号", example = "灿能") @Schema(description = "用户编号", example = "1")
private Long userId; private Long userId;
@Schema(description = "操作模块业务编号", example = "1") @Schema(description = "操作模块业务编号", example = "1")
private Long bizId; private Long bizId;
@Schema(description = "操作模块,模匹配", example = "订单") @Schema(description = "操作模块,模匹配", example = "订单")
private String type; private String type;
@Schema(description = "操作名,模匹配", example = "创建订单") @Schema(description = "操作名,模匹配", example = "创建订单")
private String subType; private String subType;
@Schema(description = "操作明细,模匹配", example = "修改编号为 1 的用户信息") @Schema(description = "操作明细,模匹配", example = "修改编号为 1 的用户信息")
private String action; private String action;
@Schema(description = "请求方式", example = "GET")
private String requestMethod;
@Schema(description = "开始时间", example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]") @Schema(description = "开始时间", example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime; private LocalDateTime[] createTime;

View File

@@ -1,13 +1,10 @@
package com.njcn.rdms.module.system.controller.admin.logger.vo.operatelog; package com.njcn.rdms.module.system.controller.admin.logger.vo.operatelog;
import com.njcn.rdms.framework.excel.core.annotations.DictFormat;
import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated; import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty; import cn.idev.excel.annotation.ExcelProperty;
import com.njcn.rdms.module.system.enums.DictTypeConstants;
import com.fhs.core.trans.anno.Trans;
import com.fhs.core.trans.constant.TransType;
import com.fhs.core.trans.vo.VO; import com.fhs.core.trans.vo.VO;
import com.njcn.rdms.framework.excel.core.annotations.DictFormat;
import com.njcn.rdms.module.system.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotEmpty;
import lombok.Data; import lombok.Data;
@@ -27,8 +24,8 @@ public class OperateLogRespVO implements VO {
private String traceId; private String traceId;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Trans(type = TransType.SIMPLE, target = AdminUserDO.class, fields = "nickname", ref = "userName")
private Long userId; private Long userId;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen") @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen")
@ExcelProperty("操作人") @ExcelProperty("操作人")
private String userName; private String userName;
@@ -42,18 +39,18 @@ public class OperateLogRespVO implements VO {
@ExcelProperty("操作模块类型") @ExcelProperty("操作模块类型")
private String type; private String type;
@Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建订单") @Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建订单")
@ExcelProperty("操作名") @ExcelProperty("操作名")
private String subType; private String subType;
@Schema(description = "操作模块业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @Schema(description = "操作模块业务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("操作模块业务编号") @ExcelProperty("操作模块业务编号")
private Long bizId; private Long bizId;
@Schema(description = "操作明细", example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从灿能改成源码。") @Schema(description = "操作明细", example = "修改编号为 1 的用户信息,将性别从男改成女,将姓名从芋艿改成源码。")
private String action; private String action;
@Schema(description = "展字段", example = "{'orderId': 1}") @Schema(description = "展字段", example = "{'orderId': 1}")
private String extra; private String extra;
@Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET") @Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET")

View File

@@ -18,6 +18,7 @@ public interface OperateLogMapper extends BaseMapperX<OperateLogDO> {
.likeIfPresent(OperateLogDO::getType, pageReqDTO.getType()) .likeIfPresent(OperateLogDO::getType, pageReqDTO.getType())
.likeIfPresent(OperateLogDO::getSubType, pageReqDTO.getSubType()) .likeIfPresent(OperateLogDO::getSubType, pageReqDTO.getSubType())
.likeIfPresent(OperateLogDO::getAction, pageReqDTO.getAction()) .likeIfPresent(OperateLogDO::getAction, pageReqDTO.getAction())
.eqIfPresent(OperateLogDO::getRequestMethod, pageReqDTO.getRequestMethod())
.betweenIfPresent(OperateLogDO::getCreateTime, pageReqDTO.getCreateTime()) .betweenIfPresent(OperateLogDO::getCreateTime, pageReqDTO.getCreateTime())
.orderByDesc(OperateLogDO::getId)); .orderByDesc(OperateLogDO::getId));
} }