ソースを参照

“0826权限已修复”

weizhong 1 年間 前
親
コミット
3dc8cf677a

+ 26 - 3
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<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);
+    }
+
+    /**
+     * 查询我的培训申请列表(供申请人使用)
+     */
+    @SaCheckPermission("rs:trainingapplication:query")
     @GetMapping("/my")
     public TableDataInfo<RsTrainingApplication> listMyApplications(RsTrainingApplication application, PageQuery pageQuery) {
         return trainingApplicationService.selectPageMyApplications(StpUtil.getLoginIdAsLong(), application, pageQuery);

+ 11 - 1
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<RsTrainingApproval> list(RsTrainingApproval approval, PageQuery pageQuery) {
+    public TableDataInfo<RsTrainingApproval> 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);
     }

+ 6 - 0
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;
 } 

+ 7 - 0
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;
     }
 

+ 12 - 4
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<RsTrainingApproval> selectPagePendingApprovals(Long approverId, RsTrainingApproval approval, PageQuery pageQuery) {
         LambdaQueryWrapper<RsTrainingApproval> 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<RsTrainingApproval> page = trainingApprovalMapper.selectPage(pageQuery.build(), lqw);
         return TableDataInfo.build(page);

+ 24 - 0
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<RsTrainingRecord> 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<RsTrainingRecord> allList = trainingRecordMapper.selectRsTrainingRecordList(record);
         

+ 22 - 0
backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingApplicationMapper.xml

@@ -60,6 +60,16 @@
             <if test="tenantId != null  and tenantId != ''">and a.tenant_id = #{tenantId}</if>
             <if test="location != null and location != ''">and a.location like concat('%', #{location}, '%')</if>
             <if test="duration != null">and a.duration = #{duration}</if>
+            <!-- 课程名称搜索条件:支持自定义课程名称和标准课程标题 -->
+            <if test="courseName != null and courseName != ''">
+                and (
+                    (a.course_id = 0 and a.course_name like concat('%', #{courseName}, '%'))
+                    or 
+                    (a.course_id != 0 and c.title like concat('%', #{courseName}, '%'))
+                )
+            </if>
+            <!-- 排除指定状态的条件(用于待审批接口) -->
+            <if test="excludeStatus != null and excludeStatus != ''">and a.status != #{excludeStatus}</if>
         </where>
         ORDER BY a.create_time DESC
     </select>
@@ -80,6 +90,18 @@
             <if test="app.applyTime != null ">and a.apply_time = #{app.applyTime}</if>
             <if test="app.currentApprover != null and app.currentApprover != ''">and a.current_approver = #{app.currentApprover}</if>
             <if test="app.tenantId != null and app.tenantId != ''">and a.tenant_id = #{app.tenantId}</if>
+            <if test="app.location != null and app.location != ''">and a.location like concat('%', #{app.location}, '%')</if>
+            <if test="app.duration != null">and a.duration = #{app.duration}</if>
+            <!-- 课程名称搜索条件:支持自定义课程名称和标准课程标题 -->
+            <if test="app.courseName != null and app.courseName != ''">
+                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}, '%'))
+                )
+            </if>
+            <!-- 排除指定状态的条件(用于待审批接口) -->
+            <if test="app.excludeStatus != null and app.excludeStatus != ''">and a.status != #{app.excludeStatus}</if>
         </where>
         ORDER BY a.create_time DESC
         LIMIT #{offset}, #{limit}

+ 141 - 0
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<RsTrainingApplication> 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` - 后端开发规范

+ 19 - 0
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({

+ 3 - 2
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'
   });