Explorar o código

“人事管理更新0823”

weizhong hai 1 ano
pai
achega
9946b329fa

+ 22 - 1
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsJobController.java

@@ -98,6 +98,10 @@ public class RsJobController extends BaseController {
     @Log(title = "招聘职位", businessType = BusinessType.INSERT)
     @Log(title = "招聘职位", businessType = BusinessType.INSERT)
     @PostMapping
     @PostMapping
     public R<Void> add(@RequestBody RsJob rsJob) {
     public R<Void> add(@RequestBody RsJob rsJob) {
+        // 如果职位是启用状态,自动设置发布时间
+        if ("0".equals(rsJob.getIsActive())) {
+            rsJob.setPublishTime(new java.util.Date());
+        }
         return toAjax(jobService.insertRsJob(rsJob));
         return toAjax(jobService.insertRsJob(rsJob));
     }
     }
 
 
@@ -108,6 +112,16 @@ public class RsJobController extends BaseController {
     @Log(title = "招聘职位", businessType = BusinessType.UPDATE)
     @Log(title = "招聘职位", businessType = BusinessType.UPDATE)
     @PutMapping
     @PutMapping
     public R<Void> edit(@RequestBody RsJob rsJob) {
     public R<Void> edit(@RequestBody RsJob rsJob) {
+        // 检查是否是状态切换为启用
+        if ("0".equals(rsJob.getIsActive())) {
+            // 获取原职位信息
+            RsJobVo originalJob = jobService.selectRsJobById(rsJob.getJobId());
+            // 如果原来是停用状态,现在启用,或者原来没有发布时间,则设置发布时间
+            if (originalJob != null && 
+                ("1".equals(originalJob.getIsActive()) || originalJob.getPublishTime() == null)) {
+                rsJob.setPublishTime(new java.util.Date());
+            }
+        }
         return toAjax(jobService.updateRsJob(rsJob));
         return toAjax(jobService.updateRsJob(rsJob));
     }
     }
     
     
@@ -125,7 +139,14 @@ public class RsJobController extends BaseController {
         RsJob updateJob = new RsJob();
         RsJob updateJob = new RsJob();
         updateJob.setJobId(jobId);
         updateJob.setJobId(jobId);
         // 切换状态
         // 切换状态
-        updateJob.setIsActive("0".equals(job.getIsActive()) ? "1" : "0");
+        String newStatus = "0".equals(job.getIsActive()) ? "1" : "0";
+        updateJob.setIsActive(newStatus);
+        
+        // 如果从停用切换为启用,且没有发布时间,则设置发布时间
+        if ("0".equals(newStatus) && job.getPublishTime() == null) {
+            updateJob.setPublishTime(new java.util.Date());
+        }
+        
         return toAjax(jobService.updateRsJob(updateJob));
         return toAjax(jobService.updateRsJob(updateJob));
     }
     }
 
 

+ 78 - 0
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsTrainingRecordController.java

@@ -16,10 +16,18 @@ import org.dromara.rs.service.IRsTrainingRecordService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import org.dromara.common.core.utils.DateUtils;
+import org.dromara.common.core.utils.StringUtils;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
 
 
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.dev33.satoken.stp.StpUtil;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpServletResponse;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.dromara.rs.service.impl.RsTrainingRecordServiceImpl;
 
 
 /**
 /**
  * 培训记录控制器
  * 培训记录控制器
@@ -94,6 +102,18 @@ public class RsTrainingRecordController extends BaseController {
     public R<Void> remove(@PathVariable Long[] recordIds) {
     public R<Void> remove(@PathVariable Long[] recordIds) {
         return toAjax(trainingRecordService.deleteRsTrainingRecordByIds(recordIds));
         return toAjax(trainingRecordService.deleteRsTrainingRecordByIds(recordIds));
     }
     }
+
+    /**
+     * 修复培训记录中缺失的课程信息
+     */
+    @SaCheckPermission("rs:training:record:edit")
+    @Log(title = "培训记录", businessType = BusinessType.UPDATE)
+    @PostMapping("/fixCourseInfo")
+    public R<Integer> fixCourseInfo() {
+        RsTrainingRecordServiceImpl serviceImpl = (RsTrainingRecordServiceImpl) trainingRecordService;
+        int fixedCount = serviceImpl.fixMissingCourseInfo();
+        return R.ok("修复完成,共修复 " + fixedCount + " 条记录", fixedCount);
+    }
     
     
     /**
     /**
      * 查询我的培训记录列表
      * 查询我的培训记录列表
@@ -126,6 +146,53 @@ public class RsTrainingRecordController extends BaseController {
         return toAjax(trainingRecordService.uploadCertificate(recordId, certificatePath, certificateNumber));
         return toAjax(trainingRecordService.uploadCertificate(recordId, certificatePath, certificateNumber));
     }
     }
     
     
+    /**
+     * 上传培训证书文件
+     */
+    @SaCheckPermission("rs:training:record:edit")
+    @Log(title = "上传培训证书文件", businessType = BusinessType.UPDATE)
+    @PostMapping("/certificate/upload/{recordId}")
+    public R<Map<String, String>> uploadCertificateFile(@PathVariable Long recordId, @RequestParam("file") MultipartFile file) throws IOException {
+        if (file.isEmpty()) {
+            return R.fail("上传失败,请选择文件");
+        }
+        
+        // 检查文件类型
+        String originalFilename = file.getOriginalFilename();
+        if (originalFilename == null) {
+            return R.fail("文件名不能为空");
+        }
+        
+        String fileExtension = StringUtils.substringAfterLast(originalFilename, ".").toLowerCase();
+        if (!("pdf".equals(fileExtension) || "jpg".equals(fileExtension) || "jpeg".equals(fileExtension) || "png".equals(fileExtension))) {
+            return R.fail("只支持PDF、JPG、PNG格式的文件");
+        }
+        
+        // 检查文件大小(10MB)
+        if (file.getSize() > 10 * 1024 * 1024) {
+            return R.fail("文件大小不能超过10MB");
+        }
+        
+        // 调用文件上传服务
+        String fileName = StringUtils.substringAfterLast(file.getOriginalFilename(), "/");
+        // 此处应该使用实际的文件上传工具类,可使用SysFileConfig或自定义文件上传服务
+        // 这里使用简化实现,实际项目中应替换为真正的文件上传逻辑
+        String path = "uploads/training/certificate/" + DateUtils.datePath() + "/" + System.currentTimeMillis() + "_" + fileName;
+        
+        // 更新数据库记录
+        int result = trainingRecordService.uploadCertificate(recordId, path, null);
+        
+        if (result > 0) {
+            Map<String, String> resultMap = new HashMap<>();
+            resultMap.put("fileName", fileName);
+            resultMap.put("path", path);
+            resultMap.put("url", path); // 前端可能需要url字段
+            return R.ok(resultMap);
+        } else {
+            return R.fail("更新证书路径失败");
+        }
+    }
+    
     /**
     /**
      * 获取培训统计数据
      * 获取培训统计数据
      */
      */
@@ -136,4 +203,15 @@ public class RsTrainingRecordController extends BaseController {
         // 目前返回空数据,后续可以实现
         // 目前返回空数据,后续可以实现
         return R.ok(Map.of());
         return R.ok(Map.of());
     }
     }
+    
+    /**
+     * 更新培训记录的证书附件ID
+     */
+    @SaCheckPermission("rs:training:record:edit")
+    @Log(title = "更新培训记录证书附件", businessType = BusinessType.UPDATE)
+    @PutMapping("/attachments/{recordId}")
+    public R<Void> updateAttachments(@PathVariable Long recordId, @RequestBody Map<String, String> params) {
+        String attachmentIds = params.get("attachmentIds");
+        return toAjax(trainingRecordService.updateAttachments(recordId, attachmentIds));
+    }
 } 
 } 

+ 41 - 6
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsTrainingRecord.java

@@ -34,21 +34,47 @@ public class RsTrainingRecord extends BaseEntity {
     private Long courseId;
     private Long courseId;
     private Long userId;
     private Long userId;
     private String userName;
     private String userName;
+
+    /**
+     * 所属部门
+     */
     private String department;
     private String department;
+    
+    /**
+     * 职位
+     */
     private String position;
     private String position;
+    
+    /**
+     * 申请理由
+     */
     private String reason;
     private String reason;
+    
+    /**
+     * 状态(0待审核 1已通过 2已拒绝 3进行中 4已完成)
+     */
     private String status;
     private String status;
-
+    
+    /**
+     * 申请时间
+     */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date applyTime;
     private Date applyTime;
-
+    
+    /**
+     * 当前审批人
+     */
     private String currentApprover;
     private String currentApprover;
-    private String location;
-    private Integer duration;
-
+    
+    /**
+     * 开始时间
+     */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date startTime;
     private Date startTime;
-
+    
+    /**
+     * 结束时间
+     */
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
     private Date endTime;
     private Date endTime;
 
 
@@ -78,6 +104,11 @@ public class RsTrainingRecord extends BaseEntity {
      */
      */
     private String certificateNumber;
     private String certificateNumber;
 
 
+    /**
+     * 证书附件ID(多个用逗号分隔)
+     */
+    private String attachmentIds;
+
     /**
     /**
      * 反馈意见
      * 反馈意见
      */
      */
@@ -85,5 +116,9 @@ public class RsTrainingRecord extends BaseEntity {
 
 
     private String tenantId;
     private String tenantId;
     private Long createDept;
     private Long createDept;
+    
+    // 数据库字段 - 直接存储在培训记录表中
     private String courseName;
     private String courseName;
+    private Integer duration;
+    private String location;
 } 
 } 

+ 49 - 1
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsTrainingRecordMapper.java

@@ -2,6 +2,7 @@ package org.dromara.rs.mapper;
 
 
 import org.dromara.rs.domain.RsTrainingRecord;
 import org.dromara.rs.domain.RsTrainingRecord;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import java.util.List;
 
 
 /**
 /**
  * 培训记录Mapper接口
  * 培训记录Mapper接口
@@ -10,5 +11,52 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
  * @date 2025-07-16
  * @date 2025-07-16
  */
  */
 public interface RsTrainingRecordMapper extends BaseMapperPlus<RsTrainingRecord, RsTrainingRecord> {
 public interface RsTrainingRecordMapper extends BaseMapperPlus<RsTrainingRecord, RsTrainingRecord> {
-
+    
+    /**
+     * 查询培训记录列表
+     * 
+     * @param record 培训记录
+     * @return 培训记录集合
+     */
+    List<RsTrainingRecord> selectRsTrainingRecordList(RsTrainingRecord record);
+    
+    /**
+     * 查询培训记录详情
+     * 
+     * @param recordId 记录ID
+     * @return 培训记录
+     */
+    RsTrainingRecord selectRsTrainingRecordById(Long recordId);
+    
+    /**
+     * 新增培训记录
+     * 
+     * @param record 培训记录
+     * @return 结果
+     */
+    int insertRsTrainingRecord(RsTrainingRecord record);
+    
+    /**
+     * 更新培训记录
+     * 
+     * @param record 培训记录
+     * @return 结果
+     */
+    int updateRsTrainingRecord(RsTrainingRecord record);
+    
+    /**
+     * 删除培训记录
+     * 
+     * @param recordId 记录ID
+     * @return 结果
+     */
+    int deleteRsTrainingRecordById(Long recordId);
+    
+    /**
+     * 批量删除培训记录
+     * 
+     * @param recordIds 记录ID数组
+     * @return 结果
+     */
+    int deleteRsTrainingRecordByIds(Long[] recordIds);
 } 
 } 

+ 8 - 0
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsTrainingRecordService.java

@@ -105,4 +105,12 @@ public interface IRsTrainingRecordService {
      * @return 培训记录
      * @return 培训记录
      */
      */
     RsTrainingRecord selectByApplicationId(Long applicationId);
     RsTrainingRecord selectByApplicationId(Long applicationId);
+    
+    /**
+     * 更新培训记录的证书附件ID
+     * @param recordId 记录ID
+     * @param attachmentIds 附件ID列表(逗号分隔)
+     * @return 结果
+     */
+    int updateAttachments(Long recordId, String attachmentIds);
 } 
 } 

+ 26 - 7
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingApplicationServiceImpl.java

@@ -22,6 +22,9 @@ import org.dromara.rs.service.IRsTrainingRecordService;
 import org.dromara.rs.domain.RsTrainingRecord;
 import org.dromara.rs.domain.RsTrainingRecord;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.slf4j.LoggerFactory;
+import org.dromara.common.core.utils.DateUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 
 
 /**
 /**
  * 培训申请服务实现类
  * 培训申请服务实现类
@@ -30,7 +33,7 @@ import org.slf4j.LoggerFactory;
  * @date 2025-07-16
  * @date 2025-07-16
  */
  */
 @Service
 @Service
-public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationService {
+public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationService, ApplicationContextAware {
     
     
     private static final Logger log = LoggerFactory.getLogger(RsTrainingApplicationServiceImpl.class);
     private static final Logger log = LoggerFactory.getLogger(RsTrainingApplicationServiceImpl.class);
 
 
@@ -40,8 +43,19 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
     @Autowired
     @Autowired
     private RsTrainingCourseMapper trainingCourseMapper;
     private RsTrainingCourseMapper trainingCourseMapper;
 
 
-    @Autowired
-    private IRsTrainingRecordService trainingRecordService;
+    private ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    /**
+     * 获取培训记录服务(延迟获取以避免循环依赖)
+     */
+    private IRsTrainingRecordService getRecordService() {
+        return applicationContext.getBean(IRsTrainingRecordService.class);
+    }
 
 
     /**
     /**
      * 查询培训申请列表
      * 查询培训申请列表
@@ -352,15 +366,16 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
             // 同步到培训记录表:状态为"1已通过""3进行中""4已完成"时自动写入
             // 同步到培训记录表:状态为"1已通过""3进行中""4已完成"时自动写入
             if ("1".equals(status) || "3".equals(status) || "4".equals(status)) {
             if ("1".equals(status) || "3".equals(status) || "4".equals(status)) {
                 log.info("【同步培训记录】触发,applicationId={}, status={}", applicationId, status);
                 log.info("【同步培训记录】触发,applicationId={}, status={}", applicationId, status);
-                RsTrainingRecord exist = trainingRecordService.selectByApplicationId(applicationId);
+                RsTrainingRecord exist = getRecordService().selectByApplicationId(applicationId);
                 if (exist == null) {
                 if (exist == null) {
                     log.info("【同步培训记录】插入新记录,userId={}, courseId={}", application.getUserId(), application.getCourseId());
                     log.info("【同步培训记录】插入新记录,userId={}, courseId={}", application.getUserId(), application.getCourseId());
                     RsTrainingRecord record = new RsTrainingRecord();
                     RsTrainingRecord record = new RsTrainingRecord();
+                    // 设置记录ID(使用时间戳)
+                    record.setRecordId(System.currentTimeMillis());
                     record.setApplicationId(application.getApplicationId());
                     record.setApplicationId(application.getApplicationId());
                     record.setCourseId(application.getCourseId());
                     record.setCourseId(application.getCourseId());
                     record.setUserId(application.getUserId());
                     record.setUserId(application.getUserId());
                     record.setUserName(application.getUserName());
                     record.setUserName(application.getUserName());
-                    record.setTenantId(application.getTenantId());
                     record.setDepartment(application.getDepartment());
                     record.setDepartment(application.getDepartment());
                     record.setPosition(application.getPosition());
                     record.setPosition(application.getPosition());
                     record.setReason(application.getReason());
                     record.setReason(application.getReason());
@@ -371,10 +386,14 @@ public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationS
                     record.setDuration(application.getDuration());
                     record.setDuration(application.getDuration());
                     record.setStartTime(application.getStartTime());
                     record.setStartTime(application.getStartTime());
                     record.setEndTime(application.getEndTime());
                     record.setEndTime(application.getEndTime());
+                    record.setCourseName(application.getCourseName());
                     record.setTenantId(application.getTenantId());
                     record.setTenantId(application.getTenantId());
                     record.setCreateDept(application.getCreateDept());
                     record.setCreateDept(application.getCreateDept());
-                    record.setCourseName(application.getCourseName());
-                    trainingRecordService.insertRsTrainingRecord(record);
+                    record.setCreateBy(1L); // 默认系统用户
+                    record.setCreateTime(DateUtils.getNowDate());
+                    // 默认完成状态为进行中
+                    record.setCompletionStatus("0");
+                    getRecordService().insertRsTrainingRecord(record);
                 } else {
                 } else {
                     log.info("【同步培训记录】已存在,不重复插入,applicationId={}", applicationId);
                     log.info("【同步培训记录】已存在,不重复插入,applicationId={}", applicationId);
                 }
                 }

+ 183 - 28
backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsTrainingRecordServiceImpl.java

@@ -9,9 +9,19 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.dromara.rs.mapper.RsTrainingRecordMapper;
 import org.dromara.rs.mapper.RsTrainingRecordMapper;
 import org.dromara.rs.domain.RsTrainingRecord;
 import org.dromara.rs.domain.RsTrainingRecord;
 import org.dromara.rs.service.IRsTrainingRecordService;
 import org.dromara.rs.service.IRsTrainingRecordService;
+import org.dromara.rs.domain.RsTrainingApplication;
+import org.dromara.rs.service.IRsTrainingApplicationService;
+import org.dromara.rs.domain.RsTrainingCourse;
+import org.dromara.rs.service.IRsTrainingCourseService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
 
 
 /**
 /**
  * 培训记录服务实现类
  * 培训记录服务实现类
@@ -20,11 +30,30 @@ import org.dromara.rs.service.IRsTrainingRecordService;
  * @date 2025-07-16
  * @date 2025-07-16
  */
  */
 @Service
 @Service
-public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
+public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService, ApplicationContextAware {
     
     
+    private static final Logger log = LoggerFactory.getLogger(RsTrainingRecordServiceImpl.class);
+
     @Autowired
     @Autowired
     private RsTrainingRecordMapper trainingRecordMapper;
     private RsTrainingRecordMapper trainingRecordMapper;
 
 
+    @Autowired
+    private IRsTrainingCourseService courseService;
+    
+    private ApplicationContext applicationContext;
+
+    @Override
+    public void setApplicationContext(ApplicationContext applicationContext) {
+        this.applicationContext = applicationContext;
+    }
+
+    /**
+     * 获取培训申请服务(延迟获取以避免循环依赖)
+     */
+    private IRsTrainingApplicationService getApplicationService() {
+        return applicationContext.getBean(IRsTrainingApplicationService.class);
+    }
+
     /**
     /**
      * 查询培训记录列表
      * 查询培训记录列表
      * 
      * 
@@ -34,7 +63,30 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
      */
      */
     @Override
     @Override
     public TableDataInfo<RsTrainingRecord> selectPageRsTrainingRecordList(RsTrainingRecord record, PageQuery pageQuery) {
     public TableDataInfo<RsTrainingRecord> selectPageRsTrainingRecordList(RsTrainingRecord record, PageQuery pageQuery) {
-        return TableDataInfo.build(trainingRecordMapper.selectPage(pageQuery.build(), buildQueryWrapper(record)));
+        // 使用手动分页方式,参考培训申请的实现
+        List<RsTrainingRecord> allList = trainingRecordMapper.selectRsTrainingRecordList(record);
+        
+        // 创建分页对象
+        Page<RsTrainingRecord> page = pageQuery.build();
+        long current = page.getCurrent();
+        long size = page.getSize();
+        
+        // 计算分页
+        int start = (int) ((current - 1) * size);
+        int end = Math.min(start + (int) size, allList.size());
+        
+        List<RsTrainingRecord> pageList;
+        if (start > allList.size()) {
+            pageList = Arrays.asList();
+        } else {
+            pageList = allList.subList(start, end);
+        }
+        
+        // 构建分页结果
+        page.setRecords(pageList);
+        page.setTotal(allList.size());
+        
+        return TableDataInfo.build(page);
     }
     }
 
 
     /**
     /**
@@ -45,7 +97,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
      */
      */
     @Override
     @Override
     public List<RsTrainingRecord> selectRsTrainingRecordList(RsTrainingRecord record) {
     public List<RsTrainingRecord> selectRsTrainingRecordList(RsTrainingRecord record) {
-        return trainingRecordMapper.selectList(buildQueryWrapper(record));
+        return trainingRecordMapper.selectRsTrainingRecordList(record);
     }
     }
     
     
     /**
     /**
@@ -58,9 +110,31 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
      */
      */
     @Override
     @Override
     public TableDataInfo<RsTrainingRecord> selectPageMyRecords(Long userId, RsTrainingRecord record, PageQuery pageQuery) {
     public TableDataInfo<RsTrainingRecord> selectPageMyRecords(Long userId, RsTrainingRecord record, PageQuery pageQuery) {
-        LambdaQueryWrapper<RsTrainingRecord> lqw = buildQueryWrapper(record);
-        lqw.eq(RsTrainingRecord::getUserId, userId);
-        return TableDataInfo.build(trainingRecordMapper.selectPage(pageQuery.build(), lqw));
+        // 设置用户ID条件
+        record.setUserId(userId);
+        List<RsTrainingRecord> allList = trainingRecordMapper.selectRsTrainingRecordList(record);
+        
+        // 创建分页对象
+        Page<RsTrainingRecord> page = pageQuery.build();
+        long current = page.getCurrent();
+        long size = page.getSize();
+        
+        // 计算分页
+        int start = (int) ((current - 1) * size);
+        int end = Math.min(start + (int) size, allList.size());
+        
+        List<RsTrainingRecord> pageList;
+        if (start > allList.size()) {
+            pageList = Arrays.asList();
+        } else {
+            pageList = allList.subList(start, end);
+        }
+        
+        // 构建分页结果
+        page.setRecords(pageList);
+        page.setTotal(allList.size());
+        
+        return TableDataInfo.build(page);
     }
     }
 
 
     /**
     /**
@@ -71,7 +145,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
      */
      */
     @Override
     @Override
     public RsTrainingRecord selectRsTrainingRecordById(Long recordId) {
     public RsTrainingRecord selectRsTrainingRecordById(Long recordId) {
-        return trainingRecordMapper.selectById(recordId);
+        return trainingRecordMapper.selectRsTrainingRecordById(recordId);
     }
     }
 
 
     /**
     /**
@@ -83,7 +157,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
     @Override
     @Override
     public int insertRsTrainingRecord(RsTrainingRecord record) {
     public int insertRsTrainingRecord(RsTrainingRecord record) {
         record.setCreateTime(DateUtils.getNowDate());
         record.setCreateTime(DateUtils.getNowDate());
-        return trainingRecordMapper.insert(record);
+        return trainingRecordMapper.insertRsTrainingRecord(record);
     }
     }
 
 
     /**
     /**
@@ -95,7 +169,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
     @Override
     @Override
     public int updateRsTrainingRecord(RsTrainingRecord record) {
     public int updateRsTrainingRecord(RsTrainingRecord record) {
         record.setUpdateTime(DateUtils.getNowDate());
         record.setUpdateTime(DateUtils.getNowDate());
-        return trainingRecordMapper.updateById(record);
+        return trainingRecordMapper.updateRsTrainingRecord(record);
     }
     }
 
 
     /**
     /**
@@ -106,7 +180,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
      */
      */
     @Override
     @Override
     public int deleteRsTrainingRecordByIds(Long[] recordIds) {
     public int deleteRsTrainingRecordByIds(Long[] recordIds) {
-        return trainingRecordMapper.deleteBatchIds(Arrays.asList(recordIds));
+        return trainingRecordMapper.deleteRsTrainingRecordByIds(recordIds);
     }
     }
 
 
     /**
     /**
@@ -117,7 +191,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
      */
      */
     @Override
     @Override
     public int deleteRsTrainingRecordById(Long recordId) {
     public int deleteRsTrainingRecordById(Long recordId) {
-        return trainingRecordMapper.deleteById(recordId);
+        return trainingRecordMapper.deleteRsTrainingRecordById(recordId);
     }
     }
     
     
     /**
     /**
@@ -129,7 +203,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
      */
      */
     @Override
     @Override
     public int updateCompletionStatus(Long recordId, String completionStatus) {
     public int updateCompletionStatus(Long recordId, String completionStatus) {
-        RsTrainingRecord record = trainingRecordMapper.selectById(recordId);
+        RsTrainingRecord record = trainingRecordMapper.selectRsTrainingRecordById(recordId);
         if (record != null) {
         if (record != null) {
             record.setCompletionStatus(completionStatus);
             record.setCompletionStatus(completionStatus);
             
             
@@ -139,7 +213,7 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
             }
             }
             
             
             record.setUpdateTime(DateUtils.getNowDate());
             record.setUpdateTime(DateUtils.getNowDate());
-            return trainingRecordMapper.updateById(record);
+            return trainingRecordMapper.updateRsTrainingRecord(record);
         }
         }
         return 0;
         return 0;
     }
     }
@@ -154,34 +228,115 @@ public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
      */
      */
     @Override
     @Override
     public int uploadCertificate(Long recordId, String certificatePath, String certificateNumber) {
     public int uploadCertificate(Long recordId, String certificatePath, String certificateNumber) {
-        RsTrainingRecord record = trainingRecordMapper.selectById(recordId);
+        RsTrainingRecord record = trainingRecordMapper.selectRsTrainingRecordById(recordId);
         if (record != null) {
         if (record != null) {
             record.setCertificatePath(certificatePath);
             record.setCertificatePath(certificatePath);
             record.setCertificateNumber(certificateNumber);
             record.setCertificateNumber(certificateNumber);
             record.setUpdateTime(DateUtils.getNowDate());
             record.setUpdateTime(DateUtils.getNowDate());
-            return trainingRecordMapper.updateById(record);
+            return trainingRecordMapper.updateRsTrainingRecord(record);
         }
         }
         return 0;
         return 0;
     }
     }
     
     
     /**
     /**
-     * 构建查询条件
+     * 更新培训记录的证书附件ID
+     * 
+     * @param recordId 记录ID
+     * @param attachmentIds 附件ID列表(逗号分隔)
+     * @return 结果
      */
      */
-    private LambdaQueryWrapper<RsTrainingRecord> buildQueryWrapper(RsTrainingRecord record) {
-        LambdaQueryWrapper<RsTrainingRecord> lqw = new LambdaQueryWrapper<>();
-        lqw.eq(record.getCourseId() != null, RsTrainingRecord::getCourseId, record.getCourseId());
-        lqw.like(record.getCourseName() != null, RsTrainingRecord::getCourseName, record.getCourseName());
-        lqw.eq(record.getUserId() != null, RsTrainingRecord::getUserId, record.getUserId());
-        lqw.like(record.getUserName() != null, RsTrainingRecord::getUserName, record.getUserName());
-        lqw.eq(record.getCompletionStatus() != null, RsTrainingRecord::getCompletionStatus, record.getCompletionStatus());
-        lqw.eq(record.getStatus() != null, RsTrainingRecord::getStatus, record.getStatus());
-        return lqw;
+    @Override
+    public int updateAttachments(Long recordId, String attachmentIds) {
+        RsTrainingRecord record = trainingRecordMapper.selectRsTrainingRecordById(recordId);
+        if (record != null) {
+            record.setAttachmentIds(attachmentIds);
+            record.setUpdateTime(DateUtils.getNowDate());
+            return trainingRecordMapper.updateRsTrainingRecord(record);
+        }
+        return 0;
     }
     }
 
 
     @Override
     @Override
     public RsTrainingRecord selectByApplicationId(Long applicationId) {
     public RsTrainingRecord selectByApplicationId(Long applicationId) {
-        LambdaQueryWrapper<RsTrainingRecord> lqw = new LambdaQueryWrapper<>();
-        lqw.eq(RsTrainingRecord::getApplicationId, applicationId);
-        return trainingRecordMapper.selectOne(lqw);
+        RsTrainingRecord record = new RsTrainingRecord();
+        record.setApplicationId(applicationId);
+        List<RsTrainingRecord> list = trainingRecordMapper.selectRsTrainingRecordList(record);
+        return list.isEmpty() ? null : list.get(0);
+    }
+
+    /**
+     * 修复培训记录中缺失的课程信息
+     * 
+     * @return 修复的记录数量
+     */
+    public int fixMissingCourseInfo() {
+        log.info("开始修复培训记录中缺失的课程信息");
+        
+        // 获取所有需要修复的记录
+        List<RsTrainingRecord> allRecords = trainingRecordMapper.selectRsTrainingRecordList(new RsTrainingRecord());
+        int fixedCount = 0;
+        
+        for (RsTrainingRecord record : allRecords) {
+            boolean needUpdate = false;
+            
+            // 检查是否缺失课程信息
+            if (StringUtils.isEmpty(record.getCourseName()) || 
+                StringUtils.isEmpty(record.getLocation()) || 
+                record.getDuration() == null) {
+                
+                log.info("修复记录 recordId={}, applicationId={}", record.getRecordId(), record.getApplicationId());
+                
+                // 尝试从申请表获取信息
+                RsTrainingApplication application = getApplicationService().selectRsTrainingApplicationById(record.getApplicationId());
+                if (application != null) {
+                    if (StringUtils.isEmpty(record.getCourseName()) && StringUtils.isNotBlank(application.getCourseName())) {
+                        record.setCourseName(application.getCourseName());
+                        needUpdate = true;
+                    }
+                    if (StringUtils.isEmpty(record.getLocation()) && StringUtils.isNotBlank(application.getLocation())) {
+                        record.setLocation(application.getLocation());
+                        needUpdate = true;
+                    }
+                    if (record.getDuration() == null && application.getDuration() != null) {
+                        record.setDuration(application.getDuration());
+                        needUpdate = true;
+                    }
+                }
+                
+                // 如果申请表中也没有,尝试从课程表获取
+                if ((StringUtils.isEmpty(record.getCourseName()) || StringUtils.isEmpty(record.getLocation()) || record.getDuration() == null) 
+                    && record.getCourseId() != null) {
+                    
+                    RsTrainingCourse course = courseService.selectRsTrainingCourseById(record.getCourseId());
+                    if (course != null) {
+                        if (StringUtils.isEmpty(record.getCourseName())) {
+                            record.setCourseName(course.getTitle());
+                            needUpdate = true;
+                        }
+                        if (StringUtils.isEmpty(record.getLocation())) {
+                            record.setLocation(course.getLocation());
+                            needUpdate = true;
+                        }
+                        if (record.getDuration() == null) {
+                            record.setDuration(course.getDuration());
+                            needUpdate = true;
+                        }
+                    }
+                }
+                
+                // 更新记录
+                if (needUpdate) {
+                    record.setUpdateTime(DateUtils.getNowDate());
+                    record.setUpdateBy(1L); // 系统用户
+                    trainingRecordMapper.updateRsTrainingRecord(record);
+                    fixedCount++;
+                    log.info("已修复记录: courseName={}, location={}, duration={}", 
+                            record.getCourseName(), record.getLocation(), record.getDuration());
+                }
+            }
+        }
+        
+        log.info("修复完成,共修复 {} 条记录", fixedCount);
+        return fixedCount;
     }
     }
 } 
 } 

+ 77 - 20
backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsTrainingRecordMapper.xml

@@ -10,36 +10,41 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         <result property="courseId" column="course_id"/>
         <result property="courseId" column="course_id"/>
         <result property="userId" column="user_id"/>
         <result property="userId" column="user_id"/>
         <result property="userName" column="user_name"/>
         <result property="userName" column="user_name"/>
-        <result property="department" column="department"/>
-        <result property="position" column="position"/>
-        <result property="reason" column="reason"/>
-        <result property="status" column="status"/>
-        <result property="applyTime" column="apply_time"/>
-        <result property="currentApprover" column="current_approver"/>
-        <result property="location" column="location"/>
-        <result property="duration" column="duration"/>
-        <result property="startTime" column="start_time"/>
-        <result property="endTime" column="end_time"/>
         <result property="completionStatus" column="completion_status"/>
         <result property="completionStatus" column="completion_status"/>
         <result property="completionTime" column="completion_time"/>
         <result property="completionTime" column="completion_time"/>
         <result property="score" column="score"/>
         <result property="score" column="score"/>
         <result property="certificatePath" column="certificate_path"/>
         <result property="certificatePath" column="certificate_path"/>
         <result property="certificateNumber" column="certificate_number"/>
         <result property="certificateNumber" column="certificate_number"/>
+        <result property="attachmentIds" column="attachment_ids"/>
         <result property="feedback" column="feedback"/>
         <result property="feedback" column="feedback"/>
         <result property="tenantId" column="tenant_id"/>
         <result property="tenantId" column="tenant_id"/>
         <result property="createDept" column="create_dept"/>
         <result property="createDept" column="create_dept"/>
+        <result property="createBy" column="create_by"/>
+        <result property="createTime" column="create_time"/>
+        <result property="updateBy" column="update_by"/>
+        <result property="updateTime" column="update_time"/>
+        
+        <!-- 直接从记录表查询的字段 -->
+        <result property="department" column="department"/>
+        <result property="position" column="position"/>
+        <result property="reason" column="reason"/>
+        <result property="status" column="status"/>
+        <result property="applyTime" column="apply_time"/>
+        <result property="currentApprover" column="current_approver"/>
+        <result property="startTime" column="start_time"/>
+        <result property="endTime" column="end_time"/>
         <result property="courseName" column="course_name"/>
         <result property="courseName" column="course_name"/>
+        <result property="duration" column="duration"/>
+        <result property="location" column="location"/>
     </resultMap>
     </resultMap>
 
 
     <sql id="selectRsTrainingRecordVo">
     <sql id="selectRsTrainingRecordVo">
         select r.record_id, r.application_id, r.course_id, r.user_id, r.user_name, 
         select r.record_id, r.application_id, r.course_id, r.user_id, r.user_name, 
-        r.completion_status, r.completion_time, r.score, r.certificate_path, r.certificate_number, 
-        r.feedback, r.tenant_id, r.create_by, r.create_time, r.update_by, r.update_time, r.remark,
-        r.location, r.duration, r.apply_time, r.status, r.course_name,
-        c.title as course_title, a.department
+        r.completion_status, r.completion_time, r.score, r.certificate_path, r.certificate_number, r.attachment_ids,
+        r.feedback, r.tenant_id, r.create_dept, r.create_by, r.create_time, r.update_by, r.update_time,
+        r.department, r.position, r.reason, r.status, r.apply_time, r.current_approver, r.start_time, r.end_time,
+        r.course_name, r.duration, r.location
         from rs_training_record r
         from rs_training_record r
-        left join rs_training_course c on r.course_id = c.course_id and c.deleted = '0'
-        left join rs_training_application a on r.application_id = a.application_id and a.deleted = '0'
     </sql>
     </sql>
 
 
     <select id="selectRsTrainingRecordList" parameterType="RsTrainingRecord" resultMap="RsTrainingRecordResult">
     <select id="selectRsTrainingRecordList" parameterType="RsTrainingRecord" resultMap="RsTrainingRecordResult">
@@ -52,20 +57,72 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="userName != null and userName != ''">and r.user_name like concat('%', #{userName}, '%')</if>
             <if test="userName != null and userName != ''">and r.user_name like concat('%', #{userName}, '%')</if>
             <if test="completionStatus != null and completionStatus != ''">and r.completion_status = #{completionStatus}</if>
             <if test="completionStatus != null and completionStatus != ''">and r.completion_status = #{completionStatus}</if>
             <if test="status != null and status != ''">and r.status = #{status}</if>
             <if test="status != null and status != ''">and r.status = #{status}</if>
+            <if test="department != null and department != ''">and r.department like concat('%', #{department}, '%')</if>
             <if test="params.beginTime != null and params.beginTime != ''">and date_format(r.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')</if>
             <if test="params.beginTime != null and params.beginTime != ''">and date_format(r.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')</if>
             <if test="params.endTime != null and params.endTime != ''">and date_format(r.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')</if>
             <if test="params.endTime != null and params.endTime != ''">and date_format(r.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')</if>
         </where>
         </where>
         ORDER BY r.create_time DESC
         ORDER BY r.create_time DESC
     </select>
     </select>
 
 
+    <select id="selectRsTrainingRecordById" parameterType="Long" resultMap="RsTrainingRecordResult">
+        <include refid="selectRsTrainingRecordVo"/>
+        where r.record_id = #{recordId} and r.deleted = '0'
+    </select>
+
     <insert id="insertRsTrainingRecord" parameterType="org.dromara.rs.domain.RsTrainingRecord">
     <insert id="insertRsTrainingRecord" parameterType="org.dromara.rs.domain.RsTrainingRecord">
         INSERT INTO rs_training_record (
         INSERT INTO rs_training_record (
-            application_id, course_id, user_id, user_name, department, position, reason, status,
-            apply_time, current_approver, location, duration, start_time, end_time, tenant_id, create_dept, course_name
+            record_id, application_id, course_id, user_id, user_name, completion_status, completion_time, score, 
+            certificate_path, certificate_number, attachment_ids, feedback, location, duration, apply_time,
+            status, course_name, department, position, reason, current_approver, start_time, end_time,
+            tenant_id, create_dept, create_by, create_time, deleted
         ) VALUES (
         ) VALUES (
-            #{applicationId}, #{courseId}, #{userId}, #{userName}, #{department}, #{position}, #{reason}, #{status},
-            #{applyTime}, #{currentApprover}, #{location}, #{duration}, #{startTime}, #{endTime}, #{tenantId}, #{createDept}, #{courseName}
+            #{recordId}, #{applicationId}, #{courseId}, #{userId}, #{userName}, #{completionStatus}, #{completionTime}, #{score},
+            #{certificatePath}, #{certificateNumber}, #{attachmentIds}, #{feedback}, #{location}, #{duration}, #{applyTime},
+            #{status}, #{courseName}, #{department}, #{position}, #{reason}, #{currentApprover}, #{startTime}, #{endTime},
+            #{tenantId}, #{createDept}, #{createBy}, #{createTime}, '0'
         )
         )
     </insert>
     </insert>
 
 
+    <update id="updateRsTrainingRecord" parameterType="org.dromara.rs.domain.RsTrainingRecord">
+        UPDATE rs_training_record
+        <set>
+            <if test="applicationId != null">application_id = #{applicationId},</if>
+            <if test="courseId != null">course_id = #{courseId},</if>
+            <if test="userId != null">user_id = #{userId},</if>
+            <if test="userName != null and userName != ''">user_name = #{userName},</if>
+            <if test="completionStatus != null">completion_status = #{completionStatus},</if>
+            <if test="completionTime != null">completion_time = #{completionTime},</if>
+            <if test="score != null">score = #{score},</if>
+            <if test="certificatePath != null">certificate_path = #{certificatePath},</if>
+            <if test="certificateNumber != null">certificate_number = #{certificateNumber},</if>
+            <if test="attachmentIds != null">attachment_ids = #{attachmentIds},</if>
+            <if test="feedback != null">feedback = #{feedback},</if>
+            <if test="location != null">location = #{location},</if>
+            <if test="duration != null">duration = #{duration},</if>
+            <if test="applyTime != null">apply_time = #{applyTime},</if>
+            <if test="status != null">status = #{status},</if>
+            <if test="courseName != null">course_name = #{courseName},</if>
+            <if test="department != null">department = #{department},</if>
+            <if test="position != null">position = #{position},</if>
+            <if test="reason != null">reason = #{reason},</if>
+            <if test="currentApprover != null">current_approver = #{currentApprover},</if>
+            <if test="startTime != null">start_time = #{startTime},</if>
+            <if test="endTime != null">end_time = #{endTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime}</if>
+        </set>
+        where record_id = #{recordId}
+    </update>
+
+    <delete id="deleteRsTrainingRecordById" parameterType="Long">
+        UPDATE rs_training_record SET deleted = '1' where record_id = #{recordId}
+    </delete>
+
+    <delete id="deleteRsTrainingRecordByIds" parameterType="String">
+        UPDATE rs_training_record SET deleted = '1' where record_id in
+        <foreach item="recordId" collection="array" open="(" separator="," close=")">
+            #{recordId}
+        </foreach>
+    </delete>
+
 </mapper> 
 </mapper> 

+ 14 - 18
backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training-demo-data.sql

@@ -5,24 +5,24 @@
 -- ----------------------------
 -- ----------------------------
 -- 培训课程表测试数据
 -- 培训课程表测试数据
 -- ----------------------------
 -- ----------------------------
-INSERT INTO `rs_training_course` (`course_id`, `title`, `category`, `description`, `start_time`, `end_time`, `duration`, `location`, `max_participants`, `current_participants`, `status`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
+INSERT INTO `rs_training_course` (`course_id`, `title`, `description`, `instructor`, `duration`, `location`, `capacity`, `status`, `start_time`, `end_time`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
 VALUES
 VALUES
-(10001, '新教师入职培训', 'basic', '为新入职教师提供学校规章制度、教学系统使用等基础培训', '2025-08-15 09:00:00', '2025-08-16 17:00:00', 16, '行政楼三楼会议室', 30, 12, '0', '000000', 1, NOW(), 1, NOW(), '重要培训,所有新入职教师必须参加', '0'),
-(10002, 'Excel高级应用培训', 'skill', '包括数据透视表、宏、VBA基础等Excel高级功能应用', '2025-08-20 13:30:00', '2025-08-22 16:30:00', 12, '教学楼B区205', 20, 8, '0', '000000', 1, NOW(), 1, NOW(), '面向行政人员的办公技能提升', '0'),
-(10003, '教学评估标准培训', 'teaching', '介绍最新的教学评估标准和评价体系', '2025-09-05 09:00:00', '2025-09-05 17:00:00', 8, '线上培训', 100, 45, '0', '000000', 1, NOW(), 1, NOW(), '所有教师须参加', '0'),
-(10004, '心理健康辅导技能', 'counseling', '学习学生心理问题识别和初步辅导技能', '2025-09-10 09:00:00', '2025-09-11 17:00:00', 16, '心理咨询中心', 25, 15, '0', '000000', 1, NOW(), 1, NOW(), '适合班主任和辅导员', '0'),
-(10005, '科研项目申报指南', 'research', '介绍各类科研项目的申报流程、注意事项和技巧', '2025-09-15 14:00:00', '2025-09-16 17:00:00', 10, '科研楼报告厅', 50, 22, '0', '000000', 1, NOW(), 1, NOW(), '面向有科研项目需求的教师', '0');
+(10001, '新入职教师培训', '针对新入职教师的岗前培训,包括教学理念、师德师风、教学方法等内容', '张教授', 16, '学术会议厅', 50, '1', '2025-08-01 09:00:00', '2025-08-03 17:00:00', '000000', 1, NOW(), 1, NOW(), '重要培训', '0'),
+(10002, '教育技术与信息化', '介绍现代教育技术在教学中的应用,包括多媒体教学、在线教育平台等', '李博士', 12, '多媒体教室', 30, '1', '2025-08-10 09:00:00', '2025-08-12 17:00:00', '000000', 1, NOW(), 1, NOW(), '技术培训', '0'),
+(10003, '教学方法与技能培训', '提升教师教学技能,包括课堂管理、学生互动、教学评估等', '王老师', 8, '实训楼', 40, '1', '2025-08-05 14:00:00', '2025-08-05 18:00:00', '000000', 1, NOW(), 1, NOW(), '实用培训', '0'),
+(10004, '学生管理与心理健康', '帮助教师了解学生心理,掌握学生管理技巧和心理疏导方法', '陈心理师', 6, '学术报告厅', 60, '1', '2025-08-15 14:00:00', '2025-08-15 18:00:00', '000000', 1, NOW(), 1, NOW(), '心理培训', '0'),
+(10005, '科研方法与学术写作', '指导教师进行科学研究,提升学术写作和论文发表能力', '赵教授', 20, '图书馆报告厅', 25, '1', '2025-08-25 09:00:00', '2025-08-30 17:00:00', '000000', 1, NOW(), 1, NOW(), '科研培训', '0');
 
 
 -- ----------------------------
 -- ----------------------------
 -- 培训申请表测试数据
 -- 培训申请表测试数据
 -- ----------------------------
 -- ----------------------------
-INSERT INTO `rs_training_application` (`application_id`, `course_id`, `user_id`, `user_name`, `department`, `position`, `reason`, `status`, `apply_time`, `current_approver`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
+INSERT INTO `rs_training_application` (`application_id`, `course_id`, `user_id`, `user_name`, `department`, `position`, `reason`, `status`, `apply_time`, `current_approver`, `process_instance_id`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
 VALUES
 VALUES
-(20001, 10001, 2, '张老师', '信息学院', '讲师', '作为新入职教师,需要了解学校规章制度和教学系统', '1', '2025-07-20 10:15:22', '王主任', '000000', 2, NOW(), 2, NOW(), NULL, '0'),
-(20002, 10002, 3, '李秘书', '行政部', '文员', '日常工作需要处理大量Excel表格,希望提升技能', '0', '2025-07-21 09:05:33', '赵部长', '000000', 3, NOW(), 3, NOW(), NULL, '0'),
-(20003, 10003, 4, '刘老师', '外语学院', '副教授', '了解最新教学评估标准,提升教学质量', '1', '2025-07-22 14:30:45', '孙院长', '000000', 4, NOW(), 4, NOW(), NULL, '0'),
-(20004, 10004, 5, '钱辅导员', '学生处', '辅导员', '提升学生心理问题识别和初步干预能力', '2', '2025-07-23 11:20:18', '周主任', '000000', 5, NOW(), 5, NOW(), '该老师近期有其他重要工作', '0'),
-(20005, 10005, 6, '赵研究员', '科研处', '研究员', '正在准备申报国家级项目,需要了解最新申报指南', '0', '2025-07-24 16:40:57', '吴处长', '000000', 6, NOW(), 6, NOW(), NULL, '0');
+(20001, 10001, 2, '张老师', '教务处', '教师', '新入职需要培训', '1', '2025-07-25 12:03:49', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), NULL, '0'),
+(20002, 10002, 3, '王老师', '信息技术中心', '工程师', '学习新技术', '1', '2025-08-01 12:37:17', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), '未完成', '0'),
+(20003, 10003, 4, '刘老师', '计算机学院', '讲师', '提升教学技能', '1', '2025-07-25 12:04:06', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), '培训尚未结束', '0'),
+(20004, 10004, 5, '李老师', '学生处', '辅导员', '提升学生管理能力', '1', '2025-08-06 14:24:37', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), '未完成', '0'),
+(20005, 10005, 6, '陈老师', '科研处', '副教授', '提升科研能力', '1', '2025-08-22 15:25:51', 'admin', NULL, '000000', 1, NOW(), 1, NOW(), '未完成', '0');
 
 
 -- ----------------------------
 -- ----------------------------
 -- 培训审批表测试数据
 -- 培训审批表测试数据
@@ -34,9 +34,5 @@ VALUES
 (30003, 20004, 103, '周主任', 1, '2', '近期有重要工作交接,建议下次再参加', '2025-07-24 14:20:00', '000000', 103, NOW(), 103, NOW(), NULL, '0');
 (30003, 20004, 103, '周主任', 1, '2', '近期有重要工作交接,建议下次再参加', '2025-07-24 14:20:00', '000000', 103, NOW(), 103, NOW(), NULL, '0');
 
 
 -- ----------------------------
 -- ----------------------------
--- 培训记录表测试数据
--- ----------------------------
-INSERT INTO `rs_training_record` (`record_id`, `application_id`, `course_id`, `user_id`, `user_name`, `completion_status`, `completion_time`, `score`, `certificate_path`, `certificate_number`, `feedback`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
-VALUES
-(40001, 20001, 10001, 2, '张老师', '1', '2025-08-16 17:00:00', 92.5, '/certificates/10001/20001.pdf', 'CERT20250816001', '培训内容丰富,对新入职教师很有帮助', '000000', 1, NOW(), 1, NOW(), NULL, '0'),
-(40002, 20003, 10003, 4, '刘老师', '0', NULL, NULL, NULL, NULL, NULL, '000000', 1, NOW(), 1, NOW(), '培训尚未结束', '0'); 
+-- 培训记录表测试数据已删除,请通过申请界面新增
+-- ---------------------------- 

+ 13 - 0
backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/training.sql

@@ -91,8 +91,21 @@ create table rs_training_record (
   score               decimal(5,2)                               comment '成绩',
   score               decimal(5,2)                               comment '成绩',
   certificate_path    varchar(255)                               comment '证书路径',
   certificate_path    varchar(255)                               comment '证书路径',
   certificate_number  varchar(50)                                comment '证书编号',
   certificate_number  varchar(50)                                comment '证书编号',
+  attachment_ids      varchar(255)                               comment '证书附件ID(多个用逗号分隔)',
   feedback            text                                       comment '培训反馈',
   feedback            text                                       comment '培训反馈',
+  location            varchar(255)                               comment '培训地点',
+  duration            int(11)                                    comment '学时',
+  apply_time          datetime                                   comment '申请时间',
+  status              varchar(10)                                comment '申请状态',
+  course_name         varchar(255)                               comment '课程名称',
+  department          varchar(255)                               comment '所属部门',
+  position            varchar(255)                               comment '职位',
+  reason              varchar(255)                               comment '申请理由',
+  current_approver    varchar(255)                               comment '当前审批人',
+  start_time          datetime                                   comment '开始时间',
+  end_time            datetime                                   comment '结束时间',
   tenant_id           varchar(20)     default '000000'           comment '租户编号',
   tenant_id           varchar(20)     default '000000'           comment '租户编号',
+  create_dept         bigint(20)                                 comment '创建部门',
   create_by           bigint(20)                                 comment '创建者',
   create_by           bigint(20)                                 comment '创建者',
   create_time         datetime                                   comment '创建时间',
   create_time         datetime                                   comment '创建时间',
   update_by           bigint(20)                                 comment '更新者',
   update_by           bigint(20)                                 comment '更新者',

+ 12 - 0
web/src/api/rs/training.ts

@@ -126,6 +126,7 @@ export interface RecordVO {
   completionTime?: string;
   completionTime?: string;
   certificatePath?: string;
   certificatePath?: string;
   certificateNumber?: string;
   certificateNumber?: string;
+  certificateFileName?: string;
   feedback?: string;
   feedback?: string;
   createTime: string;
   createTime: string;
   updateTime: string;
   updateTime: string;
@@ -148,6 +149,8 @@ export interface RecordForm {
   completionTime?: string;
   completionTime?: string;
   certificatePath?: string;
   certificatePath?: string;
   certificateNumber?: string;
   certificateNumber?: string;
+  certificateFileName?: string;
+  attachmentIds?: string;
   feedback?: string;
   feedback?: string;
   remark?: string;
   remark?: string;
 }
 }
@@ -424,3 +427,12 @@ export function resubmitTrainingApplication(applicationId: string | number, data
     data: data
     data: data
   });
   });
 }
 }
+
+// 更新培训记录的证书附件ID
+export function updateRecordAttachments(recordId: string | number, data: { attachmentIds: string }) {
+  return request({
+    url: `/rs/training/record/attachments/${recordId}`,
+    method: 'put',
+    data: data
+  });
+}

+ 3 - 1
web/src/views/rs/index.vue

@@ -372,7 +372,9 @@
         </el-descriptions-item>
         </el-descriptions-item>
         <el-descriptions-item label="申请人数">{{ viewForm.applicantCount }}</el-descriptions-item>
         <el-descriptions-item label="申请人数">{{ viewForm.applicantCount }}</el-descriptions-item>
         <el-descriptions-item label="浏览次数">{{ viewForm.viewCount }}</el-descriptions-item>
         <el-descriptions-item label="浏览次数">{{ viewForm.viewCount }}</el-descriptions-item>
-        <el-descriptions-item label="发布时间">{{ parseTime(viewForm.publishTime) || '未发布' }}</el-descriptions-item>
+        <el-descriptions-item label="发布时间">
+          {{ parseTime(viewForm.publishTime) || (viewForm.isActive === '0' ? parseTime(viewForm.createTime) || '长期有效' : '未发布') }}
+        </el-descriptions-item>
         <el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime) }}</el-descriptions-item>
         <el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime) }}</el-descriptions-item>
         <el-descriptions-item label="职位描述" :span="2">
         <el-descriptions-item label="职位描述" :span="2">
           <div class="view-description">{{ viewForm.description || '暂无描述' }}</div>
           <div class="view-description">{{ viewForm.description || '暂无描述' }}</div>

+ 11 - 8
web/src/views/rs/recruitment-portal.vue

@@ -82,12 +82,7 @@
                 <el-table-column prop="location" label="工作地点" width="120" />
                 <el-table-column prop="location" label="工作地点" width="120" />
                 <el-table-column prop="publishTime" label="发布时间" width="120">
                 <el-table-column prop="publishTime" label="发布时间" width="120">
                   <template #default="scope">
                   <template #default="scope">
-                    {{ formatDate(scope.row.publishTime) }}
-                  </template>
-                </el-table-column>
-                <el-table-column prop="publishTime" label="发布时间" width="120">
-                  <template #default="scope">
-                    <span>{{ formatDate(scope.row.publishTime) }}</span>
+                    {{ formatDate(scope.row.publishTime) || formatDate(scope.row.createTime) || '长期有效' }}
                   </template>
                   </template>
                 </el-table-column>
                 </el-table-column>
                 <el-table-column label="操作" width="160" fixed="right">
                 <el-table-column label="操作" width="160" fixed="right">
@@ -181,10 +176,10 @@
             <h4>招聘信息</h4>
             <h4>招聘信息</h4>
             <el-descriptions :column="2" border>
             <el-descriptions :column="2" border>
               <el-descriptions-item label="发布时间">
               <el-descriptions-item label="发布时间">
-                {{ formatDate(currentJob.publishTime) }}
+                {{ formatDate(currentJob.publishTime) || formatDate(currentJob.createTime) || '长期有效' }}
               </el-descriptions-item>
               </el-descriptions-item>
               <el-descriptions-item label="截止时间">
               <el-descriptions-item label="截止时间">
-                <span>{{ formatDate(currentJob.publishTime) }}</span>
+                <span>{{ getDeadlineDate(currentJob.publishTime) }}</span>
               </el-descriptions-item>
               </el-descriptions-item>
               <el-descriptions-item label="浏览次数"> {{ currentJob.viewCount || 0 }} 次 </el-descriptions-item>
               <el-descriptions-item label="浏览次数"> {{ currentJob.viewCount || 0 }} 次 </el-descriptions-item>
               <el-descriptions-item label="应聘人数"> {{ currentJob.applicantCount || 0 }} 人 </el-descriptions-item>
               <el-descriptions-item label="应聘人数"> {{ currentJob.applicantCount || 0 }} 人 </el-descriptions-item>
@@ -257,6 +252,14 @@ const formatDate = (dateStr: string) => {
   return parseTime(dateStr, '{y}-{m}-{d}');
   return parseTime(dateStr, '{y}-{m}-{d}');
 };
 };
 
 
+// 计算截止日期(发布日期+30天)
+const getDeadlineDate = (publishTime: string) => {
+  if (!publishTime) return '长期有效';
+  const publishDate = new Date(publishTime);
+  const deadlineDate = new Date(publishDate.getTime() + 30 * 24 * 60 * 60 * 1000); // 30天后
+  return parseTime(deadlineDate.toString(), '{y}-{m}-{d}');
+};
+
 // 检查是否过期(使用publishTime判断,假设30天有效期)
 // 检查是否过期(使用publishTime判断,假设30天有效期)
 const isJobExpired = (publishTime: string) => {
 const isJobExpired = (publishTime: string) => {
   if (!publishTime) return false;
   if (!publishTime) return false;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 949 - 193
web/src/views/rs/training/index.vue


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio