“1028审批权限修改v0.1”
This commit is contained in:
1 parent
851ebe86e4
commit
76ee97a90c
21 files changed
+2054
-1370
No files matched your search
+91
-42
@@ -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<RsOverseasApplicationVo> 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<List<RsOverseasApplicationVo>> pendingList() {
|
||||
List<RsOverseasApplicationVo> list = applicationService.queryPendingByCurrentUser();
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 临时测试API - 查询待审批的申请列表(调试用)
|
||||
*/
|
||||
@SaCheckPermission("rs:overseas:approve")
|
||||
@GetMapping("/pending/debug")
|
||||
public R<Object> pendingDebug(RsOverseasApplicationQuery query) {
|
||||
// 获取当前用户信息
|
||||
String currentUser = LoginHelper.getUsername();
|
||||
Long userId = LoginHelper.getUserId();
|
||||
String tenantId = LoginHelper.getTenantId();
|
||||
|
||||
// 设置查询条件
|
||||
query.setStatus("0"); // 只查询待审核状态
|
||||
query.setTenantId(tenantId); // 设置租户ID
|
||||
|
||||
// 执行查询
|
||||
TableDataInfo<RsOverseasApplicationVo> result = applicationService.queryPageList(query);
|
||||
|
||||
// 返回调试信息
|
||||
Map<String, Object> 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<Long> 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<Void> 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<Long> 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<List<Long>> getHrApprovers(@PathVariable Long applicantId) {
|
||||
List<Long> hrApprovers = applicationService.getHrApprovers(applicantId);
|
||||
return R.ok(hrApprovers);
|
||||
}
|
||||
|
||||
// ==================== 盖章文件上传接口 ====================
|
||||
|
||||
/**
|
||||
|
||||
+14
-5
@@ -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<RsOverseasApprovalMonitorVo> pendingList(RsOverseasApprovalMonitorQuery query) {
|
||||
log.info("查询审批中的申请列表,查询条件:{}", query);
|
||||
// 只查询待审核状态(status = 0)
|
||||
query.setStatus("0");
|
||||
return monitorService.queryPendingList(query);
|
||||
public R<String> pendingList(RsOverseasApprovalMonitorQuery query) {
|
||||
// 提示用户使用标准工作流API
|
||||
return R.fail("请使用标准工作流待办页面查看审批任务。访问:工作流 > 我的任务 > 待办任务");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+10
-7
@@ -62,13 +62,17 @@ public class RsOverseasItineraryChangeController extends BaseController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询待审批的行程变更申请列表
|
||||
* 🔴 标准化改造:删除自定义待审批查询接口
|
||||
*
|
||||
* 原因:出国行程变更工作流应使用标准工作流模式
|
||||
* 用户应通过工作流API pageByTaskWait() 获取待办任务
|
||||
* 而不是调用自定义的业务API
|
||||
*/
|
||||
@SaCheckPermission("rs:itineraryChange:approve")
|
||||
@GetMapping("/pending/list")
|
||||
public TableDataInfo<RsOverseasItineraryChangeVo> pendingList(RsOverseasItineraryChangeQuery query) {
|
||||
query.setTenantId(LoginHelper.getTenantId());
|
||||
return changeService.queryPendingList(query);
|
||||
public R<String> 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<RsOverseasRecordVo> 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));
|
||||
}
|
||||
}
|
||||
|
||||
+43
-44
@@ -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<Void> remove(@PathVariable Long[] applicationIds) {
|
||||
return toAjax(trainingApplicationService.deleteRsTrainingApplicationByIds(applicationIds));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 取消培训申请
|
||||
*/
|
||||
@@ -248,27 +253,19 @@ public class RsTrainingApplicationController extends BaseController {
|
||||
public R<Void> cancel(@PathVariable("applicationId") Long applicationId) {
|
||||
return toAjax(trainingApplicationService.cancelApplication(applicationId));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询待审批的培训申请列表(供审批人使用)
|
||||
* 🔴 标准化改造:删除自定义待审批查询接口
|
||||
*
|
||||
* 原因:培训申请工作流应使用标准工作流模式
|
||||
* 用户应通过工作流API pageByTaskWait() 获取待办任务
|
||||
* 而不是调用自定义的业务API
|
||||
*/
|
||||
@SaCheckPermission("rs:trainingapproval:process")
|
||||
@GetMapping("/pending/list")
|
||||
public TableDataInfo<RsTrainingApplication> 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<String> 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<RsTrainingApplication> listMyApplications(RsTrainingApplication application, PageQuery pageQuery) {
|
||||
public TableDataInfo<RsTrainingApplication> listMyApplications(RsTrainingApplication application,
|
||||
PageQuery pageQuery) {
|
||||
return trainingApplicationService.selectPageMyApplications(StpUtil.getLoginIdAsLong(), application, pageQuery);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 查询指定课程的申请列表
|
||||
*/
|
||||
@SaCheckPermission("rs:trainingapplication:list")
|
||||
@GetMapping("/list/{courseId}")
|
||||
public TableDataInfo<RsTrainingApplication> listByCourse(@PathVariable("courseId") Long courseId, RsTrainingApplication application, PageQuery pageQuery) {
|
||||
public TableDataInfo<RsTrainingApplication> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -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;
|
||||
|
||||
+13
-7
@@ -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;
|
||||
|
||||
/**
|
||||
* 登记备案表盖章文件路径
|
||||
*/
|
||||
|
||||
+7
-1
@@ -86,4 +86,10 @@ public class RsOverseasApplicationQuery extends PageQuery {
|
||||
* 租户ID
|
||||
*/
|
||||
private String tenantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔴 申请人部门ID(用于部门负责人权限过滤)
|
||||
* 部门负责人只能看到本部门员工提交的申请
|
||||
*/
|
||||
private Long applicantDeptId;
|
||||
}
|
||||
+16
-16
@@ -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<RsEducationVo> educationList;
|
||||
|
||||
|
||||
/**
|
||||
* 工作经历列表
|
||||
*/
|
||||
private List<RsWorkExperienceVo> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-2
@@ -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;
|
||||
|
||||
|
||||
+185
-146
@@ -17,168 +17,207 @@ import java.util.Collection;
|
||||
*/
|
||||
public interface IRsOverseasApplicationService {
|
||||
|
||||
/**
|
||||
* 查询出国申请分页列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 出国申请分页集合
|
||||
*/
|
||||
TableDataInfo<RsOverseasApplicationVo> queryPageList(RsOverseasApplicationQuery query);
|
||||
/**
|
||||
* 查询出国申请分页列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 出国申请分页集合
|
||||
*/
|
||||
TableDataInfo<RsOverseasApplicationVo> queryPageList(RsOverseasApplicationQuery query);
|
||||
|
||||
/**
|
||||
* 查询我的出国申请分页列表(不包括已通过的申请)
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 出国申请分页集合
|
||||
*/
|
||||
TableDataInfo<RsOverseasApplicationVo> queryMyApplicationList(RsOverseasApplicationQuery query);
|
||||
/**
|
||||
* 查询我的出国申请分页列表(不包括已通过的申请)
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 出国申请分页集合
|
||||
*/
|
||||
TableDataInfo<RsOverseasApplicationVo> queryMyApplicationList(RsOverseasApplicationQuery query);
|
||||
|
||||
/**
|
||||
* 查询出国申请列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryList(RsOverseasApplicationQuery query);
|
||||
/**
|
||||
* 查询出国申请列表
|
||||
*
|
||||
* @param query 查询条件
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryList(RsOverseasApplicationQuery query);
|
||||
|
||||
/**
|
||||
* 查询出国申请详细信息
|
||||
*
|
||||
* @param applicationId 出国申请主键
|
||||
* @return 出国申请
|
||||
*/
|
||||
RsOverseasApplicationVo queryById(Long applicationId);
|
||||
/**
|
||||
* 查询当前用户的待审批申请列表
|
||||
*
|
||||
* @return 待审批申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryPendingByCurrentUser();
|
||||
|
||||
/**
|
||||
* 根据申请人ID查询出国申请列表
|
||||
*
|
||||
* @param applicantId 申请人ID
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryByApplicantId(Long applicantId);
|
||||
/**
|
||||
* 查询出国申请详细信息
|
||||
*
|
||||
* @param applicationId 出国申请主键
|
||||
* @return 出国申请
|
||||
*/
|
||||
RsOverseasApplicationVo queryById(Long applicationId);
|
||||
|
||||
/**
|
||||
* 根据状态查询出国申请列表
|
||||
*
|
||||
* @param status 申请状态
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryByStatus(String status);
|
||||
/**
|
||||
* 根据申请人ID查询出国申请列表
|
||||
*
|
||||
* @param applicantId 申请人ID
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryByApplicantId(Long applicantId);
|
||||
|
||||
/**
|
||||
* 根据当前审批人查询待审批申请列表
|
||||
*
|
||||
* @param currentApprover 当前审批人
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryPendingByApprover(String currentApprover);
|
||||
/**
|
||||
* 根据状态查询出国申请列表
|
||||
*
|
||||
* @param status 申请状态
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryByStatus(String status);
|
||||
|
||||
/**
|
||||
* 根据审批级别查询待审批申请列表
|
||||
*
|
||||
* @param approvalLevel 审批级别
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryPendingByLevel(String approvalLevel);
|
||||
/**
|
||||
* 根据当前审批人查询待审批申请列表
|
||||
*
|
||||
* @param currentApprover 当前审批人
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> queryPendingByApprover(String currentApprover);
|
||||
|
||||
/**
|
||||
* 新增出国申请
|
||||
*
|
||||
* @param bo 出国申请业务对象
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean insertByBo(RsOverseasApplicationBo bo);
|
||||
/**
|
||||
* 根据审批级别查询待审批申请列表
|
||||
*
|
||||
* @param approvalLevel 审批级别
|
||||
* @return 出国申请集合
|
||||
*/
|
||||
List<RsOverseasApplicationVo> 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<Long> 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<Long> 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<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 重新提交已被拒绝的申请
|
||||
*
|
||||
* @param applicationId 申请ID
|
||||
* @param applicationData 申请更新数据
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean resubmitApplication(Long applicationId, RsOverseasApplicationBo applicationData);
|
||||
/**
|
||||
* 批量删除出国申请
|
||||
*
|
||||
* @param ids 主键集合
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(List<Long> 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<String, Object> 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<String, Object> validateRegistrationPrerequisite(Long applicantId);
|
||||
|
||||
// ==================== 动态审批人获取方法 ====================
|
||||
|
||||
/**
|
||||
* 根据申请人ID获取部门审批人ID
|
||||
*
|
||||
* @param applicantId 申请人ID
|
||||
* @return 部门审批人ID
|
||||
*/
|
||||
Long getDepartmentApproverId(Long applicantId);
|
||||
|
||||
/**
|
||||
* 根据申请人ID获取人事处审批人列表
|
||||
*
|
||||
* @param applicantId 申请人ID
|
||||
* @return 人事处审批人ID列表
|
||||
*/
|
||||
List<Long> getHrApprovers(Long applicantId);
|
||||
}
|
||||
+483
-141
@@ -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<RsOverseasApplicationMapper, RsOverseasApplication>
|
||||
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<RsOverseasApplicationVo> 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<RsOverseasApplicationVo> list = baseMapper.selectOverseasApplicationList(query);
|
||||
|
||||
@@ -179,6 +219,49 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS
|
||||
return baseMapper.selectPendingApplicationsByLevel(approvalLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户的待审批申请列表
|
||||
*/
|
||||
@Override
|
||||
public List<RsOverseasApplicationVo> queryPendingByCurrentUser() {
|
||||
Long currentUserId = LoginHelper.getUserId();
|
||||
log.info("查询当前用户待审批申请,用户ID: {}", currentUserId);
|
||||
|
||||
// 获取当前用户信息
|
||||
String currentUserName = LoginHelper.getUsername();
|
||||
log.info("当前用户名: {}", currentUserName);
|
||||
|
||||
List<RsOverseasApplication> applications = baseMapper.selectList(
|
||||
new LambdaQueryWrapper<RsOverseasApplication>()
|
||||
.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<Long> 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<String, Object> 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<Long> getHrApprovers(Long applicantId) {
|
||||
try {
|
||||
// 1. 查询人事处部门(假设人事处的部门名称包含"人事"或者有特定的部门编码)
|
||||
List<SysDeptVo> hrDepts = deptService.selectDeptList(new SysDeptBo());
|
||||
List<Long> 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<SysRoleVo> 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<SysUserVo> 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<SysUserVo> 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<SysRoleVo> 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<SysUserVo> 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;
|
||||
}
|
||||
|
||||
}
|
||||
+53
-44
@@ -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<RsOverseasApplication> wrapper = buildQueryWrapper(query);
|
||||
wrapper.eq(RsOverseasApplication::getStatus, "0"); // 只查询待审核状态
|
||||
QueryWrapper<RsOverseasApplication> wrapper = buildQueryWrapper(query);
|
||||
wrapper.eq("status", "0"); // 只查询待审核状态
|
||||
|
||||
// 分页查询
|
||||
Page<RsOverseasApplication> page = applicationMapper.selectPage(
|
||||
@@ -80,8 +81,8 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
|
||||
log.info("查询已完成审批的申请列表,查询条件:{}", query);
|
||||
|
||||
// 构建查询条件
|
||||
LambdaQueryWrapper<RsOverseasApplication> wrapper = buildQueryWrapper(query);
|
||||
wrapper.eq(RsOverseasApplication::getStatus, "1"); // 只查询已通过状态
|
||||
QueryWrapper<RsOverseasApplication> wrapper = buildQueryWrapper(query);
|
||||
wrapper.eq("status", "1"); // 只查询已通过状态
|
||||
|
||||
// 分页查询
|
||||
Page<RsOverseasApplication> page = applicationMapper.selectPage(
|
||||
@@ -137,9 +138,9 @@ public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApproval
|
||||
|
||||
// 1. 待审批总数
|
||||
Long pendingCount = applicationMapper.selectCount(
|
||||
Wrappers.<RsOverseasApplication>lambdaQuery()
|
||||
.eq(RsOverseasApplication::getStatus, "0")
|
||||
.eq(RsOverseasApplication::getTenantId, tenantId));
|
||||
Wrappers.<RsOverseasApplication>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.<RsOverseasApplication>lambdaQuery()
|
||||
.eq(RsOverseasApplication::getStatus, "1")
|
||||
.eq(RsOverseasApplication::getTenantId, tenantId));
|
||||
Wrappers.<RsOverseasApplication>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.<RsOverseasApplication>lambdaQuery()
|
||||
.ge(RsOverseasApplication::getApplyTime, startOfToday)
|
||||
.eq(RsOverseasApplication::getTenantId, tenantId));
|
||||
Wrappers.<RsOverseasApplication>query()
|
||||
.ge("apply_time", startOfToday)
|
||||
.eq("tenant_id", tenantId));
|
||||
statistics.put("todayCount", todayCount);
|
||||
|
||||
// 5. 已拒绝总数
|
||||
Long rejectedCount = applicationMapper.selectCount(
|
||||
Wrappers.<RsOverseasApplication>lambdaQuery()
|
||||
.eq(RsOverseasApplication::getStatus, "2")
|
||||
.eq(RsOverseasApplication::getTenantId, tenantId));
|
||||
Wrappers.<RsOverseasApplication>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<RsOverseasApplication> buildQueryWrapper(RsOverseasApprovalMonitorQuery query) {
|
||||
LambdaQueryWrapper<RsOverseasApplication> wrapper = Wrappers.lambdaQuery();
|
||||
private QueryWrapper<RsOverseasApplication> buildQueryWrapper(RsOverseasApprovalMonitorQuery query) {
|
||||
QueryWrapper<RsOverseasApplication> 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<RsOverseasApproval> approvals = approvalMapper.selectList(
|
||||
Wrappers.<RsOverseasApproval>lambdaQuery()
|
||||
.eq(RsOverseasApproval::getApplicationId, application.getApplicationId())
|
||||
.orderByAsc(RsOverseasApproval::getApprovalLevel)
|
||||
.orderByDesc(RsOverseasApproval::getApprovalTime));
|
||||
Wrappers.<RsOverseasApproval>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.<RsOverseasApplication>lambdaQuery()
|
||||
.eq(RsOverseasApplication::getStatus, status)
|
||||
.eq(RsOverseasApplication::getApprovalLevel, level)
|
||||
.eq(RsOverseasApplication::getTenantId, tenantId));
|
||||
Wrappers.<RsOverseasApplication>query()
|
||||
.eq("status", status)
|
||||
.eq("approval_level", level)
|
||||
.eq("tenant_id", tenantId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+61
-14
@@ -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<RsOverseasApprovalVo> 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<RsOverseasApprovalVo> 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;
|
||||
}
|
||||
|
||||
|
||||
+10
@@ -227,6 +227,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<if test="tenantId != null and tenantId != ''">
|
||||
AND oa.tenant_id = #{tenantId}
|
||||
</if>
|
||||
<!-- 🔴 新增:部门负责人权限过滤 - 只能查看本部门员工提交的申请 -->
|
||||
<if test="applicantDeptId != null">
|
||||
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'
|
||||
)
|
||||
</if>
|
||||
</where>
|
||||
order by oa.apply_time desc, oa.create_time desc
|
||||
</select>
|
||||
@@ -279,6 +288,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</where>
|
||||
order by oa.apply_time desc, oa.create_time desc
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectOverseasApplicationById" parameterType="Long" resultMap="RsOverseasApplicationVoResult">
|
||||
<include refid="selectOverseasApplicationVo"/>
|
||||
|
||||
+157
-7
@@ -24,9 +24,26 @@
|
||||
|
||||
**模块名称**:overseas / 出国申请管理
|
||||
**基础路径**:`/api/rs/overseas`
|
||||
**版本**:v1.0
|
||||
**版本**:v1.1
|
||||
**负责人**:weizzzz
|
||||
**更新时间**:2025-01-28
|
||||
**更新时间**:2025-10-28
|
||||
|
||||
### 🆕 最近更新
|
||||
|
||||
**v1.1 (2025-10-28) - 分级审批权限升级**
|
||||
- ✅ **审批权限升级为五级审批制度**
|
||||
- 新增 `rs:overseas:approve1-5` 五个分级审批权限
|
||||
- 保留 `rs:overseas:approve` 作为通用审批权限
|
||||
- ✅ **Controller层采用OR模式权限验证**
|
||||
- 用户拥有任一审批权限即可访问审批接口
|
||||
- 解决部门负责人403权限错误问题
|
||||
- ✅ **Service层增加审批级别验证**
|
||||
- 新增 `validateApproverLevel()` 方法
|
||||
- 精确验证用户权限是否匹配申请的审批级别
|
||||
- 防止越级审批
|
||||
- ✅ **前端按钮权限优化**
|
||||
- 审批按钮支持多级权限显示
|
||||
- 改善用户体验,按钮显示逻辑更合理
|
||||
|
||||
### 模块功能
|
||||
|
||||
@@ -59,12 +76,106 @@
|
||||
- `rs:overseas:record:create` - 创建记录权限
|
||||
- `rs:overseas:record:complete` - 完成记录权限
|
||||
|
||||
#### 出国审批权限
|
||||
- **权限前缀**:`rs:overseas:approval:`
|
||||
- **权限列表**:
|
||||
#### 出国审批权限(🆕 2025-10-28 更新为五级审批)
|
||||
- **权限前缀**:`rs:overseas:`
|
||||
- **审批记录查看权限**:
|
||||
- `rs:overseas:approval:list` - 查询审批记录列表权限
|
||||
- `rs:overseas:approval:query` - 查询审批记录详情权限
|
||||
- `rs:overseas:approval:export` - 导出审批记录权限
|
||||
|
||||
- **分级审批权限**(五级审批制度):
|
||||
- `rs:overseas:approve` - 通用审批权限(超级管理员/高级审批者,可审批任意级别)
|
||||
- `rs:overseas:approve1` - 第1级:部门负责人审批权限
|
||||
- `rs:overseas:approve2` - 第2级:人事处审批权限
|
||||
- `rs:overseas:approve3` - 第3级:纪委审批权限
|
||||
- `rs:overseas:approve4` - 第4级:学院分管领导审批权限
|
||||
- `rs:overseas:approve5` - 第5级:学院党委书记审批权限
|
||||
|
||||
- **权限使用说明**:
|
||||
- 审批接口采用 **OR模式**,用户拥有以上任一权限即可调用接口
|
||||
- 具体审批权限在业务层验证,必须匹配申请的 `approval_level`
|
||||
- 部门负责人通过 `rs:overseas:approve1` 权限识别,自动应用部门数据过滤
|
||||
|
||||
### 🔐 数据权限控制
|
||||
|
||||
#### 权限分级说明
|
||||
|
||||
出国申请系统采用**基于角色和部门的多级数据权限控制**,确保用户只能访问其权限范围内的数据。
|
||||
|
||||
#### 角色权限矩阵
|
||||
|
||||
| 角色 | 角色标识 | 数据访问范围 | 可见申请范围 | 备注 |
|
||||
|------|----------|------------|-------------|------|
|
||||
| **超级管理员** | `admin` | 全局数据 | 所有申请 | 不受任何限制 |
|
||||
| **人事管理员** | `rs-admin` | 全局数据 | 所有申请 | 仅当不是部门负责人时 |
|
||||
| **申请人** | `rs-Applicant` | 个人数据 | 仅自己的申请 | 只能查看和管理自己的申请 |
|
||||
| **部门负责人** | 具有`rs:overseas:approve1`权限 | 本部门数据 | 本部门所有申请 | **优先级最高** |
|
||||
| **普通审批人** | 其他审批权限 | 分配数据 | 分配给自己的审批 | 只能看到分配的审批任务 |
|
||||
|
||||
#### 部门权限过滤规则(🆕 2025-01-28)
|
||||
|
||||
**核心原则**:部门负责人**只能看到本部门成员提交的申请**,不能看到其他部门的申请。
|
||||
|
||||
**实现逻辑**:
|
||||
1. **判断用户是否为部门负责人**(三种方式任选其一):
|
||||
- 具有 `rs:overseas:approve1` 权限
|
||||
- 具有 `dept_head_role` 角色
|
||||
- 具有 `overseas:application:dept_head:approve` 权限(兼容旧版)
|
||||
|
||||
2. **应用部门过滤**:
|
||||
- 查询条件:`申请人的dept_id = 当前用户的dept_id`
|
||||
- SQL实现:通过 `sys_user` 表关联,筛选申请人所属部门
|
||||
- **重要**:不限制审批人(approverId),只限制申请人部门(deptId)
|
||||
|
||||
3. **权限优先级**(从高到低):
|
||||
```
|
||||
超级管理员 > 申请人(自己的数据)> 部门负责人(本部门)> 人事管理员(全局)> 普通审批人(分配数据)
|
||||
```
|
||||
|
||||
**示例场景**:
|
||||
|
||||
假设有以下用户和部门:
|
||||
- **张三**:`dept_id = 001`(测试部门A),角色:申请人
|
||||
- **李四**:`dept_id = 001`(测试部门A),角色:部门负责人
|
||||
- **王五**:`dept_id = 002`(测试部门B),角色:部门负责人
|
||||
- **赵六**:`dept_id = 003`(测试部门C),角色:部门负责人
|
||||
|
||||
**张三提交申请后:**
|
||||
- ✅ **李四**(同部门):可以看到张三的申请
|
||||
- ❌ **王五**(不同部门):不能看到张三的申请
|
||||
- ❌ **赵六**(不同部门):不能看到张三的申请
|
||||
|
||||
**技术实现**:
|
||||
|
||||
```java
|
||||
// Service层权限过滤逻辑
|
||||
private void applyDeptPermissionFilter(RsOverseasApprovalQuery query) {
|
||||
// 1. 判断是否为部门负责人
|
||||
boolean isDeptApprover = isDeptApprover(currentUserId);
|
||||
|
||||
// 2. 如果是部门负责人,只设置部门过滤
|
||||
if (isDeptApprover) {
|
||||
query.setDeptId(currentDeptId); // 只应用部门过滤
|
||||
return; // 不设置approverId,避免过度限制
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- Mapper XML中的部门权限过滤SQL -->
|
||||
<if test="deptId != null">
|
||||
AND EXISTS (
|
||||
SELECT 1 FROM sys_user u
|
||||
WHERE u.user_id = app.applicant_id
|
||||
AND u.dept_id = #{deptId}
|
||||
)
|
||||
</if>
|
||||
```
|
||||
|
||||
**相关文档**:
|
||||
- 测试指南:`docs/测试文档/出国申请部门权限过滤测试指南.md`
|
||||
- 验证脚本:`部门权限过滤验证脚本.sql`
|
||||
- 代码实现:`RsOverseasApprovalServiceImpl.java`
|
||||
|
||||
---
|
||||
|
||||
@@ -152,6 +263,7 @@ interface OverseasApplicationVO {
|
||||
accommodationPlan: string; // 住宿安排
|
||||
transportationPlan: string; // 交通安排
|
||||
status: string; // 申请状态编码
|
||||
approvalStatus: string; // 工作流状态编码
|
||||
currentApprover: string; // 当前审批人
|
||||
approvalLevel: string; // 审批级别
|
||||
applyTime: string; // 申请时间
|
||||
@@ -162,6 +274,7 @@ interface OverseasApplicationVO {
|
||||
updateTime: string; // 更新时间
|
||||
// 显示字段
|
||||
statusText: string; // 状态文本
|
||||
approvalStatusText: string; // 工作流状态文本
|
||||
purposeText: string; // 目的文本
|
||||
budgetSourceText: string; // 经费来源文本
|
||||
approvalLevelText: string; // 审批级别文本
|
||||
@@ -403,7 +516,27 @@ interface OverseasApplicationForm {
|
||||
### 4. 审批出国申请
|
||||
|
||||
**接口地址**:`POST /rs/overseas/application/approve`
|
||||
**权限要求**:`rs:overseas:application:approve`
|
||||
**权限要求**:`rs:overseas:approve` (通用) 或 `rs:overseas:approve1-5` (分级) - **支持多级审批权限(2025-10-28更新)**
|
||||
|
||||
#### 🔴 权限说明(重要更新)
|
||||
|
||||
**分级审批权限体系**(OR模式,满足任一权限即可访问接口):
|
||||
- `rs:overseas:approve` - 通用审批权限(超级管理员/高级审批者)
|
||||
- `rs:overseas:approve1` - 第1级:部门负责人审批权限
|
||||
- `rs:overseas:approve2` - 第2级:人事处审批权限
|
||||
- `rs:overseas:approve3` - 第3级:纪委审批权限
|
||||
- `rs:overseas:approve4` - 第4级:学院分管领导审批权限
|
||||
- `rs:overseas:approve5` - 第5级:学院党委书记审批权限
|
||||
|
||||
**权限验证机制**:
|
||||
1. **接口层验证**:Controller使用 `@SaCheckPermission(value={...}, mode=SaMode.OR)`,满足任一权限即可调用接口
|
||||
2. **业务层验证**:Service层根据申请的 `approval_level` 字段验证用户是否拥有对应级别的审批权限
|
||||
3. **权限不匹配处理**:如果用户审批级别与申请要求不符,返回业务异常"您没有第X级审批权限"
|
||||
|
||||
**示例场景**:
|
||||
- 部门负责人(拥有 `approve1`)只能审批 `approval_level=1` 的申请
|
||||
- 尝试审批 `approval_level=2` 的申请将被拒绝
|
||||
- 拥有 `rs:overseas:approve` 通用权限的用户可以审批任意级别
|
||||
|
||||
#### 请求参数
|
||||
|
||||
@@ -412,6 +545,7 @@ interface ApprovalRequest {
|
||||
applicationId: number | string; // 申请ID(必须)
|
||||
approvalStatus: string; // 审批状态:1-通过,2-拒绝(必须)
|
||||
approvalOpinion?: string; // 审批意见
|
||||
taskId?: string; // 🔴 工作流任务ID(可选,支持标准工作流模式)
|
||||
}
|
||||
```
|
||||
|
||||
@@ -600,6 +734,20 @@ referer: http://localhost
|
||||
| 3 | 已取消 | 申请被申请人主动取消 |
|
||||
| 4 | 已完成 | 出国活动已完成 |
|
||||
|
||||
### 工作流状态 (approvalStatus)
|
||||
|
||||
| 编码 | 含义 | 描述 |
|
||||
| ----------- | ------ | ------------------------ |
|
||||
| cancel | 已撤销 | 申请人主动撤销申请 |
|
||||
| draft | 草稿 | 申请尚未提交,处于编辑状态 |
|
||||
| waiting | 待审核 | 申请已提交,正在审批流程中 |
|
||||
| finish | 已完成 | 所有审批节点都已通过,流程结束 |
|
||||
| invalid | 已作废 | 申请因某种原因被标记为无效 |
|
||||
| back | 已退回 | 审批被拒绝,退回给申请人修改 |
|
||||
| termination | 已终止 | 流程被管理员或系统强制终止 |
|
||||
|
||||
> **📝 说明**:工作流状态由 Warm-Flow 工作流引擎自动管理,用于总体流程监听和状态跟踪。
|
||||
|
||||
### 出国目的 (purpose)
|
||||
|
||||
| 编码 | 含义 | 描述 |
|
||||
@@ -630,7 +778,7 @@ referer: http://localhost
|
||||
| 2 | 分管领导 | 第二级审批:分管领导 |
|
||||
| 3 | 校领导 | 第三级审批:校领导 |
|
||||
|
||||
### 审批状态 (approvalStatus)
|
||||
### 审批记录状态 (审批记录表中的 approvalStatus)
|
||||
|
||||
| 编码 | 含义 | 描述 |
|
||||
| ---- | ------ | ------------------------ |
|
||||
@@ -638,6 +786,8 @@ referer: http://localhost
|
||||
| 1 | 通过 | 审批通过 |
|
||||
| 2 | 拒绝 | 审批拒绝 |
|
||||
|
||||
> **⚠️ 注意**:此状态是审批记录表中的状态,与申请表中的工作流状态(approvalStatus)是不同的字段。
|
||||
|
||||
### 完成状态 (completionStatus)
|
||||
|
||||
> **⚠️ 重要说明**:完成状态(completionStatus)与申请状态(status)是两个不同的概念!
|
||||
|
||||
@@ -152,11 +152,12 @@ export interface OverseasApplicationForm {
|
||||
approvalStatus?: string; // 工作流审批状态
|
||||
}
|
||||
|
||||
// 审批请求类型
|
||||
// 审批请求类型(支持标准工作流模式)
|
||||
export interface ApprovalRequest {
|
||||
applicationId: number | string;
|
||||
approvalStatus: string;
|
||||
approvalOpinion?: string;
|
||||
taskId?: string; // 🔴 新增:工作流任务ID,支持标准工作流模式
|
||||
}
|
||||
|
||||
// 出国记录查询参数类型
|
||||
@@ -327,15 +328,30 @@ export function listMyOverseasApplication(query: OverseasApplicationQuery) {
|
||||
});
|
||||
}
|
||||
|
||||
// 查询待审批的申请列表
|
||||
export function listPendingOverseasApplication(query: OverseasApplicationQuery) {
|
||||
// 查询当前用户的待审批申请列表(五级审批模式)
|
||||
export function listPendingOverseasApplication() {
|
||||
return request({
|
||||
url: '/rs/overseas/application/pending/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// 🚨 标准化改造:删除自定义待审批查询API
|
||||
//
|
||||
// 原因:出国申请工作流应使用标准工作流模式
|
||||
// 用户应通过工作流API pageByTaskWait() 获取待办任务
|
||||
// 而不是调用自定义的业务API
|
||||
//
|
||||
// 删除的API:listPendingOverseasApplication()
|
||||
//
|
||||
// 标准工作流模式:
|
||||
// 1. 导入工作流API:import { pageByTaskWait } from '@/api/workflow/task'
|
||||
// 2. 调用标准API:pageByTaskWait(queryParams)
|
||||
// 3. 获得待办任务列表,包含businessId
|
||||
// 4. 点击办理跳转:/rs/overseas/application/edit?taskId=xxx&applicationId=businessId
|
||||
// ========================================
|
||||
|
||||
// 根据状态查询申请列表
|
||||
export function listOverseasApplicationByStatus(status: string) {
|
||||
return request({
|
||||
@@ -990,3 +1006,27 @@ export function getItineraryChangeHistory(recordId: string | number) {
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
// ==================== 动态审批人获取API ====================
|
||||
|
||||
/**
|
||||
* 根据申请人ID获取部门审批人ID
|
||||
* @param applicantId 申请人ID
|
||||
*/
|
||||
export function getDepartmentApproverId(applicantId: string | number): Promise<{ data: number }> {
|
||||
return request({
|
||||
url: `/rs/overseas/application/getDepartmentApproverId/${applicantId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据申请人ID获取人事处审批人列表
|
||||
* @param applicantId 申请人ID
|
||||
*/
|
||||
export function getHrApprovers(applicantId: string | number): Promise<{ data: number[] }> {
|
||||
return request({
|
||||
url: `/rs/overseas/application/getHrApprovers/${applicantId}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -78,14 +78,7 @@
|
||||
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="工作流状态" align="center" prop="approvalStatusText" min-width="110">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.approvalStatus" :type="getApprovalStatusTagType(scope.row.approvalStatus)">
|
||||
{{ scope.row.approvalStatusText || '-' }}
|
||||
</el-tag>
|
||||
<el-tag v-else type="info">未启动</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- 移除工作流状态列 - 避免与申请状态重复,简化用户界面 -->
|
||||
<el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="操作" align="center" width="240">
|
||||
<template #default="scope">
|
||||
@@ -469,7 +462,9 @@
|
||||
destroy-on-close
|
||||
>
|
||||
<template #default>
|
||||
<!-- 工作流按钮组件 -->
|
||||
<!-- 工作流按钮组件 - 在弹窗模式下隐藏,使用footer按钮 -->
|
||||
<!-- 注释掉,因为弹窗模式下使用footer中的按钮 -->
|
||||
<!--
|
||||
<approvalButton
|
||||
v-if="state.pageType"
|
||||
@submitForm="submitForm"
|
||||
@@ -479,6 +474,7 @@
|
||||
:status="state.applicationForm.approvalStatus"
|
||||
:pageType="state.pageType"
|
||||
/>
|
||||
-->
|
||||
|
||||
<div style="max-height: 65vh; overflow-y: auto; padding-right: 10px;">
|
||||
<el-form ref="applicationFormRef" :model="applicationForm" :rules="applicationRules" label-width="120px">
|
||||
@@ -572,8 +568,8 @@
|
||||
<!-- 第五排:前往国家、出境事由 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="前往国家" prop="destinationCountry">
|
||||
<el-input v-model="applicationForm.destinationCountry" placeholder="请输入前往国家" />
|
||||
<el-form-item label="前往国家或地区" prop="destinationCountry">
|
||||
<el-input v-model="applicationForm.destinationCountry" placeholder="请输入前往国家或地区" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
@@ -589,7 +585,7 @@
|
||||
<!-- 探亲时显示的额外字段 -->
|
||||
<el-row :gutter="20" v-if="applicationForm.exitReason === '探亲'">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="探望对象关系" prop="visitRelationship">
|
||||
<el-form-item label="探望对象姓名及与申请者关系" prop="visitRelationship">
|
||||
<el-input
|
||||
v-model="applicationForm.visitRelationship"
|
||||
placeholder="请输入探望对象姓名及与申请者关系"
|
||||
@@ -711,10 +707,10 @@
|
||||
style="width: 60%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="应急联系人及电话" prop="emergencyContact" label-width="160px" style="margin-left: 10%;">
|
||||
<el-form-item label="应急联系人及联系电话" prop="emergencyContact" label-width="160px" style="margin-left: 10%;">
|
||||
<el-input
|
||||
v-model="applicationForm.emergencyContact"
|
||||
placeholder="请输入应急联系人及电话"
|
||||
placeholder="请输入应急联系人及联系电话"
|
||||
style="width: 90%;"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -1233,9 +1229,11 @@
|
||||
<el-table v-loading="approvalHistoryLoading" :data="approvalHistoryList" border style="width: 100%">
|
||||
<el-table-column label="审批级别" align="center" prop="approvalLevel" width="100">
|
||||
<template #default="scope">
|
||||
<span v-if="scope.row.approvalLevel === '1'">部门主管</span>
|
||||
<span v-else-if="scope.row.approvalLevel === '2'">分管领导</span>
|
||||
<span v-else-if="scope.row.approvalLevel === '3'">校领导</span>
|
||||
<span v-if="scope.row.approvalLevel === '1'">部门负责人</span>
|
||||
<span v-else-if="scope.row.approvalLevel === '2'">人事处</span>
|
||||
<span v-else-if="scope.row.approvalLevel === '3'">纪委</span>
|
||||
<span v-else-if="scope.row.approvalLevel === '4'">学院分管领导</span>
|
||||
<span v-else-if="scope.row.approvalLevel === '5'">学院党委书记</span>
|
||||
<span v-else>{{ scope.row.approvalLevel }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -1279,7 +1277,7 @@
|
||||
<approvalRecord
|
||||
ref="approvalRecordRef"
|
||||
:businessId="state.applicationForm.applicationId"
|
||||
flowCode="overseas-application"
|
||||
flowCode="dyjs-cgsq"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1307,6 +1305,7 @@ import {
|
||||
validateApplicationPrerequisites,
|
||||
listOverseasAttachments,
|
||||
uploadOverseasAttachment,
|
||||
getDepartmentApproverId,
|
||||
deleteOverseasAttachment,
|
||||
getItineraryChangeHistory,
|
||||
type OverseasApplicationQuery,
|
||||
@@ -1413,7 +1412,7 @@ const state = reactive({
|
||||
department: [{ required: true, message: '现工作部门不能为空', trigger: 'blur' }],
|
||||
professionalTitle: [{ required: true, message: '职称不能为空', trigger: 'blur' }],
|
||||
position: [{ required: true, message: '职务不能为空', trigger: 'blur' }],
|
||||
destinationCountry: [{ required: true, message: '前往国家不能为空', trigger: 'blur' }],
|
||||
destinationCountry: [{ required: true, message: '前往国家或地区不能为空', trigger: 'blur' }],
|
||||
exitReason: [{ required: true, message: '出境事由不能为空', trigger: 'change' }],
|
||||
startDate: [{ required: true, message: '出发日期不能为空', trigger: 'change' }],
|
||||
endDate: [{ required: true, message: '返回日期不能为空', trigger: 'change' }],
|
||||
@@ -1421,7 +1420,7 @@ const state = reactive({
|
||||
{ required: true, message: '联系电话不能为空', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||
],
|
||||
emergencyContact: [{ required: true, message: '应急联系人及电话不能为空', trigger: 'blur' }]
|
||||
emergencyContact: [{ required: true, message: '应急联系人及联系电话不能为空', trigger: 'blur' }]
|
||||
} as any,
|
||||
|
||||
// 查看详情
|
||||
@@ -1678,13 +1677,12 @@ const resetRecordQuery = () => {
|
||||
|
||||
// 新增登记
|
||||
const handleAddApplication = () => {
|
||||
// 跳转到新增申请页面
|
||||
proxy.$router.push({
|
||||
path: '/rs/overseas/overseas-form',
|
||||
query: {
|
||||
type: 'add'
|
||||
}
|
||||
});
|
||||
// 显示文件通知弹窗
|
||||
state.noticeDialog.visible = true;
|
||||
state.countdown = 5;
|
||||
state.countdownActive = true;
|
||||
state.readCompleted = false;
|
||||
startCountdown();
|
||||
};
|
||||
|
||||
// 开始倒计时
|
||||
@@ -1721,14 +1719,17 @@ const handleCloseNotice = () => {
|
||||
|
||||
// 编辑申请
|
||||
const handleUpdateApplication = async (row: OverseasApplicationVO) => {
|
||||
// 跳转到编辑申请页面
|
||||
proxy.$router.push({
|
||||
path: '/rs/overseas/overseas-form',
|
||||
query: {
|
||||
type: 'edit',
|
||||
id: row.applicationId
|
||||
}
|
||||
});
|
||||
try {
|
||||
// 加载申请详情
|
||||
const response = await getOverseasApplication(row.applicationId);
|
||||
state.applicationForm = response.data;
|
||||
state.applicationDialog.title = '编辑因私出国(境)申请登记报备表';
|
||||
state.pageType = 'edit';
|
||||
state.applicationDialog.visible = true;
|
||||
} catch (error) {
|
||||
console.error('加载申请详情失败:', error);
|
||||
ElMessage.error('加载申请详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 查看申请详情
|
||||
@@ -1791,14 +1792,18 @@ const handleSubmitApplication = (row: OverseasApplicationVO) => {
|
||||
|
||||
// 修改并重新提交申请(用于已拒绝状态)
|
||||
const handleUpdateAndResubmitApplication = async (row: OverseasApplicationVO) => {
|
||||
// 跳转到编辑申请页面,标记为重新提交模式
|
||||
proxy.$router.push({
|
||||
path: '/rs/overseas/overseas-form',
|
||||
query: {
|
||||
type: 'resubmit',
|
||||
id: row.applicationId
|
||||
}
|
||||
});
|
||||
try {
|
||||
// 加载申请详情
|
||||
const response = await getOverseasApplication(row.applicationId);
|
||||
state.applicationForm = response.data;
|
||||
state.applicationDialog.title = '修改并重新提交因私出国(境)申请';
|
||||
state.pageType = 'resubmit';
|
||||
state.isResubmitMode = true; // 标记为重新提交模式
|
||||
state.applicationDialog.visible = true;
|
||||
} catch (error) {
|
||||
console.error('加载申请详情失败:', error);
|
||||
ElMessage.error('加载申请详情失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 取消申请
|
||||
@@ -1944,11 +1949,13 @@ const getOldApprovalStatusTagType = (status: string): 'success' | 'warning' | 'd
|
||||
*/
|
||||
const getApprovalStatusTagType = (status: string): 'success' | 'warning' | 'danger' | 'info' | 'primary' => {
|
||||
const statusMap: Record<string, 'success' | 'warning' | 'danger' | 'info' | 'primary'> = {
|
||||
'draft': 'info', // 草稿 - 灰色
|
||||
'waiting': 'warning', // 待审批 - 橙色
|
||||
'finish': 'success', // 已完成 - 绿色
|
||||
'back': 'danger', // 已退回 - 红色
|
||||
'cancel': 'info', // 已取消 - 灰色
|
||||
'cancel': 'info', // 已撤销 - 灰色
|
||||
'draft': 'info', // 草稿 - 灰色
|
||||
'waiting': 'warning', // 待审核 - 橙色
|
||||
'finish': 'success', // 已完成 - 绿色
|
||||
'invalid': 'danger', // 已作废 - 红色
|
||||
'back': 'danger', // 已退回 - 红色
|
||||
'termination': 'primary' // 已终止 - 蓝色
|
||||
};
|
||||
return statusMap[status] || 'info';
|
||||
};
|
||||
@@ -1981,7 +1988,7 @@ const handleViewProcess = (row: OverseasApplicationVO) => {
|
||||
|
||||
if (approvalRecordRef.value && row.applicationId) {
|
||||
// 使用标准工作流组件查看审批记录
|
||||
approvalRecordRef.value.open(row.applicationId, 'overseas-application');
|
||||
approvalRecordRef.value.open(row.applicationId, 'dyjs-cgsq');
|
||||
} else {
|
||||
ElMessage.warning('无法查看流程信息');
|
||||
}
|
||||
@@ -2022,16 +2029,42 @@ const submitForm = async (type: string) => {
|
||||
|
||||
// 2. 如果是提交审批,使用标准工作流API
|
||||
if (type === 'submit') {
|
||||
// 动态获取审批人信息
|
||||
let departmentApproverId: number | null = null;
|
||||
|
||||
try {
|
||||
// 获取部门审批人ID
|
||||
const deptApproverResponse = await getDepartmentApproverId(state.applicationForm.applicantId || userStore.userId);
|
||||
if (deptApproverResponse && deptApproverResponse.data) {
|
||||
departmentApproverId = deptApproverResponse.data;
|
||||
console.log('获取到部门审批人ID:', departmentApproverId);
|
||||
} else {
|
||||
console.warn('获取部门审批人ID失败');
|
||||
ElMessage.error('获取部门审批人信息失败,请稍后重试');
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('调用获取部门审批人ID接口失败:', error);
|
||||
ElMessage.error('获取部门审批人信息失败,请稍后重试');
|
||||
return;
|
||||
}
|
||||
|
||||
// 注意:人事处审批通过角色配置,不需要动态获取审批人
|
||||
console.log('人事处审批人通过hr_dept_role角色配置,无需动态获取');
|
||||
|
||||
// 标准工作流启动参数
|
||||
const workflowData = {
|
||||
flowCode: 'overseas-application',
|
||||
flowCode: 'dyjs-cgsq', // 出国申请工作流标识KEY
|
||||
businessId: String(applicationId),
|
||||
variables: {
|
||||
// dyjs-cgsq 使用的流程变量
|
||||
applicantName: state.applicationForm.applicantName,
|
||||
department: state.applicationForm.department,
|
||||
destination: state.applicationForm.destination,
|
||||
duration: state.applicationForm.duration,
|
||||
budget: state.applicationForm.budget
|
||||
budget: state.applicationForm.budget,
|
||||
// 动态审批人变量
|
||||
bmfzrsp: departmentApproverId // 部门负责人审批(对应工作流中的入库主键)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2078,7 +2111,7 @@ const handleApprovalRecord = () => {
|
||||
console.log('📜 [审批记录] 打开审批记录');
|
||||
|
||||
if (approvalRecordRef.value && state.applicationForm.applicationId) {
|
||||
approvalRecordRef.value.open(state.applicationForm.applicationId, 'overseas-application');
|
||||
approvalRecordRef.value.open(state.applicationForm.applicationId, 'dyjs-cgsq');
|
||||
} else {
|
||||
ElMessage.warning('无法查看审批记录');
|
||||
}
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card class="box-card">
|
||||
<!-- 🔴 标准化改造:提示用户使用标准工作流页面 -->
|
||||
<el-alert
|
||||
title="功能迁移通知"
|
||||
type="info"
|
||||
description="出国行程变更审批功能已标准化,请使用系统统一的工作流页面查看和处理任务"
|
||||
show-icon
|
||||
:closable="false"
|
||||
class="mb-4"
|
||||
>
|
||||
<template #default>
|
||||
<div class="mt-2">
|
||||
<p><strong>推荐操作:</strong></p>
|
||||
<ul class="ml-4">
|
||||
<li>访问 <strong>工作流 > 我的任务 > 待办任务</strong> 页面</li>
|
||||
<li>在统一的待办列表中查看"出国行程变更审批"任务</li>
|
||||
<li>使用工作流的筛选功能按业务类型查看</li>
|
||||
</ul>
|
||||
<div class="mt-3">
|
||||
<el-button type="primary" @click="navigateToTaskWaiting"> 前往待办任务页面 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<!-- 审批管理选项卡 -->
|
||||
<el-tabs v-model="activeTab" type="border-card" @tab-click="handleTabClick">
|
||||
|
||||
<!-- 待审批 -->
|
||||
<el-tab-pane label="待审批变更" name="pending">
|
||||
<div class="tab-content">
|
||||
@@ -46,8 +69,13 @@
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<!-- 待审批列表 -->
|
||||
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleView">
|
||||
<!-- 🔴 标准化改造:显示迁移提示 -->
|
||||
<el-empty description="请使用标准工作流待办页面查看审批任务" :image-size="120">
|
||||
<el-button type="primary" @click="navigateToTaskWaiting">前往待办页面</el-button>
|
||||
</el-empty>
|
||||
|
||||
<!-- 保留原表格结构用于兼容(隐藏显示) -->
|
||||
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleView" style="display: none">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column label="申请人" align="center" prop="applicantName" min-width="100" />
|
||||
<el-table-column label="部门" align="center" prop="department" min-width="120" />
|
||||
@@ -55,14 +83,10 @@
|
||||
<el-table-column label="新目的地" align="center" prop="newDestination" min-width="120" />
|
||||
<el-table-column label="变更类型" align="center" prop="changeType" min-width="150" />
|
||||
<el-table-column label="原出国时间" align="center" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.originalStartDate) }} 至 {{ formatDate(scope.row.originalEndDate) }}
|
||||
</template>
|
||||
<template #default="scope"> {{ formatDate(scope.row.originalStartDate) }} 至 {{ formatDate(scope.row.originalEndDate) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="新出国时间" align="center" min-width="200">
|
||||
<template #default="scope">
|
||||
{{ formatDate(scope.row.newStartDate) }} 至 {{ formatDate(scope.row.newEndDate) }}
|
||||
</template>
|
||||
<template #default="scope"> {{ formatDate(scope.row.newStartDate) }} 至 {{ formatDate(scope.row.newEndDate) }} </template>
|
||||
</el-table-column>
|
||||
<el-table-column label="申请时间" align="center" prop="createTime" min-width="160" />
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
@@ -71,10 +95,22 @@
|
||||
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['rs:itineraryChange:query']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="通过" placement="top">
|
||||
<el-button link type="success" icon="Check" @click="handleApprovalPass(scope.row)" v-hasPermi="['rs:itineraryChange:approve']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="success"
|
||||
icon="Check"
|
||||
@click="handleApprovalPass(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="拒绝" placement="top">
|
||||
<el-button link type="danger" icon="Close" @click="handleApprovalReject(scope.row)" v-hasPermi="['rs:itineraryChange:approve']"></el-button>
|
||||
<el-button
|
||||
link
|
||||
type="danger"
|
||||
icon="Close"
|
||||
@click="handleApprovalReject(scope.row)"
|
||||
v-hasPermi="['rs:itineraryChange:approve']"
|
||||
></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -184,14 +220,14 @@
|
||||
<!-- 基本信息 -->
|
||||
<el-descriptions-item label="申请人">{{ viewData.applicantName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="部门">{{ viewData.department || '-' }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 原申请信息 -->
|
||||
<el-descriptions-item label="原目的地">{{ viewData.originalDestination || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原出境事由">{{ viewData.originalExitReason || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原出发日期">{{ formatDate(viewData.originalStartDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原返回日期">{{ formatDate(viewData.originalEndDate) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="原出国天数" :span="2">{{ viewData.originalDuration || 0 }}天</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 变更信息 -->
|
||||
<el-descriptions-item label="变更类型" :span="2">{{ viewData.changeType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="新目的地" v-if="viewData.newDestination">{{ viewData.newDestination }}</el-descriptions-item>
|
||||
@@ -202,7 +238,7 @@
|
||||
<el-descriptions-item label="新行程安排" :span="2" v-if="viewData.newItinerary">{{ viewData.newItinerary }}</el-descriptions-item>
|
||||
<el-descriptions-item label="变更原因" :span="2">{{ viewData.changeReason || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="备注" :span="2" v-if="viewData.remark">{{ viewData.remark }}</el-descriptions-item>
|
||||
|
||||
|
||||
<!-- 审批信息 -->
|
||||
<el-descriptions-item label="变更状态">
|
||||
<el-tag :type="getStatusTagType(viewData.status)">{{ viewData.statusText }}</el-tag>
|
||||
@@ -230,14 +266,7 @@
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="审批意见" prop="opinion">
|
||||
<el-input
|
||||
v-model="approvalForm.opinion"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder="请输入审批意见"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input v-model="approvalForm.opinion" type="textarea" :rows="4" placeholder="请输入审批意见" maxlength="500" show-word-limit />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
@@ -248,16 +277,16 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ItineraryChangeApproval">
|
||||
import { ref, reactive, toRefs, onMounted } from 'vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { useRouter } from 'vue-router';
|
||||
import animate from '@/animate';
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
import {
|
||||
import {
|
||||
listPendingItineraryChange,
|
||||
listItineraryChange,
|
||||
getItineraryChange,
|
||||
@@ -269,6 +298,7 @@ import {
|
||||
|
||||
// ==================== 响应式数据 ====================
|
||||
|
||||
const router = useRouter();
|
||||
const pendingQueryFormRef = ref();
|
||||
const recordQueryFormRef = ref();
|
||||
const approvalFormRef = ref();
|
||||
@@ -276,7 +306,7 @@ const approvalFormRef = ref();
|
||||
const state = reactive({
|
||||
// Tab切换
|
||||
activeTab: 'pending',
|
||||
|
||||
|
||||
// 待审批列表
|
||||
pendingLoading: false,
|
||||
showPendingSearch: true,
|
||||
@@ -288,7 +318,7 @@ const state = reactive({
|
||||
applicantName: '',
|
||||
destination: ''
|
||||
} as ItineraryChangeQuery,
|
||||
|
||||
|
||||
// 审批记录列表
|
||||
recordLoading: false,
|
||||
showRecordSearch: true,
|
||||
@@ -301,7 +331,7 @@ const state = reactive({
|
||||
destination: '',
|
||||
status: ''
|
||||
} as ItineraryChangeQuery,
|
||||
|
||||
|
||||
// 对话框
|
||||
viewDialog: {
|
||||
visible: false
|
||||
@@ -310,7 +340,7 @@ const state = reactive({
|
||||
visible: false,
|
||||
title: ''
|
||||
},
|
||||
|
||||
|
||||
// 表单数据
|
||||
viewData: null as ItineraryChangeVO | null,
|
||||
approvalForm: {
|
||||
@@ -319,7 +349,7 @@ const state = reactive({
|
||||
opinion: ''
|
||||
} as ItineraryChangeApprovalRequest,
|
||||
approvalLoading: false,
|
||||
|
||||
|
||||
// 表单验证规则
|
||||
approvalRules: {
|
||||
approved: [{ required: true, message: '请选择审批结果', trigger: 'change' }]
|
||||
@@ -358,7 +388,7 @@ onMounted(() => {
|
||||
const handleTabClick = (tab: any) => {
|
||||
const tabName = tab.props?.name || tab.paneName;
|
||||
state.activeTab = tabName;
|
||||
|
||||
|
||||
if (tabName === 'pending') {
|
||||
getPendingList();
|
||||
} else if (tabName === 'record') {
|
||||
@@ -366,18 +396,17 @@ const handleTabClick = (tab: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
// 查询待审批列表
|
||||
// 🔴 标准化改造:简化待审批列表获取(仅用于兼容模式)
|
||||
const getPendingList = async () => {
|
||||
state.pendingLoading = true;
|
||||
try {
|
||||
const response = await listPendingItineraryChange(state.pendingQueryParams);
|
||||
state.pendingList = response.rows;
|
||||
state.pendingTotal = response.total;
|
||||
} catch (error) {
|
||||
console.error('获取待审批列表失败:', error);
|
||||
} finally {
|
||||
state.pendingLoading = false;
|
||||
}
|
||||
|
||||
// 提示用户使用标准工作流页面
|
||||
ElMessage.info('请使用标准工作流待办页面查看审批任务');
|
||||
|
||||
// 清空数据,不再调用自定义API
|
||||
state.pendingList = [];
|
||||
state.pendingTotal = 0;
|
||||
state.pendingLoading = false;
|
||||
};
|
||||
|
||||
// 查询审批记录列表
|
||||
@@ -400,16 +429,20 @@ const getRecordList = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// 🔴 标准化改造:导航到标准工作流待办页面
|
||||
const navigateToTaskWaiting = () => {
|
||||
// 跳转到标准工作流待办页面
|
||||
router.push('/workflow/task/taskWaiting');
|
||||
};
|
||||
|
||||
// 搜索查询 - 待审批列表
|
||||
const handlePendingQuery = () => {
|
||||
state.pendingQueryParams.pageNum = 1;
|
||||
getPendingList();
|
||||
ElMessage.info('请使用标准工作流待办页面查看审批任务');
|
||||
};
|
||||
|
||||
// 重置搜索 - 待审批列表
|
||||
const resetPendingQuery = () => {
|
||||
pendingQueryFormRef.value?.resetFields();
|
||||
handlePendingQuery();
|
||||
ElMessage.info('请使用标准工作流待办页面查看审批任务');
|
||||
};
|
||||
|
||||
// 搜索查询 - 审批记录
|
||||
@@ -463,7 +496,7 @@ const submitApproval = () => {
|
||||
await approveItineraryChange(state.approvalForm);
|
||||
ElMessage.success('审批成功');
|
||||
state.approvalDialog.visible = false;
|
||||
|
||||
|
||||
// 刷新两个列表(审批后需要同时更新待审批和审批记录列表)
|
||||
await getPendingList();
|
||||
await getRecordList();
|
||||
@@ -488,11 +521,10 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
|
||||
const typeMap: Record<string, 'primary' | 'success' | 'info' | 'warning' | 'danger'> = {
|
||||
'0': 'warning', // 待审核
|
||||
'1': 'success', // 已通过
|
||||
'2': 'danger' // 已拒绝
|
||||
'2': 'danger' // 已拒绝
|
||||
};
|
||||
return typeMap[status] || 'info';
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -518,4 +550,3 @@ const getStatusTagType = (status: string): 'primary' | 'success' | 'info' | 'war
|
||||
min-height: 400px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<!-- 文件通知阅读弹窗 -->
|
||||
<el-dialog
|
||||
:title="noticeDialog.title"
|
||||
v-model="noticeDialog.visible"
|
||||
width="800px"
|
||||
<el-dialog
|
||||
:title="noticeDialog.title"
|
||||
v-model="noticeDialog.visible"
|
||||
width="800px"
|
||||
top="5vh"
|
||||
:close-on-click-modal="false"
|
||||
:close-on-press-escape="false"
|
||||
@@ -12,76 +12,55 @@
|
||||
>
|
||||
<div class="notice-content">
|
||||
<!-- 文件头部 -->
|
||||
<div class="notice-header" style="text-align: center; margin-bottom: 20px;">
|
||||
<h2 style="color: #1890ff; margin: 0;">杭州第一技师学院</h2>
|
||||
<h3 style="color: #666; margin: 10px 0;">关于因私出国(境)申请管理的通知</h3>
|
||||
<div class="notice-header" style="text-align: center; margin-bottom: 20px">
|
||||
<h2 style="color: #1890ff; margin: 0">杭州第一技师学院</h2>
|
||||
<h3 style="color: #666; margin: 10px 0">关于因私出国(境)申请管理的通知</h3>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 文件内容 -->
|
||||
<div class="notice-body" style="line-height: 1.8; font-size: 14px; color: #333;">
|
||||
<div class="notice-body" style="line-height: 1.8; font-size: 14px; color: #333">
|
||||
<p><strong>各部门、各位教职工:</strong></p>
|
||||
|
||||
|
||||
<p>为规范我院教职工因私出国(境)申请管理,根据相关规定,现将有关事项通知如下:</p>
|
||||
|
||||
|
||||
<p><strong>一、申请范围</strong></p>
|
||||
<p>本通知适用于我院在职教职工因私事需要出国(境)的申请管理。</p>
|
||||
|
||||
|
||||
<p><strong>二、申请条件</strong></p>
|
||||
<p>1. 申请人须为我院正式在职教职工;</p>
|
||||
<p>2. 无违法违纪记录;</p>
|
||||
<p>3. 工作表现良好,能够按时完成本职工作。</p>
|
||||
|
||||
|
||||
<p><strong>三、申请流程</strong></p>
|
||||
<p>1. 填写《因私出国(境)申请登记报备表》;</p>
|
||||
<p>2. 提交相关证明材料;</p>
|
||||
<p>3. 按规定程序进行审批;</p>
|
||||
<p>4. 获得批准后方可办理相关手续。</p>
|
||||
|
||||
|
||||
<p><strong>四、注意事项</strong></p>
|
||||
<p>1. 申请人应如实填写申请表,不得隐瞒或虚报;</p>
|
||||
<p>2. 出国(境)期间应遵守当地法律法规;</p>
|
||||
<p>3. 按时回国并及时报告相关情况;</p>
|
||||
<p>4. 如有特殊情况,应及时与学院联系。</p>
|
||||
|
||||
|
||||
<p><strong>五、其他</strong></p>
|
||||
<p>本通知自发布之日起执行,如有疑问请联系人事处。</p>
|
||||
|
||||
<div style="text-align: right; margin-top: 30px;">
|
||||
|
||||
<div style="text-align: right; margin-top: 30px">
|
||||
<p>杭州第一技师学院</p>
|
||||
<p>{{ new Date().getFullYear() }}年{{ new Date().getMonth() + 1 }}月{{ new Date().getDate() }}日</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- 弹窗底部操作按钮 -->
|
||||
<template #footer>
|
||||
<div class="dialog-footer" style="text-align: center;">
|
||||
<el-button
|
||||
v-if="countdownActive"
|
||||
type="info"
|
||||
disabled
|
||||
size="large"
|
||||
style="min-width: 150px;"
|
||||
>
|
||||
请阅读文件内容 ({{ countdown }}s)
|
||||
</el-button>
|
||||
<el-button
|
||||
v-else-if="readCompleted"
|
||||
type="primary"
|
||||
@click="handleReadComplete"
|
||||
size="large"
|
||||
style="min-width: 150px;"
|
||||
>
|
||||
<div class="dialog-footer" style="text-align: center">
|
||||
<el-button v-if="countdownActive" type="info" disabled size="large" style="min-width: 150px"> 请阅读文件内容 ({{ countdown }}s) </el-button>
|
||||
<el-button v-else-if="readCompleted" type="primary" @click="handleReadComplete" size="large" style="min-width: 150px">
|
||||
阅读完成,进入申请
|
||||
</el-button>
|
||||
<el-button
|
||||
type="default"
|
||||
@click="handleCloseNotice"
|
||||
size="large"
|
||||
style="margin-left: 20px;"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<el-button type="default" @click="handleCloseNotice" size="large" style="margin-left: 20px"> 取消 </el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
@@ -90,10 +69,28 @@
|
||||
<!-- 顶部按钮区域 -->
|
||||
<div class="top-button-section">
|
||||
<div class="left-buttons">
|
||||
<el-button type="primary" :loading="loading" @click="handleSubmit" v-hasPermi="['rs:overseas:add', 'rs:overseas:edit']">
|
||||
{{ isResubmit ? '修改并重新提交' : (isEdit ? '更新' : '提交') }}
|
||||
</el-button>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<!-- ========================================
|
||||
🚨 标准化改造:根据模式显示不同按钮
|
||||
======================================== -->
|
||||
|
||||
<!-- 审批模式按钮 -->
|
||||
<template v-if="isApprovalMode">
|
||||
<el-button type="success" :loading="approvalLoading" @click="handleApprove('1')" v-hasPermi="['rs:overseas:approve']">
|
||||
<el-icon><Check /></el-icon> 通过
|
||||
</el-button>
|
||||
<el-button type="danger" :loading="approvalLoading" @click="handleApprove('2')" v-hasPermi="['rs:overseas:approve']">
|
||||
<el-icon><Close /></el-icon> 拒绝
|
||||
</el-button>
|
||||
<el-button @click="handleBackToWorkflow">返回待办</el-button>
|
||||
</template>
|
||||
|
||||
<!-- 普通编辑模式按钮 -->
|
||||
<template v-else>
|
||||
<el-button type="primary" :loading="loading" @click="handleSubmit" v-hasPermi="['rs:overseas:add', 'rs:overseas:edit']">
|
||||
{{ isResubmit ? '修改并重新提交' : isEdit ? '更新' : '提交' }}
|
||||
</el-button>
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,17 +98,15 @@
|
||||
<el-card shadow="never" class="form-card">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span class="section-title">
|
||||
{{ isResubmit ? '修改并重新提交' : (isEdit ? '编辑' : '新增') }}因私出国(境)申请登记报备表
|
||||
</span>
|
||||
<span class="section-title"> {{ isResubmit ? '修改并重新提交' : isEdit ? '编辑' : '新增' }}因私出国(境)申请登记报备表 </span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-form
|
||||
ref="applicationFormRef"
|
||||
:model="applicationForm"
|
||||
:rules="applicationRules"
|
||||
label-width="140px"
|
||||
<el-form
|
||||
ref="applicationFormRef"
|
||||
:model="applicationForm"
|
||||
:rules="applicationRules"
|
||||
label-width="140px"
|
||||
class="application-form"
|
||||
v-loading="formLoading"
|
||||
>
|
||||
@@ -202,16 +197,45 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第五排:户籍所在地、移动电话 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="户籍所在地" prop="registeredAddress">
|
||||
<el-input v-model="applicationForm.registeredAddress" placeholder="请输入户籍所在地(填写至县市区)" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="移动电话" prop="mobilePhone">
|
||||
<el-input v-model="applicationForm.mobilePhone" placeholder="请输入移动电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 第六排:人员类别 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="人员类别" prop="personnelCategory">
|
||||
<el-select v-model="applicationForm.personnelCategory" placeholder="请选择人员类别" style="width: 100%">
|
||||
<el-option label="教学人员" value="教学人员" />
|
||||
<el-option label="管理人员" value="管理人员" />
|
||||
<el-option label="教辅人员" value="教辅人员" />
|
||||
<el-option label="工勤人员" value="工勤人员" />
|
||||
<el-option label="其他人员" value="其他" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 出国信息 -->
|
||||
<div class="form-section">
|
||||
<div class="section-title">出国信息</div>
|
||||
<!-- 第五排:前往国家、出境事由 -->
|
||||
<!-- 第五排:前往国家或地区、出境事由 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="前往国家" prop="destinationCountry">
|
||||
<el-input v-model="applicationForm.destinationCountry" placeholder="请输入前往国家" />
|
||||
<el-form-item label="前往国家或地区" prop="destinationCountry">
|
||||
<el-input v-model="applicationForm.destinationCountry" placeholder="请输入前往国家或地区" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
@@ -227,10 +251,21 @@
|
||||
<!-- 探亲时显示的额外字段 -->
|
||||
<el-row :gutter="20" v-if="applicationForm.exitReason === '探亲'">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="探望对象关系" prop="visitRelationship">
|
||||
<el-input
|
||||
v-model="applicationForm.visitRelationship"
|
||||
placeholder="请输入探望对象姓名及与申请者关系"
|
||||
<el-form-item label="探望对象姓名及与申请者关系" prop="visitRelationship">
|
||||
<el-input v-model="applicationForm.visitRelationship" placeholder="请输入探望对象姓名及与申请者关系" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 备注 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="applicationForm.remarks"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
placeholder="请输入备注信息(可选)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -240,10 +275,10 @@
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="行程安排" prop="itinerary">
|
||||
<el-input
|
||||
v-model="applicationForm.itinerary"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
<el-input
|
||||
v-model="applicationForm.itinerary"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="请输入详细的行程安排(如:访问城市、主要活动、住宿安排等)"
|
||||
/>
|
||||
</el-form-item>
|
||||
@@ -253,7 +288,7 @@
|
||||
<!-- 出国(境)时间 -->
|
||||
<el-row :gutter="10">
|
||||
<el-col :span="2">
|
||||
<el-form-item label="出国(境)时间" label-width="110px" style="font-weight: 600; white-space: nowrap;">
|
||||
<el-form-item label="出国(境)时间" label-width="110px" style="font-weight: 600; white-space: nowrap">
|
||||
<span></span>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
@@ -263,7 +298,7 @@
|
||||
v-model="applicationForm.startDate"
|
||||
type="date"
|
||||
placeholder="请选择出发日期"
|
||||
style="width: 100%; min-width: 180px;"
|
||||
style="width: 100%; min-width: 180px"
|
||||
format="YYYY年MM月DD日"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
@@ -275,7 +310,7 @@
|
||||
v-model="applicationForm.endDate"
|
||||
type="date"
|
||||
placeholder="请选择返回日期"
|
||||
style="width: 100%; min-width: 180px;"
|
||||
style="width: 100%; min-width: 180px"
|
||||
format="YYYY年MM月DD日"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
@@ -283,7 +318,7 @@
|
||||
</el-col>
|
||||
<el-col :span="5" :offset="1">
|
||||
<el-form-item label="出国天数" prop="duration">
|
||||
<el-input v-model="applicationForm.duration" placeholder="天数" disabled style="min-width: 120px;">
|
||||
<el-input v-model="applicationForm.duration" placeholder="天数" disabled style="min-width: 120px">
|
||||
<template #append>天</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
@@ -291,30 +326,22 @@
|
||||
</el-row>
|
||||
|
||||
<!-- 党籍保留附件上传区域(仅当中共党员且出国天数>30天时显示) -->
|
||||
<div
|
||||
<div
|
||||
v-if="showPartyRetentionUpload"
|
||||
class="party-retention-upload-section"
|
||||
style="margin: 10px 0; padding: 10px; border: 1px dashed #ff4757; border-radius: 4px; background: #fff5f5;">
|
||||
<div style="font-size: 14px; color: #ff4757; margin-bottom: 8px; font-weight: bold;">
|
||||
class="party-retention-upload-section"
|
||||
style="margin: 10px 0; padding: 10px; border: 1px dashed #ff4757; border-radius: 4px; background: #fff5f5"
|
||||
>
|
||||
<div style="font-size: 14px; color: #ff4757; margin-bottom: 8px; font-weight: bold">
|
||||
<el-icon><Warning /></el-icon>
|
||||
党籍保留附件上传(必填)
|
||||
</div>
|
||||
<div style="font-size: 12px; color: #606266; margin-bottom: 8px;">
|
||||
<div style="font-size: 12px; color: #606266; margin-bottom: 8px">
|
||||
您是中共党员且出国天数超过30天,需要先提交党籍保留流程,审批通过后下载并上传盖章附件
|
||||
</div>
|
||||
<!-- 下载PDF空白模板按钮 -->
|
||||
<div style="margin-bottom: 10px;">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
icon="Download"
|
||||
@click="handleDownloadPartyRetention"
|
||||
>
|
||||
下载党籍保留申请PDF模板
|
||||
</el-button>
|
||||
<span style="margin-left: 10px; font-size: 12px; color: #909399;">
|
||||
* 请先下载空白模板并手工填写,然后上传盖章后的文件
|
||||
</span>
|
||||
<div style="margin-bottom: 10px">
|
||||
<el-button type="primary" size="small" icon="Download" @click="handleDownloadPartyRetention"> 下载党籍保留申请PDF模板 </el-button>
|
||||
<span style="margin-left: 10px; font-size: 12px; color: #909399"> * 请先下载空白模板并手工填写,然后上传盖章后的文件 </span>
|
||||
</div>
|
||||
<div class="attachment-upload-container">
|
||||
<el-upload
|
||||
@@ -332,13 +359,9 @@
|
||||
list-type="text"
|
||||
class="upload-demo"
|
||||
>
|
||||
<el-button size="small" type="primary" icon="Upload">
|
||||
上传党籍保留附件
|
||||
</el-button>
|
||||
<el-button size="small" type="primary" icon="Upload"> 上传党籍保留附件 </el-button>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip" style="font-size: 12px; color: #909399;">
|
||||
支持 PDF、图片、Word 格式,文件大小不超过 10MB
|
||||
</div>
|
||||
<div class="el-upload__tip" style="font-size: 12px; color: #909399">支持 PDF、图片、Word 格式,文件大小不超过 10MB</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
</div>
|
||||
@@ -354,19 +377,17 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
|
||||
<!-- 备注 -->
|
||||
<div class="form-section">
|
||||
<!-- 联系方式 -->
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24">
|
||||
<el-form-item label="备注" prop="remarks">
|
||||
<el-input
|
||||
v-model="applicationForm.remarks"
|
||||
type="textarea"
|
||||
:rows="1"
|
||||
placeholder="请输入备注信息"
|
||||
/>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系电话" prop="mobilePhone">
|
||||
<el-input v-model="applicationForm.mobilePhone" placeholder="请输入联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="应急联系人及联系电话" prop="emergencyContact">
|
||||
<el-input v-model="applicationForm.emergencyContact" placeholder="请输入应急联系人及联系电话" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -381,9 +402,17 @@ import { ref, reactive, computed, onMounted, getCurrentInstance, watch, nextTick
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import type { FormInstance, FormRules } from 'element-plus';
|
||||
import { Warning, Download, Upload } from '@element-plus/icons-vue';
|
||||
import { Warning, Download, Upload, Check, Close } from '@element-plus/icons-vue';
|
||||
import { getToken } from '@/utils/auth';
|
||||
import request from '@/utils/request';
|
||||
import {
|
||||
addOverseasApplication,
|
||||
updateOverseasApplication,
|
||||
getOverseasApplication,
|
||||
approveOverseasApplication,
|
||||
type OverseasApplicationForm,
|
||||
type ApprovalRequest
|
||||
} from '@/api/rs/overseas';
|
||||
|
||||
// 获取当前实例和路由
|
||||
const { proxy } = getCurrentInstance();
|
||||
@@ -396,6 +425,16 @@ const formLoading = ref(false);
|
||||
const applicationFormRef = ref<FormInstance>();
|
||||
const partyRetentionUploadRef = ref();
|
||||
|
||||
// ========================================
|
||||
// 🚨 标准化改造:审批相关状态
|
||||
// ========================================
|
||||
const approvalLoading = ref(false);
|
||||
const approvalDialogVisible = ref(false);
|
||||
const approvalForm = reactive({
|
||||
approvalOpinion: '',
|
||||
approvalStatus: '1'
|
||||
});
|
||||
|
||||
// 党籍保留附件相关
|
||||
const partyRetentionFileList = ref([]);
|
||||
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + '/rs/overseas/application/attachment/upload');
|
||||
@@ -430,12 +469,27 @@ const uploadHeaders = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// 🚨 标准化改造:支持工作流审批模式
|
||||
// ========================================
|
||||
|
||||
// 判断是否为审批模式(通过工作流跳转)
|
||||
const isApprovalMode = computed(() => !!route.query.taskId);
|
||||
|
||||
// 判断是否为编辑模式
|
||||
const isEdit = computed(() => route.query.type === 'edit' || route.query.type === 'resubmit' || !!route.query.id);
|
||||
|
||||
// 判断是否为重新提交模式
|
||||
const isResubmit = computed(() => route.query.type === 'resubmit');
|
||||
|
||||
// 获取工作流任务ID
|
||||
const taskId = computed(() => route.query.taskId as string);
|
||||
|
||||
// 获取申请ID(从路由参数)
|
||||
const applicationId = computed(() => {
|
||||
return route.query.applicationId || route.query.id;
|
||||
});
|
||||
|
||||
// 申请表单数据
|
||||
const applicationForm = reactive({
|
||||
applicationId: '',
|
||||
@@ -448,6 +502,9 @@ const applicationForm = reactive({
|
||||
position: '',
|
||||
documentName: '',
|
||||
documentNumber: '',
|
||||
registeredAddress: '', // ✅ 新增:户籍所在地
|
||||
mobilePhone: '', // ✅ 新增:移动电话
|
||||
personnelCategory: '', // ✅ 新增:人员类别
|
||||
destinationCountry: '',
|
||||
exitReason: '',
|
||||
visitRelationship: '',
|
||||
@@ -471,6 +528,13 @@ const applicationRules: FormRules = {
|
||||
position: [{ required: true, message: '请输入职务', trigger: 'blur' }],
|
||||
documentName: [{ required: true, message: '请输入申领证件名称', trigger: 'blur' }],
|
||||
documentNumber: [{ required: true, message: '请输入证件号码', trigger: 'blur' }],
|
||||
// ✅ 新增必填字段验证
|
||||
registeredAddress: [{ required: true, message: '请输入户籍所在地', trigger: 'blur' }],
|
||||
mobilePhone: [
|
||||
{ required: true, message: '请输入移动电话', trigger: 'blur' },
|
||||
{ pattern: /^1[3-9]\d{9}$/, message: '移动电话格式不正确', trigger: 'blur' }
|
||||
],
|
||||
personnelCategory: [{ required: true, message: '请选择人员类别', trigger: 'change' }],
|
||||
destinationCountry: [{ required: true, message: '请输入前往国家', trigger: 'blur' }],
|
||||
exitReason: [{ required: true, message: '请选择出境事由', trigger: 'change' }],
|
||||
itinerary: [{ required: true, message: '请输入行程安排', trigger: 'blur' }],
|
||||
@@ -497,19 +561,19 @@ watch([() => applicationForm.startDate, () => applicationForm.endDate], () => {
|
||||
// 计算属性:是否显示党籍保留上传组件
|
||||
const showPartyRetentionUpload = computed(() => {
|
||||
// 首先检查是否为中共党员
|
||||
const isPartyMember = applicationForm.politicalStatus === '1' || applicationForm.politicalStatus === '中共党员';
|
||||
|
||||
const isPartyMember = applicationForm.politicalStatus === '1' || applicationForm.politicalStatus === '中共党员';
|
||||
|
||||
// 如果不是党员,直接返回false
|
||||
if (!isPartyMember) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 检查出国天数
|
||||
const duration = applicationForm.duration;
|
||||
if (!duration) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// 出国天数大于30天才显示
|
||||
return Number(duration) > 30;
|
||||
});
|
||||
@@ -529,7 +593,7 @@ watch(
|
||||
const handleDownloadPartyRetention = async () => {
|
||||
try {
|
||||
ElMessage.info('正在生成PDF,请稍候...');
|
||||
|
||||
|
||||
// 使用request工具发送请求,自动包含认证头
|
||||
const response = await request({
|
||||
url: '/rs/overseas/record/downloadPartyRetention',
|
||||
@@ -538,17 +602,16 @@ const handleDownloadPartyRetention = async () => {
|
||||
});
|
||||
|
||||
// 创建blob并触发下载(response已经是blob对象)
|
||||
const blob = response instanceof Blob
|
||||
? new Blob([response], { type: 'application/pdf' })
|
||||
: new Blob([response.data], { type: 'application/pdf' });
|
||||
|
||||
const blob =
|
||||
response instanceof Blob ? new Blob([response], { type: 'application/pdf' }) : new Blob([response.data], { type: 'application/pdf' });
|
||||
|
||||
console.log('📥 生成的Blob大小:', blob.size, 'bytes');
|
||||
console.log('📥 Blob类型:', blob.type);
|
||||
|
||||
|
||||
if (blob.size === 0) {
|
||||
throw new Error('下载的文件为空');
|
||||
}
|
||||
|
||||
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
@@ -569,9 +632,14 @@ const handleDownloadPartyRetention = async () => {
|
||||
// 党籍保留附件上传前检查
|
||||
const beforePartyRetentionUpload = async (file: any) => {
|
||||
// 检查文件格式
|
||||
const isValidType = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png',
|
||||
'application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']
|
||||
.includes(file.type);
|
||||
const isValidType = [
|
||||
'application/pdf',
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/png',
|
||||
'application/msword',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
].includes(file.type);
|
||||
const isLt10M = file.size / 1024 / 1024 < 10;
|
||||
|
||||
if (!isValidType) {
|
||||
@@ -637,7 +705,7 @@ const handlePartyRetentionRemove = (file: any) => {
|
||||
const startCountdown = () => {
|
||||
countdownActive.value = true;
|
||||
readCompleted.value = false;
|
||||
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (countdown.value > 0) {
|
||||
countdown.value--;
|
||||
@@ -672,85 +740,276 @@ const handleCloseNotice = () => {
|
||||
// 提交表单
|
||||
const handleSubmit = async () => {
|
||||
if (!applicationFormRef.value) return;
|
||||
|
||||
|
||||
try {
|
||||
await applicationFormRef.value.validate();
|
||||
|
||||
|
||||
// 检查党员是否需要上传党籍保留附件
|
||||
if (showPartyRetentionUpload.value && !applicationForm.partyRetentionAttachment) {
|
||||
ElMessage.error('请上传党籍保留附件');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
loading.value = true;
|
||||
|
||||
// 这里调用保存API
|
||||
// 由于我只能修改rs模块内容,暂时模拟提交成功
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
const successMessage = isResubmit.value
|
||||
? '修改并重新提交成功!您的申请已重新进入审批流程。'
|
||||
: (isEdit.value ? '更新成功' : '申请提交成功!您的申请已进入审批流程,请耐心等待审批结果。');
|
||||
|
||||
// 准备提交数据 - 修复字段映射关系
|
||||
const submitData: OverseasApplicationForm = {
|
||||
...applicationForm,
|
||||
// ✅ 按照纸质版表格字段直接映射,不做转换
|
||||
// 保持前端字段名与数据库字段名一致
|
||||
destinationCountry: applicationForm.destinationCountry || '', // 前往国家或地区
|
||||
exitReason: applicationForm.exitReason || '', // 出境事由
|
||||
itinerary: applicationForm.itinerary || '', // 行程安排
|
||||
// 以下字段用于兼容后端现有逻辑
|
||||
purpose: applicationForm.exitReason || '', // 兼容字段
|
||||
purposeDetail: applicationForm.itinerary || '', // 兼容字段
|
||||
destination: applicationForm.destinationCountry || '', // 兼容字段
|
||||
// ✅ 使用用户实际填写的字段值,不再使用默认值
|
||||
registeredAddress: applicationForm.registeredAddress || '',
|
||||
mobilePhone: applicationForm.mobilePhone || '',
|
||||
personnelCategory: applicationForm.personnelCategory || '',
|
||||
emergencyContact: applicationForm.emergencyContact || '', // 应急联系人及联系电话
|
||||
remarks: applicationForm.remarks || '', // 备注
|
||||
// 保留的默认值字段
|
||||
budget: 0, // 如果没有预算字段,设置默认值
|
||||
contactPhone: applicationForm.mobilePhone || '暂无', // 使用移动电话作为联系电话
|
||||
duration: applicationForm.duration ? Number(applicationForm.duration) : 0
|
||||
};
|
||||
|
||||
// 新增模式:删除系统自动设置的字段,避免验证错误
|
||||
if (!isEdit.value) {
|
||||
delete submitData.applicantId;
|
||||
// ✅ 保留用户填写的姓名字段,不要删除
|
||||
// delete submitData.applicantName;
|
||||
// ✅ 保留部门字段,后端需要此字段
|
||||
// delete submitData.department;
|
||||
delete submitData.tenantId;
|
||||
delete submitData.applyTime;
|
||||
}
|
||||
|
||||
// 保留办公电话字段的过滤逻辑(可选字段)
|
||||
if (!submitData.officePhone) delete submitData.officePhone;
|
||||
|
||||
let result;
|
||||
if (isEdit.value && applicationForm.applicationId) {
|
||||
// 更新模式
|
||||
result = await updateOverseasApplication(submitData);
|
||||
} else {
|
||||
// 新增模式
|
||||
result = await addOverseasApplication(submitData);
|
||||
// 保存返回的applicationId
|
||||
if (result.data?.applicationId) {
|
||||
applicationForm.applicationId = result.data.applicationId;
|
||||
}
|
||||
}
|
||||
|
||||
const successMessage = isResubmit.value
|
||||
? '修改并重新提交成功!您的申请已重新进入审批流程。'
|
||||
: isEdit.value
|
||||
? '更新成功'
|
||||
: '申请提交成功!您的申请已进入审批流程,请耐心等待审批结果。';
|
||||
ElMessage.success(successMessage);
|
||||
|
||||
|
||||
// 返回上一页
|
||||
handleCancel();
|
||||
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error);
|
||||
handleGoBack();
|
||||
} catch (error: any) {
|
||||
console.error('提交失败:', error);
|
||||
ElMessage.error(error?.message || '提交失败,请稍后重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 取消操作
|
||||
// 保存申请(用于获取applicationId)
|
||||
const handleSave = async () => {
|
||||
if (!applicationFormRef.value) return;
|
||||
|
||||
await applicationFormRef.value.validate();
|
||||
|
||||
// 如果已有ID,无需重复保存
|
||||
if (applicationForm.applicationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备保存数据 - 修复字段映射关系
|
||||
const saveData: OverseasApplicationForm = {
|
||||
...applicationForm,
|
||||
// ✅ 按照纸质版表格字段直接映射,不做转换
|
||||
// 保持前端字段名与数据库字段名一致
|
||||
destinationCountry: applicationForm.destinationCountry || '', // 前往国家或地区
|
||||
exitReason: applicationForm.exitReason || '', // 出境事由
|
||||
itinerary: applicationForm.itinerary || '', // 行程安排
|
||||
// 以下字段用于兼容后端现有逻辑
|
||||
purpose: applicationForm.exitReason || '', // 兼容字段
|
||||
purposeDetail: applicationForm.itinerary || '', // 兼容字段
|
||||
destination: applicationForm.destinationCountry || '', // 兼容字段
|
||||
// ✅ 使用用户实际填写的字段值,不再使用默认值
|
||||
registeredAddress: applicationForm.registeredAddress || '',
|
||||
mobilePhone: applicationForm.mobilePhone || '',
|
||||
personnelCategory: applicationForm.personnelCategory || '',
|
||||
emergencyContact: applicationForm.emergencyContact || '', // 应急联系人及联系电话
|
||||
remarks: applicationForm.remarks || '', // 备注
|
||||
// 保留的默认值字段
|
||||
budget: 0, // 如果没有预算字段,设置默认值
|
||||
contactPhone: applicationForm.mobilePhone || '暂无', // 使用移动电话作为联系电话
|
||||
duration: applicationForm.duration ? Number(applicationForm.duration) : 0
|
||||
};
|
||||
|
||||
// 删除系统自动设置的字段,避免验证错误
|
||||
delete saveData.applicantId;
|
||||
// ✅ 保留用户填写的姓名字段,不要删除
|
||||
// delete saveData.applicantName;
|
||||
delete saveData.department;
|
||||
delete saveData.tenantId;
|
||||
delete saveData.applyTime;
|
||||
|
||||
// 保留办公电话字段的过滤逻辑(可选字段)
|
||||
if (!saveData.officePhone) delete saveData.officePhone;
|
||||
|
||||
try {
|
||||
const result = await addOverseasApplication(saveData);
|
||||
if (result.data?.applicationId) {
|
||||
applicationForm.applicationId = result.data.applicationId;
|
||||
ElMessage.success('申请信息保存成功');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error);
|
||||
throw new Error('保存申请信息失败');
|
||||
}
|
||||
};
|
||||
|
||||
// 直接返回上一页(不弹确认框)
|
||||
const handleGoBack = () => {
|
||||
// 关闭当前页面,返回上一页
|
||||
proxy.$tab.closePage(route);
|
||||
router.go(-1);
|
||||
};
|
||||
|
||||
// 取消操作(弹出确认框)
|
||||
const handleCancel = () => {
|
||||
ElMessageBox.confirm('是否确认取消操作?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 关闭当前页面,返回上一页
|
||||
proxy.$tab.closePage(route);
|
||||
router.go(-1);
|
||||
}).catch(() => {
|
||||
// 用户取消
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
handleGoBack();
|
||||
})
|
||||
.catch(() => {
|
||||
// 用户取消
|
||||
});
|
||||
};
|
||||
|
||||
// 初始化数据
|
||||
const initData = async () => {
|
||||
if (isEdit.value && route.query.id) {
|
||||
// 🔴 标准化改造:支持审批模式和编辑模式
|
||||
const targetId = applicationId.value;
|
||||
if ((isEdit.value && route.query.id) || (isApprovalMode.value && targetId)) {
|
||||
formLoading.value = true;
|
||||
try {
|
||||
// 这里应该根据ID获取数据
|
||||
// 由于我只能修改rs模块内容,暂时模拟数据加载
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
// 模拟填充编辑数据
|
||||
Object.assign(applicationForm, {
|
||||
applicationId: route.query.id,
|
||||
applicantName: '张三',
|
||||
gender: '男',
|
||||
// ... 其他字段
|
||||
});
|
||||
|
||||
// 根据ID获取申请数据(支持审批模式和编辑模式)
|
||||
const result = await getOverseasApplication(targetId as string);
|
||||
if (result.data) {
|
||||
// 映射数据到表单 - 修复字段映射关系
|
||||
Object.assign(applicationForm, {
|
||||
applicationId: result.data.applicationId,
|
||||
applicantName: result.data.applicantName || '',
|
||||
gender: result.data.gender || '',
|
||||
birthDate: result.data.birthDate || '',
|
||||
politicalStatus: result.data.politicalStatus || '',
|
||||
department: result.data.department || '',
|
||||
professionalTitle: result.data.professionalTitle || '',
|
||||
position: result.data.position || '',
|
||||
documentName: result.data.documentName || '',
|
||||
documentNumber: result.data.documentNumber || '',
|
||||
// ✅ 新增字段的映射
|
||||
registeredAddress: result.data.registeredAddress || '',
|
||||
mobilePhone: result.data.mobilePhone || '',
|
||||
personnelCategory: result.data.personnelCategory || '',
|
||||
// ✅ 修复字段映射关系
|
||||
destinationCountry: result.data.destination || '', // 后端destination -> 前端destinationCountry
|
||||
exitReason: result.data.purpose || '', // 后端purpose -> 前端exitReason
|
||||
itinerary: result.data.purposeDetail || result.data.itinerary || '', // 后端purposeDetail -> 前端itinerary
|
||||
visitRelationship: result.data.visitRelationship || '',
|
||||
startDate: result.data.startDate || '',
|
||||
endDate: result.data.endDate || '',
|
||||
duration: result.data.duration || '',
|
||||
remarks: result.data.applicantRemark || result.data.remarks || '',
|
||||
partyRetentionAttachment: result.data.partyRetentionFilePath || '',
|
||||
partyRetentionAttachmentId: result.data.partyRetentionFileOssId || ''
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取数据失败:', error);
|
||||
ElMessage.error('获取数据失败');
|
||||
ElMessage.error('获取申请数据失败');
|
||||
} finally {
|
||||
formLoading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ========================================
|
||||
// 🚨 标准化改造:审批相关处理函数
|
||||
// ========================================
|
||||
|
||||
// 处理审批操作
|
||||
const handleApprove = async (approvalStatus: string) => {
|
||||
try {
|
||||
const title = approvalStatus === '1' ? '审批通过' : '审批拒绝';
|
||||
const placeholder = approvalStatus === '1' ? '请填写审批意见(可选)' : '请填写拒绝理由';
|
||||
|
||||
const { value: opinion } = await ElMessageBox.prompt(
|
||||
`确定要${title}此申请吗?`,
|
||||
title,
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputPlaceholder: placeholder,
|
||||
inputType: 'textarea',
|
||||
inputValidator: approvalStatus === '2' ? (value) => {
|
||||
return value && value.trim() !== '' || '拒绝时必须填写理由';
|
||||
} : null
|
||||
}
|
||||
);
|
||||
|
||||
approvalLoading.value = true;
|
||||
|
||||
const approvalRequest: ApprovalRequest = {
|
||||
applicationId: applicationId.value as string,
|
||||
approvalStatus,
|
||||
approvalOpinion: opinion || '',
|
||||
taskId: taskId.value
|
||||
};
|
||||
|
||||
await approveOverseasApplication(approvalRequest);
|
||||
|
||||
ElMessage.success(`${title}成功!`);
|
||||
|
||||
// 返回到待办任务页面
|
||||
handleBackToWorkflow();
|
||||
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
console.error('审批失败:', error);
|
||||
ElMessage.error(error?.message || '审批失败,请稍后重试');
|
||||
}
|
||||
} finally {
|
||||
approvalLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 返回到工作流待办页面
|
||||
const handleBackToWorkflow = () => {
|
||||
router.push('/workflow/task/taskWaiting');
|
||||
};
|
||||
|
||||
// 组件挂载时初始化
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
initData();
|
||||
|
||||
// 新增模式下显示文件阅读弹窗
|
||||
if (route.query.type === 'add') {
|
||||
|
||||
// 新增模式下显示文件阅读弹窗(审批模式下不显示)
|
||||
if (route.query.type === 'add' && !isApprovalMode.value) {
|
||||
noticeDialog.visible = true;
|
||||
startCountdown();
|
||||
}
|
||||
@@ -816,7 +1075,7 @@ onMounted(() => {
|
||||
.el-col-8 {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
|
||||
.el-col-12 {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
@@ -463,14 +463,23 @@ function handleTabClick(tab) {
|
||||
/** 查询出国审批列表 */
|
||||
function getList() {
|
||||
loading.value = true;
|
||||
console.log('临时清空出国审批列表内容');
|
||||
|
||||
// 临时清空列表内容
|
||||
approvalList.value = [];
|
||||
total.value = 0;
|
||||
loading.value = false;
|
||||
|
||||
console.log('出国审批列表已清空');
|
||||
const params = {
|
||||
...queryParams.value,
|
||||
pageNum: queryParams.value.pageNum,
|
||||
pageSize: queryParams.value.pageSize
|
||||
};
|
||||
|
||||
listOverseasApproval(params)
|
||||
.then((response) => {
|
||||
approvalList.value = response.rows;
|
||||
total.value = response.total;
|
||||
loading.value = false;
|
||||
console.log('出国审批列表查询成功,记录数:', total.value);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('查询出国审批列表失败:', error);
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
/** 查询审批记录列表 */
|
||||
|
||||
Reference in new issue
Block a user