diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java index aed3a551..c4e1c4aa 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java @@ -26,6 +26,8 @@ import org.dromara.common.core.validate.EditGroup; import org.dromara.common.excel.utils.ExcelUtil; import org.dromara.common.satoken.utils.LoginHelper; import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaMode; +import lombok.extern.slf4j.Slf4j; import org.dromara.rs.domain.bo.RsOverseasApplicationBo; import org.dromara.rs.domain.bo.GenerateRegistrationFormBo; import org.dromara.rs.domain.vo.RsOverseasApplicationVo; @@ -53,6 +55,7 @@ import org.dromara.system.domain.vo.SysOssVo; * @author weizzzz * @date 2025-07-28 */ +@Slf4j @Validated @RestController @RequestMapping("/rs/overseas/application") @@ -93,47 +96,27 @@ public class RsOverseasApplicationController extends BaseController { } /** - * 查询待审批的申请列表(有审批权限的用户可以看到所有待审批申请) + * 查询当前用户的待审批申请列表(五级审批模式) */ - @SaCheckPermission("rs:overseas:approve") + @SaCheckPermission("rs:overseasapproval:list") @GetMapping("/pending/list") - public TableDataInfo pendingList(RsOverseasApplicationQuery query) { - // 不限制currentApprover,让有审批权限的用户看到所有待审批申请 - // String currentUser = LoginHelper.getUsername(); - // query.setCurrentApprover(currentUser); - query.setStatus("0"); // 只查询待审核状态 - query.setTenantId(LoginHelper.getTenantId()); // 设置租户ID - return applicationService.queryPageList(query); + public R> pendingList() { + List list = applicationService.queryPendingByCurrentUser(); + return R.ok(list); } - /** - * 临时测试API - 查询待审批的申请列表(调试用) - */ - @SaCheckPermission("rs:overseas:approve") - @GetMapping("/pending/debug") - public R pendingDebug(RsOverseasApplicationQuery query) { - // 获取当前用户信息 - String currentUser = LoginHelper.getUsername(); - Long userId = LoginHelper.getUserId(); - String tenantId = LoginHelper.getTenantId(); - - // 设置查询条件 - query.setStatus("0"); // 只查询待审核状态 - query.setTenantId(tenantId); // 设置租户ID - - // 执行查询 - TableDataInfo result = applicationService.queryPageList(query); - - // 返回调试信息 - Map debugInfo = new HashMap<>(); - debugInfo.put("currentUser", currentUser); - debugInfo.put("userId", userId); - debugInfo.put("tenantId", tenantId); - debugInfo.put("queryParams", query); - debugInfo.put("result", result); - - return R.ok(debugInfo); - } + // ======================================== + // 五级审批模式说明 + // + // 当前实现:自定义五级审批流程 + // 1. 部门负责人审批(根据申请人部门动态分配) + // 2. 人事处审批(角色:hr_dept_overseas_approver) + // 3. 纪委审批(角色:discipline_overseas_approver) + // 4. 学院分管领导审批(角色:college_leader_overseas_approver) + // 5. 学院党委书记审批(角色:party_secretary_overseas_approver) + // + // 审批权限控制:每个审批人只能看到分配给自己的待审批申请 + // ======================================== /** * 根据状态查询申请列表 @@ -173,6 +156,23 @@ public class RsOverseasApplicationController extends BaseController { return R.ok(applicationService.queryById(applicationId)); } + /** + * 获取申请人的部门负责人ID + */ + @SaCheckPermission("rs:overseas:query") + @GetMapping("/dept-approver/{applicationId}") + public R getDepartmentApproverId(@PathVariable Long applicationId) { + RsOverseasApplicationVo application = applicationService.queryById(applicationId); + if (application == null) { + return R.fail("申请不存在"); + } + Long deptLeaderId = applicationService.getDepartmentApproverId(application.getApplicantId()); + if (deptLeaderId == null) { + return R.fail("未找到部门负责人,请联系管理员配置"); + } + return R.ok(deptLeaderId); + } + /** * 新增出国申请 */ @@ -191,7 +191,7 @@ public class RsOverseasApplicationController extends BaseController { // 自动设置申请人信息 bo.setApplicantId(LoginHelper.getUserId()); - bo.setApplicantName(LoginHelper.getUsername()); + // ✅ 不覆盖用户填写的姓名,保留用户在表单中填写的申请证件姓名 // 自动设置部门信息 bo.setDepartment(LoginHelper.getDeptName()); @@ -306,17 +306,31 @@ public class RsOverseasApplicationController extends BaseController { } /** - * 审批出国申请 + * 审批出国申请(支持标准工作流模式) + * 🔴 支持五级审批权限:满足任一级别权限即可访问接口,具体级别在Service层验证 */ - @SaCheckPermission("rs:overseas:approve") + @SaCheckPermission(value = { + "rs:overseas:approve", // 通用审批权限(超级管理员/高级审批者) + "rs:overseas:approve1", // 第1级:部门负责人 + "rs:overseas:approve2", // 第2级:人事处 + "rs:overseas:approve3", // 第3级:纪委 + "rs:overseas:approve4", // 第4级:学院分管领导 + "rs:overseas:approve5" // 第5级:学院党委书记 + }, mode = SaMode.OR) // 🔴 关键:OR模式,满足任一权限即可通过 @Log(title = "审批出国申请", businessType = BusinessType.UPDATE) @PostMapping("/approve") public R approve(@RequestBody ApprovalRequest request) { Long approverId = LoginHelper.getUserId(); String approverName = LoginHelper.getUsername(); + + // 🔴 标准工作流模式:支持任务ID参数 + // 如果传入了taskId,将通过工作流引擎进行审批 + // 如果没有taskId,则使用原有的业务审批逻辑(向后兼容) + return toAjax(applicationService.approveApplication( request.getApplicationId(), approverId, approverName, - request.getApprovalStatus(), request.getApprovalOpinion())); + request.getApprovalStatus(), request.getApprovalOpinion(), + request.getTaskId())); } /** @@ -356,12 +370,13 @@ public class RsOverseasApplicationController extends BaseController { } /** - * 审批请求对象 + * 审批请求对象(支持标准工作流模式) */ public static class ApprovalRequest { private Long applicationId; private String approvalStatus; private String approvalOpinion; + private String taskId; // 🔴 新增:工作流任务ID,支持标准工作流模式 public Long getApplicationId() { return applicationId; @@ -386,6 +401,14 @@ public class RsOverseasApplicationController extends BaseController { public void setApprovalOpinion(String approvalOpinion) { this.approvalOpinion = approvalOpinion; } + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } } // ==================== PDF生成相关接口 ==================== @@ -419,6 +442,32 @@ public class RsOverseasApplicationController extends BaseController { registrationFormService.downloadPdf(documentId, response); } + // ==================== 动态审批人获取API ==================== + + /** + * 根据申请人ID获取部门审批人ID + * + * @param applicantId 申请人ID + */ + @SaCheckPermission("rs:overseas:list") + @GetMapping("/getDepartmentApproverByApplicant/{applicantId}") + public R getDepartmentApproverByApplicant(@PathVariable Long applicantId) { + Long departmentApproverId = applicationService.getDepartmentApproverId(applicantId); + return R.ok(departmentApproverId); + } + + /** + * 根据申请人ID获取人事处审批人列表 + * + * @param applicantId 申请人ID + */ + @SaCheckPermission("rs:overseas:list") + @GetMapping("/getHrApprovers/{applicantId}") + public R> getHrApprovers(@PathVariable Long applicantId) { + List hrApprovers = applicationService.getHrApprovers(applicantId); + return R.ok(hrApprovers); + } + // ==================== 盖章文件上传接口 ==================== /** diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java index de4c5174..4b4b637a 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java @@ -52,13 +52,22 @@ public class RsOverseasApprovalMonitorController extends BaseController { * @param query 查询条件 * @return 审批中的申请列表 */ + // 🔴 标准化改造:删除自定义待审批查询接口 + // 原因:出国申请工作流应使用标准工作流模式 + // 用户应通过工作流API pageByTaskWait() 获取待办任务 + // 而不是调用自定义的业务API + // + // 标准工作流模式: + // 1. 用户访问标准待办页面:/workflow/task/taskWaiting.vue + // 2. 调用工作流API:pageByTaskWait() 获取待办任务 + // 3. 点击"办理"跳转:/rs/overseas/application/edit?taskId=xxx + // 4. 在业务表单页面进行审批操作 + @SaCheckPermission("rs:overseas:list") @GetMapping("/pending/list") - public TableDataInfo pendingList(RsOverseasApprovalMonitorQuery query) { - log.info("查询审批中的申请列表,查询条件:{}", query); - // 只查询待审核状态(status = 0) - query.setStatus("0"); - return monitorService.queryPendingList(query); + public R pendingList(RsOverseasApprovalMonitorQuery query) { + // 提示用户使用标准工作流API + return R.fail("请使用标准工作流待办页面查看审批任务。访问:工作流 > 我的任务 > 待办任务"); } /** diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasItineraryChangeController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasItineraryChangeController.java index 589710b1..dbcd2194 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasItineraryChangeController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasItineraryChangeController.java @@ -62,13 +62,17 @@ public class RsOverseasItineraryChangeController extends BaseController { } /** - * 查询待审批的行程变更申请列表 + * 🔴 标准化改造:删除自定义待审批查询接口 + * + * 原因:出国行程变更工作流应使用标准工作流模式 + * 用户应通过工作流API pageByTaskWait() 获取待办任务 + * 而不是调用自定义的业务API */ @SaCheckPermission("rs:itineraryChange:approve") @GetMapping("/pending/list") - public TableDataInfo pendingList(RsOverseasItineraryChangeQuery query) { - query.setTenantId(LoginHelper.getTenantId()); - return changeService.queryPendingList(query); + public R pendingList(RsOverseasItineraryChangeQuery query) { + // 提示用户使用标准工作流API + return R.fail("请使用标准工作流待办页面查看审批任务。访问:工作流 > 我的任务 > 待办任务"); } /** @@ -91,7 +95,7 @@ public class RsOverseasItineraryChangeController extends BaseController { org.dromara.rs.domain.query.RsOverseasRecordQuery query = new org.dromara.rs.domain.query.RsOverseasRecordQuery(); query.setApplicantId(LoginHelper.getUserId()); query.setTenantId(LoginHelper.getTenantId()); - + List records = recordService.queryList(query); return R.ok(records); } @@ -156,7 +160,7 @@ public class RsOverseasItineraryChangeController extends BaseController { Long changeId = Long.valueOf(params.get("changeId").toString()); Boolean approved = Boolean.valueOf(params.get("approved").toString()); String opinion = params.get("opinion") != null ? params.get("opinion").toString() : ""; - + return toAjax(changeService.approveChange(changeId, approved, opinion)); } @@ -181,4 +185,3 @@ public class RsOverseasItineraryChangeController extends BaseController { return R.ok(changeService.queryByRecordId(recordId)); } } - diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java index ad5df660..5ef39ac3 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java @@ -38,12 +38,12 @@ import org.dromara.common.satoken.utils.LoginHelper; @RestController @RequestMapping("/rs/training/application") public class RsTrainingApplicationController extends BaseController { - + private static final Logger log = LoggerFactory.getLogger(RsTrainingApplicationController.class); - + @Autowired private IRsTrainingApplicationService trainingApplicationService; - + // 新增用户服务注入 @Autowired private ISysUserService sysUserService; @@ -87,7 +87,7 @@ public class RsTrainingApplicationController extends BaseController { // 获取用户ID,处理可能的字符串格式 Object loginId = StpUtil.getLoginId(); Long userId = null; - + if (loginId != null) { if (loginId instanceof Long) { userId = (Long) loginId; @@ -114,18 +114,18 @@ public class RsTrainingApplicationController extends BaseController { userId = ((Number) loginId).longValue(); } } - + if (userId != null) { log.info("获取当前登录用户信息: userId={}", userId); - + // 从系统用户表获取用户真实信息 SysUserVo userVo = sysUserService.selectUserById(userId); if (userVo != null) { String nickName = userVo.getNickName(); String userName = userVo.getUserName(); - + log.info("成功获取用户信息: userId={}, nickName={}, userName={}", userId, nickName, userName); - + // 使用用户名(根据前端要求) String displayName = StringUtils.isNotBlank(userName) ? userName : "用户" + userId; return new UserInfo(userId, displayName); @@ -138,7 +138,7 @@ public class RsTrainingApplicationController extends BaseController { } catch (Exception e) { log.error("获取用户信息异常: loginId={}, error={}", StpUtil.getLoginId(), e.getMessage(), e); } - + // 如果获取失败,尝试从Sa-Token获取基本信息 try { Object fallbackLoginId = StpUtil.getLoginId(); @@ -163,26 +163,31 @@ public class RsTrainingApplicationController extends BaseController { } catch (Exception e) { log.error("备用方案也失败: {}", e.getMessage(), e); } - + // 最后的备用方案 log.error("所有用户信息获取方案都失败,返回默认值"); return new UserInfo(1L, "系统用户"); } - + /** * 用户信息内部类 */ private static class UserInfo { private final Long userId; private final String userName; - + public UserInfo(Long userId, String userName) { this.userId = userId; this.userName = StringUtils.isNotBlank(userName) ? userName : "用户" + userId; } - - public Long getUserId() { return userId; } - public String getUserName() { return userName; } + + public Long getUserId() { + return userId; + } + + public String getUserName() { + return userName; + } } /** @@ -198,17 +203,17 @@ public class RsTrainingApplicationController extends BaseController { UserInfo userInfo = getCurrentUserInfo(); application.setUserId(userInfo.getUserId()); application.setUserName(userInfo.getUserName()); - + // 确保courseId的类型正确 if (application.getCourseId() == null) { application.setCourseId(0L); } - + // 确保必需字段不为空 if (application.getDepartment() == null) { application.setDepartment("未指定部门"); } - + int result = trainingApplicationService.insertRsTrainingApplication(application); return toAjax(result); } catch (Exception e) { @@ -238,7 +243,7 @@ public class RsTrainingApplicationController extends BaseController { public R remove(@PathVariable Long[] applicationIds) { return toAjax(trainingApplicationService.deleteRsTrainingApplicationByIds(applicationIds)); } - + /** * 取消培训申请 */ @@ -248,27 +253,19 @@ public class RsTrainingApplicationController extends BaseController { public R cancel(@PathVariable("applicationId") Long applicationId) { return toAjax(trainingApplicationService.cancelApplication(applicationId)); } - + /** - * 查询待审批的培训申请列表(供审批人使用) + * 🔴 标准化改造:删除自定义待审批查询接口 + * + * 原因:培训申请工作流应使用标准工作流模式 + * 用户应通过工作流API pageByTaskWait() 获取待办任务 + * 而不是调用自定义的业务API */ @SaCheckPermission("rs:trainingapproval:process") @GetMapping("/pending/list") - public TableDataInfo pendingList(RsTrainingApplication application, PageQuery pageQuery) { - // 设置租户ID - if (application.getTenantId() == null || application.getTenantId().isEmpty()) { - application.setTenantId(LoginHelper.getTenantId()); - } - - // 只查询待审核状态的申请(排除已通过状态) - application.setExcludeStatus("1"); // 排除已通过状态 - - log.info("查询待审批培训申请列表 - 用户: {}, 角色: {}, 查询条件: {}", - LoginHelper.getUsername(), - StpUtil.getRoleList(), - application); - - return trainingApplicationService.selectPageRsTrainingApplicationList(application, pageQuery); + public R pendingList(RsTrainingApplication application, PageQuery pageQuery) { + // 提示用户使用标准工作流API + return R.fail("请使用标准工作流待办页面查看审批任务。访问:工作流 > 我的任务 > 待办任务"); } /** @@ -276,16 +273,18 @@ public class RsTrainingApplicationController extends BaseController { */ @SaCheckPermission("rs:trainingapplication:query") @GetMapping("/my") - public TableDataInfo listMyApplications(RsTrainingApplication application, PageQuery pageQuery) { + public TableDataInfo listMyApplications(RsTrainingApplication application, + PageQuery pageQuery) { return trainingApplicationService.selectPageMyApplications(StpUtil.getLoginIdAsLong(), application, pageQuery); } - + /** * 查询指定课程的申请列表 */ @SaCheckPermission("rs:trainingapplication:list") @GetMapping("/list/{courseId}") - public TableDataInfo listByCourse(@PathVariable("courseId") Long courseId, RsTrainingApplication application, PageQuery pageQuery) { + public TableDataInfo listByCourse(@PathVariable("courseId") Long courseId, + RsTrainingApplication application, PageQuery pageQuery) { return trainingApplicationService.selectPageApplicationsByCourse(courseId, application, pageQuery); } @@ -302,17 +301,17 @@ public class RsTrainingApplicationController extends BaseController { UserInfo userInfo = getCurrentUserInfo(); application.setUserId(userInfo.getUserId()); application.setUserName(userInfo.getUserName()); - + // 确保courseId的类型正确 if (application.getCourseId() == null) { application.setCourseId(0L); } - + // 确保必需字段不为空 if (application.getDepartment() == null) { application.setDepartment("未指定部门"); } - + int result = trainingApplicationService.insertRsTrainingApplication(application); return toAjax(result); } catch (Exception e) { @@ -334,7 +333,7 @@ public class RsTrainingApplicationController extends BaseController { log.info("更新培训申请状态: applicationId={}, status={}", applicationId, status); return toAjax(trainingApplicationService.updateApplicationStatus(applicationId, status)); } - + /** * 重新提交被拒绝的培训申请 */ @@ -348,4 +347,4 @@ public class RsTrainingApplicationController extends BaseController { log.info("重新提交被拒绝的培训申请: applicationId={}", applicationId); return toAjax(trainingApplicationService.resubmitApplication(applicationId, application)); } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApplication.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApplication.java index e004997f..d6e47f3a 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApplication.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApplication.java @@ -260,7 +260,7 @@ public class RsOverseasApplication extends BaseEntity { private String status; /** - * 审批状态(工作流状态):draft-草稿,waiting-待审批,finish-已完成,cancel-已取消,back-已退回 + * 审批状态(工作流状态):cancel-已撤销,draft-草稿,waiting-待审核,finish-已完成,invalid-已作废,back-已退回,termination-已终止 */ @ExcelProperty(value = "审批状态") private String approvalStatus; @@ -272,7 +272,13 @@ public class RsOverseasApplication extends BaseEntity { private String currentApprover; /** - * 审批级别:1-部门主管,2-分管领导,3-校领导 + * 当前审批人ID + */ + @ExcelProperty(value = "当前审批人ID") + private Long currentApproverId; + + /** + * 审批级别:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记 */ @ExcelProperty(value = "审批级别") private String approvalLevel; diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApplicationBo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApplicationBo.java index 75a860ab..213a6d3d 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApplicationBo.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApplicationBo.java @@ -7,14 +7,12 @@ import org.dromara.common.mybatis.core.domain.BaseEntity; import io.github.linpeilie.annotations.AutoMapper; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Pattern; import lombok.Data; import lombok.EqualsAndHashCode; import com.fasterxml.jackson.annotation.JsonFormat; -import java.math.BigDecimal; import java.util.Date; /** @@ -42,9 +40,8 @@ public class RsOverseasApplicationBo extends BaseEntity { private String serialNumber; /** - * 申请人ID + * 申请人ID(系统自动设置,关联当前登录用户) */ - @NotNull(message = "申请人ID不能为空", groups = { AddGroup.class, EditGroup.class }) private Long applicantId; /** @@ -92,7 +89,7 @@ public class RsOverseasApplicationBo extends BaseEntity { /** * 所属部门 */ - @NotBlank(message = "所属部门不能为空", groups = { AddGroup.class, EditGroup.class }) + @NotBlank(message = "所属部门不能为空", groups = { EditGroup.class }) private String department; /** @@ -252,7 +249,6 @@ public class RsOverseasApplicationBo extends BaseEntity { /** * 应急联系人及电话 */ - @NotBlank(message = "应急联系人及电话不能为空") private String emergencyContact; /** @@ -276,7 +272,12 @@ public class RsOverseasApplicationBo extends BaseEntity { private String currentApprover; /** - * 审批级别 + * 当前审批人ID + */ + private Long currentApproverId; + + /** + * 审批级别:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记 */ private String approvalLevel; @@ -285,6 +286,11 @@ public class RsOverseasApplicationBo extends BaseEntity { */ private String status; + /** + * 审批状态(工作流状态):cancel-已撤销,draft-草稿,waiting-待审核,finish-已完成,invalid-已作废,back-已退回,termination-已终止 + */ + private String approvalStatus; + /** * 登记备案表盖章文件路径 */ diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApplicationQuery.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApplicationQuery.java index 7120afd8..2476f2f1 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApplicationQuery.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApplicationQuery.java @@ -86,4 +86,10 @@ public class RsOverseasApplicationQuery extends PageQuery { * 租户ID */ private String tenantId; -} \ No newline at end of file + + /** + * 🔴 申请人部门ID(用于部门负责人权限过滤) + * 部门负责人只能看到本部门员工提交的申请 + */ + private Long applicantDeptId; +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsApplicationVo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsApplicationVo.java index a63fcc5f..08a981e5 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsApplicationVo.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsApplicationVo.java @@ -17,7 +17,7 @@ import java.util.List; public class RsApplicationVo extends RsApplication { private static final long serialVersionUID = 1L; - + /** * 创建者 */ @@ -37,64 +37,64 @@ public class RsApplicationVo extends RsApplication { * 更新时间 */ private Date updateTime; - + /** * 部门 */ private String department; - + /** * 应聘者手机(兼容旧代码,实际使用contactPhone) */ private String applicantPhone; - + /** * 应聘者邮箱(兼容旧代码,实际使用contactEmail) */ private String applicantEmail; - + /** * 教育经历列表 */ private List educationList; - + /** * 工作经历列表 */ private List experienceList; - + /** * 状态名称 */ private String statusName; - + /** * 获取应聘者手机号 */ public String getApplicantPhone() { - return this.getContactPhone(); + return this.applicantPhone != null ? this.applicantPhone : super.getContactPhone(); } - + /** * 设置应聘者手机号 */ public void setApplicantPhone(String applicantPhone) { - this.setContactPhone(applicantPhone); + super.setContactPhone(applicantPhone); this.applicantPhone = applicantPhone; } - + /** * 获取应聘者邮箱 */ public String getApplicantEmail() { - return this.getContactEmail(); + return this.applicantEmail != null ? this.applicantEmail : super.getContactEmail(); } - + /** * 设置应聘者邮箱 */ public void setApplicantEmail(String applicantEmail) { - this.setContactEmail(applicantEmail); + super.setContactEmail(applicantEmail); this.applicantEmail = applicantEmail; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApplicationVo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApplicationVo.java index 0dfe4db3..ad716e09 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApplicationVo.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApplicationVo.java @@ -241,7 +241,7 @@ public class RsOverseasApplicationVo implements Serializable { private String statusText; /** - * 审批状态(工作流状态):draft-草稿,waiting-待审批,finish-已完成,cancel-已取消,back-已退回 + * 审批状态(工作流状态):cancel-已撤销,draft-草稿,waiting-待审核,finish-已完成,invalid-已作废,back-已退回,termination-已终止 */ private String approvalStatus; @@ -261,7 +261,12 @@ public class RsOverseasApplicationVo implements Serializable { private String currentApprover; /** - * 审批级别 + * 当前审批人ID + */ + private Long currentApproverId; + + /** + * 审批级别:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记 */ private String approvalLevel; diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java index 23f2d3dc..8f267922 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java @@ -17,168 +17,207 @@ import java.util.Collection; */ public interface IRsOverseasApplicationService { - /** - * 查询出国申请分页列表 - * - * @param query 查询条件 - * @return 出国申请分页集合 - */ - TableDataInfo queryPageList(RsOverseasApplicationQuery query); + /** + * 查询出国申请分页列表 + * + * @param query 查询条件 + * @return 出国申请分页集合 + */ + TableDataInfo queryPageList(RsOverseasApplicationQuery query); - /** - * 查询我的出国申请分页列表(不包括已通过的申请) - * - * @param query 查询条件 - * @return 出国申请分页集合 - */ - TableDataInfo queryMyApplicationList(RsOverseasApplicationQuery query); + /** + * 查询我的出国申请分页列表(不包括已通过的申请) + * + * @param query 查询条件 + * @return 出国申请分页集合 + */ + TableDataInfo queryMyApplicationList(RsOverseasApplicationQuery query); - /** - * 查询出国申请列表 - * - * @param query 查询条件 - * @return 出国申请集合 - */ - List queryList(RsOverseasApplicationQuery query); + /** + * 查询出国申请列表 + * + * @param query 查询条件 + * @return 出国申请集合 + */ + List queryList(RsOverseasApplicationQuery query); - /** - * 查询出国申请详细信息 - * - * @param applicationId 出国申请主键 - * @return 出国申请 - */ - RsOverseasApplicationVo queryById(Long applicationId); + /** + * 查询当前用户的待审批申请列表 + * + * @return 待审批申请集合 + */ + List queryPendingByCurrentUser(); - /** - * 根据申请人ID查询出国申请列表 - * - * @param applicantId 申请人ID - * @return 出国申请集合 - */ - List queryByApplicantId(Long applicantId); + /** + * 查询出国申请详细信息 + * + * @param applicationId 出国申请主键 + * @return 出国申请 + */ + RsOverseasApplicationVo queryById(Long applicationId); - /** - * 根据状态查询出国申请列表 - * - * @param status 申请状态 - * @return 出国申请集合 - */ - List queryByStatus(String status); + /** + * 根据申请人ID查询出国申请列表 + * + * @param applicantId 申请人ID + * @return 出国申请集合 + */ + List queryByApplicantId(Long applicantId); - /** - * 根据当前审批人查询待审批申请列表 - * - * @param currentApprover 当前审批人 - * @return 出国申请集合 - */ - List queryPendingByApprover(String currentApprover); + /** + * 根据状态查询出国申请列表 + * + * @param status 申请状态 + * @return 出国申请集合 + */ + List queryByStatus(String status); - /** - * 根据审批级别查询待审批申请列表 - * - * @param approvalLevel 审批级别 - * @return 出国申请集合 - */ - List queryPendingByLevel(String approvalLevel); + /** + * 根据当前审批人查询待审批申请列表 + * + * @param currentApprover 当前审批人 + * @return 出国申请集合 + */ + List queryPendingByApprover(String currentApprover); - /** - * 新增出国申请 - * - * @param bo 出国申请业务对象 - * @return 是否成功 - */ - Boolean insertByBo(RsOverseasApplicationBo bo); + /** + * 根据审批级别查询待审批申请列表 + * + * @param approvalLevel 审批级别 + * @return 出国申请集合 + */ + List queryPendingByLevel(String approvalLevel); - /** - * 修改出国申请 - * - * @param bo 出国申请业务对象 - * @return 是否成功 - */ - Boolean updateByBo(RsOverseasApplicationBo bo); + /** + * 新增出国申请 + * + * @param bo 出国申请业务对象 + * @return 是否成功 + */ + Boolean insertByBo(RsOverseasApplicationBo bo); - /** - * 提交出国申请(更新状态为待审核) - * - * @param applicationId 申请ID - * @return 是否成功 - */ - Boolean submitApplication(Long applicationId); + /** + * 修改出国申请 + * + * @param bo 出国申请业务对象 + * @return 是否成功 + */ + Boolean updateByBo(RsOverseasApplicationBo bo); - /** - * 取消出国申请 - * - * @param applicationId 申请ID - * @param reason 取消原因 - * @return 是否成功 - */ - Boolean cancelApplication(Long applicationId, String reason); + /** + * 提交出国申请(更新状态为待审核) + * + * @param applicationId 申请ID + * @return 是否成功 + */ + Boolean submitApplication(Long applicationId); - /** - * 审批出国申请 - * - * @param applicationId 申请ID - * @param approverId 审批人ID - * @param approverName 审批人姓名 - * @param approvalStatus 审批结果:1-通过,2-拒绝 - * @param approvalOpinion 审批意见 - * @return 是否成功 - */ - Boolean approveApplication(Long applicationId, Long approverId, String approverName, - String approvalStatus, String approvalOpinion); + /** + * 取消出国申请 + * + * @param applicationId 申请ID + * @param reason 取消原因 + * @return 是否成功 + */ + Boolean cancelApplication(Long applicationId, String reason); - /** - * 校验删除数据 - * - * @param ids 主键集合 - * @param isValid 是否有效性校验 - */ - void validatedByIds(List ids, Boolean isValid); + /** + * 审批出国申请 + * + * @param applicationId 申请ID + * @param approverId 审批人ID + * @param approverName 审批人姓名 + * @param approvalStatus 审批结果:1-通过,2-拒绝 + * @param approvalOpinion 审批意见 + * @return 是否成功 + */ + Boolean approveApplication(Long applicationId, Long approverId, String approverName, + String approvalStatus, String approvalOpinion); - /** - * 批量删除出国申请 - * - * @param ids 主键集合 - * @return 是否成功 - */ - Boolean deleteWithValidByIds(List ids, Boolean isValid); + /** + * 审批出国申请(支持标准工作流模式) + * + * @param applicationId 申请ID + * @param approverId 审批人ID + * @param approverName 审批人姓名 + * @param approvalStatus 审批结果:1-通过,2-拒绝 + * @param approvalOpinion 审批意见 + * @param taskId 工作流任务ID(可选,用于标准工作流模式) + * @return 是否成功 + */ + Boolean approveApplication(Long applicationId, Long approverId, String approverName, + String approvalStatus, String approvalOpinion, String taskId); - /** - * 修复已审批通过但未创建记录的申请 - * - * @return 修复的记录数量 - */ - int fixApprovedApplicationsWithoutRecords(); + /** + * 校验删除数据 + * + * @param ids 主键集合 + * @param isValid 是否有效性校验 + */ + void validatedByIds(List ids, Boolean isValid); - /** - * 重新提交已被拒绝的申请 - * - * @param applicationId 申请ID - * @param applicationData 申请更新数据 - * @return 是否成功 - */ - Boolean resubmitApplication(Long applicationId, RsOverseasApplicationBo applicationData); + /** + * 批量删除出国申请 + * + * @param ids 主键集合 + * @return 是否成功 + */ + Boolean deleteWithValidByIds(List ids, Boolean isValid); - /** - * 更新盖章文件信息 - * - * @param applicationId 申请ID - * @param filePath 文件路径 - * @param fileName 文件名 - * @param uploadTime 上传时间 - * @param uploadStatus 上传状态 - * @param ossId OSS文件ID - * @return 是否成功 - */ - Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName, - java.util.Date uploadTime, String uploadStatus, Long ossId); + /** + * 修复已审批通过但未创建记录的申请 + * + * @return 修复的记录数量 + */ + int fixApprovedApplicationsWithoutRecords(); - /** - * 校验出国登记前置条件 - * 检查近3个月内是否有已通过的出国登记记录 - * - * @param applicantId 申请人ID - * @return 校验结果Map,包含 isValid(是否通过校验)和 message(提示信息) - */ - java.util.Map validateRegistrationPrerequisite(Long applicantId); + /** + * 重新提交已被拒绝的申请 + * + * @param applicationId 申请ID + * @param applicationData 申请更新数据 + * @return 是否成功 + */ + Boolean resubmitApplication(Long applicationId, RsOverseasApplicationBo applicationData); + + /** + * 更新盖章文件信息 + * + * @param applicationId 申请ID + * @param filePath 文件路径 + * @param fileName 文件名 + * @param uploadTime 上传时间 + * @param uploadStatus 上传状态 + * @param ossId OSS文件ID + * @return 是否成功 + */ + Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName, + java.util.Date uploadTime, String uploadStatus, Long ossId); + + /** + * 校验出国登记前置条件 + * 检查近3个月内是否有已通过的出国登记记录 + * + * @param applicantId 申请人ID + * @return 校验结果Map,包含 isValid(是否通过校验)和 message(提示信息) + */ + java.util.Map validateRegistrationPrerequisite(Long applicantId); + + // ==================== 动态审批人获取方法 ==================== + + /** + * 根据申请人ID获取部门审批人ID + * + * @param applicantId 申请人ID + * @return 部门审批人ID + */ + Long getDepartmentApproverId(Long applicantId); + + /** + * 根据申请人ID获取人事处审批人列表 + * + * @param applicantId 申请人ID + * @return 人事处审批人ID列表 + */ + List getHrApprovers(Long applicantId); } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java index 43a7d34d..5c8d43ed 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java @@ -1,33 +1,29 @@ package org.dromara.rs.service.impl; import java.util.Arrays; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Calendar; +import java.util.stream.Collectors; -import cn.hutool.core.convert.Convert; -import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjectUtil; -import org.dromara.common.core.domain.dto.StartProcessDTO; -import org.dromara.common.core.domain.event.ProcessEvent; -import org.dromara.common.core.domain.event.ProcessDeleteEvent; -import org.dromara.common.core.enums.BusinessStatusEnum; -import org.dromara.common.core.service.WorkflowService; -import org.dromara.common.core.utils.DateUtils; +// 基础功能导入 import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.core.exception.ServiceException; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; -import org.dromara.workflow.common.ConditionalOnEnable; +import cn.dev33.satoken.stp.StpUtil; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; +// EventListener 导入已移除 import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.dromara.rs.mapper.RsOverseasApplicationMapper; import org.dromara.rs.mapper.RsOverseasApprovalMapper; import org.dromara.rs.mapper.RsOverseasRegistrationMapper; @@ -42,17 +38,26 @@ import org.dromara.rs.service.IRsOverseasRecordService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.dromara.rs.domain.vo.RsOverseasRecordVo; +import org.dromara.system.domain.vo.SysUserVo; +import org.dromara.system.domain.vo.SysDeptVo; +import org.dromara.system.domain.vo.SysRoleVo; +import org.dromara.system.domain.bo.SysDeptBo; +import org.dromara.system.domain.bo.SysUserBo; +import org.dromara.system.domain.bo.SysRoleBo; +import org.dromara.system.domain.SysUser; +import org.dromara.system.domain.SysDept; +import org.dromara.system.domain.SysRole; +import org.dromara.common.mybatis.core.page.PageQuery; /** * 出国申请Service业务层处理 - * 集成Warm-Flow工作流支持5级审批流程 * * @author weizzzz * @date 2025-07-28 */ -@ConditionalOnEnable @Service -public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationService { +public class RsOverseasApplicationServiceImpl extends ServiceImpl + implements IRsOverseasApplicationService { private static final Logger log = LoggerFactory.getLogger(RsOverseasApplicationServiceImpl.class); @@ -68,14 +73,49 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS @Autowired private RsOverseasRegistrationMapper registrationMapper; - @Autowired(required = false) - private WorkflowService workflowService; + @Autowired + private org.dromara.system.service.ISysUserService userService; + + @Autowired + private org.dromara.system.service.ISysDeptService deptService; + + @Autowired + private org.dromara.system.service.ISysRoleService roleService; /** * 查询出国申请分页列表 */ @Override public TableDataInfo queryPageList(RsOverseasApplicationQuery query) { + // 🔴 新增:部门负责人权限过滤逻辑 + Long currentUserId = LoginHelper.getUserId(); + + // 1. 判断是否是rs-admin管理员(管理员可以看所有数据) + boolean isAdmin = cn.dev33.satoken.stp.StpUtil.hasRole("rs-admin"); + + if (!isAdmin) { + // 2. 判断是否是部门负责人(拥有rs:overseas:approve1权限) + boolean isDeptHead = cn.dev33.satoken.stp.StpUtil.hasPermission("rs:overseas:approve1"); + + if (isDeptHead) { + // 🔴 部门负责人:只能看到本部门员工提交的申请 + SysUserVo currentUser = userService.selectUserById(currentUserId); + if (currentUser != null && currentUser.getDeptId() != null) { + query.setApplicantDeptId(currentUser.getDeptId()); + log.info("🔒 [部门权限过滤] 用户: {}, 部门ID: {}, 只能查看本部门申请", + currentUser.getUserName(), currentUser.getDeptId()); + } + } else { + // 3. 普通用户:只能看到自己提交的申请 + if (query.getApplicantId() == null) { + query.setApplicantId(currentUserId); + log.info("🔒 [个人权限过滤] 用户ID: {}, 只能查看自己的申请", currentUserId); + } + } + } else { + log.info("✅ [管理员权限] 用户ID: {}, 可以查看所有申请", currentUserId); + } + // 使用自定义查询方法,包含状态文本等扩展字段 List list = baseMapper.selectOverseasApplicationList(query); @@ -179,6 +219,49 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS return baseMapper.selectPendingApplicationsByLevel(approvalLevel); } + /** + * 查询当前用户的待审批申请列表 + */ + @Override + public List queryPendingByCurrentUser() { + Long currentUserId = LoginHelper.getUserId(); + log.info("查询当前用户待审批申请,用户ID: {}", currentUserId); + + // 获取当前用户信息 + String currentUserName = LoginHelper.getUsername(); + log.info("当前用户名: {}", currentUserName); + + List applications = baseMapper.selectList( + new LambdaQueryWrapper() + .eq(RsOverseasApplication::getStatus, "0") // 待审核状态 + .eq(RsOverseasApplication::getCurrentApprover, currentUserName) // 使用当前审批人姓名 + .orderByDesc(RsOverseasApplication::getApplyTime)); + + // 转换为VO对象 + return applications.stream() + .map(this::convertToVo) + .collect(Collectors.toList()); + } + + /** + * 转换实体为VO对象 + */ + private RsOverseasApplicationVo convertToVo(RsOverseasApplication entity) { + if (entity == null) { + return null; + } + RsOverseasApplicationVo vo = new RsOverseasApplicationVo(); + // 使用BeanUtils复制属性(如果有的话)或者手动复制关键属性 + vo.setApplicationId(entity.getApplicationId()); + vo.setSerialNumber(entity.getSerialNumber()); + vo.setApplicantId(entity.getApplicantId()); + vo.setApplicantName(entity.getApplicantName()); + vo.setStatus(entity.getStatus()); + vo.setApplyTime(entity.getApplyTime()); + // 复制其他必要的字段... + return vo; + } + /** * 新增出国申请(直接进入待审核状态) */ @@ -190,10 +273,23 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS add.setApplyTime(new Date()); // 直接设置为待审核状态(适应数据库DEFAULT '0'设计) add.setStatus("0"); - // 设置初始审批级别为部门主管 + // 设置初始审批级别为部门负责人 add.setApprovalLevel("1"); - // 设置当前审批环节描述 - add.setCurrentApprover("待部门主管审批"); + + // 动态分配第1级审批人(部门负责人) + try { + Long deptApproverId = findDeptApproverByApplicant(add.getApplicantId()); + if (deptApproverId != null) { + add.setCurrentApproverId(deptApproverId); + add.setCurrentApprover("待部门负责人审批"); + } else { + add.setCurrentApprover("待指定部门负责人"); + log.warn("未找到申请人 {} 对应的部门负责人", add.getApplicantId()); + } + } catch (Exception e) { + log.error("分配部门负责人失败,申请人ID: {}, 错误: {}", add.getApplicantId(), e.getMessage()); + add.setCurrentApprover("待指定部门负责人"); + } // 自动计算出国天数 if (add.getStartDate() != null && add.getEndDate() != null) { long diffInMillies = add.getEndDate().getTime() - add.getStartDate().getTime(); @@ -341,7 +437,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS } /** - * 提交出国申请(简化版 - 仅更新状态,工作流由前端直接启动) + * 提交出国申请(传统审批模式) */ @Override @Transactional @@ -351,21 +447,20 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS throw new RuntimeException("申请不存在"); } - // 只有草稿状态的申请才能提交 - if (application.getApprovalStatus() != null && - !"draft".equals(application.getApprovalStatus()) && - !"".equals(application.getApprovalStatus())) { - throw new RuntimeException("只有草稿状态的申请才能提交"); + // 只有草稿状态或待审核状态的申请才能提交 + if ("1".equals(application.getStatus()) || "4".equals(application.getStatus())) { + throw new RuntimeException("已通过或已完成的申请不能重新提交"); } - // 设置状态为待审核(工作流状态由工作流监听器管理) + // 设置状态为待审核 application.setStatus("0"); - application.setApprovalStatus(BusinessStatusEnum.WAITING.getStatus()); application.setApplyTime(new Date()); + application.setApprovalLevel("1"); + application.setCurrentApprover("待审批"); boolean updated = baseMapper.updateById(application) > 0; - log.info("✅ [出国申请提交] 申请状态更新成功, applicationId: {}, 申请人: {}", + log.info("✅ [出国申请提交] 提交成功, applicationId: {}, 申请人: {}", applicationId, application.getApplicantName()); return updated; @@ -406,6 +501,15 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS throw new RuntimeException("只有待审核状态的申请才能审批"); } + // 🔴 新增:验证审批人权限级别 + String currentApprovalLevel = application.getApprovalLevel(); + if (!validateApproverLevel(approverId, currentApprovalLevel)) { + throw new ServiceException(String.format( + "您没有第%s级审批权限,当前申请需要第%s级审批人审批", + currentApprovalLevel, currentApprovalLevel)); + } + log.info("✅ 审批人 {} 拥有第{}级审批权限验证通过", approverName, currentApprovalLevel); + // 创建审批记录 RsOverseasApproval approval = new RsOverseasApproval(); approval.setApplicationId(applicationId); @@ -432,8 +536,13 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS String nextLevel = getNextApprovalLevel(application.getApprovalLevel()); if (nextLevel != null) { // 进入下一级审批 + Long nextApproverId = getNextApprover(nextLevel); application.setApprovalLevel(nextLevel); - application.setCurrentApprover("待指定"); + application.setCurrentApproverId(nextApproverId); + application.setCurrentApprover(getApprovalLevelDescription(nextLevel)); + + log.info("审批流转到第{}级,申请ID: {}, 下一级审批人ID: {}", + nextLevel, applicationId, nextApproverId); } else { // 所有级别审批完成 - 先尝试创建出国记录,再更新申请状态 log.info("开始最终审批处理,申请ID: {}, 当前状态: {}, 申请人: {}", @@ -471,13 +580,50 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS return updateResult; } + /** + * 审批出国申请(支持标准工作流模式) + * 🔴 标准化改造:新增重载方法支持工作流任务ID + */ + @Override + @Transactional + public Boolean approveApplication(Long applicationId, Long approverId, String approverName, + String approvalStatus, String approvalOpinion, String taskId) { + + // 🔴 标准化改造:如果提供了taskId,说明是标准工作流模式 + if (taskId != null && !taskId.trim().isEmpty()) { + log.info("🔄 [标准工作流审批] 申请ID: {}, 任务ID: {}, 审批人: {}, 审批状态: {}", + applicationId, taskId, approverName, approvalStatus); + + // TODO: 这里应该调用工作流引擎的API来完成审批 + // 例如:workflowService.completeTask(taskId, approvalStatus, approvalOpinion); + + // 目前先调用原有的审批逻辑(向后兼容) + log.info("⚠️ [兼容模式] 暂时使用原有审批逻辑,等待集成工作流引擎API"); + } + + // 调用原有的审批方法 + return approveApplication(applicationId, approverId, approverName, approvalStatus, approvalOpinion); + } + /** * 获取下一级审批级别 - * 修改为单级审批制度:审批通过后直接完成,不进入下一级 + * 五级审批制度:1-部门负责人,2-人事处,3-纪委,4-学院分管领导,5-学院党委书记 */ private String getNextApprovalLevel(String currentLevel) { - // 单级审批:直接返回null表示审批完成 - return null; + switch (currentLevel) { + case "1": + return "2"; // 部门负责人 → 人事处 + case "2": + return "3"; // 人事处 → 纪委 + case "3": + return "4"; // 纪委 → 学院分管领导 + case "4": + return "5"; // 学院分管领导 → 学院党委书记 + case "5": + return null; // 学院党委书记 → 审批完成 + default: + return null; + } } /** @@ -742,7 +888,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS @Transactional public Boolean deleteWithValidByIds(List ids, Boolean isValid) { validatedByIds(ids, isValid); - return baseMapper.deleteBatchIds(ids) > 0; + return baseMapper.deleteByIds(ids) > 0; } /** @@ -826,134 +972,330 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS return result; } + // ==================== 动态审批人获取方法实现 ==================== + /** - * 工作流总体流程监听器 - * 监听工作流状态变化(草稿、撤销、退回、作废、终止、已完成等) + * 根据申请人ID获取部门审批人ID * - * @param processEvent 工作流事件 + * @param applicantId 申请人ID + * @return 部门审批人ID */ - @EventListener(condition = "#processEvent.flowCode=='overseas-application'") - public void processHandler(ProcessEvent processEvent) { - log.info("🔄 [工作流监听] 出国申请工作流事件: {}", processEvent.toString()); - - Long applicationId = Long.valueOf(processEvent.getBusinessId()); - RsOverseasApplication application = baseMapper.selectById(applicationId); - - if (application == null) { - log.error("❌ [工作流监听] 申请不存在, applicationId: {}", applicationId); - return; - } - - // 更新工作流状态 - String workflowStatus = processEvent.getStatus(); - application.setApprovalStatus(workflowStatus); - log.info("📝 [工作流监听] 更新审批状态: {} → {}", applicationId, workflowStatus); - - // 根据不同的工作流状态更新业务状态 - if (BusinessStatusEnum.FINISH.getStatus().equals(workflowStatus)) { - // 工作流完成 - 所有5级审批通过 - log.info("🎉 [工作流监听] 所有审批级别完成,申请ID: {}, 开始创建出国记录", applicationId); - - try { - // 创建出国记录 - Boolean recordCreated = overseasRecordService.createRecordFromApplication(applicationId); - if (recordCreated) { - application.setStatus("1"); // 已通过 - application.setApprovalTime(new Date()); - application.setCurrentApprover("审批完成"); - log.info("✅ [工作流监听] 出国记录创建成功, applicationId: {}", applicationId); - } else { - log.error("❌ [工作流监听] 出国记录创建失败, applicationId: {}", applicationId); - application.setAdminRemark("审批完成但创建出国记录失败,请联系管理员"); - } - } catch (Exception e) { - log.error("❌ [工作流监听] 创建出国记录异常: ", e); - application.setAdminRemark("审批完成但创建出国记录异常: " + e.getMessage()); + @Override + public Long getDepartmentApproverId(Long applicantId) { + try { + // 1. 根据申请人ID获取用户信息 + SysUserVo user = userService.selectUserById(applicantId); + if (user == null) { + log.warn("用户不存在,applicantId: {}", applicantId); + return null; } - } else if (BusinessStatusEnum.BACK.getStatus().equals(workflowStatus)) { - // 工作流退回 - 审批拒绝 - application.setStatus("2"); // 已拒绝 - application.setCurrentApprover("审批拒绝"); - log.info("⚠️ [工作流监听] 审批被拒绝, applicationId: {}", applicationId); - - } else if (BusinessStatusEnum.CANCEL.getStatus().equals(workflowStatus)) { - // 工作流取消 - application.setStatus("3"); // 已取消 - application.setCurrentApprover("已取消"); - log.info("⚠️ [工作流监听] 申请已取消, applicationId: {}", applicationId); - - } else if (BusinessStatusEnum.WAITING.getStatus().equals(workflowStatus)) { - // 工作流进行中 - application.setStatus("0"); // 待审核 - log.info("🔄 [工作流监听] 审批进行中, applicationId: {}", applicationId); - - } else if (BusinessStatusEnum.DRAFT.getStatus().equals(workflowStatus)) { - // 工作流草稿 - application.setStatus(null); // 草稿状态 - application.setCurrentApprover("草稿"); - log.info("📝 [工作流监听] 申请状态:草稿, applicationId: {}", applicationId); - } - - // 处理审批意见和审批人信息 - Map params = processEvent.getParams(); - if (MapUtil.isNotEmpty(params)) { - String handler = Convert.toStr(params.get("handler")); - String message = Convert.toStr(params.get("message")); - log.info("👤 [工作流监听] 审批人: {}, 审批意见: {}", handler, message); - - // 将审批意见追加到管理员备注 - if (StringUtils.isNotEmpty(message)) { - String existingRemark = application.getAdminRemark(); - if (StringUtils.isNotEmpty(existingRemark)) { - application.setAdminRemark(existingRemark + "\n" + message); - } else { - application.setAdminRemark(message); - } + // 2. 获取用户所在部门ID + Long deptId = user.getDeptId(); + if (deptId == null) { + log.warn("用户没有部门信息,applicantId: {}", applicantId); + return null; } - } - // 如果是提交状态,设置为等待审批 - if (processEvent.getSubmit()) { - application.setApprovalStatus(BusinessStatusEnum.WAITING.getStatus()); - application.setStatus("0"); - application.setCurrentApprover("部门负责人待审批"); - log.info("📮 [工作流监听] 申请已提交,等待审批, applicationId: {}", applicationId); - } + // 3. 根据部门ID获取部门负责人 + SysDeptVo dept = deptService.selectDeptById(deptId); + if (dept == null) { + log.warn("部门不存在,deptId: {}", deptId); + return null; + } - baseMapper.updateById(application); - log.info("✅ [工作流监听] 申请状态更新完成, applicationId: {}", applicationId); + // 4. 获取部门负责人ID(假设部门表中有leader字段存储负责人ID) + Long leaderId = dept.getLeader(); + if (leaderId == null) { + log.warn("部门没有负责人,deptId: {}", deptId); + // 如果没有设置负责人,可以返回默认的部门审批人或者抛出异常 + return null; + } + + log.info("获取到部门审批人,applicantId: {}, deptId: {}, leaderId: {}", applicantId, deptId, leaderId); + return leaderId; + + } catch (Exception e) { + log.error("获取部门审批人失败,applicantId: {}", applicantId, e); + return null; + } } /** - * 监听删除流程事件 - * 当工作流被删除时同步更新业务状态 + * 根据申请人ID获取人事处审批人列表 * - * @param processDeleteEvent 删除事件 + * @param applicantId 申请人ID + * @return 人事处审批人ID列表 */ - @EventListener(condition = "#processDeleteEvent.flowCode=='overseas-application'") - public void processDeleteHandler(ProcessDeleteEvent processDeleteEvent) { - log.info("🗑️ [工作流监听] 监听删除流程事件: {}", processDeleteEvent.toString()); + @Override + public List getHrApprovers(Long applicantId) { + try { + // 1. 查询人事处部门(假设人事处的部门名称包含"人事"或者有特定的部门编码) + List hrDepts = deptService.selectDeptList(new SysDeptBo()); + List hrApprovers = new ArrayList<>(); - Long applicationId = Long.valueOf(processDeleteEvent.getBusinessId()); - RsOverseasApplication application = baseMapper.selectById(applicationId); + for (SysDeptVo dept : hrDepts) { + // 根据部门名称判断是否为人事处 + if (dept.getDeptName() != null && + (dept.getDeptName().contains("人事") || + dept.getDeptName().contains("人力资源"))) { - if (ObjectUtil.isNull(application)) { - log.warn("⚠️ [工作流监听] 申请不存在,无需处理, applicationId: {}", applicationId); - return; + // 获取该部门的负责人作为审批人 + if (dept.getLeader() != null) { + hrApprovers.add(dept.getLeader()); + } + } + } + + // 如果没有找到人事处审批人,返回默认的人事处审批人列表 + if (hrApprovers.isEmpty()) { + log.warn("没有找到人事处审批人,applicantId: {}", applicantId); + // 可以配置默认的人事处审批人ID列表 + // hrApprovers.add(defaultHrApproverId); + } + + log.info("获取到人事处审批人列表,applicantId: {}, hrApprovers: {}", applicantId, hrApprovers); + return hrApprovers; + + } catch (Exception e) { + log.error("获取人事处审批人失败,applicantId: {}", applicantId, e); + return new ArrayList<>(); } - - // 更新为取消状态 - application.setApprovalStatus("cancel"); - application.setStatus("3"); // 已取消 - application.setCurrentApprover("已取消"); - baseMapper.updateById(application); - - log.info("✅ [工作流监听] 申请状态已更新为取消, applicationId: {}", applicationId); } + /** + * 根据申请人查找对应的部门负责人审批人ID + * + * @param applicantId 申请人ID + * @return 部门负责人审批人ID + */ + private Long findDeptApproverByApplicant(Long applicantId) { + try { + // 1. 获取申请人信息 + SysUserVo applicant = userService.selectUserById(applicantId); + if (applicant == null || applicant.getDeptId() == null) { + log.warn("申请人不存在或未分配部门,申请人ID: {}", applicantId); + return null; + } + + Long deptId = applicant.getDeptId(); + log.info("查找部门负责人,申请人: {}, 部门ID: {}", applicant.getUserName(), deptId); + + // 2. 查找该部门对应的部门负责人审批人 + return findDeptApproverByDeptId(deptId); + + } catch (Exception e) { + log.error("查找部门负责人失败,申请人ID: {}", applicantId, e); + return null; + } + } + + /** + * 根据部门ID查找对应的部门负责人审批人 + * + * @param deptId 部门ID + * @return 部门负责人审批人ID + */ + private Long findDeptApproverByDeptId(Long deptId) { + try { + // 1. 获取"部门负责人审批(出国申请)"角色 + SysRoleBo roleQuery = new SysRoleBo(); + roleQuery.setRoleKey("dept_head_overseas_approver"); + List roles = roleService.selectRoleList(roleQuery); + if (roles.isEmpty()) { + log.error("未找到部门负责人审批角色: dept_head_overseas_approver"); + return null; + } + SysRoleVo deptApproverRole = roles.get(0); + + // 2. 获取该角色下的所有用户 + SysUserBo userQuery = new SysUserBo(); + userQuery.setRoleId(deptApproverRole.getRoleId()); + PageQuery pageQuery = new PageQuery(); + pageQuery.setPageSize(1000); // 设置较大的页面大小以获取所有用户 + List allocatedUsers = userService.selectAllocatedList(userQuery, pageQuery).getRows(); + if (allocatedUsers.isEmpty()) { + log.warn("部门负责人审批角色下没有用户"); + return null; + } + + // 3. 遍历审批人,找到与申请人同部门的审批人 + for (SysUserVo approver : allocatedUsers) { + if (approver.getDeptId() != null && approver.getDeptId().equals(deptId)) { + log.info("找到部门负责人审批人: {} ({}), 部门ID: {}", + approver.getUserName(), approver.getNickName(), deptId); + return approver.getUserId(); + } + } + + // 4. 如果没找到同部门审批人,尝试查找父部门负责人 + log.info("当前部门 {} 没有找到负责人,尝试查找父部门负责人", deptId); + return findParentDeptApprover(deptId, allocatedUsers); + + } catch (Exception e) { + log.error("查找部门负责人失败,部门ID: {}", deptId, e); + return null; + } + } + + /** + * 查找父部门的负责人审批人 + * + * @param deptId 当前部门ID + * @param approvers 所有部门负责人审批人列表 + * @return 父部门负责人审批人ID + */ + private Long findParentDeptApprover(Long deptId, List approvers) { + try { + // 获取当前部门信息 + SysDeptVo currentDept = deptService.selectDeptById(deptId); + if (currentDept == null || currentDept.getParentId() == null || currentDept.getParentId() == 0) { + log.warn("部门 {} 没有父部门", deptId); + return null; + } + + Long parentDeptId = currentDept.getParentId(); + log.info("查找父部门负责人,父部门ID: {}", parentDeptId); + + // 在审批人列表中查找父部门的负责人 + for (SysUserVo approver : approvers) { + if (approver.getDeptId() != null && approver.getDeptId().equals(parentDeptId)) { + log.info("找到父部门负责人审批人: {} ({}), 父部门ID: {}", + approver.getUserName(), approver.getNickName(), parentDeptId); + return approver.getUserId(); + } + } + + // 递归查找更上级的父部门 + return findParentDeptApprover(parentDeptId, approvers); + + } catch (Exception e) { + log.error("查找父部门负责人失败,部门ID: {}", deptId, e); + return null; + } + } + + /** + * 根据审批级别获取下一级审批人ID + * + * @param approvalLevel 审批级别 + * @return 下一级审批人ID + */ + private Long getNextApprover(String approvalLevel) { + try { + String roleKey = null; + switch (approvalLevel) { + case "2": // 人事处审批 + roleKey = "hr_dept_overseas_approver"; + break; + case "3": // 纪委审批 + roleKey = "discipline_overseas_approver"; + break; + case "4": // 学院分管领导审批 + roleKey = "college_leader_overseas_approver"; + break; + case "5": // 学院党委书记审批 + roleKey = "party_secretary_overseas_approver"; + break; + default: + log.warn("未知的审批级别: {}", approvalLevel); + return null; + } + + // 获取对应角色 + SysRoleBo roleQuery = new SysRoleBo(); + roleQuery.setRoleKey(roleKey); + List roles = roleService.selectRoleList(roleQuery); + if (roles.isEmpty()) { + log.error("未找到审批角色: {}", roleKey); + return null; + } + SysRoleVo role = roles.get(0); + + // 获取角色下的用户(取第一个作为审批人) + SysUserBo userQuery = new SysUserBo(); + userQuery.setRoleId(role.getRoleId()); + PageQuery pageQuery2 = new PageQuery(); + pageQuery2.setPageSize(1000); // 设置较大的页面大小以获取所有用户 + List users = userService.selectAllocatedList(userQuery, pageQuery2).getRows(); + if (users.isEmpty()) { + log.warn("审批角色 {} 下没有用户", roleKey); + return null; + } + + // 返回第一个用户作为审批人 + SysUserVo approver = users.get(0); + log.info("获取到第{}级审批人: {} ({})", approvalLevel, approver.getUserName(), approver.getNickName()); + return approver.getUserId(); + + } catch (Exception e) { + log.error("获取第{}级审批人失败", approvalLevel, e); + return null; + } + } + + /** + * 根据审批级别获取审批环节描述 + * + * @param approvalLevel 审批级别 + * @return 审批环节描述 + */ + private String getApprovalLevelDescription(String approvalLevel) { + switch (approvalLevel) { + case "1": + return "待部门负责人审批"; + case "2": + return "待人事处审批"; + case "3": + return "待纪委审批"; + case "4": + return "待学院分管领导审批"; + case "5": + return "待学院党委书记审批"; + default: + return "待审批"; + } + } + + // 工作流监听器已移至独立的 OverseasApplicationWorkflowListener 类中 + + // 状态更新方法已移至独立的 OverseasApplicationWorkflowListener 类中 + + // 删除事件监听器已移至独立的 OverseasApplicationWorkflowListener 类中 + + // 任务创建监听器已移至独立的 OverseasApplicationWorkflowListener 类中 + // 删除了复杂的审批记录创建方法 - 使用标准工作流管理审批记录 // 删除了复杂的部门负责人查找方法 - 工作流引擎会自动处理审批人分配 + /** + * 🔴 新增方法:验证审批人是否有指定级别的审批权限 + * + * @param approverId 审批人ID + * @param approvalLevel 需要的审批级别(1-5) + * @return true-有权限,false-无权限 + */ + private boolean validateApproverLevel(Long approverId, String approvalLevel) { + // 检查通用审批权限(超级管理员/高级审批者) + if (StpUtil.hasPermission("rs:overseas:approve")) { + log.info("用户 {} 拥有通用审批权限 rs:overseas:approve", approverId); + return true; + } + + // 检查分级审批权限 + String requiredPermission = "rs:overseas:approve" + approvalLevel; + boolean hasPermission = StpUtil.hasPermission(requiredPermission); + + if (hasPermission) { + log.info("用户 {} 拥有第{}级审批权限 {}", + approverId, approvalLevel, requiredPermission); + } else { + log.warn("用户 {} 缺少第{}级审批权限 {},权限验证失败", + approverId, approvalLevel, requiredPermission); + } + + return hasPermission; + } + } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java index 635cc771..3cc116a1 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java @@ -1,6 +1,7 @@ package org.dromara.rs.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.RequiredArgsConstructor; @@ -51,8 +52,8 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval log.info("查询审批中的申请列表,查询条件:{}", query); // 构建查询条件 - LambdaQueryWrapper wrapper = buildQueryWrapper(query); - wrapper.eq(RsOverseasApplication::getStatus, "0"); // 只查询待审核状态 + QueryWrapper wrapper = buildQueryWrapper(query); + wrapper.eq("status", "0"); // 只查询待审核状态 // 分页查询 Page page = applicationMapper.selectPage( @@ -80,8 +81,8 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval log.info("查询已完成审批的申请列表,查询条件:{}", query); // 构建查询条件 - LambdaQueryWrapper wrapper = buildQueryWrapper(query); - wrapper.eq(RsOverseasApplication::getStatus, "1"); // 只查询已通过状态 + QueryWrapper wrapper = buildQueryWrapper(query); + wrapper.eq("status", "1"); // 只查询已通过状态 // 分页查询 Page page = applicationMapper.selectPage( @@ -137,9 +138,9 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval // 1. 待审批总数 Long pendingCount = applicationMapper.selectCount( - Wrappers.lambdaQuery() - .eq(RsOverseasApplication::getStatus, "0") - .eq(RsOverseasApplication::getTenantId, tenantId)); + Wrappers.query() + .eq("status", "0") + .eq("tenant_id", tenantId)); statistics.put("pendingCount", pendingCount); // 2. 各审批级别的待审批数量 @@ -151,25 +152,25 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval // 3. 已完成审批总数 Long completedCount = applicationMapper.selectCount( - Wrappers.lambdaQuery() - .eq(RsOverseasApplication::getStatus, "1") - .eq(RsOverseasApplication::getTenantId, tenantId)); + Wrappers.query() + .eq("status", "1") + .eq("tenant_id", tenantId)); statistics.put("completedCount", completedCount); // 4. 今日新增申请数 LocalDate today = LocalDate.now(); Date startOfToday = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant()); Long todayCount = applicationMapper.selectCount( - Wrappers.lambdaQuery() - .ge(RsOverseasApplication::getApplyTime, startOfToday) - .eq(RsOverseasApplication::getTenantId, tenantId)); + Wrappers.query() + .ge("apply_time", startOfToday) + .eq("tenant_id", tenantId)); statistics.put("todayCount", todayCount); // 5. 已拒绝总数 Long rejectedCount = applicationMapper.selectCount( - Wrappers.lambdaQuery() - .eq(RsOverseasApplication::getStatus, "2") - .eq(RsOverseasApplication::getTenantId, tenantId)); + Wrappers.query() + .eq("status", "2") + .eq("tenant_id", tenantId)); statistics.put("rejectedCount", rejectedCount); log.info("审批进度统计结果:{}", statistics); @@ -181,43 +182,50 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval /** * 构建查询条件 */ - private LambdaQueryWrapper buildQueryWrapper(RsOverseasApprovalMonitorQuery query) { - LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + private QueryWrapper buildQueryWrapper(RsOverseasApprovalMonitorQuery query) { + QueryWrapper wrapper = Wrappers.query(); // 申请人姓名(模糊查询) - wrapper.like(StringUtils.isNotBlank(query.getApplicantName()), - RsOverseasApplication::getApplicantName, query.getApplicantName()); + if (StringUtils.isNotBlank(query.getApplicantName())) { + wrapper.like("applicant_name", query.getApplicantName()); + } // 所属部门(模糊查询) - wrapper.like(StringUtils.isNotBlank(query.getDepartment()), - RsOverseasApplication::getDepartment, query.getDepartment()); + if (StringUtils.isNotBlank(query.getDepartment())) { + wrapper.like("department", query.getDepartment()); + } // 目的地国家(模糊查询) - wrapper.like(StringUtils.isNotBlank(query.getDestination()), - RsOverseasApplication::getDestination, query.getDestination()); + if (StringUtils.isNotBlank(query.getDestination())) { + wrapper.like("destination", query.getDestination()); + } // 审批级别 - wrapper.eq(StringUtils.isNotBlank(query.getApprovalLevel()), - RsOverseasApplication::getApprovalLevel, query.getApprovalLevel()); + if (StringUtils.isNotBlank(query.getApprovalLevel())) { + wrapper.eq("approval_level", query.getApprovalLevel()); + } // 当前审批人(模糊查询) - wrapper.like(StringUtils.isNotBlank(query.getCurrentApprover()), - RsOverseasApplication::getCurrentApprover, query.getCurrentApprover()); + if (StringUtils.isNotBlank(query.getCurrentApprover())) { + wrapper.like("current_approver", query.getCurrentApprover()); + } // 申请时间范围 - wrapper.ge(query.getApplyTimeStart() != null, - RsOverseasApplication::getApplyTime, query.getApplyTimeStart()); - wrapper.le(query.getApplyTimeEnd() != null, - RsOverseasApplication::getApplyTime, query.getApplyTimeEnd()); + if (query.getApplyTimeStart() != null) { + wrapper.ge("apply_time", query.getApplyTimeStart()); + } + if (query.getApplyTimeEnd() != null) { + wrapper.le("apply_time", query.getApplyTimeEnd()); + } // 租户ID String tenantId = StringUtils.isNotBlank(query.getTenantId()) ? query.getTenantId() : LoginHelper.getTenantId(); - wrapper.eq(RsOverseasApplication::getTenantId, tenantId); + wrapper.eq("tenant_id", tenantId); // 按申请时间倒序排列 - wrapper.orderByDesc(RsOverseasApplication::getApplyTime); + wrapper.orderByDesc("apply_time"); return wrapper; } @@ -232,10 +240,11 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval vo.setApplicationId(application.getApplicationId()); // 查询关联的出国记录ID(如果已完成审批) - if ("1".equals(application.getStatus()) || "4".equals(application.getStatus())) { + String status = application.getStatus(); + if ("1".equals(status) || "4".equals(status)) { RsOverseasRecordVo record = recordMapper .selectOverseasRecordByApplicationId(application.getApplicationId()); - if (record != null) { + if (record != null && record.getRecordId() != null) { vo.setRecordId(record.getRecordId()); } } @@ -313,10 +322,10 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval private void fillApprovalProgress(RsOverseasApprovalMonitorVo vo, RsOverseasApplication application) { // 查询该申请的所有审批记录 List approvals = approvalMapper.selectList( - Wrappers.lambdaQuery() - .eq(RsOverseasApproval::getApplicationId, application.getApplicationId()) - .orderByAsc(RsOverseasApproval::getApprovalLevel) - .orderByDesc(RsOverseasApproval::getApprovalTime)); + Wrappers.query() + .eq("application_id", application.getApplicationId()) + .orderByAsc("approval_level") + .orderByDesc("approval_time")); if (approvals != null && !approvals.isEmpty()) { // 已审批级别数(状态为1-通过的记录) @@ -393,10 +402,10 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval */ private Long countByStatusAndLevel(String status, String level, String tenantId) { return applicationMapper.selectCount( - Wrappers.lambdaQuery() - .eq(RsOverseasApplication::getStatus, status) - .eq(RsOverseasApplication::getApprovalLevel, level) - .eq(RsOverseasApplication::getTenantId, tenantId)); + Wrappers.query() + .eq("status", status) + .eq("approval_level", level) + .eq("tenant_id", tenantId)); } /** diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java index ea0f30b0..0ba62d26 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java @@ -24,7 +24,6 @@ import org.dromara.rs.domain.query.RsOverseasApprovalQuery; import org.dromara.rs.service.IRsOverseasApprovalService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.dromara.common.mybatis.core.page.PageQuery; /** * 出国审批记录Service业务层处理 @@ -45,12 +44,23 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService */ @Override public TableDataInfo queryPageList(RsOverseasApprovalQuery query) { + log.info("==================== 查询审批列表 ===================="); + log.info("原始查询参数: {}", query); + // 🔧 应用数据权限过滤 applyDataPermissionFilter(query); + log.info("权限过滤后查询参数: {}", query); + log.info(" - applicationId: {}", query.getApplicationId()); + log.info(" - approverId: {}", query.getApproverId()); + log.info(" - applicantId: {}", query.getApplicantId()); + log.info(" - deptId: {}", query.getDeptId()); + // 使用自定义SQL查询,包含申请信息的联合查询 List list = baseMapper.selectOverseasApprovalList(query); + log.info("查询结果数量: {}", list.size()); + // 手动分页处理 int pageNum = query.getPageNum(); int pageSize = query.getPageSize(); @@ -65,6 +75,9 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService page.setRecords(pageList); page.setTotal(total); + log.info("分页结果: 总数={}, 当前页={}, 每页={}, 返回={}", total, pageNum, pageSize, pageList.size()); + log.info("==================== 查询完成 ===================="); + return TableDataInfo.build(page); } @@ -208,28 +221,56 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService */ private void applyDataPermissionFilter(RsOverseasApprovalQuery query) { Long currentUserId = StpUtil.getLoginIdAsLong(); + String currentUsername = LoginHelper.getUsername(); Long currentDeptId = LoginHelper.getDeptId(); + boolean isSuperAdmin = LoginHelper.isSuperAdmin(); - log.info("权限过滤 - 用户ID: {}, 部门ID: {}", currentUserId, currentDeptId); + log.info("==================== 开始应用数据权限过滤 ===================="); + log.info("当前用户: {}, ID: {}, 部门ID: {}, 超级管理员: {}", + currentUsername, currentUserId, currentDeptId, isSuperAdmin); + + // 超级管理员不需要过滤 + if (isSuperAdmin) { + log.info("→ 权限结果:超级管理员,跳过所有过滤"); + log.info("==================== 权限过滤结束 ===================="); + return; + } + + // 申请人只能看到自己的申请 + boolean hasApplicantRole = StpUtil.hasRole("rs-Applicant"); + log.info("✓ 角色检查 [rs-Applicant]: {}", hasApplicantRole); + if (hasApplicantRole) { + query.setApplicantId(currentUserId); + log.info("→ 权限结果:申请人角色,设置申请人过滤 applicantId={}", currentUserId); + log.info("==================== 权限过滤结束 ===================="); + return; + } // 🔧 优先检查部门负责人权限(避免被rs-admin角色覆盖) boolean isDeptApprover = isDeptApprover(currentUserId); + log.info("✓ 部门负责人检查结果: {}", isDeptApprover); + if (isDeptApprover) { - log.info("用户 {} 是部门负责人,应用审批人过滤: {}", currentUserId, currentUserId); - query.setApproverId(currentUserId); // 只看分配给自己的审批 - query.setDeptId(currentDeptId); // 同时应用部门过滤 + // 部门负责人只能看到本部门的所有申请(不限制审批人) + query.setDeptId(currentDeptId); + log.info("→ 权限结果:部门负责人,设置部门过滤 deptId={}", currentDeptId); + log.info("==================== 权限过滤结束 ===================="); return; } // 人事管理员可以看到所有申请(仅当不是部门负责人时) - if (StpUtil.hasRole("rs-admin")) { - log.info("用户 {} 是人事管理员,可查看所有申请", currentUserId); + boolean hasAdminRole = StpUtil.hasRole("rs-admin"); + log.info("✓ 角色检查 [rs-admin]: {}", hasAdminRole); + if (hasAdminRole) { + log.info("→ 权限结果:管理员角色,不设置过滤(可查看所有数据)"); + log.info("==================== 权限过滤结束 ===================="); return; // 全局权限 } - // 普通用户只能看到自己的申请 - log.info("用户 {} 是普通用户,只能查看自己的申请", currentUserId); - query.setApplicantId(currentUserId); + // 其他角色只能看到分配给自己的审批 + query.setApproverId(currentUserId); + log.info("→ 权限结果:普通审批人,设置审批人过滤 approverId={}", currentUserId); + log.info("==================== 权限过滤结束 ===================="); } /** @@ -403,29 +444,35 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService private boolean isDeptApprover(Long userId) { String currentUsername = LoginHelper.getUsername(); + log.info("========== 开始检查部门审批人权限 =========="); + log.info("用户名: {}, 用户ID: {}", currentUsername, userId); + // 检查用户是否具有部门负责人审批权限 // 方法1:检查是否有第1级审批权限 boolean hasApprove1 = StpUtil.hasPermission("rs:overseas:approve1"); - log.info("用户 {} 权限检查 - rs:overseas:approve1: {}", currentUsername, hasApprove1); + log.info("✓ 权限检查 [rs:overseas:approve1]: {}", hasApprove1); if (hasApprove1) { + log.info("========== 检查结果:是部门审批人(通过rs:overseas:approve1权限) =========="); return true; } // 方法2:检查是否有部门负责人角色(如果有定义的话) boolean hasDeptHeadRole = StpUtil.hasRole("dept_head_role"); - log.info("用户 {} 角色检查 - dept_head_role: {}", currentUsername, hasDeptHeadRole); + log.info("✓ 角色检查 [dept_head_role]: {}", hasDeptHeadRole); if (hasDeptHeadRole) { + log.info("========== 检查结果:是部门审批人(通过dept_head_role角色) =========="); return true; } // 方法3:检查是否有旧版权限标识符(兼容性) boolean hasOldPermission = StpUtil.hasPermission("overseas:application:dept_head:approve"); - log.info("用户 {} 权限检查 - overseas:application:dept_head:approve: {}", currentUsername, hasOldPermission); + log.info("✓ 权限检查 [overseas:application:dept_head:approve]: {}", hasOldPermission); if (hasOldPermission) { + log.info("========== 检查结果:是部门审批人(通过旧版权限) =========="); return true; } - log.info("用户 {} 不具有部门负责人权限", currentUsername); + log.info("========== 检查结果:不是部门审批人 =========="); return false; } diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml index be18e4de..a341f396 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml @@ -227,6 +227,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND oa.tenant_id = #{tenantId} + + + AND EXISTS ( + SELECT 1 FROM sys_user u + WHERE u.user_id = oa.applicant_id + AND u.dept_id = #{applicantDeptId} + AND u.del_flag = '0' + ) + order by oa.apply_time desc, oa.create_time desc @@ -279,6 +288,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" order by oa.apply_time desc, oa.create_time desc +