diff --git a/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java b/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java index 8898796..90e9396 100644 --- a/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java +++ b/rdms-project/rdms-project-api/src/main/java/com/njcn/rdms/module/project/enums/ErrorCodeConstants.java @@ -227,4 +227,17 @@ public interface ErrorCodeConstants { ErrorCode PERSONAL_ITEM_STATUS_NOT_ALLOW_EDIT = new ErrorCode(1_008_008_007, "当前个人事项状态不允许编辑"); ErrorCode PERSONAL_ITEM_NOT_ALLOW_DELETE = new ErrorCode(1_008_008_008, "仅初始态(待开始)的个人事项允许删除"); ErrorCode PERSONAL_ITEM_WRITE_FORBIDDEN = new ErrorCode(1_008_008_009, "无权修改个人事项"); + + // ========== 加班申请 1_008_009_xxx ========== + ErrorCode OVERTIME_APPLICATION_NOT_EXISTS = new ErrorCode(1_008_009_001, "加班申请不存在"); + ErrorCode OVERTIME_APPLICATION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_008_009_002, "加班申请状态定义不存在或已停用"); + ErrorCode OVERTIME_APPLICATION_STATUS_ACTION_NOT_ALLOWED = new ErrorCode(1_008_009_003, "当前加班申请状态不支持动作【{}】"); + ErrorCode OVERTIME_APPLICATION_STATUS_ACTION_REASON_REQUIRED = new ErrorCode(1_008_009_004, "动作【{}】必须填写原因"); + ErrorCode OVERTIME_APPLICATION_STATUS_CONCURRENT_MODIFIED = new ErrorCode(1_008_009_005, "加班申请状态已发生变化,请刷新后重试"); + ErrorCode OVERTIME_APPLICATION_APPLICANT_ONLY = new ErrorCode(1_008_009_006, "仅申请人可执行该操作"); + ErrorCode OVERTIME_APPLICATION_APPROVER_ONLY = new ErrorCode(1_008_009_007, "仅当前审核人可执行该操作"); + ErrorCode OVERTIME_APPLICATION_APPROVER_INVALID = new ErrorCode(1_008_009_008, "审核人不是有效系统用户"); + ErrorCode OVERTIME_APPLICATION_APPROVER_SELF_FORBIDDEN = new ErrorCode(1_008_009_009, "审核人不能选择申请人本人"); + ErrorCode OVERTIME_APPLICATION_READ_FORBIDDEN = new ErrorCode(1_008_009_010, "无权查看该加班申请"); + ErrorCode OVERTIME_APPLICATION_DELETE_ONLY_CANCELLED = new ErrorCode(1_008_009_011, "仅已撤销的加班申请允许删除"); } diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/OvertimeApplicationConstants.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/OvertimeApplicationConstants.java new file mode 100644 index 0000000..b9a5cc5 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/constant/OvertimeApplicationConstants.java @@ -0,0 +1,33 @@ +package com.njcn.rdms.module.project.constant; + +/** + * 加班申请常量。 + */ +public final class OvertimeApplicationConstants { + + private OvertimeApplicationConstants() { + } + + public static final String BIZ_TYPE = "overtime_application"; + public static final String STATUS_OBJECT_TYPE = BIZ_TYPE; + + public static final String STATUS_PENDING = "pending"; + public static final String STATUS_APPROVED = "approved"; + public static final String STATUS_REJECTED = "rejected"; + public static final String STATUS_CANCELLED = "cancelled"; + + public static final String ACTION_SUBMIT = "submit"; + public static final String ACTION_RESUBMIT = "resubmit"; + public static final String ACTION_APPROVE = "approve"; + public static final String ACTION_REJECT = "reject"; + public static final String ACTION_CANCEL = "cancel"; + public static final String ACTION_DELETE = "delete"; + + public static final String PERMISSION_QUERY = "project:overtime-application:query"; + public static final String PERMISSION_CREATE = "project:overtime-application:create"; + public static final String PERMISSION_UPDATE = "project:overtime-application:update"; + public static final String PERMISSION_DELETE = "project:overtime-application:delete"; + public static final String PERMISSION_APPROVE = "project:overtime-application:approve"; + public static final String PERMISSION_EXPORT = "project:overtime-application:export"; + +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/OvertimeApplicationController.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/OvertimeApplicationController.java new file mode 100644 index 0000000..99488a4 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/OvertimeApplicationController.java @@ -0,0 +1,135 @@ +package com.njcn.rdms.module.project.controller.admin.overtime; + +import com.njcn.rdms.framework.apilog.core.annotation.ApiAccessLog; +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.excel.core.util.ExcelUtils; +import com.njcn.rdms.module.project.constant.OvertimeApplicationConstants; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationExportVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationPageReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationRespVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationSaveReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusLogRespVO; +import com.njcn.rdms.module.project.service.overtime.OvertimeApplicationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; + +import static com.njcn.rdms.framework.apilog.core.enums.OperateTypeEnum.EXPORT; +import static com.njcn.rdms.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - 加班申请") +@RestController +@RequestMapping("/project/overtime-applications") +@Validated +public class OvertimeApplicationController { + + @Resource + private OvertimeApplicationService overtimeApplicationService; + + @PostMapping + @Operation(summary = "新增加班申请并提交") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_CREATE + "')") + public CommonResult create(@Valid @RequestBody OvertimeApplicationSaveReqVO reqVO) { + return success(overtimeApplicationService.createApplication(reqVO)); + } + + @PutMapping("/{id}") + @Operation(summary = "退回后修改并重新提交加班申请") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_UPDATE + "')") + public CommonResult updateRejected(@PathVariable("id") Long id, + @Valid @RequestBody OvertimeApplicationSaveReqVO reqVO) { + overtimeApplicationService.updateRejectedApplication(id, reqVO); + return success(true); + } + + @GetMapping("/{id}") + @Operation(summary = "获取加班申请详情") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_QUERY + "')") + public CommonResult get(@PathVariable("id") Long id) { + return success(overtimeApplicationService.getApplication(id)); + } + + @GetMapping("/page") + @Operation(summary = "获取我的加班申请分页") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_QUERY + "')") + public CommonResult> page(@Valid OvertimeApplicationPageReqVO reqVO) { + return success(overtimeApplicationService.getMyPage(reqVO)); + } + + @GetMapping("/approval-page") + @Operation(summary = "获取待我审批的加班申请分页") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_APPROVE + "')") + public CommonResult> approvalPage(@Valid OvertimeApplicationPageReqVO reqVO) { + return success(overtimeApplicationService.getApprovalPage(reqVO)); + } + + @PostMapping("/{id}/approve") + @Operation(summary = "审核通过加班申请") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_APPROVE + "')") + public CommonResult approve(@PathVariable("id") Long id, + @Valid @RequestBody OvertimeApplicationStatusActionReqVO reqVO) { + overtimeApplicationService.approve(id, reqVO); + return success(true); + } + + @PostMapping("/{id}/reject") + @Operation(summary = "审核退回加班申请") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_APPROVE + "')") + public CommonResult reject(@PathVariable("id") Long id, + @Valid @RequestBody OvertimeApplicationStatusActionReqVO reqVO) { + overtimeApplicationService.reject(id, reqVO); + return success(true); + } + + @PostMapping("/{id}/cancel") + @Operation(summary = "撤销加班申请") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_UPDATE + "')") + public CommonResult cancel(@PathVariable("id") Long id, + @Valid @RequestBody OvertimeApplicationStatusActionReqVO reqVO) { + overtimeApplicationService.cancel(id, reqVO); + return success(true); + } + + @DeleteMapping("/{id}") + @Operation(summary = "删除已撤销的加班申请") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_DELETE + "')") + public CommonResult delete(@PathVariable("id") Long id) { + overtimeApplicationService.deleteApplication(id); + return success(true); + } + + @GetMapping("/{id}/status-logs") + @Operation(summary = "获取加班申请状态日志") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_QUERY + "')") + public CommonResult> statusLogs(@PathVariable("id") Long id) { + return success(overtimeApplicationService.getStatusLogs(id)); + } + + @GetMapping("/export") + @Operation(summary = "导出我的加班申请") + @PreAuthorize("@ss.hasPermission('" + OvertimeApplicationConstants.PERMISSION_EXPORT + "')") + @ApiAccessLog(operateType = EXPORT) + public void export(@Valid OvertimeApplicationPageReqVO reqVO, HttpServletResponse response) throws IOException { + List list = overtimeApplicationService.getExportList(reqVO); + ExcelUtils.write(response, "加班申请_" + LocalDate.now() + ".xls", "加班申请", + OvertimeApplicationExportVO.class, list); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationExportVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationExportVO.java new file mode 100644 index 0000000..a6eba33 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationExportVO.java @@ -0,0 +1,40 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.vo; + +import cn.idev.excel.annotation.ExcelIgnoreUnannotated; +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Data +@ExcelIgnoreUnannotated +public class OvertimeApplicationExportVO { + + @ExcelProperty("申请人") + private String applicantName; + + @ExcelProperty("加班日期") + private LocalDate overtimeDate; + + @ExcelProperty("加班时长") + private String overtimeDuration; + + @ExcelProperty("加班原因") + private String overtimeReason; + + @ExcelProperty("加班内容") + private String overtimeContent; + + @ExcelProperty("状态") + private String statusName; + + @ExcelProperty("审核人") + private String approverName; + + @ExcelProperty("提交时间") + private LocalDateTime submitTime; + + @ExcelProperty("审核时间") + private LocalDateTime approvalTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationPageReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationPageReqVO.java new file mode 100644 index 0000000..22f3bb8 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationPageReqVO.java @@ -0,0 +1,44 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.vo; + +import com.njcn.rdms.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; +import static com.njcn.rdms.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; + +@Schema(description = "管理后台 - 加班申请分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class OvertimeApplicationPageReqVO extends PageParam { + + @Schema(description = "关键词,匹配加班原因或加班内容", example = "上线") + private String keyword; + + @Schema(description = "申请人姓名,模糊匹配", example = "张三") + private String applicantName; + + @Schema(description = "审核人用户编号", example = "1001") + private Long approverId; + + @Schema(description = "审核人姓名,模糊匹配", example = "李四") + private String approverName; + + @Schema(description = "状态编码", example = "pending") + @Size(max = 32, message = "状态编码长度不能超过32个字符") + private String statusCode; + + @Schema(description = "加班日期范围", example = "[2026-06-01, 2026-06-30]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + private LocalDate[] overtimeDate; + + @Schema(description = "创建时间", example = "[2026-06-01 00:00:00, 2026-06-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationRespVO.java new file mode 100644 index 0000000..0030c56 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationRespVO.java @@ -0,0 +1,66 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 加班申请 Response VO") +@Data +public class OvertimeApplicationRespVO { + + @Schema(description = "加班申请编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "9001") + private Long id; + + @Schema(description = "申请人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3001") + private Long applicantId; + + @Schema(description = "申请人姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三") + private String applicantName; + + @Schema(description = "加班日期", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDate overtimeDate; + + @Schema(description = "加班时长", requiredMode = Schema.RequiredMode.REQUIRED, example = "1天") + private String overtimeDuration; + + @Schema(description = "加班原因", requiredMode = Schema.RequiredMode.REQUIRED) + private String overtimeReason; + + @Schema(description = "加班内容", requiredMode = Schema.RequiredMode.REQUIRED) + private String overtimeContent; + + @Schema(description = "审核人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") + private Long approverId; + + @Schema(description = "审核人姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + private String approverName; + + @Schema(description = "状态编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "pending") + private String statusCode; + + @Schema(description = "状态名称", example = "待审批") + private String statusName; + + @Schema(description = "当前状态是否允许编辑", example = "false") + private Boolean allowEdit; + + @Schema(description = "是否终态", example = "false") + private Boolean terminal; + + @Schema(description = "最近一次审核意见") + private String approvalComment; + + @Schema(description = "提交时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime submitTime; + + @Schema(description = "最近一次审核时间") + private LocalDateTime approvalTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationSaveReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationSaveReqVO.java new file mode 100644 index 0000000..a7ca505 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationSaveReqVO.java @@ -0,0 +1,37 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.time.LocalDate; + +@Schema(description = "管理后台 - 加班申请保存 Request VO") +@Data +public class OvertimeApplicationSaveReqVO { + + @Schema(description = "加班日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-06-01") + @NotNull(message = "加班日期不能为空") + private LocalDate overtimeDate; + + @Schema(description = "加班时长", requiredMode = Schema.RequiredMode.REQUIRED, example = "1天") + @NotBlank(message = "加班时长不能为空") + @Size(max = 30, message = "加班时长长度不能超过30个字符") + private String overtimeDuration; + + @Schema(description = "加班原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "版本上线保障") + @NotBlank(message = "加班原因不能为空") + @Size(max = 500, message = "加班原因长度不能超过500个字符") + private String overtimeReason; + + @Schema(description = "加班内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "处理上线验证和问题修复") + @NotBlank(message = "加班内容不能为空") + @Size(max = 1000, message = "加班内容长度不能超过1000个字符") + private String overtimeContent; + + @Schema(description = "审核人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001") + @NotNull(message = "审核人不能为空") + private Long approverId; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusActionReqVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusActionReqVO.java new file mode 100644 index 0000000..70e8ecd --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusActionReqVO.java @@ -0,0 +1,14 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - 加班申请状态动作 Request VO") +@Data +public class OvertimeApplicationStatusActionReqVO { + + @Schema(description = "动作原因或审核意见。退回、撤销必填", example = "请补充加班内容") + @Size(max = 1000, message = "动作原因长度不能超过1000个字符") + private String reason; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusLogRespVO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusLogRespVO.java new file mode 100644 index 0000000..faa9762 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/controller/admin/overtime/vo/OvertimeApplicationStatusLogRespVO.java @@ -0,0 +1,51 @@ +package com.njcn.rdms.module.project.controller.admin.overtime.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - 加班申请状态日志 Response VO") +@Data +public class OvertimeApplicationStatusLogRespVO { + + @Schema(description = "日志编号", requiredMode = Schema.RequiredMode.REQUIRED) + private Long id; + + @Schema(description = "加班申请编号", requiredMode = Schema.RequiredMode.REQUIRED) + private Long applicationId; + + @Schema(description = "动作编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "approve") + private String actionType; + + @Schema(description = "变更前状态", example = "pending") + private String fromStatus; + + @Schema(description = "变更后状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "approved") + private String toStatus; + + @Schema(description = "原因或审核意见") + private String reason; + + @Schema(description = "操作人用户编号", requiredMode = Schema.RequiredMode.REQUIRED) + private Long operatorUserId; + + @Schema(description = "操作人名称", requiredMode = Schema.RequiredMode.REQUIRED) + private String operatorName; + + @Schema(description = "申请人姓名快照", requiredMode = Schema.RequiredMode.REQUIRED) + private String applicantNameSnapshot; + + @Schema(description = "加班日期快照", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDate overtimeDateSnapshot; + + @Schema(description = "加班时长快照", requiredMode = Schema.RequiredMode.REQUIRED) + private String overtimeDurationSnapshot; + + @Schema(description = "备注") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationDO.java new file mode 100644 index 0000000..e5e942a --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationDO.java @@ -0,0 +1,51 @@ +package com.njcn.rdms.module.project.dal.dataobject.overtime; + +import com.baomidou.mybatisplus.annotation.FieldStrategy; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +/** + * 加班申请表。 + */ +@TableName("rdms_overtime_application") +@Data +@EqualsAndHashCode(callSuper = true) +public class OvertimeApplicationDO extends BaseDO { + + @TableId + private Long id; + + private Long applicantId; + + private String applicantName; + + private LocalDate overtimeDate; + + private String overtimeDuration; + + private String overtimeReason; + + private String overtimeContent; + + private Long approverId; + + private String approverName; + + private String statusCode; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private String approvalComment; + + private LocalDateTime submitTime; + + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private LocalDateTime approvalTime; + +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationStatusLogDO.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationStatusLogDO.java new file mode 100644 index 0000000..7e9a1ad --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/dataobject/overtime/OvertimeApplicationStatusLogDO.java @@ -0,0 +1,44 @@ +package com.njcn.rdms.module.project.dal.dataobject.overtime; + +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.time.LocalDate; + +/** + * 加班申请状态日志表。 + */ +@TableName("rdms_overtime_application_status_log") +@Data +@EqualsAndHashCode(callSuper = true) +public class OvertimeApplicationStatusLogDO extends BaseDO { + + @TableId + private Long id; + + private Long applicationId; + + private String actionType; + + private String fromStatus; + + private String toStatus; + + private String reason; + + private Long operatorUserId; + + private String operatorName; + + private String applicantNameSnapshot; + + private LocalDate overtimeDateSnapshot; + + private String overtimeDurationSnapshot; + + private String remark; + +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationMapper.java new file mode 100644 index 0000000..7ea3f30 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationMapper.java @@ -0,0 +1,76 @@ +package com.njcn.rdms.module.project.dal.mysql.overtime; + +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.mybatis.core.dataobject.BaseDO; +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationPageReqVO; +import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationDO; +import org.apache.ibatis.annotations.Mapper; +import org.springframework.util.StringUtils; + +import java.util.Collection; + +@Mapper +public interface OvertimeApplicationMapper extends BaseMapperX { + + default PageResult selectMyPage(Long applicantId, OvertimeApplicationPageReqVO reqVO) { + LambdaQueryWrapperX queryWrapper = buildPageQuery(reqVO); + queryWrapper.eq(OvertimeApplicationDO::getApplicantId, applicantId); + queryWrapper.orderByDesc(OvertimeApplicationDO::getSubmitTime) + .orderByDesc(OvertimeApplicationDO::getId); + return selectPage(reqVO, queryWrapper); + } + + default PageResult selectApprovalPage(Long approverId, OvertimeApplicationPageReqVO reqVO) { + LambdaQueryWrapperX queryWrapper = buildPageQuery(reqVO); + queryWrapper.eq(OvertimeApplicationDO::getApproverId, approverId); + queryWrapper.orderByDesc(OvertimeApplicationDO::getSubmitTime) + .orderByDesc(OvertimeApplicationDO::getId); + return selectPage(reqVO, queryWrapper); + } + + default OvertimeApplicationDO selectByIdAndApplicantId(Long id, Long applicantId) { + return selectOne(new LambdaQueryWrapperX() + .eq(OvertimeApplicationDO::getId, id) + .eq(OvertimeApplicationDO::getApplicantId, applicantId)); + } + + default int updateByIdAndStatus(OvertimeApplicationDO update, Long id, String fromStatus) { + return update(update, new LambdaQueryWrapperX() + .eq(OvertimeApplicationDO::getId, id) + .eq(OvertimeApplicationDO::getStatusCode, fromStatus)); + } + + default int updateByIdAndStatusAndApplicantId(OvertimeApplicationDO update, Long id, String fromStatus, + Long applicantId) { + return update(update, new LambdaQueryWrapperX() + .eq(OvertimeApplicationDO::getId, id) + .eq(OvertimeApplicationDO::getStatusCode, fromStatus) + .eq(OvertimeApplicationDO::getApplicantId, applicantId)); + } + + default int updateByIdAndStatusesAndApplicantId(OvertimeApplicationDO update, Long id, + Collection fromStatuses, Long applicantId) { + return update(update, new LambdaQueryWrapperX() + .eq(OvertimeApplicationDO::getId, id) + .in(OvertimeApplicationDO::getStatusCode, fromStatuses) + .eq(OvertimeApplicationDO::getApplicantId, applicantId)); + } + + private LambdaQueryWrapperX buildPageQuery(OvertimeApplicationPageReqVO reqVO) { + LambdaQueryWrapperX queryWrapper = new LambdaQueryWrapperX<>(); + queryWrapper.likeIfPresent(OvertimeApplicationDO::getApplicantName, reqVO.getApplicantName()) + .eqIfPresent(OvertimeApplicationDO::getApproverId, reqVO.getApproverId()) + .likeIfPresent(OvertimeApplicationDO::getApproverName, reqVO.getApproverName()) + .eqIfPresent(OvertimeApplicationDO::getStatusCode, reqVO.getStatusCode()) + .betweenIfPresent(OvertimeApplicationDO::getOvertimeDate, reqVO.getOvertimeDate()) + .betweenIfPresent(BaseDO::getCreateTime, reqVO.getCreateTime()); + if (StringUtils.hasText(reqVO.getKeyword())) { + queryWrapper.and(wrapper -> wrapper.like(OvertimeApplicationDO::getOvertimeReason, reqVO.getKeyword()) + .or() + .like(OvertimeApplicationDO::getOvertimeContent, reqVO.getKeyword())); + } + return queryWrapper; + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationStatusLogMapper.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationStatusLogMapper.java new file mode 100644 index 0000000..b2b423d --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/dal/mysql/overtime/OvertimeApplicationStatusLogMapper.java @@ -0,0 +1,19 @@ +package com.njcn.rdms.module.project.dal.mysql.overtime; + +import com.njcn.rdms.framework.mybatis.core.mapper.BaseMapperX; +import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationStatusLogDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +@Mapper +public interface OvertimeApplicationStatusLogMapper extends BaseMapperX { + + default List selectListByApplicationId(Long applicationId) { + return selectList(new LambdaQueryWrapperX() + .eq(OvertimeApplicationStatusLogDO::getApplicationId, applicationId) + .orderByDesc(OvertimeApplicationStatusLogDO::getCreateTime) + .orderByDesc(OvertimeApplicationStatusLogDO::getId)); + } +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationService.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationService.java new file mode 100644 index 0000000..d9c1aad --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationService.java @@ -0,0 +1,36 @@ +package com.njcn.rdms.module.project.service.overtime; + +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationExportVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationPageReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationRespVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationSaveReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusLogRespVO; + +import java.util.List; + +public interface OvertimeApplicationService { + + Long createApplication(OvertimeApplicationSaveReqVO reqVO); + + void updateRejectedApplication(Long id, OvertimeApplicationSaveReqVO reqVO); + + void approve(Long id, OvertimeApplicationStatusActionReqVO reqVO); + + void reject(Long id, OvertimeApplicationStatusActionReqVO reqVO); + + void cancel(Long id, OvertimeApplicationStatusActionReqVO reqVO); + + void deleteApplication(Long id); + + OvertimeApplicationRespVO getApplication(Long id); + + PageResult getMyPage(OvertimeApplicationPageReqVO reqVO); + + PageResult getApprovalPage(OvertimeApplicationPageReqVO reqVO); + + List getStatusLogs(Long id); + + List getExportList(OvertimeApplicationPageReqVO reqVO); +} diff --git a/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java new file mode 100644 index 0000000..e8046e4 --- /dev/null +++ b/rdms-project/rdms-project-boot/src/main/java/com/njcn/rdms/module/project/service/overtime/OvertimeApplicationServiceImpl.java @@ -0,0 +1,467 @@ +package com.njcn.rdms.module.project.service.overtime; + +import com.njcn.rdms.framework.common.pojo.CommonResult; +import com.njcn.rdms.framework.common.pojo.PageParam; +import com.njcn.rdms.framework.common.pojo.PageResult; +import com.njcn.rdms.framework.common.util.json.JsonUtils; +import com.njcn.rdms.framework.common.util.object.BeanUtils; +import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils; +import com.njcn.rdms.module.project.constant.OvertimeApplicationConstants; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationExportVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationPageReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationRespVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationSaveReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusActionReqVO; +import com.njcn.rdms.module.project.controller.admin.overtime.vo.OvertimeApplicationStatusLogRespVO; +import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO; +import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationDO; +import com.njcn.rdms.module.project.dal.dataobject.overtime.OvertimeApplicationStatusLogDO; +import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusModelDO; +import com.njcn.rdms.module.project.dal.dataobject.status.ObjectStatusTransitionDO; +import com.njcn.rdms.module.project.dal.mysql.audit.BizAuditLogMapper; +import com.njcn.rdms.module.project.dal.mysql.overtime.OvertimeApplicationMapper; +import com.njcn.rdms.module.project.dal.mysql.overtime.OvertimeApplicationStatusLogMapper; +import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusModelMapper; +import com.njcn.rdms.module.project.dal.mysql.status.ObjectStatusTransitionMapper; +import com.njcn.rdms.module.project.enums.ErrorCodeConstants; +import com.njcn.rdms.module.system.api.user.AdminUserApi; +import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; + +import java.time.LocalDateTime; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.njcn.rdms.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; + +@Service +public class OvertimeApplicationServiceImpl implements OvertimeApplicationService { + + @Resource + private OvertimeApplicationMapper overtimeApplicationMapper; + @Resource + private OvertimeApplicationStatusLogMapper overtimeApplicationStatusLogMapper; + @Resource + private BizAuditLogMapper bizAuditLogMapper; + @Resource + private ObjectStatusModelMapper objectStatusModelMapper; + @Resource + private ObjectStatusTransitionMapper objectStatusTransitionMapper; + @Resource + private AdminUserApi adminUserApi; + + @Override + @Transactional(rollbackFor = Exception.class) + public Long createApplication(OvertimeApplicationSaveReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + AdminUserRespDTO approver = validateApprover(reqVO.getApproverId()); + String initialStatus = getInitialStatusCode(); + + OvertimeApplicationDO application = new OvertimeApplicationDO(); + application.setApplicantId(loginUserId); + application.setApplicantName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + applySaveFields(application, reqVO, approver); + application.setStatusCode(initialStatus); + application.setApprovalComment(null); + application.setSubmitTime(LocalDateTime.now()); + application.setApprovalTime(null); + overtimeApplicationMapper.insert(application); + + writeStatusLog(application, OvertimeApplicationConstants.ACTION_SUBMIT, null, initialStatus, null); + writeAuditLog(application, OvertimeApplicationConstants.ACTION_SUBMIT, null, initialStatus, null, null, null); + return application.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateRejectedApplication(Long id, OvertimeApplicationSaveReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + OvertimeApplicationDO current = validateApplicationExists(id); + if (!Objects.equals(current.getApplicantId(), loginUserId)) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_APPLICANT_ONLY); + } + String fromStatus = current.getStatusCode(); + ObjectStatusTransitionDO transition = validateTransition(fromStatus, OvertimeApplicationConstants.ACTION_RESUBMIT, + null); + AdminUserRespDTO approver = validateApprover(reqVO.getApproverId()); + + OvertimeApplicationDO before = cloneApplication(current); + OvertimeApplicationDO update = new OvertimeApplicationDO(); + applySaveFields(update, reqVO, approver); + update.setStatusCode(transition.getToStatusCode()); + update.setApprovalComment(null); + update.setSubmitTime(LocalDateTime.now()); + update.setApprovalTime(null); + + int updateCount = overtimeApplicationMapper.updateByIdAndStatusesAndApplicantId(update, id, + List.of(OvertimeApplicationConstants.STATUS_PENDING, OvertimeApplicationConstants.STATUS_REJECTED), + loginUserId); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_CONCURRENT_MODIFIED); + } + + OvertimeApplicationDO after = mergeUpdated(current, update); + writeStatusLog(after, OvertimeApplicationConstants.ACTION_RESUBMIT, fromStatus, transition.getToStatusCode(), + null); + writeAuditLog(after, OvertimeApplicationConstants.ACTION_RESUBMIT, fromStatus, transition.getToStatusCode(), + buildFieldChanges(before, after), null, null); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void approve(Long id, OvertimeApplicationStatusActionReqVO reqVO) { + processApprovalAction(id, OvertimeApplicationConstants.ACTION_APPROVE, reqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void reject(Long id, OvertimeApplicationStatusActionReqVO reqVO) { + processApprovalAction(id, OvertimeApplicationConstants.ACTION_REJECT, reqVO); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void cancel(Long id, OvertimeApplicationStatusActionReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + OvertimeApplicationDO current = validateApplicationExists(id); + if (!Objects.equals(current.getApplicantId(), loginUserId)) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_APPLICANT_ONLY); + } + String reason = normalizeNullableText(reqVO == null ? null : reqVO.getReason()); + String fromStatus = current.getStatusCode(); + ObjectStatusTransitionDO transition = validateTransition(fromStatus, OvertimeApplicationConstants.ACTION_CANCEL, + reason); + + OvertimeApplicationDO update = new OvertimeApplicationDO(); + update.setStatusCode(transition.getToStatusCode()); + update.setApprovalComment(reason); + update.setApprovalTime(LocalDateTime.now()); + int updateCount = overtimeApplicationMapper.updateByIdAndStatusAndApplicantId(update, id, fromStatus, + loginUserId); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_CONCURRENT_MODIFIED); + } + + OvertimeApplicationDO after = mergeUpdated(current, update); + writeStatusLog(after, OvertimeApplicationConstants.ACTION_CANCEL, fromStatus, transition.getToStatusCode(), + reason); + writeAuditLog(after, OvertimeApplicationConstants.ACTION_CANCEL, fromStatus, transition.getToStatusCode(), + null, reason, null); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteApplication(Long id) { + OvertimeApplicationDO current = validateApplicationExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + if (!Objects.equals(current.getApplicantId(), loginUserId)) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_APPLICANT_ONLY); + } + if (!OvertimeApplicationConstants.STATUS_CANCELLED.equals(current.getStatusCode())) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_DELETE_ONLY_CANCELLED); + } + overtimeApplicationMapper.deleteById(id); + writeAuditLog(current, OvertimeApplicationConstants.ACTION_DELETE, current.getStatusCode(), null, null, null, + null); + } + + @Override + public OvertimeApplicationRespVO getApplication(Long id) { + OvertimeApplicationDO application = validateReadableApplication(id); + return toRespVO(application); + } + + @Override + public PageResult getMyPage(OvertimeApplicationPageReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + PageResult page = overtimeApplicationMapper.selectMyPage(loginUserId, reqVO); + return BeanUtils.toBean(page, OvertimeApplicationRespVO.class, this::applyStatusView); + } + + @Override + public PageResult getApprovalPage(OvertimeApplicationPageReqVO reqVO) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + PageResult page = overtimeApplicationMapper.selectApprovalPage(loginUserId, reqVO); + return BeanUtils.toBean(page, OvertimeApplicationRespVO.class, this::applyStatusView); + } + + @Override + public List getStatusLogs(Long id) { + validateReadableApplication(id); + return BeanUtils.toBean(overtimeApplicationStatusLogMapper.selectListByApplicationId(id), + OvertimeApplicationStatusLogRespVO.class); + } + + @Override + public List getExportList(OvertimeApplicationPageReqVO reqVO) { + reqVO.setPageSize(PageParam.PAGE_SIZE_NONE); + PageResult page = getMyPage(reqVO); + return BeanUtils.toBean(page.getList(), OvertimeApplicationExportVO.class); + } + + private void processApprovalAction(Long id, String actionCode, OvertimeApplicationStatusActionReqVO reqVO) { + OvertimeApplicationDO current = validateApplicationExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + if (!Objects.equals(current.getApproverId(), loginUserId)) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_APPROVER_ONLY); + } + String reason = normalizeNullableText(reqVO == null ? null : reqVO.getReason()); + String fromStatus = current.getStatusCode(); + ObjectStatusTransitionDO transition = validateTransition(fromStatus, actionCode, reason); + + OvertimeApplicationDO update = new OvertimeApplicationDO(); + update.setStatusCode(transition.getToStatusCode()); + update.setApprovalComment(reason); + update.setApprovalTime(LocalDateTime.now()); + int updateCount = overtimeApplicationMapper.updateByIdAndStatus(update, id, fromStatus); + if (updateCount != 1) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_CONCURRENT_MODIFIED); + } + + OvertimeApplicationDO after = mergeUpdated(current, update); + writeStatusLog(after, actionCode, fromStatus, transition.getToStatusCode(), reason); + writeAuditLog(after, actionCode, fromStatus, transition.getToStatusCode(), null, reason, null); + } + + private OvertimeApplicationDO validateApplicationExists(Long id) { + OvertimeApplicationDO application = overtimeApplicationMapper.selectById(id); + if (application == null) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_NOT_EXISTS); + } + return application; + } + + private OvertimeApplicationDO validateReadableApplication(Long id) { + OvertimeApplicationDO application = validateApplicationExists(id); + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + if (!Objects.equals(application.getApplicantId(), loginUserId) + && !Objects.equals(application.getApproverId(), loginUserId)) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_READ_FORBIDDEN); + } + return application; + } + + private ObjectStatusTransitionDO validateTransition(String fromStatus, String actionCode, String reason) { + ObjectStatusTransitionDO transition = objectStatusTransitionMapper + .selectByObjectTypeAndFromStatusAndAction(OvertimeApplicationConstants.STATUS_OBJECT_TYPE, fromStatus, + actionCode); + if (transition == null) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_ACTION_NOT_ALLOWED, actionCode); + } + if (Boolean.TRUE.equals(transition.getNeedReason()) && !StringUtils.hasText(reason)) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_ACTION_REASON_REQUIRED, actionCode); + } + ObjectStatusModelDO toModel = objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled( + OvertimeApplicationConstants.STATUS_OBJECT_TYPE, transition.getToStatusCode()); + if (toModel == null) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED); + } + return transition; + } + + private String getInitialStatusCode() { + ObjectStatusModelDO statusModel = objectStatusModelMapper + .selectInitialByObjectTypeEnabled(OvertimeApplicationConstants.STATUS_OBJECT_TYPE); + if (statusModel == null || !StringUtils.hasText(statusModel.getStatusCode())) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_STATUS_MODEL_NOT_EXISTS_OR_DISABLED); + } + return statusModel.getStatusCode(); + } + + private AdminUserRespDTO validateApprover(Long approverId) { + Long loginUserId = SecurityFrameworkUtils.getLoginUserId(); + if (Objects.equals(approverId, loginUserId)) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_APPROVER_SELF_FORBIDDEN); + } + return loadUser(approverId); + } + + private AdminUserRespDTO loadUser(Long userId) { + if (userId == null) { + throw invalidParamException("用户编号不能为空"); + } + adminUserApi.validateUserList(Collections.singleton(userId)).getCheckedData(); + CommonResult result = adminUserApi.getUser(userId); + AdminUserRespDTO user = result.getCheckedData(); + if (user == null) { + throw exception(ErrorCodeConstants.OVERTIME_APPLICATION_APPROVER_INVALID); + } + return user; + } + + private void applySaveFields(OvertimeApplicationDO target, OvertimeApplicationSaveReqVO reqVO, + AdminUserRespDTO approver) { + target.setOvertimeDate(reqVO.getOvertimeDate()); + target.setOvertimeDuration(normalizeRequiredText(reqVO.getOvertimeDuration(), "加班时长不能为空")); + target.setOvertimeReason(normalizeRequiredText(reqVO.getOvertimeReason(), "加班原因不能为空")); + target.setOvertimeContent(normalizeRequiredText(reqVO.getOvertimeContent(), "加班内容不能为空")); + target.setApproverId(approver.getId()); + target.setApproverName(defaultText(approver.getNickname())); + } + + private OvertimeApplicationRespVO toRespVO(OvertimeApplicationDO application) { + return BeanUtils.toBean(application, OvertimeApplicationRespVO.class, this::applyStatusView); + } + + private void applyStatusView(OvertimeApplicationRespVO respVO) { + ObjectStatusModelDO statusModel = objectStatusModelMapper.selectByObjectTypeAndStatusCodeEnabled( + OvertimeApplicationConstants.STATUS_OBJECT_TYPE, respVO.getStatusCode()); + if (statusModel == null) { + respVO.setStatusName(respVO.getStatusCode()); + respVO.setAllowEdit(false); + respVO.setTerminal(false); + return; + } + respVO.setStatusName(statusModel.getStatusName()); + respVO.setAllowEdit(Boolean.TRUE.equals(statusModel.getAllowEdit())); + respVO.setTerminal(Boolean.TRUE.equals(statusModel.getTerminalFlag())); + } + + private void writeStatusLog(OvertimeApplicationDO application, String actionType, String fromStatus, + String toStatus, String reason) { + OvertimeApplicationStatusLogDO log = new OvertimeApplicationStatusLogDO(); + log.setApplicationId(application.getId()); + log.setActionType(actionType); + log.setFromStatus(fromStatus); + log.setToStatus(toStatus); + log.setReason(reason); + log.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId()); + log.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + log.setApplicantNameSnapshot(application.getApplicantName()); + log.setOvertimeDateSnapshot(application.getOvertimeDate()); + log.setOvertimeDurationSnapshot(application.getOvertimeDuration()); + log.setRemark(buildSnapshotRemark(application)); + overtimeApplicationStatusLogMapper.insert(log); + } + + private void writeAuditLog(OvertimeApplicationDO application, String actionType, String fromStatus, + String toStatus, String fieldChanges, String reason, String remark) { + BizAuditLogDO auditLog = new BizAuditLogDO(); + auditLog.setBizType(OvertimeApplicationConstants.BIZ_TYPE); + auditLog.setBizId(application.getId()); + auditLog.setActionType(actionType); + auditLog.setFromStatus(fromStatus); + auditLog.setToStatus(toStatus); + auditLog.setFieldChanges(fieldChanges); + auditLog.setReason(reason); + auditLog.setOperatorUserId(SecurityFrameworkUtils.getLoginUserId()); + auditLog.setOperatorName(defaultText(SecurityFrameworkUtils.getLoginUserNickname())); + auditLog.setRemark(StringUtils.hasText(remark) ? remark : buildSnapshotRemark(application)); + bizAuditLogMapper.insert(auditLog); + } + + private String buildSnapshotRemark(OvertimeApplicationDO application) { + return "申请人:" + defaultText(application.getApplicantName()) + + ",加班日期:" + application.getOvertimeDate() + + ",加班时长:" + defaultText(application.getOvertimeDuration()); + } + + private String buildFieldChanges(OvertimeApplicationDO before, OvertimeApplicationDO after) { + Map fieldChanges = new LinkedHashMap<>(); + appendFieldChange(fieldChanges, "overtimeDate", valueOf(before, OvertimeApplicationDO::getOvertimeDate), + valueOf(after, OvertimeApplicationDO::getOvertimeDate)); + appendFieldChange(fieldChanges, "overtimeDuration", + valueOf(before, OvertimeApplicationDO::getOvertimeDuration), + valueOf(after, OvertimeApplicationDO::getOvertimeDuration)); + appendFieldChange(fieldChanges, "overtimeReason", valueOf(before, OvertimeApplicationDO::getOvertimeReason), + valueOf(after, OvertimeApplicationDO::getOvertimeReason)); + appendFieldChange(fieldChanges, "overtimeContent", valueOf(before, OvertimeApplicationDO::getOvertimeContent), + valueOf(after, OvertimeApplicationDO::getOvertimeContent)); + appendFieldChange(fieldChanges, "approverId", valueOf(before, OvertimeApplicationDO::getApproverId), + valueOf(after, OvertimeApplicationDO::getApproverId)); + appendFieldChange(fieldChanges, "approverName", valueOf(before, OvertimeApplicationDO::getApproverName), + valueOf(after, OvertimeApplicationDO::getApproverName)); + appendFieldChange(fieldChanges, "statusCode", valueOf(before, OvertimeApplicationDO::getStatusCode), + valueOf(after, OvertimeApplicationDO::getStatusCode)); + return fieldChanges.isEmpty() ? null : JsonUtils.toJsonString(fieldChanges); + } + + private void appendFieldChange(Map fieldChanges, String fieldName, Object before, Object after) { + if (Objects.equals(before, after)) { + return; + } + Map value = new LinkedHashMap<>(); + value.put("before", before); + value.put("after", after); + fieldChanges.put(fieldName, value); + } + + private T valueOf(OvertimeApplicationDO application, Function getter) { + return application == null ? null : getter.apply(application); + } + + private OvertimeApplicationDO cloneApplication(OvertimeApplicationDO source) { + OvertimeApplicationDO target = new OvertimeApplicationDO(); + target.setId(source.getId()); + target.setApplicantId(source.getApplicantId()); + target.setApplicantName(source.getApplicantName()); + target.setOvertimeDate(source.getOvertimeDate()); + target.setOvertimeDuration(source.getOvertimeDuration()); + target.setOvertimeReason(source.getOvertimeReason()); + target.setOvertimeContent(source.getOvertimeContent()); + target.setApproverId(source.getApproverId()); + target.setApproverName(source.getApproverName()); + target.setStatusCode(source.getStatusCode()); + target.setApprovalComment(source.getApprovalComment()); + target.setSubmitTime(source.getSubmitTime()); + target.setApprovalTime(source.getApprovalTime()); + return target; + } + + private OvertimeApplicationDO mergeUpdated(OvertimeApplicationDO current, OvertimeApplicationDO update) { + OvertimeApplicationDO after = cloneApplication(current); + if (update.getOvertimeDate() != null) { + after.setOvertimeDate(update.getOvertimeDate()); + } + if (update.getOvertimeDuration() != null) { + after.setOvertimeDuration(update.getOvertimeDuration()); + } + if (update.getOvertimeReason() != null) { + after.setOvertimeReason(update.getOvertimeReason()); + } + if (update.getOvertimeContent() != null) { + after.setOvertimeContent(update.getOvertimeContent()); + } + if (update.getApproverId() != null) { + after.setApproverId(update.getApproverId()); + } + if (update.getApproverName() != null) { + after.setApproverName(update.getApproverName()); + } + if (update.getStatusCode() != null) { + after.setStatusCode(update.getStatusCode()); + } + after.setApprovalComment(update.getApprovalComment()); + if (update.getSubmitTime() != null) { + after.setSubmitTime(update.getSubmitTime()); + } + after.setApprovalTime(update.getApprovalTime()); + return after; + } + + private String normalizeRequiredText(String value, String message) { + if (!StringUtils.hasText(value)) { + throw invalidParamException(message); + } + return value.trim(); + } + + private String normalizeNullableText(String value) { + if (!StringUtils.hasText(value)) { + return null; + } + return value.trim(); + } + + private String defaultText(String value) { + return StringUtils.hasText(value) ? value : ""; + } +} diff --git a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApi.java b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApi.java index 88c23d4..7a73153 100644 --- a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApi.java +++ b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApi.java @@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.util.collection.CollectionUtils; +import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; import com.njcn.rdms.module.system.api.user.dto.UserManagementRelationRespDTO; import com.njcn.rdms.module.system.enums.ApiConstants; import io.swagger.v3.oas.annotations.Operation; @@ -33,6 +34,11 @@ public interface UserManagementRelationApi { @Parameter(name = "subordinateUserId", description = "被管理者用户ID", example = "2", required = true) CommonResult> getRelationListBySubordinateUserId(@RequestParam("subordinateUserId") Long subordinateUserId); + @GetMapping(PREFIX + "/direct-manager") + @Operation(summary = "根据用户ID获得当前生效的直属上级") + @Parameter(name = "userId", description = "用户ID", example = "2", required = true) + CommonResult getDirectManager(@RequestParam("userId") Long userId); + @GetMapping(PREFIX + "/list") @Operation(summary = "获得管理链路列表") @Parameter(name = "ids", description = "关系编号数组", example = "1,2", required = true) diff --git a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/DictTypeConstants.java b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/DictTypeConstants.java index 93599a6..d04331d 100644 --- a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/DictTypeConstants.java +++ b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/DictTypeConstants.java @@ -32,4 +32,14 @@ public interface DictTypeConstants { */ String RDMS_TASK_ITEM_TYPE="rdms_task_item_type"; + /** + * 加班申请审批状态字典。 + */ + String RDMS_OVERTIME_APPLICATION_STATUS = "rdms_overtime_application_status"; + + /** + * 加班申请时长快捷选项字典。 + */ + String RDMS_OVERTIME_DURATION = "rdms_overtime_duration"; + } diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApiImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApiImpl.java index b9bd24c..5953436 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApiImpl.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/api/user/UserManagementRelationApiImpl.java @@ -2,7 +2,9 @@ package com.njcn.rdms.module.system.api.user; import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.util.object.BeanUtils; +import com.njcn.rdms.module.system.api.user.dto.AdminUserRespDTO; import com.njcn.rdms.module.system.api.user.dto.UserManagementRelationRespDTO; +import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO; import com.njcn.rdms.module.system.dal.dataobject.user.UserManagementRelationDO; import com.njcn.rdms.module.system.service.user.UserManagementRelationService; import io.swagger.v3.oas.annotations.Hidden; @@ -36,6 +38,15 @@ public class UserManagementRelationApiImpl implements UserManagementRelationApi return success(BeanUtils.toBean(list, UserManagementRelationRespDTO.class)); } + @Override + public CommonResult getDirectManager(Long userId) { + AdminUserDO manager = userManagementRelationService.getDirectManager(userId); + if (manager == null) { + return success(null); + } + return success(BeanUtils.toBean(manager, AdminUserRespDTO.class)); + } + @Override public CommonResult> getRelationList(Collection ids) { if (ids == null || ids.isEmpty()) { diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserManagementRelationController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserManagementRelationController.java index 96e73d6..8259a7f 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserManagementRelationController.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserManagementRelationController.java @@ -26,6 +26,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -157,6 +158,26 @@ public class UserManagementRelationController { return success(list); } + /** + * 获得某用户当前生效的直属上级 + * + * @param userId 用户ID + * @return 直属上级用户,不存在则返回 null + */ + @GetMapping("/direct-manager") + @Operation(summary = "获得某用户当前生效的直属上级") + @Parameter(name = "userId", description = "用户ID", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('system:user-management-relation:query')") + public CommonResult getDirectManager(@RequestParam("userId") Long userId) { + AdminUserDO manager = userManagementRelationService.getDirectManager(userId); + if (manager == null) { + return success(null); + } + List deptIds = manager.getDeptId() == null ? Collections.emptyList() : List.of(manager.getDeptId()); + Map deptMap = deptService.getDeptMap(deptIds); + return success(UserConvert.INSTANCE.convertSimpleList(List.of(manager), deptMap).get(0)); + } + /** * 获取未绑定直属上级的候选下级用户列表 * @return 候选下级用户列表 diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.java index c353414..1b5c3c7 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.java @@ -6,6 +6,7 @@ import com.njcn.rdms.framework.encrypt.core.annotation.ApiEncrypt; import com.njcn.rdms.module.system.controller.admin.user.vo.profile.UserProfileRespVO; import com.njcn.rdms.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; import com.njcn.rdms.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO; +import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserSimpleRespVO; import com.njcn.rdms.module.system.convert.user.UserConvert; import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO; import com.njcn.rdms.module.system.dal.dataobject.dept.PostDO; @@ -17,6 +18,7 @@ import com.njcn.rdms.module.system.service.dept.PostService; import com.njcn.rdms.module.system.service.permission.PermissionService; import com.njcn.rdms.module.system.service.permission.RoleService; import com.njcn.rdms.module.system.service.user.AdminUserService; +import com.njcn.rdms.module.system.service.user.UserManagementRelationService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; @@ -26,7 +28,9 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.util.Collections; import java.util.List; +import java.util.Map; import static com.njcn.rdms.framework.common.pojo.CommonResult.success; import static com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -48,6 +52,8 @@ public class UserProfileController { private PermissionService permissionService; @Resource private RoleService roleService; + @Resource + private UserManagementRelationService userManagementRelationService; @GetMapping("/get") @Operation(summary = "获得登录用户信息") @@ -67,6 +73,18 @@ public class UserProfileController { return success(UserConvert.INSTANCE.convert(user, userRoles, dept, position)); } + @GetMapping("/direct-manager") + @Operation(summary = "获得当前登录用户的直属上级") + public CommonResult getLoginUserDirectManager() { + AdminUserDO manager = userManagementRelationService.getDirectManager(getLoginUserId()); + if (manager == null) { + return success(null); + } + List deptIds = manager.getDeptId() == null ? Collections.emptyList() : List.of(manager.getDeptId()); + Map deptMap = deptService.getDeptMap(deptIds); + return success(UserConvert.INSTANCE.convertSimpleList(List.of(manager), deptMap).get(0)); + } + @PutMapping("/update") @Operation(summary = "修改用户个人信息") public CommonResult updateUserProfile(@Valid @RequestBody UserProfileUpdateReqVO reqVO) { diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/dict/DictDataDO.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/dict/DictDataDO.java index 2062cca..cb17ea4 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/dict/DictDataDO.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/dataobject/dict/DictDataDO.java @@ -51,6 +51,7 @@ public class DictDataDO extends BaseDO { * * 对应到 element-ui 为 default、primary、success、info、warning、danger */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) private String colorType; /** * css 样式 diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/user/UserManagementRelationMapper.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/user/UserManagementRelationMapper.java index 094b33c..4f8495c 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/user/UserManagementRelationMapper.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/dal/mysql/user/UserManagementRelationMapper.java @@ -73,4 +73,23 @@ public interface UserManagementRelationMapper extends BaseMapperX selectValidListBySubordinateUserId(Long subordinateUserId) { + LocalDateTime now = LocalDateTime.now(); + return selectList(new LambdaQueryWrapperX() + .eq(UserManagementRelationDO::getSubordinateUserId, subordinateUserId) + // (from IS NULL OR from <= now) + .and(w -> w.isNull(UserManagementRelationDO::getEffectiveFrom) + .or().le(UserManagementRelationDO::getEffectiveFrom, now)) + // (until IS NULL OR until >= now) + .and(w -> w.isNull(UserManagementRelationDO::getEffectiveUntil) + .or().ge(UserManagementRelationDO::getEffectiveUntil, now)) + .orderByDesc(UserManagementRelationDO::getId)); + } + } diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationService.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationService.java index 8c29891..f304f87 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationService.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationService.java @@ -89,6 +89,14 @@ public interface UserManagementRelationService { */ List getRelationListBySubordinateUserId(Long subordinateUserId); + /** + * 获得某用户当前生效的直属上级 + * + * @param userId 用户ID + * @return 直属上级用户,不存在则返回 null + */ + AdminUserDO getDirectManager(Long userId); + /** * 获得用户管理链路树形结构 * diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationServiceImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationServiceImpl.java index a0a14d5..27d61b8 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationServiceImpl.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/UserManagementRelationServiceImpl.java @@ -270,7 +270,33 @@ public class UserManagementRelationServiceImpl implements UserManagementRelation */ @Override public List getRelationListBySubordinateUserId(Long subordinateUserId) { - return userManagementRelationMapper.selectListBySubordinateUserId(subordinateUserId); + if (subordinateUserId == null) { + return Collections.emptyList(); + } + return userManagementRelationMapper.selectValidListBySubordinateUserId(subordinateUserId); + } + + /** + * 获得某用户当前生效的直属上级 + * + * @param userId 用户ID + * @return 直属上级用户,不存在则返回 null + */ + @Override + public AdminUserDO getDirectManager(Long userId) { + List relations = getRelationListBySubordinateUserId(userId); + if (CollUtil.isEmpty(relations)) { + return null; + } + Long managerUserId = relations.get(0).getManagerUserId(); + if (managerUserId == null) { + return null; + } + AdminUserDO manager = adminUserService.getUser(managerUserId); + if (!adminUserService.isUserAvailable(manager)) { + return null; + } + return manager; } /**