From 3dc8cf677a896aeac950991d98f41a9376ecc242 Mon Sep 17 00:00:00 2001 From: weizhong Date: Tue, 26 Aug 2025 13:24:55 +0800 Subject: [PATCH] =?UTF-8?q?=E2=80=9C0826=E6=9D=83=E9=99=90=E5=B7=B2?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RsTrainingApplicationController.java | 29 +++- .../RsTrainingApprovalController.java | 12 +- .../rs/domain/RsTrainingApplication.java | 6 + .../RsTrainingApplicationServiceImpl.java | 7 + .../impl/RsTrainingApprovalServiceImpl.java | 16 +- .../impl/RsTrainingRecordServiceImpl.java | 24 +++ .../mapper/rs/RsTrainingApplicationMapper.xml | 22 +++ docs/开发文档/API/物业报修管理API.md | 141 ++++++++++++++++++ web/src/api/rs/training.ts | 19 +++ web/src/views/rs/trainingApproval/index.vue | 5 +- 10 files changed, 271 insertions(+), 10 deletions(-) diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java index 3ecb90ed..ad5df660 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApplicationController.java @@ -27,6 +27,7 @@ import jakarta.servlet.http.HttpServletResponse; import org.dromara.system.domain.vo.SysUserVo; import org.dromara.system.service.ISysUserService; import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.satoken.utils.LoginHelper; /** * 培训申请控制器 @@ -48,7 +49,7 @@ public class RsTrainingApplicationController extends BaseController { private ISysUserService sysUserService; /** - * 查询培训申请列表 + * 查询培训申请列表(供管理员和审批人使用) */ @SaCheckPermission("rs:trainingapplication:list") @GetMapping("/list") @@ -249,9 +250,31 @@ public class RsTrainingApplicationController extends BaseController { } /** - * 查询我的培训申请列表 + * 查询待审批的培训申请列表(供审批人使用) */ - @SaCheckPermission("rs:trainingapplication:list") + @SaCheckPermission("rs:trainingapproval:process") + @GetMapping("/pending/list") + public TableDataInfo pendingList(RsTrainingApplication application, PageQuery pageQuery) { + // 设置租户ID + if (application.getTenantId() == null || application.getTenantId().isEmpty()) { + application.setTenantId(LoginHelper.getTenantId()); + } + + // 只查询待审核状态的申请(排除已通过状态) + application.setExcludeStatus("1"); // 排除已通过状态 + + log.info("查询待审批培训申请列表 - 用户: {}, 角色: {}, 查询条件: {}", + LoginHelper.getUsername(), + StpUtil.getRoleList(), + application); + + return trainingApplicationService.selectPageRsTrainingApplicationList(application, pageQuery); + } + + /** + * 查询我的培训申请列表(供申请人使用) + */ + @SaCheckPermission("rs:trainingapplication:query") @GetMapping("/my") public TableDataInfo listMyApplications(RsTrainingApplication application, PageQuery pageQuery) { return trainingApplicationService.selectPageMyApplications(StpUtil.getLoginIdAsLong(), application, pageQuery); diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApprovalController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApprovalController.java index 0b284cb7..f6af716c 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApprovalController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingApprovalController.java @@ -40,7 +40,17 @@ public class RsTrainingApprovalController extends BaseController { */ @SaCheckPermission("rs:trainingapproval:list") @GetMapping("/list") - public TableDataInfo list(RsTrainingApproval approval, PageQuery pageQuery) { + public TableDataInfo list(RsTrainingApproval approval, PageQuery pageQuery, + @RequestParam(value = "courseName", required = false) String courseName, + @RequestParam(value = "userName", required = false) String userName) { + // 参数转换:将前端传递的参数名转换为后端Mapper期望的参数名 + if (courseName != null && !courseName.trim().isEmpty()) { + approval.setCourseTitle(courseName.trim()); + } + if (userName != null && !userName.trim().isEmpty()) { + approval.setApplicationUserName(userName.trim()); + } + // 使用带详细信息的查询方法,返回申请人姓名和课程名称 return trainingApprovalService.selectPageRsTrainingApprovalWithDetailsList(approval, pageQuery); } diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingApplication.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingApplication.java index 0dc6f2d6..9e5fb734 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingApplication.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingApplication.java @@ -118,4 +118,10 @@ public class RsTrainingApplication extends BaseEntity { */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date rejectTime; + + /** + * 排除状态(查询时排除指定状态,不存储到数据库) + */ + @TableField(exist = false) + private String excludeStatus; } \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java index b7ff22ba..5b5c1d39 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java @@ -84,6 +84,9 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS } else if (StpUtil.hasRole("rs-admin")) { // 人事管理员可以看到所有申请(包括审批和部门管理功能) // 不设置额外过滤条件,让管理员能看到所有申请 + } else if (StpUtil.hasRole("rs-Approver")) { + // 审批人角色:可以看到所有申请(用于审批) + // 不设置过滤条件,允许查看所有申请 } else { // 默认情况:只能看到自己创建的申请 application.setCreateBy(currentUserId); @@ -196,6 +199,10 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS lqw.eq(application.getStatus() != null, RsTrainingApplication::getStatus, application.getStatus()); lqw.like(application.getLocation() != null, RsTrainingApplication::getLocation, application.getLocation()); lqw.eq(application.getDuration() != null, RsTrainingApplication::getDuration, application.getDuration()); + // 课程名称搜索:搜索自定义课程名称字段 + lqw.like(application.getCourseName() != null, RsTrainingApplication::getCourseName, application.getCourseName()); + // 排除指定状态的条件(用于待审批接口) + lqw.ne(application.getExcludeStatus() != null, RsTrainingApplication::getStatus, application.getExcludeStatus()); return lqw; } diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApprovalServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApprovalServiceImpl.java index eb8b103b..8cae68fa 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApprovalServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApprovalServiceImpl.java @@ -78,9 +78,9 @@ public class RsTrainingApprovalServiceImpl implements IRsTrainingApprovalService // 申请人只能看到与自己相关的审批记录 // 这里需要通过申请表关联查询 lqw.exists("SELECT 1 FROM rs_training_application app WHERE app.application_id = rs_training_approval.application_id AND app.user_id = " + currentUserId); - } else if (StpUtil.hasRole("rs-admin")) { - // 人事管理员可以看到所有审批记录 - // 不设置额外过滤条件,让管理员能看到所有审批 + } else if (StpUtil.hasRole("rs-admin") || StpUtil.hasRole("rs-Approver")) { + // 人事管理员和审批人可以看到所有审批记录 + // 不设置额外过滤条件,让管理员和审批人能看到所有审批 } else { // 默认情况:只能看到自己创建或处理的审批 lqw.and(wrapper -> wrapper.eq(RsTrainingApproval::getCreateBy, currentUserId) @@ -126,7 +126,15 @@ public class RsTrainingApprovalServiceImpl implements IRsTrainingApprovalService @Override public TableDataInfo selectPagePendingApprovals(Long approverId, RsTrainingApproval approval, PageQuery pageQuery) { LambdaQueryWrapper lqw = buildQueryWrapper(approval); - lqw.eq(RsTrainingApproval::getApproverId, approverId); + + // 简化权限模型:审批人和管理员可以看到所有待审核记录 + if (StpUtil.hasRole("rs-admin") || StpUtil.hasRole("rs-Approver")) { + // 管理员和审批人可以看到所有待审核记录,不限制approverId + } else { + // 其他角色只能看到指派给自己的待审核记录 + lqw.eq(RsTrainingApproval::getApproverId, approverId); + } + lqw.eq(RsTrainingApproval::getStatus, "0"); // 待审核状态 com.baomidou.mybatisplus.extension.plugins.pagination.Page page = trainingApprovalMapper.selectPage(pageQuery.build(), lqw); return TableDataInfo.build(page); diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java index 194ac935..86a17c1a 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java @@ -21,6 +21,8 @@ import org.slf4j.LoggerFactory; import org.apache.commons.lang3.StringUtils; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; +import org.dromara.common.satoken.utils.LoginHelper; +import cn.dev33.satoken.stp.StpUtil; /** * 培训记录服务实现类 @@ -61,6 +63,28 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService, Ap */ @Override public TableDataInfo selectPageRsTrainingRecordList(RsTrainingRecord record, PageQuery pageQuery) { + // ===== 业务逻辑层权限过滤 ===== + // 根据用户角色进行数据权限过滤 + if (!LoginHelper.isSuperAdmin()) { + // 获取当前登录用户信息 + Long currentUserId = LoginHelper.getUserId(); + + // 检查用户角色并应用相应的数据过滤 + if (StpUtil.hasRole("rs-Applicant")) { + // 申请人只能看到自己的培训记录 + record.setUserId(currentUserId); + } else if (StpUtil.hasRole("rs-admin")) { + // 人事管理员可以看到所有培训记录 + // 不设置额外过滤条件,让管理员能看到所有记录 + } else if (StpUtil.hasRole("rs-Approver")) { + // 审批人角色:可以看到所有培训记录(用于管理) + // 不设置过滤条件,允许查看所有记录 + } else { + // 默认情况:只能看到自己的培训记录 + record.setUserId(currentUserId); + } + } + // 使用手动分页方式,参考培训申请的实现 List allList = trainingRecordMapper.selectRsTrainingRecordList(record); diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingApplicationMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingApplicationMapper.xml index d8bff300..965aa2f5 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingApplicationMapper.xml +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingApplicationMapper.xml @@ -60,6 +60,16 @@ and a.tenant_id = #{tenantId} and a.location like concat('%', #{location}, '%') and a.duration = #{duration} + + + and ( + (a.course_id = 0 and a.course_name like concat('%', #{courseName}, '%')) + or + (a.course_id != 0 and c.title like concat('%', #{courseName}, '%')) + ) + + + and a.status != #{excludeStatus} ORDER BY a.create_time DESC @@ -80,6 +90,18 @@ and a.apply_time = #{app.applyTime} and a.current_approver = #{app.currentApprover} and a.tenant_id = #{app.tenantId} + and a.location like concat('%', #{app.location}, '%') + and a.duration = #{app.duration} + + + and ( + (a.course_id = 0 and a.course_name like concat('%', #{app.courseName}, '%')) + or + (a.course_id != 0 and c.title like concat('%', #{app.courseName}, '%')) + ) + + + and a.status != #{app.excludeStatus} ORDER BY a.create_time DESC LIMIT #{offset}, #{limit} diff --git a/docs/开发文档/API/物业报修管理API.md b/docs/开发文档/API/物业报修管理API.md index ddae470c..b7141d5c 100644 --- a/docs/开发文档/API/物业报修管理API.md +++ b/docs/开发文档/API/物业报修管理API.md @@ -668,3 +668,144 @@ **📝 维护**: 物业报修管理 API 文档 **🔄 更新**: 2025-07-01 **�� 文件**: 物业报修管理 API.md + +## 📊 统计分析接口 + +**详细信息参考**: `docs/业务模块/物业报修管理/统计分析功能.md` + +--- + +## 🔧 培训申请待审批接口 (新增 2025-01-03) + +### 查询待审批培训申请列表 + +**接口地址**: `GET /rs/training/application/pending/list` + +**权限要求**: `rs:trainingapproval:process` + +**接口说明**: 专门为审批人员提供的待审批培训申请列表接口,解决了rs-Approver角色权限问题 + +#### 请求参数 + +| 参数名 | 类型 | 必填 | 说明 | +|--------|------|------|------| +| pageNum | number | 否 | 页码,默认1 | +| pageSize | number | 否 | 每页数量,默认10 | +| courseName | string | 否 | 课程名称(模糊查询) | +| location | string | 否 | 培训地点(模糊查询) | +| status | string | 否 | 申请状态 | +| excludeStatus | string | 否 | 排除状态(默认排除已通过状态"1") | + +#### 响应参数 + +```json +{ + "code": 200, + "msg": "查询成功", + "rows": [ + { + "applicationId": 1234567890123456789, + "courseId": 1, + "courseName": "Spring Boot开发培训", + "userId": 123, + "userName": "张三", + "department": "技术部", + "position": "开发工程师", + "reason": "提升技术能力", + "status": "0", + "applyTime": "2025-01-03 10:00:00", + "location": "培训中心A座", + "duration": 3, + "startTime": "2025-01-10 09:00:00", + "endTime": "2025-01-12 17:00:00" + } + ], + "total": 15 +} +``` + +#### 状态码说明 + +| 状态 | 含义 | 类型 | +|------|------|------| +| 0 | 待审核 | warning | +| 1 | 已通过 | success | +| 2 | 已拒绝 | danger | +| 3 | 已取消 | info | +| 4 | 已完成 | success | + +#### 权限控制 + +- **接口权限**: `rs:trainingapproval:process` +- **角色支持**: rs-Approver, rs-admin +- **数据过滤**: + - rs-Approver角色:可查看所有待审批申请 + - rs-admin角色:可查看所有申请 + - 自动排除已通过状态的申请 + +#### 使用示例 + +**前端调用**: +```typescript +import { listPendingTrainingApplication } from '@/api/rs/training' + +// 查询待审批列表 +const getPendingList = async () => { + const params = { + pageNum: 1, + pageSize: 10, + excludeStatus: '1' // 排除已通过状态 + } + const response = await listPendingTrainingApplication(params) + return response +} +``` + +**Curl示例**: +```bash +curl -X GET "http://localhost:8080/rs/training/application/pending/list?pageNum=1&pageSize=10" \ + -H "Authorization: Bearer your-token" \ + -H "clientid: e5cd7e4891bf95d1d19206ce24a7b32e" \ + -H "referer: http://localhost" +``` + +#### 与原接口对比 + +| 项目 | 原接口 | 新接口 | +|------|--------|--------| +| 接口地址 | `/rs/training/application/list` | `/rs/training/application/pending/list` | +| 权限要求 | `rs:trainingapplication:list` | `rs:trainingapproval:process` | +| 角色支持 | rs-admin | rs-Approver, rs-admin | +| 设计目的 | 申请管理 | 审批处理 | +| 权限独立性 | ❌ 权限混合 | ✅ 权限分离 | + +#### 技术实现 + +**后端Controller**: +```java +@SaCheckPermission("rs:trainingapproval:process") +@GetMapping("/pending/list") +public TableDataInfo pendingList( + RsTrainingApplication application, + PageQuery pageQuery) { + // 设置租户ID和排除状态 + application.setTenantId(LoginHelper.getTenantId()); + application.setExcludeStatus("1"); // 排除已通过状态 + + return trainingApplicationService.selectPageRsTrainingApplicationList( + application, pageQuery); +} +``` + +**权限过滤逻辑**: +- Service层根据rs-Approver角色自动放开数据权限 +- Mapper层支持excludeStatus参数过滤 +- 前端调用专门的审批接口,权限控制更精确 + +--- + +**接口文档最后更新**: 2025-01-03 +**维护责任人**: 系统架构师 +**相关文档**: +- `docs/开发文档/培训申请权限一致性修复报告.md` +- `.cursor/cursorrules-backend.md` - 后端开发规范 diff --git a/web/src/api/rs/training.ts b/web/src/api/rs/training.ts index 18c1eeb2..12c40d89 100644 --- a/web/src/api/rs/training.ts +++ b/web/src/api/rs/training.ts @@ -52,6 +52,7 @@ export interface ApplicationQuery { pageSize: number; courseId?: number | string; courseName?: string; + location?: string; // 培训地点 userId?: number | string; userName?: string; department?: string; @@ -175,6 +176,15 @@ export function listTrainingApplication(query: ApplicationQuery) { }); } +// 查询待审批的培训申请列表(供审批人使用) +export function listPendingTrainingApplication(query: ApplicationQuery) { + return request({ + url: '/rs/training/application/pending/list', + method: 'get', + params: query + }); +} + // 获取培训申请详情 export function getTrainingApplication(applicationId: string | number) { return request({ @@ -245,6 +255,15 @@ export function listTrainingRecord(query: RecordQuery) { }); } +// 获取我的培训记录列表 +export function listMyTrainingRecord(query: RecordQuery) { + return request({ + url: '/rs/training/record/my', + method: 'get', + params: query + }); +} + // 获取培训记录详情 export function getTrainingRecord(recordId: string | number) { return request({ diff --git a/web/src/views/rs/trainingApproval/index.vue b/web/src/views/rs/trainingApproval/index.vue index ad0a58f2..e3808a91 100644 --- a/web/src/views/rs/trainingApproval/index.vue +++ b/web/src/views/rs/trainingApproval/index.vue @@ -916,10 +916,11 @@ const handleViewApprovalHistory = async (row: any) => { } // 设置当前应用信息 + // 兼容两种数据结构:培训申请(courseName, userName) 和 培训记录(courseTitle, applicationUserName) Object.assign(currentApplication, { applicationId: applicationId, - courseName: row.courseName || '未知课程', - userName: row.userName || '未知用户', + courseName: row.courseName || row.courseTitle || '未知课程', + userName: row.userName || row.applicationUserName || '未知用户', applyTime: row.applyTime || '', status: row.status || '0' });