wuzhen update

This commit is contained in:
wuzhen committed 2025-08-07 18:41:15 +08:00
commit 0945fe15ab
1097 files changed
+66881 -116449

No files matched your search

+195
View File
@@ -358,6 +358,51 @@ export default {
## 5. 🎨 图标使用规范 (SVG Data URI 方案)
### 🚨 强制性图标使用规范(2025-08-06 新增)
**⚠️ 重要说明**: 移动端项目已全面禁用 uview-plus 的 u-icon 组件,强制使用 iconfont 图标系统!
#### 📋 强制要求
1. **🚫 禁止使用 u-icon 组件**
```vue
<!-- ❌ 禁止这样写 -->
<u-icon name="search" size="20" color="#666"></u-icon>
<u-icon name="account" size="24"></u-icon>
<u-icon name="close" @click="handleClose"></u-icon>
```
2. **✅ 必须使用 iconfont 图标**
```vue
<!-- ✅ 正确写法 -->
<text class="iconfont icon-search" style="font-size: 20px; color: #666;"></text>
<text class="iconfont icon-account-circle" style="font-size: 24px;"></text>
<text class="iconfont icon-close" @click="handleClose"></text>
```
3. **🔄 动态图标类名**
```vue
<!-- ✅ 动态切换图标 -->
<text
:class="'iconfont ' + (expanded ? 'icon-arrow-down' : 'icon-arrow-right')"
style="font-size: 16px; color: #999;"
@click="toggleExpand"
></text>
```
#### 🎯 替换映射表
| u-icon name | iconfont class | 说明 |
|-------------|----------------|------|
| `search` | `icon-search` | 搜索图标 |
| `account` | `icon-account-circle` | 用户账户图标 |
| `close` | `icon-close` | 关闭图标 |
| `email` | `icon-email` | 邮件图标 |
| `arrow-right` | `icon-arrow-right` | 右箭头图标 |
| `arrow-down` | `icon-arrow-down` | 下箭头图标 |
| `home` | `icon-home` | 首页图标 |
| `setting` | `icon-setting` | 设置图标 |
### 背景说明
由于 uview-plus 的 u-icon 组件在某些环境下存在显示异常(显示为横线),项目采用 **SVG Data URI** 方案实现图标系统,确保跨平台兼容性和显示稳定性。
@@ -369,6 +414,7 @@ export default {
- ✅ **加载稳定**: 无 404 错误,无网络请求,性能优异
- ✅ **样式灵活**: 通过 CSS filter 实现颜色自定义
- ✅ **维护简单**: 基于 Material Design,图标清晰美观
- ✅ **显示稳定**: 彻底解决 u-icon 组件显示异常问题
### 图标配置文件
@@ -1784,3 +1830,152 @@ npm run lint:fix
**📝 维护**: 移动端开发规范文档
**🔄 更新**: 2025-01-03 (新增登录状态管理规范)
**📁 文件**: .cursor/cursorrules-mobile.md
# uview-plus 图标组件规范(2025-07-01 新增)
- 移动端UI开发中,图标组件统一使用 <u-icon />(uview-plus 3.x 官方标准),禁止使用 <up-icon /> 或 <uv-icon />。
- 推荐通过 easycom 自动全局注册,无需手动 import。
- 标准用法示例:
```vue
<u-icon name="account" size="22" color="#b0b8c9" />
<u-icon name="lock-open" size="22" color="#b0b8c9" />
```
- 图标名称请参考 uview-plus 官方文档:https://v3.uviewui.com/components/icon.html
## uview-plus 图标字体配置规范(2025-08-06 更新)
### 📦 版本升级经验总结
**升级记录**: uview-plus 3.4.45 → 3.4.73 (+28个版本)
**升级时间**: 2025-08-06
**配置优化**: 采用官方推荐的字体加载机制
### 🔧 官方推荐配置方案
#### 方案1: 自托管字体配置(官方推荐 ⭐)
**参考文档**: https://uview-plus.jiangruyi.com/components/icon.html#自定义默认字体图标自托管资源
```javascript
// main.js - 官方推荐配置方式
app.use(uviewPlus, () => {
return {
options: {
config: {
// 🔧 自托管字体图标资源地址,避免CDN依赖问题
iconUrl: '/static/fonts/uview-plus-official.ttf'
}
}
}
});
```
#### 方案2: 手动加载字体(官方兼容性方案)
**参考文档**: https://uview-plus.jiangruyi.com/components/icon.html#新版本全局加载兼容性
```javascript
// App.vue - 如果在小程序等场景出现图标无法全局加载
import { fontUtil } from 'uview-plus';
export default {
onLaunch: function () {
// 官方推荐:手动加载uview-plus字体图标
fontUtil.loadFont();
}
};
```
#### 方案3: CSS备用方案(平台兼容)
```css
/* App.vue - 其他平台如抖音/QQ/百度小程序的备用方案 */
@font-face {
font-family: 'uicon-iconfont';
src: url('/static/fonts/uview-plus-official.ttf') format('truetype');
}
```
### 🏆 最佳实践配置
```javascript
// main.js - 完整配置示例
import { createSSRApp } from 'vue';
import App from './App.vue';
import uviewPlus from 'uview-plus';
import 'uview-plus/index.scss';
import '@/static/fonts/iconfont.css'; // 自定义SVG图标库
export function createApp() {
const app = createSSRApp(App);
// 🔧 官方推荐:uview-plus配置
app.use(uviewPlus, () => {
return {
options: {
config: {
iconUrl: '/static/fonts/uview-plus-official.ttf'
}
}
}
});
return { app };
}
```
```javascript
// App.vue - 兼容性配置
import { fontUtil } from 'uview-plus';
export default {
onLaunch: function () {
// 🔧 官方推荐:手动加载字体图标
try {
fontUtil.loadFont();
console.log('✅ 手动加载uview-plus字体成功');
} catch (error) {
console.error('❌ 手动加载uview-plus字体失败:', error);
}
}
};
```
#### 混合图标使用策略
- **uview-plus图标**: 使用 `<u-icon name="home" />` 系统通用图标
- **项目iconfont**: 使用 `<text class="iconfont icon-user"></text>` 业务专用图标
- **优先级**: 项目iconfont > uview-plus图标 > CDN字体
### ⚠️ 重要注意事项
#### 升级前检查
1. **字体文件**: 确保本地字体文件存在且可访问
2. **网络环境**: 测试CDN地址访问性
3. **版本兼容**: 检查字体版本与组件版本匹配
#### 最佳实践
1. **优先级**: 项目iconfont > 本地字体 > CDN字体
2. **测试覆盖**: 在不同网络环境下测试图标显示
3. **回退机制**: 准备备用图标方案
4. **文档记录**: 及时更新字体配置变更
### 📚 相关文件位置
- **字体配置**: `mobile/src/main.js`
- **测试页面**: `mobile/src/pages/demo/uview-icon-test/index.vue`
- **字体文件**: `mobile/src/static/fonts/`
- **项目图标**: `mobile/src/static/fonts/iconfont.css`
### 🧪 测试验证
```bash
# 启动开发服务器
npm run dev:h5
# 访问测试页面
http://localhost:3000/#/pages/demo/uview-icon-test/index
```
### 📖 参考文档
- uview-plus官方文档: https://uview-plus.jiangruyi.com/
- 字体图标组件: https://uview-plus.jiangruyi.com/components/icon.html
- 自托管配置: https://uview-plus.jiangruyi.com/components/icon.html#自定义默认字体图标自托管资源
- 兼容性配置: https://uview-plus.jiangruyi.com/components/icon.html#新版本全局加载兼容性
- 项目图标文档: `docs/开发文档/API/u-icon替换映射表.md`
+1 -2
View File
@@ -1,3 +1,2 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
temp/
test/
+8 -1
View File
@@ -161,4 +161,11 @@ coverage/
*.crt
*.p12
*.keystore/xgxt/
excel/
excel/
# 吴自龙注释
test/
script/
stgl-api-test.js
update/
本地临时文件目录/
@@ -1,2 +0,0 @@
-- 添加批次ID字段
ALTER TABLE xljk_consultation_schedule ADD COLUMN batch_id VARCHAR(50) DEFAULT NULL COMMENT '批次ID,用于标识同一批次创建的预约时段';
@@ -128,7 +128,7 @@ spring.data:
# 端口,默认为6379
port: 16379
# 数据库索引
database: 0
database: 8
# redis 密码必须配置
password: Hzdyjs@123456
# 连接超时时间
@@ -92,6 +92,10 @@ spring:
deserialization:
# 允许对象忽略json中不存在的属性
fail_on_unknown_properties: false
# thymeleaf模板配置 weizhong
thymeleaf:
# 是否检查模板位置
check-template-location: false
# Sa-Token配置
sa-token:
@@ -124,6 +128,10 @@ security:
- /v3/api-docs/**
- /webjars/**
- /warm-flow-ui/config
# 招聘门户公开接口 weizhong
- /rs/job/public/**
- /rs/job/view/**
- /rs/application/submit
- /system/dict/data/type/*
# 多租户配置
@@ -119,6 +119,12 @@ public class PageQuery implements Serializable {
return (pageNum - 1) * pageSize;
}
/**
* 无参构造器
*/
public PageQuery() {
}
public PageQuery(Integer pageSize, Integer pageNum) {
this.pageSize = pageSize;
this.pageNum = pageNum;
+12
View File
@@ -104,6 +104,18 @@
<artifactId>ruoyi-workflow</artifactId>
</dependency>
<!-- 添加SaToken依赖 -->
<dependency>
<groupId>org.dromara</groupId>
<artifactId>ruoyi-common-satoken</artifactId>
</dependency>
<!-- iText PDF生成库 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13.3</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,35 @@
package org.dromara.rs.config;
import org.dromara.rs.util.SqlScriptRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* 数据库初始化器,在应用启动时执行SQL脚本
*/
@Component
public class DbInitializer implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(DbInitializer.class);
@Autowired
private SqlScriptRunner sqlScriptRunner;
@Override
public void run(String... args) throws Exception {
try {
// 吴自龙 注释了
// logger.info("开始执行数据库升级脚本...");
// 执行升级脚本
// sqlScriptRunner.executeScript("mapper/rs/update-rs-application-table.sql");
// logger.info("数据库升级脚本执行完成");
} catch (Exception e) {
logger.error("执行数据库升级脚本失败", e);
// 这里选择记录错误但不中断应用启动,因为可能是表结构已经更新过
// 如果需要严格控制,可以抛出异常中断应用启动
}
}
}
@@ -0,0 +1,349 @@
package org.dromara.rs.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.dromara.common.core.domain.R;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.core.utils.file.MimeTypeUtils;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.exception.ServiceException;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.rs.domain.RsApplication;
import org.dromara.rs.service.IRsApplicationService;
import java.io.IOException;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactory;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Paragraph;
import com.itextpdf.text.pdf.PdfWriter;
/**
* 应聘申请Controller
*
* @author ruoyi
*/
@RestController
@RequestMapping("/rs/application")
public class RsApplicationController extends BaseController {
@Autowired
private IRsApplicationService applicationService;
/**
* 查询应聘申请列表
*/
@SaCheckPermission("rs:application:list")
@GetMapping("/list")
public TableDataInfo<RsApplication> list(RsApplication application, PageQuery pageQuery) {
List<RsApplication> list = applicationService.selectRsApplicationList(application);
return TableDataInfo.build(list);
}
/**
* 查询指定职位的应聘申请
*/
@SaCheckPermission("rs:application:list")
@GetMapping("/list/{jobId}")
public TableDataInfo<RsApplication> listByJobId(@PathVariable("jobId") Long jobId, RsApplication application, PageQuery pageQuery) {
application.setJobId(jobId);
List<RsApplication> list = applicationService.selectRsApplicationList(application);
return TableDataInfo.build(list);
}
/**
* 导出应聘申请列表
*/
@SaCheckPermission("rs:application:export")
@Log(title = "应聘申请", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, RsApplication application) {
List<RsApplication> list = applicationService.selectRsApplicationList(application);
ExcelUtil.exportExcel(list, "申请记录数据", RsApplication.class, response);
}
/**
* 导出简历为PDF
*/
@SaCheckPermission("rs:application:export")
@Log(title = "简历导出", businessType = BusinessType.EXPORT)
@GetMapping("/exportPdf/{applicationId}")
public void exportPdf(@PathVariable("applicationId") Long applicationId, HttpServletResponse response) throws IOException {
// 获取应聘申请详情
RsApplication application = applicationService.selectRsApplicationById(applicationId);
if (application == null) {
throw new ServiceException("未找到指定的简历记录");
}
// 临时测试:导出文本文件而不是PDF
response.setContentType("text/plain");
String fileName = URLEncoder.encode("简历_" + application.getApplicantName() + "_" + System.currentTimeMillis(), "UTF-8");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".txt");
// 设置允许跨域
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
try {
// 直接写入文本内容
response.getWriter().write("应聘者简历详情\n\n");
// 添加基本信息部分
response.getWriter().write("=== 基本信息 ===\n");
response.getWriter().write("姓名: " + application.getApplicantName() + "\n");
response.getWriter().write("应聘职位: " + application.getJobTitle() + "\n");
response.getWriter().write("联系电话: " + application.getContactPhone() + "\n");
response.getWriter().write("联系邮箱: " + application.getContactEmail() + "\n");
// 添加申请状态和时间
String status = "未知";
switch (application.getStatus()) {
case "0":
status = "待处理";
break;
case "1":
status = "已通过";
break;
case "2":
status = "已拒绝";
break;
default:
status = "未知";
}
response.getWriter().write("申请状态: " + status + "\n");
response.getWriter().write("申请时间: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(application.getApplyTime()) + "\n\n");
// 添加教育背景
response.getWriter().write("=== 教育背景 ===\n");
response.getWriter().write("最高学历: " + (application.getEducation() != null ? application.getEducation() : "未提供") + "\n");
if (application.getEducationBackground() != null) {
response.getWriter().write("教育经历: " + application.getEducationBackground() + "\n\n");
}
// 添加工作经验
response.getWriter().write("=== 工作经验 ===\n");
response.getWriter().write(application.getExperience() != null ? application.getExperience() : "未提供工作经验");
response.getWriter().write("\n\n");
// 添加自我介绍
response.getWriter().write("=== 自我介绍 ===\n");
response.getWriter().write(application.getSelfDescription() != null ? application.getSelfDescription() : "未提供自我介绍");
response.getWriter().write("\n\n");
// 添加生成日期
response.getWriter().write("\n生成日期:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
} catch (Exception e) {
throw new ServiceException("导出文件失败:" + e.getMessage());
}
}
/**
* 获取应聘申请详细信息
*/
@SaCheckPermission("rs:application:query")
@GetMapping(value = "/{applicationId}")
public R<RsApplication> getInfo(@PathVariable("applicationId") Long applicationId) {
return R.ok(applicationService.selectRsApplicationById(applicationId));
}
/**
* 新增应聘申请
*/
@SaCheckPermission("rs:application:add")
@Log(title = "应聘申请", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@RequestBody RsApplication application) {
// 设置默认状态
if (StringUtils.isEmpty(application.getStatus())) {
application.setStatus("pending");
}
return toAjax(applicationService.insertRsApplication(application));
}
/**
* 上传附件
*/
@SaCheckPermission("rs:application:edit")
@Log(title = "上传应聘附件", businessType = BusinessType.UPDATE)
@PostMapping("/attachment/{applicationId}")
public R<Map<String, String>> uploadAttachment(@PathVariable Long applicationId, @RequestParam("file") MultipartFile file) throws IOException {
if (file.isEmpty()) {
return R.fail("上传失败,请选择文件");
}
// 调用文件上传服务
String fileName = StringUtils.substringAfterLast(file.getOriginalFilename(), "/");
// 此处应该使用实际的文件上传工具类,可使用SysFileConfig或自定义文件上传服务
// 这里使用简化实现,实际项目中应替换为真正的文件上传逻辑
String path = "uploads/application/" + DateUtils.datePath() + "/" + System.currentTimeMillis() + "_" + fileName;
// 更新数据库记录
RsApplication application = new RsApplication();
application.setApplicationId(applicationId);
application.setAttachmentPath(path);
application.setAttachmentName(fileName);
applicationService.updateRsApplication(application);
Map<String, String> result = new HashMap<>();
result.put("fileName", fileName);
result.put("path", path);
return R.ok(result);
}
/**
* 提交职位申请
*/
@Log(title = "提交职位申请", businessType = BusinessType.INSERT)
@PostMapping("/apply")
public R<Void> apply(@RequestBody RsApplication application) {
// 检查是否已经申请过
RsApplication existingApp = applicationService.checkApplied(application.getJobId(), application.getApplicantId());
if (existingApp != null) {
return R.fail("您已经申请过该职位,请勿重复申请");
}
// 设置默认状态
application.setStatus("pending");
return toAjax(applicationService.insertRsApplication(application));
}
/**
* 我的申请列表
*/
@GetMapping("/my/{applicantId}")
public TableDataInfo<RsApplication> myApplications(@PathVariable("applicantId") Long applicantId, PageQuery pageQuery) {
List<RsApplication> list = applicationService.selectApplicationsByApplicant(applicantId);
return TableDataInfo.build(list);
}
/**
* 修改应聘申请
*/
@SaCheckPermission("rs:application:edit")
@Log(title = "应聘申请", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@RequestBody RsApplication application) {
return toAjax(applicationService.updateRsApplication(application));
}
/**
* 更新申请状态
*/
@SaCheckPermission("rs:application:edit")
@Log(title = "更新申请状态", businessType = BusinessType.UPDATE)
@PutMapping("/status/{applicationId}/{status}")
public R<Void> updateStatus(@PathVariable("applicationId") Long applicationId, @PathVariable("status") String status) {
RsApplication application = new RsApplication();
application.setApplicationId(applicationId);
application.setStatus(status);
return toAjax(applicationService.updateRsApplication(application));
}
/**
* 更新应聘者信息(包含状态和HR备注)
*/
@SaCheckPermission("rs:application:edit")
@Log(title = "更新应聘者信息", businessType = BusinessType.UPDATE)
@PutMapping("/update/{applicationId}")
public R<Void> updateApplicationInfo(@PathVariable("applicationId") Long applicationId, @RequestBody Map<String, String> updateData) {
RsApplication application = new RsApplication();
application.setApplicationId(applicationId);
// 更新状态
if (updateData.containsKey("status")) {
application.setStatus(updateData.get("status"));
}
// 更新HR备注
if (updateData.containsKey("hrNotes")) {
application.setHrNotes(updateData.get("hrNotes"));
}
return toAjax(applicationService.updateRsApplication(application));
}
/**
* 提交应聘申请(公开接口)
*/
@SaIgnore
@Log(title = "提交应聘申请", businessType = BusinessType.INSERT)
@PostMapping("/submit")
public R<Map<String, Object>> submitApplication(@RequestBody RsApplication application) {
try {
// 设置默认状态
if (StringUtils.isEmpty(application.getStatus())) {
application.setStatus("pending");
}
// 设置申请时间
application.setApplyTime(new java.util.Date());
// 对于匿名用户,applicant_id 设置为 null
// 如果将来需要支持已登录用户,可以从 token 中获取用户ID
application.setApplicantId(null);
// 根据jobId设置职位标题
if (application.getJobId() != null) {
try {
// 这里需要注入JobService来获取职位信息
// 暂时通过Service方法来设置职位标题
applicationService.setJobTitleForApplication(application);
} catch (Exception e) {
// 如果获取职位信息失败,记录日志但不影响申请提交
System.err.println("设置职位标题失败: " + e.getMessage());
}
}
int result = applicationService.insertRsApplication(application);
if (result > 0) {
Map<String, Object> data = new HashMap<>();
data.put("applicationId", application.getApplicationId());
return R.ok("申请提交成功", data);
} else {
return R.fail("申请提交失败");
}
} catch (Exception e) {
return R.fail("申请提交失败:" + e.getMessage());
}
}
/**
* 删除应聘申请
*/
@SaCheckPermission("rs:application:remove")
@Log(title = "应聘申请", businessType = BusinessType.DELETE)
@DeleteMapping("/{applicationIds}")
public R<Void> remove(@PathVariable Long[] applicationIds) {
return toAjax(applicationService.deleteRsApplicationByIds(applicationIds));
}
}
@@ -0,0 +1,162 @@
package org.dromara.rs.controller;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import org.dromara.common.core.domain.R;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.annotation.SaIgnore;
/**
* 人事管理初始化控制器
*
* @author wuzilong
*/
@RestController
@RequestMapping("/rs/init")
public class RsInitController extends BaseController {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 初始化菜单数据
*/
@PostMapping("/menu")
@Log(title = "初始化人事管理菜单", businessType = BusinessType.INSERT)
public R<Void> initMenu() {
try {
// 人事管理顶级菜单
String insertTopMenu = "INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2000, '人事管理', 0, 50, 'rs', null, '', 1, 0, 'M', '0', '0', '', 'peoples', 1, sysdate(), null, null, '人事管理目录')";
jdbcTemplate.execute(insertTopMenu);
// 人事管理整合菜单
String insertIntegratedMenu = "INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2001, '人事管理', 2000, 1, 'index', 'rs/index', '', 1, 0, 'C', '0', '0', 'rs:job:list', 'job', 1, sysdate(), null, null, '人事管理整合菜单')";
jdbcTemplate.execute(insertIntegratedMenu);
// 人事管理按钮权限
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2002, '职位管理查询', 2001, 1, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:query', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2003, '职位管理新增', 2001, 2, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:add', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2004, '职位管理修改', 2001, 3, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:edit', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2005, '职位管理删除', 2001, 4, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:remove', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2006, '职位管理导出', 2001, 5, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:export', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2007, '申请管理查询', 2001, 6, '', '', '', 1, 0, 'F', '0', '0', 'rs:application:query', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2008, '申请管理修改', 2001, 7, '', '', '', 1, 0, 'F', '0', '0', 'rs:application:edit', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2009, '应聘者管理查询', 2001, 8, '', '', '', 1, 0, 'F', '0', '0', 'rs:applicant:query', '#', 1, sysdate(), null, null, '')");
// 为管理员角色添加权限
String adminRoleId = "1";
for (int i = 2000; i <= 2009; i++) {
String insertRoleMenu = "INSERT INTO sys_role_menu(role_id, menu_id) VALUES (" + adminRoleId + ", " + i + ")";
jdbcTemplate.execute(insertRoleMenu);
}
// 添加数据字典
// 部门字典
String insertDeptType = "INSERT INTO sys_dict_type(dict_id, dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES (100, '部门', 'rs_department', '0', 1, sysdate(), null, null, '人事管理部门字典')";
jdbcTemplate.execute(insertDeptType);
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1001, 1, '技术部', 'tech', 'rs_department', '', 'default', 'Y', '0', 1, sysdate(), null, null, '技术部')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1002, 2, '人事部', 'hr', 'rs_department', '', 'default', 'N', '0', 1, sysdate(), null, null, '人事部')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1003, 3, '市场部', 'market', 'rs_department', '', 'default', 'N', '0', 1, sysdate(), null, null, '市场部')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1004, 4, '销售部', 'sales', 'rs_department', '', 'default', 'N', '0', 1, sysdate(), null, null, '销售部')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1005, 5, '财务部', 'finance', 'rs_department', '', 'default', 'N', '0', 1, sysdate(), null, null, '财务部')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1006, 6, '行政部', 'admin', 'rs_department', '', 'default', 'N', '0', 1, sysdate(), null, null, '行政部')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1007, 7, '后勤部', 'logistics', 'rs_department', '', 'default', 'N', '0', 1, sysdate(), null, null, '后勤部')");
// 应聘状态字典
String insertStatusType = "INSERT INTO sys_dict_type(dict_id, dict_name, dict_type, status, create_by, create_time, update_by, update_time, remark) VALUES (101, '应聘状态', 'rs_application_status', '0', 1, sysdate(), null, null, '应聘状态字典')";
jdbcTemplate.execute(insertStatusType);
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1008, 1, '待处理', 'pending', 'rs_application_status', '', 'info', 'Y', '0', 1, sysdate(), null, null, '待处理')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1009, 2, '已面试', 'interviewed', 'rs_application_status', '', 'primary', 'N', '0', 1, sysdate(), null, null, '已面试')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1010, 3, '已录用', 'offered', 'rs_application_status', '', 'success', 'N', '0', 1, sysdate(), null, null, '已录用')");
jdbcTemplate.execute("INSERT INTO sys_dict_data(dict_code, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, update_by, update_time, remark) VALUES (1011, 4, '已拒绝', 'rejected', 'rs_application_status', '', 'danger', 'N', '0', 1, sysdate(), null, null, '已拒绝')");
return R.ok("菜单初始化成功");
} catch (Exception e) {
e.printStackTrace();
return R.fail("菜单初始化失败: " + e.getMessage());
}
}
/**
* 清除菜单数据
*/
@PostMapping("/clear")
@Log(title = "清除人事管理菜单", businessType = BusinessType.DELETE)
public R<Void> clearMenu() {
try {
// 删除角色-菜单关联
jdbcTemplate.execute("DELETE FROM sys_role_menu WHERE menu_id BETWEEN 2000 AND 2009");
// 删除菜单
jdbcTemplate.execute("DELETE FROM sys_menu WHERE menu_id BETWEEN 2000 AND 2009");
// 删除字典数据
jdbcTemplate.execute("DELETE FROM sys_dict_data WHERE dict_type = 'rs_department'");
jdbcTemplate.execute("DELETE FROM sys_dict_data WHERE dict_type = 'rs_application_status'");
// 删除字典类型
jdbcTemplate.execute("DELETE FROM sys_dict_type WHERE dict_type = 'rs_department'");
jdbcTemplate.execute("DELETE FROM sys_dict_type WHERE dict_type = 'rs_application_status'");
return R.ok("菜单清除成功");
} catch (Exception e) {
e.printStackTrace();
return R.fail("菜单清除失败: " + e.getMessage());
}
}
/**
* 更新菜单数据(不需要认证)
*/
@SaIgnore
@PostMapping("/update-menu")
@Log(title = "更新人事管理菜单", businessType = BusinessType.UPDATE)
public R<Void> updateMenu() {
try {
// 删除旧的菜单配置
jdbcTemplate.execute("DELETE FROM sys_role_menu WHERE menu_id BETWEEN 2000 AND 2028");
jdbcTemplate.execute("DELETE FROM sys_menu WHERE menu_id BETWEEN 2000 AND 2028");
// 创建新的菜单配置
// 人事管理顶级菜单
String insertTopMenu = "INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2000, '人事管理', 0, 50, 'rs', null, '', 1, 0, 'M', '0', '0', '', 'peoples', 1, sysdate(), null, null, '人事管理目录')";
jdbcTemplate.execute(insertTopMenu);
// 人事管理整合菜单
String insertIntegratedMenu = "INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2001, '人事管理', 2000, 1, 'index', 'rs/index', '', 1, 0, 'C', '0', '0', 'rs:job:list', 'job', 1, sysdate(), null, null, '人事管理整合菜单')";
jdbcTemplate.execute(insertIntegratedMenu);
// 人事管理按钮权限
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2002, '职位管理查询', 2001, 1, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:query', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2003, '职位管理新增', 2001, 2, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:add', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2004, '职位管理修改', 2001, 3, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:edit', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2005, '职位管理删除', 2001, 4, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:remove', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2006, '职位管理导出', 2001, 5, '', '', '', 1, 0, 'F', '0', '0', 'rs:job:export', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2007, '申请管理查询', 2001, 6, '', '', '', 1, 0, 'F', '0', '0', 'rs:application:query', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2008, '申请管理修改', 2001, 7, '', '', '', 1, 0, 'F', '0', '0', 'rs:application:edit', '#', 1, sysdate(), null, null, '')");
jdbcTemplate.execute("INSERT INTO sys_menu(menu_id, menu_name, parent_id, order_num, path, component, query_param, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark) VALUES (2009, '应聘者管理查询', 2001, 8, '', '', '', 1, 0, 'F', '0', '0', 'rs:applicant:query', '#', 1, sysdate(), null, null, '')");
// 为管理员角色添加权限
String adminRoleId = "1";
for (int i = 2000; i <= 2009; i++) {
String insertRoleMenu = "INSERT INTO sys_role_menu(role_id, menu_id) VALUES (" + adminRoleId + ", " + i + ")";
jdbcTemplate.execute(insertRoleMenu);
}
return R.ok("菜单更新成功");
} catch (Exception e) {
e.printStackTrace();
return R.fail("菜单更新失败: " + e.getMessage());
}
}
}
@@ -0,0 +1,151 @@
package org.dromara.rs.controller;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import org.dromara.rs.domain.RsJob;
import org.dromara.rs.domain.vo.RsJobVo;
import org.dromara.rs.service.IRsJobService;
/**
* 招聘职位Controller
*
* @author ruoyi
*/
@RestController
@RequestMapping("/rs/job")
public class RsJobController extends BaseController {
@Autowired
private IRsJobService jobService;
/**
* 查询招聘职位列表
*/
@SaCheckPermission("rs:job:list")
@GetMapping("/list")
public TableDataInfo<RsJobVo> list(RsJob rsJob, PageQuery pageQuery) {
List<RsJobVo> list = jobService.selectRsJobList(rsJob);
return TableDataInfo.build(list);
}
/**
* 获取公开的招聘职位列表(无需权限)
*/
@SaIgnore
@GetMapping("/public/list")
public TableDataInfo<RsJobVo> publicList(RsJob rsJob, PageQuery pageQuery) {
// 只查询启用状态的职位
rsJob.setIsActive("0");
List<RsJobVo> list = jobService.selectRsJobList(rsJob);
return TableDataInfo.build(list);
}
/**
* 导出招聘职位列表
*/
@SaCheckPermission("rs:job:export")
@Log(title = "招聘职位", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, RsJob rsJob) {
List<RsJobVo> list = jobService.selectRsJobList(rsJob);
ExcelUtil.exportExcel(list, "招聘职位数据", RsJobVo.class, response);
}
/**
* 获取招聘职位详细信息
*/
@SaCheckPermission("rs:job:query")
@GetMapping(value = "/{jobId}")
public R<RsJobVo> getInfo(@PathVariable("jobId") Long jobId) {
return R.ok(jobService.selectRsJobById(jobId));
}
/**
* 获取招聘职位公开详情(无需权限)
*/
@SaIgnore
@GetMapping(value = "/public/{jobId}")
public R<RsJobVo> getPublicInfo(@PathVariable("jobId") Long jobId) {
RsJobVo job = jobService.selectRsJobById(jobId);
if (job != null && "0".equals(job.getIsActive())) {
return R.ok(job);
}
return R.fail("职位不存在或已下线");
}
/**
* 新增招聘职位
*/
@SaCheckPermission("rs:job:add")
@Log(title = "招聘职位", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@RequestBody RsJob rsJob) {
return toAjax(jobService.insertRsJob(rsJob));
}
/**
* 修改招聘职位
*/
@SaCheckPermission("rs:job:edit")
@Log(title = "招聘职位", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@RequestBody RsJob rsJob) {
return toAjax(jobService.updateRsJob(rsJob));
}
/**
* 切换职位状态
*/
@SaCheckPermission("rs:job:edit")
@Log(title = "切换职位状态", businessType = BusinessType.UPDATE)
@PutMapping("/toggle/{jobId}")
public R<Void> toggleStatus(@PathVariable Long jobId) {
RsJobVo job = jobService.selectRsJobById(jobId);
if (job == null) {
return R.fail("职位不存在");
}
RsJob updateJob = new RsJob();
updateJob.setJobId(jobId);
// 切换状态
updateJob.setIsActive("0".equals(job.getIsActive()) ? "1" : "0");
return toAjax(jobService.updateRsJob(updateJob));
}
/**
* 增加岗位浏览次数(公开接口)
*/
@SaIgnore
@Log(title = "增加岗位浏览次数", businessType = BusinessType.UPDATE)
@PostMapping("/view/{jobId}")
public R<Void> incrementViewCount(@PathVariable Long jobId) {
return toAjax(jobService.incrementViewCount(jobId));
}
/**
* 删除招聘职位
*/
@SaCheckPermission("rs:job:remove")
@Log(title = "招聘职位", businessType = BusinessType.DELETE)
@DeleteMapping("/{jobIds}")
public R<Void> remove(@PathVariable Long[] jobIds) {
return toAjax(jobService.deleteRsJobByIds(jobIds));
}
}
@@ -0,0 +1,301 @@
package org.dromara.rs.controller;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
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 org.dromara.rs.domain.bo.RsOverseasApplicationBo;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.vo.RsOverseasApprovalVo;
import org.dromara.rs.domain.query.RsOverseasApplicationQuery;
import org.dromara.rs.service.IRsOverseasApplicationService;
import org.dromara.rs.service.IRsOverseasApprovalService;
import java.util.Date;
import java.util.Map;
import java.util.HashMap;
/**
* 出国申请Controller
*
* @author weizzzz
* @date 2025-07-28
*/
@Validated
@RestController
@RequestMapping("/rs/overseas/application")
public class RsOverseasApplicationController extends BaseController {
@Autowired
private IRsOverseasApplicationService applicationService;
@Autowired
private IRsOverseasApprovalService overseasApprovalService;
/**
* 查询出国申请列表
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/list")
public TableDataInfo<RsOverseasApplicationVo> list(RsOverseasApplicationQuery query) {
query.setTenantId(LoginHelper.getTenantId()); // 设置租户ID
return applicationService.queryPageList(query);
}
/**
* 查询我的出国申请列表(排除已通过的申请,已通过的申请显示在出国记录中)
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/my/list")
public TableDataInfo<RsOverseasApplicationVo> myList(RsOverseasApplicationQuery query) {
Long userId = LoginHelper.getUserId();
query.setApplicantId(userId);
query.setTenantId(LoginHelper.getTenantId()); // 设置租户ID
return applicationService.queryMyApplicationList(query);
}
/**
* 查询待审批的申请列表(有审批权限的用户可以看到所有待审批申请)
*/
@SaCheckPermission("rs:overseas:approve")
@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);
}
/**
* 临时测试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);
}
/**
* 根据状态查询申请列表
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/status/{status}")
public R<List<RsOverseasApplicationVo>> listByStatus(@PathVariable String status) {
List<RsOverseasApplicationVo> list = applicationService.queryByStatus(status);
return R.ok(list);
}
/**
* 导出出国申请列表
*/
@SaCheckPermission("rs:overseas:export")
@Log(title = "出国申请", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(RsOverseasApplicationQuery query, HttpServletResponse response) {
List<RsOverseasApplicationVo> list = applicationService.queryList(query);
ExcelUtil.exportExcel(list, "出国申请数据", RsOverseasApplicationVo.class, response);
}
/**
* 获取出国申请详细信息
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/{applicationId}")
public R<RsOverseasApplicationVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long applicationId) {
return R.ok(applicationService.queryById(applicationId));
}
/**
* 新增出国申请
*/
@SaCheckPermission("rs:overseas:add")
@Log(title = "出国申请", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody RsOverseasApplicationBo bo) {
// 自动设置申请人信息
bo.setApplicantId(LoginHelper.getUserId());
bo.setApplicantName(LoginHelper.getUsername());
// 自动设置部门信息
bo.setDepartment(LoginHelper.getDeptName());
// 自动计算出国天数
if (bo.getStartDate() != null && bo.getEndDate() != null) {
long diffTime = Math.abs(bo.getEndDate().getTime() - bo.getStartDate().getTime());
int diffDays = (int) Math.ceil(diffTime / (1000.0 * 60 * 60 * 24)) + 1;
bo.setDuration(diffDays);
}
// 状态由Service层控制,新增时为草稿状态
bo.setApplyTime(new Date());
return toAjax(applicationService.insertByBo(bo));
}
/**
* 修改出国申请
*/
@SaCheckPermission("rs:overseas:edit")
@Log(title = "出国申请", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated(EditGroup.class) @RequestBody RsOverseasApplicationBo bo) {
return toAjax(applicationService.updateByBo(bo));
}
/**
* 提交出国申请
*/
@SaCheckPermission("rs:overseas:submit")
@Log(title = "提交出国申请", businessType = BusinessType.UPDATE)
@PutMapping("/submit/{applicationId}")
public R<Void> submit(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId) {
return toAjax(applicationService.submitApplication(applicationId));
}
/**
* 重新提交被拒绝的出国申请
*/
@SaCheckPermission("rs:overseas:edit")
@Log(title = "重新提交出国申请", businessType = BusinessType.UPDATE)
@PutMapping("/resubmit/{applicationId}")
public R<Void> resubmit(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId,
@Validated @RequestBody RsOverseasApplicationBo bo) {
return toAjax(applicationService.resubmitApplication(applicationId, bo));
}
/**
* 取消出国申请
*/
@SaCheckPermission("rs:overseas:cancel")
@Log(title = "取消出国申请", businessType = BusinessType.UPDATE)
@PutMapping("/cancel/{applicationId}")
public R<Void> cancel(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId,
@RequestParam(required = false) String reason) {
return toAjax(applicationService.cancelApplication(applicationId, reason));
}
/**
* 审批出国申请
*/
@SaCheckPermission("rs:overseas:approve")
@Log(title = "审批出国申请", businessType = BusinessType.UPDATE)
@PostMapping("/approve")
public R<Void> approve(@RequestBody ApprovalRequest request) {
Long approverId = LoginHelper.getUserId();
String approverName = LoginHelper.getUsername();
return toAjax(applicationService.approveApplication(
request.getApplicationId(), approverId, approverName,
request.getApprovalStatus(), request.getApprovalOpinion()));
}
/**
* 批量删除出国申请
*/
@SaCheckPermission("rs:overseas:remove")
@Log(title = "出国申请", businessType = BusinessType.DELETE)
@DeleteMapping("/{applicationIds}")
public R<Void> remove(@NotEmpty(message = "主键集合不能为空") @PathVariable List<Long> applicationIds) {
return toAjax(applicationService.deleteWithValidByIds(applicationIds, true));
}
/**
* 查询出国申请审批历史
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/approval/history/{applicationId}")
public R<List<RsOverseasApprovalVo>> getApprovalHistory(@PathVariable("applicationId") Long applicationId) {
List<RsOverseasApprovalVo> approvalList = overseasApprovalService.selectApprovalHistoryByApplicationId(applicationId);
return R.ok(approvalList);
}
/**
* 修复已审批通过但未创建记录的申请
*/
@SaCheckPermission("rs:overseas:approve")
@Log(title = "修复出国记录", businessType = BusinessType.UPDATE)
@PostMapping("/fix/records")
public R<String> fixApprovedRecords() {
try {
int fixedCount = applicationService.fixApprovedApplicationsWithoutRecords();
return R.ok("修复完成,共修复 " + fixedCount + " 条记录");
} catch (Exception e) {
return R.fail("修复失败:" + e.getMessage());
}
}
/**
* 审批请求对象
*/
public static class ApprovalRequest {
private Long applicationId;
private String approvalStatus;
private String approvalOpinion;
public Long getApplicationId() {
return applicationId;
}
public void setApplicationId(Long applicationId) {
this.applicationId = applicationId;
}
public String getApprovalStatus() {
return approvalStatus;
}
public void setApprovalStatus(String approvalStatus) {
this.approvalStatus = approvalStatus;
}
public String getApprovalOpinion() {
return approvalOpinion;
}
public void setApprovalOpinion(String approvalOpinion) {
this.approvalOpinion = approvalOpinion;
}
}
}
@@ -0,0 +1,156 @@
package org.dromara.rs.controller;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
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 org.dromara.rs.domain.bo.RsOverseasApprovalBo;
import org.dromara.rs.domain.vo.RsOverseasApprovalVo;
import org.dromara.rs.domain.query.RsOverseasApprovalQuery;
import org.dromara.rs.service.IRsOverseasApprovalService;
import lombok.extern.slf4j.Slf4j;
/**
* 出国审批记录Controller
*
* @author weizzzz
* @date 2025-07-28
*/
@Validated
@Slf4j
@RestController
@RequestMapping("/rs/overseas/approval")
public class RsOverseasApprovalController extends BaseController {
@Autowired
private IRsOverseasApprovalService approvalService;
/**
* 查询出国审批记录列表
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/list")
public TableDataInfo<RsOverseasApprovalVo> list(RsOverseasApprovalQuery query) {
return approvalService.queryPageList(query);
}
/**
* 查询我的审批记录列表
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/my/list")
public TableDataInfo<RsOverseasApprovalVo> myList(RsOverseasApprovalQuery query) {
Long userId = LoginHelper.getUserId();
query.setApproverId(userId);
return approvalService.queryPageList(query);
}
/**
* 联合查询出国审批记录列表(包含待审批申请和已审批记录)
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/combined/list")
public TableDataInfo<RsOverseasApprovalVo> combinedList(RsOverseasApprovalQuery query) {
return approvalService.queryCombinedPageList(query);
}
/**
* 根据申请ID查询审批记录列表
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/application/{applicationId}")
public R<List<RsOverseasApprovalVo>> getByApplicationId(@PathVariable Long applicationId) {
List<RsOverseasApprovalVo> list = approvalService.queryByApplicationId(applicationId);
return R.ok(list);
}
/**
* 导出出国审批记录列表
*/
@SaCheckPermission("rs:overseas:export")
@Log(title = "出国审批记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(RsOverseasApprovalQuery query, HttpServletResponse response) {
List<RsOverseasApprovalVo> list = approvalService.queryList(query);
ExcelUtil.exportExcel(list, "出国审批记录数据", RsOverseasApprovalVo.class, response);
}
/**
* 获取出国审批记录详细信息
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/{approvalId}")
public R<RsOverseasApprovalVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long approvalId) {
return R.ok(approvalService.queryById(approvalId));
}
/**
* 新增出国审批记录
*/
@SaCheckPermission("rs:overseas:add")
@Log(title = "出国审批记录", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody RsOverseasApprovalBo bo) {
// 设置审批人信息
bo.setApproverId(LoginHelper.getUserId());
bo.setApproverName(LoginHelper.getUsername());
return toAjax(approvalService.insertByBo(bo));
}
/**
* 修改出国审批记录
*/
@SaCheckPermission("rs:overseas:edit")
@Log(title = "出国审批记录", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated(EditGroup.class) @RequestBody RsOverseasApprovalBo bo) {
return toAjax(approvalService.updateByBo(bo));
}
/**
* 批量删除出国审批记录
*/
@SaCheckPermission("rs:overseas:remove")
@Log(title = "出国审批记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{approvalIds}")
public R<Void> remove(@NotEmpty(message = "主键集合不能为空") @PathVariable List<Long> approvalIds) {
return toAjax(approvalService.deleteWithValidByIds(approvalIds, true));
}
/**
* 修复审批记录测试数据
*/
@SaCheckPermission("rs:overseas:edit")
@Log(title = "出国审批记录", businessType = BusinessType.UPDATE)
@PostMapping("/fixData")
public R<String> fixApprovalData() {
try {
approvalService.fixApprovalTestData();
return R.ok("审批记录数据修复成功!");
} catch (Exception e) {
log.error("修复审批记录数据失败", e);
return R.fail("修复失败:" + e.getMessage());
}
}
}
@@ -0,0 +1,226 @@
package org.dromara.rs.controller;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
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 org.dromara.rs.domain.bo.RsOverseasRecordBo;
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
import org.dromara.rs.domain.query.RsOverseasRecordQuery;
import org.dromara.rs.service.IRsOverseasRecordService;
/**
* 出国记录Controller
*
* @author weizzzz
* @date 2025-07-28
*/
@Validated
@RestController
@RequestMapping("/rs/overseas/record")
public class RsOverseasRecordController extends BaseController {
@Autowired
private IRsOverseasRecordService recordService;
/**
* 查询出国记录列表
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/list")
public TableDataInfo<RsOverseasRecordVo> list(RsOverseasRecordQuery query) {
return recordService.queryPageList(query);
}
/**
* 查询我的出国记录列表(联合查询:记录+已通过申请)
*/
@SaCheckPermission("rs:overseas:list")
@GetMapping("/my/list")
public TableDataInfo<RsOverseasRecordVo> myList(RsOverseasRecordQuery query) {
Long userId = LoginHelper.getUserId();
query.setApplicantId(userId);
return recordService.queryMyRecordPageList(query);
}
/**
* 根据申请ID查询出国记录
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/application/{applicationId}")
public R<RsOverseasRecordVo> getByApplicationId(@PathVariable Long applicationId) {
RsOverseasRecordVo record = recordService.queryByApplicationId(applicationId);
return R.ok(record);
}
/**
* 导出出国记录列表
*/
@SaCheckPermission("rs:overseas:export")
@Log(title = "出国记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(RsOverseasRecordQuery query, HttpServletResponse response) {
List<RsOverseasRecordVo> list = recordService.queryList(query);
ExcelUtil.exportExcel(list, "出国记录数据", RsOverseasRecordVo.class, response);
}
/**
* 获取出国记录详细信息
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/{recordId}")
public R<RsOverseasRecordVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long recordId) {
return R.ok(recordService.queryById(recordId));
}
/**
* 新增出国记录
*/
@SaCheckPermission("rs:overseas:add")
@Log(title = "出国记录", businessType = BusinessType.INSERT)
@PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody RsOverseasRecordBo bo) {
return toAjax(recordService.insertByBo(bo));
}
/**
* 修改出国记录
*/
@SaCheckPermission("rs:overseas:edit")
@Log(title = "出国记录", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> edit(@Validated(EditGroup.class) @RequestBody RsOverseasRecordBo bo) {
return toAjax(recordService.updateByBo(bo));
}
/**
* 基于申请信息创建出国记录
*/
@SaCheckPermission("rs:overseas:create")
@Log(title = "创建出国记录", businessType = BusinessType.INSERT)
@PostMapping("/create/{applicationId}")
public R<Void> createFromApplication(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId) {
return toAjax(recordService.createRecordFromApplication(applicationId));
}
/**
* 更新出国记录完成状态
*/
@SaCheckPermission("rs:overseas:complete")
@Log(title = "更新出国记录完成状态", businessType = BusinessType.UPDATE)
@PutMapping("/complete")
public R<Void> updateCompletion(@RequestBody CompletionRequest request) {
return toAjax(recordService.updateCompletionStatus(
request.getRecordId(), request.getCompletionStatus(),
request.getAchievement(), request.getReportContent()));
}
/**
* 批量删除出国记录
*/
@SaCheckPermission("rs:overseas:remove")
@Log(title = "出国记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{recordIds}")
public R<Void> remove(@NotEmpty(message = "主键集合不能为空") @PathVariable List<Long> recordIds) {
return toAjax(recordService.deleteWithValidByIds(recordIds, true));
}
/**
* 初始化测试数据(临时接口,用于解决出国记录列表显示问题)
*/
@SaCheckPermission("rs:overseas:add")
@PostMapping("/initTestData")
public R<Void> initTestData() {
try {
Long currentUserId = LoginHelper.getUserId();
String currentUserName = LoginHelper.getUsername();
// 创建一个测试出国记录
RsOverseasRecordBo testRecord = new RsOverseasRecordBo();
testRecord.setApplicationId(1L);
testRecord.setApplicantId(currentUserId);
testRecord.setApplicantName(currentUserName);
testRecord.setDepartment("智能控制学院");
testRecord.setPurpose("academic_exchange");
testRecord.setDestination("德国");
testRecord.setActualStartDate(java.sql.Date.valueOf("2025-08-15"));
testRecord.setActualEndDate(java.sql.Date.valueOf("2025-08-21"));
testRecord.setActualDuration(6);
testRecord.setActualBudget(new java.math.BigDecimal("14500.00"));
testRecord.setCompletionStatus("1");
testRecord.setAchievement("参加国际智能控制学术会议,发表论文,与德国同行建立合作关系");
testRecord.setReportContent("此次德国学术交流收获丰富,重点学习了最新的智能控制理论和技术");
testRecord.setCertificatePath("/uploads/overseas/records/2025/08/conference_certificate.pdf");
recordService.insertByBo(testRecord);
return R.ok("测试数据初始化成功,请刷新页面查看");
} catch (Exception e) {
return R.fail("测试数据初始化失败: " + e.getMessage());
}
}
/**
* 完成状态更新请求对象
*/
public static class CompletionRequest {
private Long recordId;
private String completionStatus;
private String achievement;
private String reportContent;
public Long getRecordId() {
return recordId;
}
public void setRecordId(Long recordId) {
this.recordId = recordId;
}
public String getCompletionStatus() {
return completionStatus;
}
public void setCompletionStatus(String completionStatus) {
this.completionStatus = completionStatus;
}
public String getAchievement() {
return achievement;
}
public void setAchievement(String achievement) {
this.achievement = achievement;
}
public String getReportContent() {
return reportContent;
}
public void setReportContent(String reportContent) {
this.reportContent = reportContent;
}
}
}
@@ -0,0 +1,253 @@
package org.dromara.rs.controller;
import java.util.List;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.rs.domain.RsTrainingApplication;
import org.dromara.rs.domain.RsTrainingCourse;
import org.dromara.rs.service.IRsTrainingApplicationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletResponse;
/**
* 培训申请控制器
*
* @author weizzzz
* @date 2025-07-16
*/
@RestController
@RequestMapping("/rs/training/application")
public class RsTrainingApplicationController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(RsTrainingApplicationController.class);
@Autowired
private IRsTrainingApplicationService trainingApplicationService;
/**
* 查询培训申请列表
*/
@SaCheckPermission("rs:training:application:list")
@GetMapping("/list")
public TableDataInfo<RsTrainingApplication> list(RsTrainingApplication application, PageQuery pageQuery) {
return trainingApplicationService.selectPageRsTrainingApplicationList(application, pageQuery);
}
/**
* 导出培训申请列表
*/
@SaCheckPermission("rs:training:application:export")
@Log(title = "培训申请", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, RsTrainingApplication application) {
List<RsTrainingApplication> list = trainingApplicationService.selectRsTrainingApplicationList(application);
ExcelUtil.exportExcel(list, "培训申请", RsTrainingApplication.class, response);
}
/**
* 获取培训申请详细信息
*/
@SaCheckPermission("rs:training:application:query")
@GetMapping("/{applicationId}")
public R<RsTrainingApplication> getInfo(@PathVariable("applicationId") Long applicationId) {
return R.ok(trainingApplicationService.selectRsTrainingApplicationById(applicationId));
}
/**
* 新增培训申请
*/
@SaCheckPermission("rs:training:application:add")
@Log(title = "培训申请", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated @RequestBody RsTrainingApplication application) {
try {
// 尝试设置申请人为当前登录用户
String loginId = StpUtil.getLoginIdAsString();
if (loginId != null && loginId.startsWith("sys_user:")) {
// 从"sys_user:1"格式中提取数字部分
String[] parts = loginId.split(":");
if (parts.length > 1) {
application.setUserId(Long.parseLong(parts[1]));
application.setUserName("用户" + parts[1]); // 临时用户名
} else {
// 如果无法解析,使用默认值
application.setUserId(1L);
application.setUserName("默认用户");
}
} else {
// 尝试正常转换
try {
application.setUserId(StpUtil.getLoginIdAsLong());
application.setUserName(loginId);
} catch (Exception e) {
// 转换失败,使用默认值
application.setUserId(1L);
application.setUserName("默认用户");
}
}
// 确保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) {
e.printStackTrace();
log.error("添加培训申请失败: " + e.getMessage(), e);
return R.fail("添加培训申请失败: " + e.getMessage());
}
}
/**
* 修改培训申请
*/
@SaCheckPermission("rs:training:application:edit")
@Log(title = "培训申请", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated @RequestBody RsTrainingApplication application) {
return toAjax(trainingApplicationService.updateRsTrainingApplication(application));
}
/**
* 删除培训申请
*/
@SaCheckPermission("rs:training:application:remove")
@Log(title = "培训申请", businessType = BusinessType.DELETE)
@DeleteMapping("/{applicationIds}")
public R<Void> remove(@PathVariable Long[] applicationIds) {
return toAjax(trainingApplicationService.deleteRsTrainingApplicationByIds(applicationIds));
}
/**
* 取消培训申请
*/
@SaCheckPermission("rs:training:application:edit")
@Log(title = "培训申请", businessType = BusinessType.UPDATE)
@PutMapping("/cancel/{applicationId}")
public R<Void> cancel(@PathVariable("applicationId") Long applicationId) {
return toAjax(trainingApplicationService.cancelApplication(applicationId));
}
/**
* 查询我的培训申请列表
*/
@SaCheckPermission("rs:training:application:query")
@GetMapping("/my")
public TableDataInfo<RsTrainingApplication> listMyApplications(RsTrainingApplication application, PageQuery pageQuery) {
return trainingApplicationService.selectPageMyApplications(StpUtil.getLoginIdAsLong(), application, pageQuery);
}
/**
* 查询指定课程的申请列表
*/
@SaCheckPermission("rs:training:application:list")
@GetMapping("/list/{courseId}")
public TableDataInfo<RsTrainingApplication> listByCourse(@PathVariable("courseId") Long courseId, RsTrainingApplication application, PageQuery pageQuery) {
return trainingApplicationService.selectPageApplicationsByCourse(courseId, application, pageQuery);
}
/**
* 新增培训申请(无需权限验证,供普通用户使用)
*/
@SaIgnore
@Log(title = "培训申请", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/submit")
public R<Void> submit(@Validated @RequestBody RsTrainingApplication application) {
try {
// 尝试设置申请人为当前登录用户,如果未登录则使用默认值
try {
String loginId = StpUtil.getLoginIdAsString();
if (loginId != null && loginId.startsWith("sys_user:")) {
// 从"sys_user:1"格式中提取数字部分
String[] parts = loginId.split(":");
if (parts.length > 1) {
application.setUserId(Long.parseLong(parts[1]));
application.setUserName("用户" + parts[1]); // 临时用户名
} else {
// 如果无法解析,使用默认值
application.setUserId(1L);
application.setUserName("默认用户");
}
} else {
// 尝试正常转换
application.setUserId(StpUtil.getLoginIdAsLong());
application.setUserName(loginId);
}
} catch (Exception e) {
// 如果未登录或转换失败,使用默认值
application.setUserId(1L);
application.setUserName("默认用户");
log.info("用户未登录,使用默认用户信息");
}
// 确保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) {
e.printStackTrace();
log.error("添加培训申请失败: " + e.getMessage(), e);
return R.fail("添加培训申请失败: " + e.getMessage());
}
}
/**
* 更新培训申请状态
*/
@SaCheckPermission("rs:training:application:edit")
@Log(title = "培训申请状态", businessType = BusinessType.UPDATE)
@PutMapping("/status/{applicationId}/{status}")
public R<Void> updateStatus(
@PathVariable("applicationId") Long applicationId,
@PathVariable("status") String status) {
log.info("更新培训申请状态: applicationId={}, status={}", applicationId, status);
return toAjax(trainingApplicationService.updateApplicationStatus(applicationId, status));
}
/**
* 重新提交被拒绝的培训申请
*/
@SaCheckPermission("rs:training:application:edit")
@Log(title = "重新提交培训申请", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping("/resubmit/{applicationId}")
public R<Void> resubmit(
@PathVariable("applicationId") Long applicationId,
@Validated @RequestBody RsTrainingApplication application) {
log.info("重新提交被拒绝的培训申请: applicationId={}", applicationId);
return toAjax(trainingApplicationService.resubmitApplication(applicationId, application));
}
}
@@ -0,0 +1,120 @@
package org.dromara.rs.controller;
import java.util.List;
import java.util.Map;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.rs.domain.RsTrainingApproval;
import org.dromara.rs.service.IRsTrainingApprovalService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletResponse;
/**
* 培训审批控制器
*
* @author weizzzz
* @date 2025-07-16
*/
@RestController
@RequestMapping("/rs/training/approval")
public class RsTrainingApprovalController extends BaseController {
@Autowired
private IRsTrainingApprovalService trainingApprovalService;
/**
* 查询培训审批列表
*/
@SaCheckPermission("rs:training:approval:list")
@GetMapping("/list")
public TableDataInfo<RsTrainingApproval> list(RsTrainingApproval approval, PageQuery pageQuery) {
// 使用带详细信息的查询方法,返回申请人姓名和课程名称
return trainingApprovalService.selectPageRsTrainingApprovalWithDetailsList(approval, pageQuery);
}
/**
* 获取培训审批详细信息
*/
@SaCheckPermission("rs:training:approval:query")
@GetMapping("/{approvalId}")
public R<RsTrainingApproval> getInfo(@PathVariable("approvalId") Long approvalId) {
return R.ok(trainingApprovalService.selectRsTrainingApprovalById(approvalId));
}
/**
* 新增培训审批
*/
@SaCheckPermission("rs:training:approval:add")
@Log(title = "培训审批", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated @RequestBody RsTrainingApproval approval) {
return toAjax(trainingApprovalService.insertRsTrainingApproval(approval));
}
/**
* 修改培训审批
*/
@SaCheckPermission("rs:training:approval:edit")
@Log(title = "培训审批", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated @RequestBody RsTrainingApproval approval) {
return toAjax(trainingApprovalService.updateRsTrainingApproval(approval));
}
/**
* 删除培训审批
*/
@SaCheckPermission("rs:training:approval:remove")
@Log(title = "培训审批", businessType = BusinessType.DELETE)
@DeleteMapping("/{approvalIds}")
public R<Void> remove(@PathVariable Long[] approvalIds) {
return toAjax(trainingApprovalService.deleteRsTrainingApprovalByIds(approvalIds));
}
/**
* 查询待我审批的列表
*/
@SaCheckPermission("rs:training:approval:process")
@GetMapping("/pending")
public TableDataInfo<RsTrainingApproval> listPendingApprovals(RsTrainingApproval approval, PageQuery pageQuery) {
return trainingApprovalService.selectPagePendingApprovals(StpUtil.getLoginIdAsLong(), approval, pageQuery);
}
/**
* 处理审批
*/
@SaCheckPermission("rs:training:approval:process")
@Log(title = "培训审批", businessType = BusinessType.UPDATE)
@PutMapping("/process/{applicationId}/{status}")
public R<Void> processApproval(@PathVariable("applicationId") Long applicationId, @PathVariable("status") String status, @RequestBody(required = false) Map<String, String> params) {
String opinion = params != null ? params.get("opinion") : null;
return toAjax(trainingApprovalService.processApproval(applicationId, status, opinion));
}
/**
* 查询申请审批历史
*/
@SaCheckPermission("rs:training:approval:query")
@GetMapping("/history/{applicationId}")
public R<List<RsTrainingApproval>> getApprovalHistory(@PathVariable("applicationId") Long applicationId) {
System.out.println("查询审批历史记录 - applicationId: " + applicationId);
List<RsTrainingApproval> approvalList = trainingApprovalService.selectApprovalHistoryByApplicationId(applicationId);
System.out.println("查询审批历史记录 - 找到记录数: " + (approvalList != null ? approvalList.size() : 0));
return R.ok(approvalList);
}
}
@@ -0,0 +1,124 @@
package org.dromara.rs.controller;
import java.util.List;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.rs.domain.RsTrainingCourse;
import org.dromara.rs.service.IRsTrainingCourseService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import jakarta.servlet.http.HttpServletResponse;
/**
* 培训课程控制器
*
* @author weizzzz
* @date 2025-07-16
*/
@RestController
@RequestMapping("/rs/training/course")
public class RsTrainingCourseController extends BaseController {
@Autowired
private IRsTrainingCourseService trainingCourseService;
/**
* 查询培训课程列表
*/
@SaCheckPermission("rs:training:course:list")
@GetMapping("/list")
public TableDataInfo<RsTrainingCourse> list(RsTrainingCourse course, PageQuery pageQuery) {
return trainingCourseService.selectPageRsTrainingCourseList(course, pageQuery);
}
/**
* 导出培训课程列表
*/
@SaCheckPermission("rs:training:course:export")
@Log(title = "培训课程", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, RsTrainingCourse course) {
List<RsTrainingCourse> list = trainingCourseService.selectRsTrainingCourseList(course);
ExcelUtil.exportExcel(list, "培训课程", RsTrainingCourse.class, response);
}
/**
* 获取培训课程详细信息
*/
@SaCheckPermission("rs:training:course:query")
@GetMapping("/{courseId}")
public R<RsTrainingCourse> getInfo(@PathVariable("courseId") Long courseId) {
return R.ok(trainingCourseService.selectRsTrainingCourseById(courseId));
}
/**
* 新增培训课程
*/
@SaCheckPermission("rs:training:course:add")
@Log(title = "培训课程", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated @RequestBody RsTrainingCourse course) {
return toAjax(trainingCourseService.insertRsTrainingCourse(course));
}
/**
* 修改培训课程
*/
@SaCheckPermission("rs:training:course:edit")
@Log(title = "培训课程", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated @RequestBody RsTrainingCourse course) {
return toAjax(trainingCourseService.updateRsTrainingCourse(course));
}
/**
* 删除培训课程
*/
@SaCheckPermission("rs:training:course:remove")
@Log(title = "培训课程", businessType = BusinessType.DELETE)
@DeleteMapping("/{courseIds}")
public R<Void> remove(@PathVariable Long[] courseIds) {
return toAjax(trainingCourseService.deleteRsTrainingCourseByIds(courseIds));
}
/**
* 切换培训课程状态
*/
@SaCheckPermission("rs:training:course:edit")
@Log(title = "培训课程", businessType = BusinessType.UPDATE)
@PutMapping("/toggle/{courseId}")
public R<Void> toggleStatus(@PathVariable("courseId") Long courseId) {
return toAjax(trainingCourseService.toggleCourseStatus(courseId));
}
/**
* 获取可用培训课程列表(不分页)
*/
@SaIgnore
@GetMapping("/available")
public R<List<RsTrainingCourse>> getAvailableCourses() {
RsTrainingCourse course = new RsTrainingCourse();
course.setStatus("0"); // 只查询状态为正常的课程
return R.ok(trainingCourseService.selectRsTrainingCourseList(course));
}
}
@@ -0,0 +1,139 @@
package org.dromara.rs.controller;
import java.util.List;
import java.util.Map;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.idempotent.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.web.core.BaseController;
import org.dromara.rs.domain.RsTrainingRecord;
import org.dromara.rs.service.IRsTrainingRecordService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import jakarta.servlet.http.HttpServletResponse;
/**
* 培训记录控制器
*
* @author weizzzz
* @date 2025-07-16
*/
@RestController
@RequestMapping("/rs/training/record")
public class RsTrainingRecordController extends BaseController {
@Autowired
private IRsTrainingRecordService trainingRecordService;
/**
* 查询培训记录列表
*/
@SaCheckPermission("rs:training:record:list")
@GetMapping("/list")
public TableDataInfo<RsTrainingRecord> list(RsTrainingRecord record, PageQuery pageQuery) {
return trainingRecordService.selectPageRsTrainingRecordList(record, pageQuery);
}
/**
* 导出培训记录列表
*/
@SaCheckPermission("rs:training:record:export")
@Log(title = "培训记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, RsTrainingRecord record) {
List<RsTrainingRecord> list = trainingRecordService.selectRsTrainingRecordList(record);
ExcelUtil.exportExcel(list, "培训记录", RsTrainingRecord.class, response);
}
/**
* 获取培训记录详细信息
*/
@SaCheckPermission("rs:training:record:query")
@GetMapping("/{recordId}")
public R<RsTrainingRecord> getInfo(@PathVariable("recordId") Long recordId) {
return R.ok(trainingRecordService.selectRsTrainingRecordById(recordId));
}
/**
* 新增培训记录
*/
@SaCheckPermission("rs:training:record:add")
@Log(title = "培训记录", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping
public R<Void> add(@Validated @RequestBody RsTrainingRecord record) {
return toAjax(trainingRecordService.insertRsTrainingRecord(record));
}
/**
* 修改培训记录
*/
@SaCheckPermission("rs:training:record:edit")
@Log(title = "培训记录", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PutMapping
public R<Void> edit(@Validated @RequestBody RsTrainingRecord record) {
return toAjax(trainingRecordService.updateRsTrainingRecord(record));
}
/**
* 删除培训记录
*/
@SaCheckPermission("rs:training:record:remove")
@Log(title = "培训记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{recordIds}")
public R<Void> remove(@PathVariable Long[] recordIds) {
return toAjax(trainingRecordService.deleteRsTrainingRecordByIds(recordIds));
}
/**
* 查询我的培训记录列表
*/
@SaCheckPermission("rs:training:record:query")
@GetMapping("/my")
public TableDataInfo<RsTrainingRecord> listMyRecords(RsTrainingRecord record, PageQuery pageQuery) {
return trainingRecordService.selectPageMyRecords(StpUtil.getLoginIdAsLong(), record, pageQuery);
}
/**
* 更新培训记录完成状态
*/
@SaCheckPermission("rs:training:record:edit")
@Log(title = "培训记录", businessType = BusinessType.UPDATE)
@PutMapping("/status/{recordId}/{status}")
public R<Void> updateCompletionStatus(@PathVariable("recordId") Long recordId, @PathVariable("status") String status) {
return toAjax(trainingRecordService.updateCompletionStatus(recordId, status));
}
/**
* 上传培训证书
*/
@SaCheckPermission("rs:training:record:edit")
@Log(title = "培训记录", businessType = BusinessType.UPDATE)
@PutMapping("/certificate/{recordId}")
public R<Void> uploadCertificate(@PathVariable("recordId") Long recordId, @RequestBody Map<String, String> params) {
String certificatePath = params.get("certificatePath");
String certificateNumber = params.get("certificateNumber");
return toAjax(trainingRecordService.uploadCertificate(recordId, certificatePath, certificateNumber));
}
/**
* 获取培训统计数据
*/
@SaCheckPermission("rs:training:record:list")
@GetMapping("/statistics")
public R<Map<String, Object>> getStatistics() {
// 这里可以实现培训统计数据的获取逻辑
// 目前返回空数据,后续可以实现
return R.ok(Map.of());
}
}
@@ -0,0 +1,92 @@
package org.dromara.rs.controller;
import org.dromara.common.core.domain.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
/**
* SQL更新控制器
*
* @author weizzzz
* @date 2025-07-24
*/
@RestController
@RequestMapping("/rs/db")
public class SqlUpdateController {
@Autowired
private DataSource dataSource;
/**
* 执行培训模块SQL更新脚本
*/
@GetMapping("/update/training")
public R<Map<String, Object>> executeTrainingUpdateScript() {
Map<String, Object> result = new HashMap<>();
try {
// 读取SQL脚本
InputStream is = getClass().getClassLoader().getResourceAsStream("mapper/rs/training-update.sql");
if (is == null) {
result.put("success", false);
result.put("message", "SQL脚本文件不存在");
return R.ok(result);
}
// 解析SQL语句
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
// 忽略注释行
if (line.trim().startsWith("--") || line.trim().isEmpty()) {
continue;
}
sb.append(line);
// 如果是SQL语句结束
if (line.trim().endsWith(";")) {
String sql = sb.toString();
executeSql(sql);
sb = new StringBuilder();
}
}
result.put("success", true);
result.put("message", "SQL脚本执行成功");
return R.ok(result);
} catch (Exception e) {
e.printStackTrace();
result.put("success", false);
result.put("message", "执行SQL脚本出错: " + e.getMessage());
return R.fail("执行SQL脚本出错: " + e.getMessage());
}
}
/**
* 执行单条SQL语句
*/
private void executeSql(String sql) throws SQLException {
try (Connection conn = dataSource.getConnection();
Statement stmt = conn.createStatement()) {
// 忽略SQL错误(如字段已存在)
try {
stmt.execute(sql);
} catch (SQLException e) {
// 记录错误但继续执行
System.out.println("SQL执行警告: " + e.getMessage());
}
}
}
}
@@ -0,0 +1,60 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 应聘者信息对象 rs_applicant
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_applicant")
public class RsApplicant extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 应聘者ID
*/
@TableId(value = "applicant_id")
private Long applicantId;
/**
* 关联用户ID
*/
private Long userId;
/**
* 姓名
*/
private String name;
/**
* 手机号码
*/
private String phone;
/**
* 电子邮箱
*/
private String email;
/**
* 简历文件路径
*/
private String resumeFilePath;
/**
* 自我介绍
*/
private String selfIntro;
/**
* 当前状态(active-可申请 hired-已录用 rejected-已拒绝)
*/
private String currentStatus;
}
@@ -0,0 +1,146 @@
package org.dromara.rs.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.ExcelIgnore;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Map;
/**
* 应聘申请记录对象 rs_application
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_application")
public class RsApplication extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 申请ID
*/
@ExcelProperty(value = "申请ID")
@TableId(value = "application_id", type = IdType.AUTO)
private Long applicationId;
/**
* 职位ID
*/
@ExcelProperty(value = "职位ID")
private Long jobId;
/**
* 应聘者ID
*/
private Long applicantId;
/**
* 申请时间
*/
@ExcelProperty(value = "申请时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTime;
/**
* 状态(pending-待处理 interviewed-已面试 offered-已通过 eliminated-已淘汰 rejected-已拒绝)
*/
@ExcelProperty(value = "申请状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "pending=待处理,interviewed=已面试,offered=已通过,eliminated=已淘汰,rejected=已拒绝")
private String status;
/**
* 自我介绍
*/
private String selfDescription;
/**
* 附件路径
*/
private String attachmentPath;
/**
* 附件名称
*/
private String attachmentName;
/**
* 职位名称
*/
@ExcelProperty(value = "职位名称")
private String jobTitle;
/**
* 应聘者姓名
*/
@ExcelProperty(value = "姓名")
private String applicantName;
/**
* 性别
*/
@ExcelProperty(value = "性别")
private String gender;
/**
* 联系电话
*/
@ExcelProperty(value = "联系电话")
private String contactPhone;
/**
* 联系邮箱
*/
@ExcelProperty(value = "邮箱地址")
private String contactEmail;
/**
* 年龄
*/
@ExcelProperty(value = "年龄")
private Integer age;
/**
* 学历
*/
@ExcelProperty(value = "学历")
private String education;
/**
* 教育背景详情
*/
@ExcelProperty(value = "教育背景")
private String educationBackground;
/**
* 工作经验
*/
@ExcelProperty(value = "工作经验")
private String experience;
/**
* HR备注
*/
@ExcelProperty(value = "HR备注")
private String hrNotes;
/**
* 请求参数 - 重写父类字段以添加Excel忽略注解
*/
@ExcelIgnore
private Map<String, Object> params;
/**
* 搜索值 - 重写父类字段以添加Excel忽略注解
*/
@ExcelIgnore
private String searchValue;
}
@@ -0,0 +1,59 @@
package org.dromara.rs.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 应聘者教育经历对象 rs_education
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_education")
public class RsEducation extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 教育经历ID
*/
@TableId(value = "education_id")
private Long educationId;
/**
* 申请ID
*/
private Long applicationId;
/**
* 学校名称
*/
private String schoolName;
/**
* 专业
*/
private String major;
/**
* 学位
*/
private String degree;
/**
* 开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate;
/**
* 结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate;
}
@@ -0,0 +1,113 @@
package org.dromara.rs.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.ExcelIgnore;
import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.excel.convert.ExcelDictConvert;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Map;
/**
* 招聘职位对象 rs_job
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_job")
public class RsJob extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 职位ID
*/
@ExcelProperty(value = "职位ID")
@TableId(value = "job_id")
private Long jobId;
/**
* 职位名称
*/
@ExcelProperty(value = "职位名称")
private String title;
/**
* 部门
*/
@ExcelProperty(value = "部门")
private String department;
/**
* 薪资范围
*/
@ExcelProperty(value = "薪资范围")
private String salaryRange;
/**
* 工作地点
*/
@ExcelProperty(value = "工作地点")
private String location;
/**
* 职位描述
*/
@ExcelProperty(value = "职位描述")
private String description;
/**
* 任职要求
*/
@ExcelProperty(value = "任职要求")
private String requirements;
/**
* 是否启用(0正常 1停用)
*/
@ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
@ExcelDictFormat(readConverterExp = "0=正常,1=停用")
private String isActive;
/**
* 发布时间
*/
@ExcelProperty(value = "发布时间")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date publishTime;
/**
* 浏览次数
*/
@ExcelProperty(value = "浏览次数")
@TableField(value = "views")
private Integer viewCount;
/**
* 租户ID
*/
private String tenantId;
/**
* 备注
*/
private String remark;
/**
* 请求参数 - 重写父类字段以添加Excel忽略注解
*/
@ExcelIgnore
private Map<String, Object> params;
/**
* 搜索值 - 重写父类字段以添加Excel忽略注解
*/
@ExcelIgnore
private String searchValue;
}
@@ -0,0 +1,207 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.ExcelIgnore;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
/**
* 出国申请对象 rs_overseas_application
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_overseas_application")
public class RsOverseasApplication extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 申请ID
*/
@TableId(value = "application_id", type = IdType.AUTO)
@ExcelProperty(value = "申请ID")
private Long applicationId;
/**
* 申请人ID
*/
@ExcelProperty(value = "申请人ID")
private Long applicantId;
/**
* 申请人姓名
*/
@ExcelProperty(value = "申请人姓名")
private String applicantName;
/**
* 所属部门
*/
@ExcelProperty(value = "所属部门")
private String department;
/**
* 联系电话
*/
@ExcelProperty(value = "联系电话")
private String contactPhone;
/**
* 出国目的
*/
@ExcelProperty(value = "出国目的")
private String purpose;
/**
* 目的详细说明
*/
@ExcelProperty(value = "目的详细说明")
private String purposeDetail;
/**
* 目的地国家
*/
@ExcelProperty(value = "目的地国家")
private String destination;
/**
* 目的地
*/
@ExcelProperty(value = "目的地")
private String destinationCity;
/**
* 开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@ExcelProperty(value = "开始日期")
private Date startDate;
/**
* 结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@ExcelProperty(value = "结束日期")
private Date endDate;
/**
* 出国天数
*/
@ExcelProperty(value = "出国天数")
private Integer duration;
/**
* 预算金额
*/
@ExcelProperty(value = "预算金额")
private BigDecimal budget;
/**
* 邀请方信息
*/
@ExcelProperty(value = "邀请方信息")
private String invitation;
/**
* 行程安排
*/
@ExcelProperty(value = "行程安排")
private String itinerary;
/**
* 住宿安排
*/
@ExcelProperty(value = "住宿安排")
private String accommodationPlan;
/**
* 交通安排
*/
@ExcelProperty(value = "交通安排")
private String transportationPlan;
/**
* 申请状态:0-待审核,1-已通过,2-已拒绝,3-已取消,4-已完成
*/
@ExcelProperty(value = "申请状态")
private String status;
/**
* 当前审批人
*/
@ExcelProperty(value = "当前审批人")
private String currentApprover;
/**
* 审批级别:1-部门主管,2-分管领导,3-校领导
*/
@ExcelProperty(value = "审批级别")
private String approvalLevel;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "申请时间")
private Date applyTime;
/**
* 审批时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "审批时间")
private Date approvalTime;
/**
* 管理员备注
*/
@ExcelProperty(value = "管理员备注")
private String adminRemark;
/**
* 申请人备注
*/
@ExcelProperty(value = "申请人备注")
private String applicantRemark;
/**
* 基础字段:创建者
*/
@ExcelIgnore
private Long createBy;
/**
* 基础字段:创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelIgnore
private Date createTime;
/**
* 基础字段:更新者
*/
@ExcelIgnore
private Long updateBy;
/**
* 基础字段:更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelIgnore
private Date updateTime;
}
@@ -0,0 +1,122 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.ExcelIgnore;
import java.util.Date;
import java.util.Map;
/**
* 出国审批记录对象 rs_overseas_approval
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_overseas_approval")
public class RsOverseasApproval extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 审批ID
*/
@TableId(value = "approval_id", type = IdType.AUTO)
@ExcelProperty(value = "审批ID")
private Long approvalId;
/**
* 申请ID
*/
@ExcelProperty(value = "申请ID")
private Long applicationId;
/**
* 审批人ID
*/
@ExcelProperty(value = "审批人ID")
private Long approverId;
/**
* 审批人姓名
*/
@ExcelProperty(value = "审批人姓名")
private String approverName;
/**
* 审批级别:1-部门主管,2-分管领导,3-校领导
*/
@ExcelProperty(value = "审批级别")
private String approvalLevel;
/**
* 审批结果:0-待审核,1-通过,2-拒绝
*/
@ExcelProperty(value = "审批结果")
private String approvalStatus;
/**
* 审批意见
*/
@ExcelProperty(value = "审批意见")
private String approvalOpinion;
/**
* 审批时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "审批时间")
private Date approvalTime;
/**
* 租户编号
*/
@TableField(fill = FieldFill.INSERT)
@ExcelIgnore
private String tenantId;
/**
* 删除标志:0-存在,1-删除
*/
@TableField(fill = FieldFill.INSERT)
@ExcelIgnore
private String deleted;
/**
* 基础字段:创建者
*/
@ExcelIgnore
private Long createBy;
/**
* 基础字段:创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelIgnore
private Date createTime;
/**
* 基础字段:更新者
*/
@ExcelIgnore
private Long updateBy;
/**
* 基础字段:更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelIgnore
private Date updateTime;
}
@@ -0,0 +1,166 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.ExcelIgnore;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;
/**
* 出国记录对象 rs_overseas_record
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_overseas_record")
public class RsOverseasRecord extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
@TableId(value = "record_id", type = IdType.AUTO)
@ExcelProperty(value = "记录ID")
private Long recordId;
/**
* 关联申请ID
*/
@ExcelProperty(value = "关联申请ID")
private Long applicationId;
/**
* 申请人ID
*/
@ExcelProperty(value = "申请人ID")
private Long applicantId;
/**
* 申请人姓名
*/
@ExcelProperty(value = "申请人姓名")
private String applicantName;
/**
* 所属部门
*/
@ExcelProperty(value = "所属部门")
private String department;
/**
* 联系电话
*/
@ExcelProperty(value = "联系电话")
private String contactPhone;
/**
* 出国目的
*/
@ExcelProperty(value = "出国目的")
private String purpose;
/**
* 目的详细说明
*/
@ExcelProperty(value = "目的详细说明")
private String purposeDetail;
/**
* 目的地国家
*/
@ExcelProperty(value = "目的地国家")
private String destination;
/**
* 目的地
*/
@ExcelProperty(value = "目的地")
private String destinationCity;
/**
* 实际开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@ExcelProperty(value = "实际开始日期")
private Date actualStartDate;
/**
* 实际结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@ExcelProperty(value = "实际结束日期")
private Date actualEndDate;
/**
* 实际出国天数
*/
@ExcelProperty(value = "实际出国天数")
private Integer actualDuration;
/**
* 实际花费金额
*/
@ExcelProperty(value = "实际花费金额")
private BigDecimal actualBudget;
/**
* 邀请方信息
*/
@ExcelProperty(value = "邀请方信息")
private String invitation;
/**
* 行程安排
*/
@ExcelProperty(value = "行程安排")
private String itinerary;
/**
* 住宿安排
*/
@ExcelProperty(value = "住宿安排")
private String accommodationPlan;
/**
* 交通安排
*/
@ExcelProperty(value = "交通安排")
private String transportationPlan;
/**
* 完成状态:1-已完成,2-已拒绝
*/
@ExcelProperty(value = "完成状态")
private String completionStatus;
/**
* 出国成果
*/
@ExcelProperty(value = "出国成果")
private String achievement;
/**
* 出国报告
*/
@ExcelProperty(value = "出国报告")
private String reportContent;
/**
* 证明材料路径
*/
@ExcelProperty(value = "证明材料路径")
private String certificatePath;
}
@@ -0,0 +1,120 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 培训申请对象 rs_training_application
*
* @author weizzzz
* @date 2025-07-16
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_training_application")
public class RsTrainingApplication extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 申请ID
*/
@TableId(value = "application_id")
private Long applicationId;
/**
* 课程ID
*/
private Long courseId;
/**
* 申请人ID
*/
private Long userId;
/**
* 申请人姓名
*/
private String userName;
/**
* 所属部门
*/
private String department;
/**
* 职位
*/
private String position;
/**
* 申请理由
*/
private String reason;
/**
* 状态(0待审核 1已通过 2已拒绝 3进行中 4已完成)
*/
private String status;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTime;
/**
* 当前审批人
*/
private String currentApprover;
/**
* 培训地点
*/
private String location;
/**
* 学时
*/
private Integer duration;
/**
* 开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/**
* 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/**
* 租户ID
*/
private String tenantId;
/**
* 创建部门
*/
private Long createDept;
/**
* 自定义课程名称
*/
private String courseName;
/**
* 拒绝时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date rejectTime;
}
@@ -0,0 +1,79 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 培训审批对象 rs_training_approval
*
* @author weizzzz
* @date 2025-07-16
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_training_approval")
public class RsTrainingApproval extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 审批ID
*/
@TableId(value = "approval_id")
private Long approvalId;
/**
* 申请ID
*/
private Long applicationId;
/**
* 审批人ID
*/
private Long approverId;
/**
* 审批人姓名
*/
private String approverName;
/**
* 审批级别(1部门主管 2分管领导 3校领导)
*/
private Integer approvalLevel;
/**
* 状态(0待审核 1已通过 2已拒绝)
*/
private String status;
/**
* 审批意见
*/
private String opinion;
/**
* 审批时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTime;
/**
* 租户ID
*/
private String tenantId;
/** 非数据库字段 **/
@TableField(exist = false)
private String applicationUserName;
@TableField(exist = false)
private String courseTitle;
}
@@ -0,0 +1,92 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 培训课程对象 rs_training_course
*
* @author weizzzz
* @date 2025-07-16
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_training_course")
public class RsTrainingCourse extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 课程ID
*/
@TableId(value = "course_id")
private Long courseId;
/**
* 课程名称
*/
private String title;
/**
* 课程类别
*/
private String category;
/**
* 课程描述
*/
private String description;
/**
* 开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
/**
* 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/**
* 学时数
*/
private Integer duration;
/**
* 培训地点
*/
private String location;
/**
* 最大参与人数
*/
private Integer maxParticipants;
/**
* 当前报名人数
*/
private Integer currentParticipants;
/**
* 状态(0正常 1结束)
*/
private String status;
/**
* 租户ID
*/
private String tenantId;
/** 非数据库字段 **/
@TableField(exist = false)
private Integer applicationCount;
}
@@ -0,0 +1,89 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
* 培训记录对象 rs_training_record
*
* @author weizzzz
* @date 2025-07-16
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_training_record")
public class RsTrainingRecord extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
@TableId(value = "record_id")
private Long recordId;
private Long applicationId;
private Long courseId;
private Long userId;
private String userName;
private String department;
private String position;
private String reason;
private String status;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTime;
private String currentApprover;
private String location;
private Integer duration;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date startTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
/**
* 完成状态:0=进行中,1=已完成,2=未完成
*/
private String completionStatus;
/**
* 完成时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date completionTime;
/**
* 成绩/分数
*/
private BigDecimal score;
/**
* 证书路径
*/
private String certificatePath;
/**
* 证书编号
*/
private String certificateNumber;
/**
* 反馈意见
*/
private String feedback;
private String tenantId;
private Long createDept;
private String courseName;
}
@@ -0,0 +1,59 @@
package org.dromara.rs.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 应聘者工作经历对象 rs_work_experience
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_work_experience")
public class RsWorkExperience extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 工作经历ID
*/
@TableId(value = "experience_id")
private Long experienceId;
/**
* 申请ID
*/
private Long applicationId;
/**
* 公司名称
*/
private String companyName;
/**
* 职位
*/
private String position;
/**
* 开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate;
/**
* 结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate;
/**
* 工作描述
*/
private String jobDescription;
}
@@ -0,0 +1,71 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsApplicant;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 应聘者信息业务对象 rs_applicant
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsApplicantBo extends RsApplicant {
private static final long serialVersionUID = 1L;
/**
* 应聘者ID
*/
@NotNull(message = "应聘者ID不能为空", groups = { EditGroup.class })
private Long applicantId;
/**
* 姓名
*/
@NotBlank(message = "姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String name;
/**
* 手机号码
*/
@NotBlank(message = "手机号码不能为空", groups = { AddGroup.class, EditGroup.class })
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码格式不正确", groups = { AddGroup.class, EditGroup.class })
private String phone;
/**
* 电子邮箱
*/
@NotBlank(message = "电子邮箱不能为空", groups = { AddGroup.class, EditGroup.class })
@Email(message = "电子邮箱格式不正确", groups = { AddGroup.class, EditGroup.class })
private String email;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,116 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsApplication;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 应聘申请记录业务对象 rs_application
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsApplicationBo extends RsApplication {
private static final long serialVersionUID = 1L;
/**
* 申请ID
*/
@NotNull(message = "申请ID不能为空", groups = { EditGroup.class })
private Long applicationId;
/**
* 职位ID
*/
@NotNull(message = "职位ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long jobId;
/**
* 应聘者ID
*/
@NotNull(message = "应聘者ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicantId;
/**
* 申请时间
*/
private Date applyTime;
/**
* 状态(pending-待处理 interviewed-已面试 offered-已录用 rejected-已拒绝)
*/
private String status;
/**
* 简历路径
*/
private String resumePath;
/**
* 自我介绍
*/
private String selfDescription;
/**
* 职位名称
*/
private String jobTitle;
/**
* 应聘者姓名
*/
private String applicantName;
/**
* 联系电话
*/
private String contactPhone;
/**
* 联系邮箱
*/
private String contactEmail;
/**
* 教育背景
*/
private String education;
/**
* 工作经验
*/
private String experience;
/**
* HR备注
*/
private String hrNotes;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,73 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsEducation;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 应聘者教育经历业务对象 rs_education
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsEducationBo extends RsEducation {
private static final long serialVersionUID = 1L;
/**
* 教育经历ID
*/
@NotNull(message = "教育经历ID不能为空", groups = { EditGroup.class })
private Long educationId;
/**
* 应聘者ID
*/
@NotNull(message = "应聘者ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicantId;
/**
* 学校名称
*/
@NotBlank(message = "学校名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String schoolName;
/**
* 专业
*/
@NotBlank(message = "专业不能为空", groups = { AddGroup.class, EditGroup.class })
private String major;
/**
* 学位
*/
@NotBlank(message = "学位不能为空", groups = { AddGroup.class, EditGroup.class })
private String degree;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,61 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsJob;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 招聘职位业务对象 rs_job
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsJobBo extends RsJob {
private static final long serialVersionUID = 1L;
/**
* 职位ID
*/
@NotNull(message = "职位ID不能为空", groups = { EditGroup.class })
private Long jobId;
/**
* 职位名称
*/
@NotBlank(message = "职位名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String title;
/**
* 部门
*/
@NotBlank(message = "部门不能为空", groups = { AddGroup.class, EditGroup.class })
private String department;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,168 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsOverseasApplication;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
* 出国申请业务对象 rs_overseas_application
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = RsOverseasApplication.class, reverseConvertGenerate = false)
public class RsOverseasApplicationBo extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 申请ID
*/
@NotNull(message = "申请ID不能为空", groups = { EditGroup.class })
private Long applicationId;
/**
* 申请人ID
*/
@NotNull(message = "申请人ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicantId;
/**
* 申请人姓名
*/
private String applicantName;
/**
* 所属部门
*/
private String department;
/**
* 联系电话
*/
private String contactPhone;
/**
* 出国目的
*/
@NotBlank(message = "出国目的不能为空", groups = { AddGroup.class, EditGroup.class })
private String purpose;
/**
* 目的详细说明
*/
@NotBlank(message = "目的详细说明不能为空", groups = { AddGroup.class, EditGroup.class })
private String purposeDetail;
/**
* 目的地国家
*/
@NotBlank(message = "目的地国家不能为空", groups = { AddGroup.class, EditGroup.class })
private String destination;
/**
* 开始日期
*/
@NotNull(message = "开始日期不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate;
/**
* 结束日期
*/
@NotNull(message = "结束日期不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate;
/**
* 出国天数
*/
@Min(value = 1, message = "出国天数必须大于0", groups = { AddGroup.class, EditGroup.class })
private Integer duration;
/**
* 预算金额
*/
@NotNull(message = "预算金额不能为空", groups = { AddGroup.class, EditGroup.class })
@DecimalMin(value = "0.01", message = "预算金额必须大于0", groups = { AddGroup.class, EditGroup.class })
private BigDecimal budget;
/**
* 行程安排
*/
@NotBlank(message = "行程安排不能为空", groups = { AddGroup.class, EditGroup.class })
private String itinerary;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTime;
/**
* 审批时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTime;
/**
* 目的地
*/
private String destinationCity;
/**
* 邀请函信息
*/
private String invitation;
/**
* 住宿安排
*/
private String accommodationPlan;
/**
* 交通安排
*/
private String transportationPlan;
/**
* 申请人备注
*/
private String applicantRemark;
/**
* 管理员备注
*/
private String adminRemark;
/**
* 当前审批人
*/
private String currentApprover;
/**
* 审批级别
*/
private String approvalLevel;
/**
* 申请状态
*/
private String status;
}
@@ -0,0 +1,75 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsOverseasApproval;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 出国审批记录业务对象 rs_overseas_approval
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = RsOverseasApproval.class, reverseConvertGenerate = false)
public class RsOverseasApprovalBo extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 审批ID
*/
@NotNull(message = "审批ID不能为空", groups = { EditGroup.class })
private Long approvalId;
/**
* 申请ID
*/
@NotNull(message = "申请ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicationId;
/**
* 审批人ID
*/
@NotNull(message = "审批人ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long approverId;
/**
* 审批人姓名
*/
@NotBlank(message = "审批人姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String approverName;
/**
* 审批级别
*/
@NotBlank(message = "审批级别不能为空", groups = { AddGroup.class, EditGroup.class })
private String approvalLevel;
/**
* 审批结果
*/
@NotBlank(message = "审批结果不能为空", groups = { AddGroup.class, EditGroup.class })
private String approvalStatus;
/**
* 审批时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTime;
/**
* 审批意见
*/
private String approvalOpinion;
}
@@ -0,0 +1,149 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsOverseasRecord;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
* 出国记录业务对象 rs_overseas_record
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = RsOverseasRecord.class, reverseConvertGenerate = false)
public class RsOverseasRecordBo extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
@NotNull(message = "记录ID不能为空", groups = { EditGroup.class })
private Long recordId;
/**
* 关联申请ID
*/
@NotNull(message = "关联申请ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicationId;
/**
* 申请人ID
*/
@NotNull(message = "申请人ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicantId;
/**
* 申请人姓名
*/
@NotBlank(message = "申请人姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String applicantName;
/**
* 所属部门
*/
@NotBlank(message = "所属部门不能为空", groups = { AddGroup.class, EditGroup.class })
private String department;
/**
* 联系电话
*/
private String contactPhone;
/**
* 出国目的
*/
@NotBlank(message = "出国目的不能为空", groups = { AddGroup.class, EditGroup.class })
private String purpose;
/**
* 目的详细说明
*/
private String purposeDetail;
/**
* 目的地国家
*/
@NotBlank(message = "目的地国家不能为空", groups = { AddGroup.class, EditGroup.class })
private String destination;
/**
* 目的地
*/
private String destinationCity;
/**
* 实际开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualStartDate;
/**
* 实际结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualEndDate;
/**
* 实际出国天数
*/
private Integer actualDuration;
/**
* 实际花费金额
*/
private BigDecimal actualBudget;
/**
* 邀请方信息
*/
private String invitation;
/**
* 行程安排
*/
private String itinerary;
/**
* 住宿安排
*/
private String accommodationPlan;
/**
* 交通安排
*/
private String transportationPlan;
/**
* 完成状态
*/
@NotBlank(message = "完成状态不能为空", groups = { AddGroup.class, EditGroup.class })
private String completionStatus;
/**
* 主要成果
*/
private String achievement;
/**
* 报告内容
*/
private String reportContent;
/**
* 证书路径
*/
private String certificatePath;
}
@@ -0,0 +1,67 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsWorkExperience;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 应聘者工作经历业务对象 rs_work_experience
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsWorkExperienceBo extends RsWorkExperience {
private static final long serialVersionUID = 1L;
/**
* 工作经历ID
*/
@NotNull(message = "工作经历ID不能为空", groups = { EditGroup.class })
private Long experienceId;
/**
* 应聘者ID
*/
@NotNull(message = "应聘者ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicantId;
/**
* 公司名称
*/
@NotBlank(message = "公司名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String companyName;
/**
* 职位
*/
@NotBlank(message = "职位不能为空", groups = { AddGroup.class, EditGroup.class })
private String position;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
}
@@ -0,0 +1,99 @@
package org.dromara.rs.domain.query;
import org.dromara.common.mybatis.core.page.PageQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
/**
* 出国申请查询对象 rs_overseas_application
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsOverseasApplicationQuery extends PageQuery {
/**
* 申请人姓名
*/
private String applicantName;
/**
* 所属部门
*/
private String department;
/**
* 出国目的
*/
private String purpose;
/**
* 目的地国家
*/
private String destination;
/**
* 申请状态
*/
private String status;
/**
* 审批级别
*/
private String approvalLevel;
/**
* 当前审批人
*/
private String currentApprover;
/**
* 申请人ID
*/
private Long applicantId;
/**
* 预算金额范围 - 最小值
*/
private BigDecimal budgetMin;
/**
* 预算金额范围 - 最大值
*/
private BigDecimal budgetMax;
/**
* 申请时间范围 - 开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTimeBegin;
/**
* 申请时间范围 - 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTimeEnd;
/**
* 出国开始日期范围 - 开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDateBegin;
/**
* 出国开始日期范围 - 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDateEnd;
/**
* 租户ID
*/
private String tenantId;
}
@@ -0,0 +1,66 @@
package org.dromara.rs.domain.query;
import org.dromara.common.mybatis.core.page.PageQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 出国审批记录查询对象 rs_overseas_approval
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsOverseasApprovalQuery extends PageQuery {
/**
* 申请ID
*/
private Long applicationId;
/**
* 审批人ID
*/
private Long approverId;
/**
* 审批人姓名
*/
private String approverName;
/**
* 审批级别
*/
private String approvalLevel;
/**
* 审批结果
*/
private String approvalStatus;
/**
* 审批时间范围 - 开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTimeBegin;
/**
* 审批时间范围 - 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTimeEnd;
/**
* 申请人姓名(用于搜索)
*/
private String applicantName;
/**
* 租户ID
*/
private String tenantId;
}
@@ -0,0 +1,83 @@
package org.dromara.rs.domain.query;
import org.dromara.common.mybatis.core.page.PageQuery;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 出国记录查询对象 rs_overseas_record
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsOverseasRecordQuery extends PageQuery {
/**
* 申请人姓名
*/
private String applicantName;
/**
* 所属部门
*/
private String department;
/**
* 出国目的
*/
private String purpose;
/**
* 目的地国家
*/
private String destination;
/**
* 完成状态
*/
private String completionStatus;
/**
* 申请人ID
*/
private Long applicantId;
/**
* 关联申请ID
*/
private Long applicationId;
/**
* 实际开始日期范围 - 开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualStartDateBegin;
/**
* 实际开始日期范围 - 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualStartDateEnd;
/**
* 实际结束日期范围 - 开始时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualEndDateBegin;
/**
* 实际结束日期范围 - 结束时间
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualEndDateEnd;
/**
* 租户ID
*/
private String tenantId;
}
@@ -0,0 +1,55 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsApplicant;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import java.util.List;
/**
* 应聘者信息VO对象
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsApplicantVo extends RsApplicant {
private static final long serialVersionUID = 1L;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 教育经历列表
*/
private List<RsEducationVo> educationList;
/**
* 工作经历列表
*/
private List<RsWorkExperienceVo> experienceList;
/**
* 申请记录数量
*/
private Integer applicationCount;
}
@@ -0,0 +1,100 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsApplication;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
import java.util.List;
/**
* 应聘申请记录VO对象
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsApplicationVo extends RsApplication {
private static final long serialVersionUID = 1L;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
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();
}
/**
* 设置应聘者手机号
*/
public void setApplicantPhone(String applicantPhone) {
this.setContactPhone(applicantPhone);
this.applicantPhone = applicantPhone;
}
/**
* 获取应聘者邮箱
*/
public String getApplicantEmail() {
return this.getContactEmail();
}
/**
* 设置应聘者邮箱
*/
public void setApplicantEmail(String applicantEmail) {
this.setContactEmail(applicantEmail);
this.applicantEmail = applicantEmail;
}
}
@@ -0,0 +1,49 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsEducation;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 应聘者教育经历VO对象
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsEducationVo extends RsEducation {
private static final long serialVersionUID = 1L;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 应聘者姓名
*/
private String applicantName;
/**
* 学习时长(月)
*/
private Integer durationMonths;
}
@@ -0,0 +1,45 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsJob;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 招聘职位VO对象
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsJobVo extends RsJob {
private static final long serialVersionUID = 1L;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 申请人数
*/
private Integer applicantCount;
}
@@ -0,0 +1,201 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsOverseasApplication;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 出国申请VO对象 rs_overseas_application
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@AutoMapper(target = RsOverseasApplication.class)
public class RsOverseasApplicationVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 申请ID
*/
private Long applicationId;
/**
* 申请人ID
*/
private Long applicantId;
/**
* 申请人姓名
*/
private String applicantName;
/**
* 所属部门
*/
private String department;
/**
* 联系电话
*/
private String contactPhone;
/**
* 出国目的
*/
private String purpose;
/**
* 出国目的文本
*/
private String purposeText;
/**
* 目的详细说明
*/
private String purposeDetail;
/**
* 目的地国家
*/
private String destination;
/**
* 目的地
*/
private String destinationCity;
/**
* 开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate;
/**
* 结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate;
/**
* 出国天数
*/
private Integer duration;
/**
* 预算金额
*/
private BigDecimal budget;
/**
* 邀请函信息
*/
private String invitation;
/**
* 行程安排
*/
private String itinerary;
/**
* 住宿安排
*/
private String accommodationPlan;
/**
* 交通安排
*/
private String transportationPlan;
/**
* 申请状态
*/
private String status;
/**
* 申请状态文本
*/
private String statusText;
/**
* 当前审批人
*/
private String currentApprover;
/**
* 审批级别
*/
private String approvalLevel;
/**
* 审批级别文本
*/
private String approvalLevelText;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applyTime;
/**
* 审批时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTime;
/**
* 申请人备注
*/
private String applicantRemark;
/**
* 管理员备注
*/
private String adminRemark;
/**
* 租户编号
*/
private String tenantId;
/**
* 删除标志
*/
private String deleted;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 获取经费来源文本
*/
}
@@ -0,0 +1,161 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsOverseasApproval;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 出国审批记录VO对象 rs_overseas_approval
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@AutoMapper(target = RsOverseasApproval.class)
public class RsOverseasApprovalVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 审批ID
*/
private Long approvalId;
/**
* 关联申请ID
*/
private Long applicationId;
/**
* 审批人ID
*/
private Long approverId;
/**
* 审批人姓名
*/
private String approverName;
/**
* 审批级别
*/
private String approvalLevel;
/**
* 审批级别文本
*/
private String approvalLevelText;
/**
* 审批状态
*/
private String approvalStatus;
/**
* 审批状态文本
*/
private String approvalStatusText;
/**
* 审批意见
*/
private String approvalOpinion;
/**
* 审批时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTime;
// ==================== 申请信息相关字段 ====================
/**
* 申请人ID
*/
private Long applicantId;
/**
* 申请人姓名
*/
private String applicantName;
/**
* 所属部门
*/
private String department;
/**
* 出国目的
*/
private String purpose;
/**
* 出国目的文本
*/
private String purposeText;
/**
* 目的地
*/
private String destination;
/**
* 出国开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date startDate;
/**
* 出国结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate;
/**
* 出国天数
*/
private Integer duration;
/**
* 记录类型 (pending-待审批, completed-已审批)
*/
private String recordType;
/**
* 租户编号
*/
private String tenantId;
/**
* 删除标志
*/
private String deleted;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,254 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsOverseasRecord;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 出国记录VO对象 rs_overseas_record
*
* @author weizzzz
* @date 2025-07-28
*/
@Data
@AutoMapper(target = RsOverseasRecord.class)
public class RsOverseasRecordVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 记录ID
*/
private Long recordId;
/**
* 关联申请ID
*/
private Long applicationId;
/**
* 申请人ID
*/
private Long applicantId;
/**
* 申请人姓名
*/
private String applicantName;
/**
* 所属部门
*/
private String department;
/**
* 联系电话
*/
private String contactPhone;
/**
* 出国目的
*/
private String purpose;
/**
* 出国目的文本
*/
private String purposeText;
/**
* 目的详细说明
*/
private String purposeDetail;
/**
* 目的地国家
*/
private String destination;
/**
* 目的地
*/
private String destinationCity;
/**
* 实际开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualStartDate;
/**
* 实际结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date actualEndDate;
/**
* 实际天数
*/
private Integer actualDuration;
/**
* 实际花费
*/
private BigDecimal actualBudget;
/**
* 邀请方信息
*/
private String invitation;
/**
* 行程安排
*/
private String itinerary;
/**
* 住宿安排
*/
private String accommodationPlan;
/**
* 交通安排
*/
private String transportationPlan;
/**
* 完成状态
*/
private String completionStatus;
/**
* 完成状态文本
*/
private String completionStatusText;
/**
* 主要成果
*/
private String achievement;
/**
* 报告内容
*/
private String reportContent;
/**
* 证书路径
*/
private String certificatePath;
/**
* 租户编号
*/
private String tenantId;
/**
* 创建部门
*/
private Long createDept;
/**
* 删除标志
*/
private String deleted;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 获取出国目的文本
*/
public String getPurposeText() {
if (this.purposeText != null && !this.purposeText.isEmpty()) {
return this.purposeText;
}
if (this.purpose == null) {
return "";
}
switch (this.purpose) {
case "academic_exchange":
return "学术交流";
case "conference":
return "学术会议";
case "training":
return "培训学习";
case "cooperation":
return "合作项目";
case "research":
return "科研访问";
case "other":
return "其他";
default:
return this.purpose;
}
}
/**
* 数据来源标识
*/
private String dataSource;
/**
* 获取完成状态文本
*/
public String getCompletionStatusText() {
if (this.completionStatusText != null && !this.completionStatusText.isEmpty()) {
return this.completionStatusText;
}
if (this.completionStatus == null) {
return "未知状态";
}
switch (this.completionStatus) {
case "1":
return "已完成";
case "2":
return "已拒绝";
default:
return "未知状态";
}
}
/**
* 获取数据来源说明
*/
public String getDataSourceText() {
if (this.dataSource == null) {
return "出国记录";
}
switch (this.dataSource) {
case "record":
return "出国记录";
case "approved_application":
return "已通过申请";
default:
return "出国记录";
}
}
}
@@ -0,0 +1,49 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsWorkExperience;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 应聘者工作经历VO对象
*
* @author ruoyi
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsWorkExperienceVo extends RsWorkExperience {
private static final long serialVersionUID = 1L;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
private Date updateTime;
/**
* 应聘者姓名
*/
private String applicantName;
/**
* 工作时长(月)
*/
private Integer durationMonths;
}
@@ -0,0 +1,37 @@
package org.dromara.rs.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.rs.domain.RsApplicant;
import org.apache.ibatis.annotations.Param;
/**
* 应聘者信息Mapper接口
*
* @author ruoyi
*/
public interface RsApplicantMapper extends BaseMapperPlus<RsApplicant, RsApplicant> {
/**
* 查询应聘者信息
*
* @param applicantId 应聘者ID
* @return 应聘者信息
*/
RsApplicant selectRsApplicantById(Long applicantId);
/**
* 通过用户ID查询应聘者信息
*
* @param userId 用户ID
* @return 应聘者信息
*/
RsApplicant selectRsApplicantByUserId(Long userId);
/**
* 通过手机号查询应聘者信息
*
* @param phone 手机号
* @return 应聘者信息
*/
RsApplicant selectRsApplicantByPhone(@Param("phone") String phone);
}
@@ -0,0 +1,88 @@
package org.dromara.rs.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.rs.domain.RsApplication;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 申请记录Mapper接口
*
* @author ruoyi
*/
public interface RsApplicationMapper extends BaseMapperPlus<RsApplication, RsApplication> {
/**
* 查询申请记录列表
*
* @param application 申请记录
* @return 申请记录集合
*/
List<RsApplication> selectRsApplicationList(RsApplication application);
/**
* 查询申请记录详细信息
*
* @param applicationId 申请记录ID
* @return 申请记录
*/
RsApplication selectRsApplicationById(Long applicationId);
/**
* 查询职位申请人数
*
* @param jobId 职位ID
* @return 申请人数
*/
int selectCountByJobId(@Param("jobId") Long jobId);
/**
* 查询申请记录
*
* @param jobId 职位ID
* @param applicantId 应聘者ID
* @return 申请记录
*/
RsApplication selectByJobIdAndApplicantId(@Param("jobId") Long jobId, @Param("applicantId") Long applicantId);
/**
* 查询应聘者的所有申请
*
* @param applicantId 应聘者ID
* @return 申请记录集合
*/
List<RsApplication> selectByApplicantId(@Param("applicantId") Long applicantId);
/**
* 新增申请记录
*
* @param application 申请记录
* @return 结果
*/
int insertRsApplication(RsApplication application);
/**
* 修改申请记录
*
* @param application 申请记录
* @return 结果
*/
int updateRsApplication(RsApplication application);
/**
* 删除申请记录
*
* @param applicationId 申请记录主键
* @return 结果
*/
int deleteRsApplicationById(Long applicationId);
/**
* 批量删除申请记录
*
* @param applicationIds 需要删除的数据主键集合
* @return 结果
*/
int deleteRsApplicationByIds(Long[] applicationIds);
}
@@ -0,0 +1,69 @@
package org.dromara.rs.mapper;
import java.util.List;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.rs.domain.RsJob;
import org.dromara.rs.domain.vo.RsJobVo;
/**
* 招聘职位Mapper接口
*
* @author ruoyi
*/
public interface RsJobMapper extends BaseMapperPlus<RsJob, RsJob> {
/**
* 查询招聘职位列表
*
* @param rsJob 招聘职位
* @return 招聘职位集合
*/
List<RsJobVo> selectRsJobList(RsJob rsJob);
/**
* 查询招聘职位详细信息
*
* @param jobId 招聘职位主键
* @return 招聘职位
*/
RsJobVo selectRsJobById(Long jobId);
/**
* 新增招聘职位
*
* @param rsJob 招聘职位
* @return 结果
*/
int insertRsJob(RsJob rsJob);
/**
* 修改招聘职位
*
* @param rsJob 招聘职位
* @return 结果
*/
int updateRsJob(RsJob rsJob);
/**
* 删除招聘职位
*
* @param jobId 招聘职位主键
* @return 结果
*/
int deleteRsJobById(Long jobId);
/**
* 批量删除招聘职位
*
* @param jobIds 需要删除的数据主键集合
* @return 结果
*/
int deleteRsJobByIds(Long[] jobIds);
/**
* 增加浏览次数
*
* @param jobId 招聘职位ID
* @return 结果
*/
int incrementViewCount(Long jobId);
}
@@ -0,0 +1,72 @@
package org.dromara.rs.mapper;
import java.util.List;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.rs.domain.RsOverseasApplication;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.query.RsOverseasApplicationQuery;
/**
* 出国申请Mapper接口
*
* @author weizzzz
* @date 2025-07-28
*/
public interface RsOverseasApplicationMapper extends BaseMapperPlus<RsOverseasApplication, RsOverseasApplicationVo> {
/**
* 查询出国申请列表
*
* @param query 查询条件
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> selectOverseasApplicationList(RsOverseasApplicationQuery query);
/**
* 查询我的出国申请列表(排除已通过的申请)
*
* @param query 查询条件
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> selectMyOverseasApplicationList(RsOverseasApplicationQuery query);
/**
* 查询出国申请详细信息
*
* @param applicationId 出国申请主键
* @return 出国申请
*/
RsOverseasApplicationVo selectOverseasApplicationById(Long applicationId);
/**
* 根据申请人ID查询出国申请列表
*
* @param applicantId 申请人ID
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> selectOverseasApplicationsByApplicantId(Long applicantId);
/**
* 根据状态查询出国申请列表
*
* @param status 申请状态
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> selectOverseasApplicationsByStatus(String status);
/**
* 根据当前审批人查询待审批申请列表
*
* @param currentApprover 当前审批人
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> selectPendingApplicationsByApprover(String currentApprover);
/**
* 根据审批级别查询待审批申请列表
*
* @param approvalLevel 审批级别
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> selectPendingApplicationsByLevel(String approvalLevel);
}
@@ -0,0 +1,56 @@
package org.dromara.rs.mapper;
import java.util.List;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.rs.domain.RsOverseasApproval;
import org.dromara.rs.domain.vo.RsOverseasApprovalVo;
import org.dromara.rs.domain.query.RsOverseasApprovalQuery;
/**
* 出国审批记录Mapper接口
*
* @author weizzzz
* @date 2025-07-28
*/
public interface RsOverseasApprovalMapper extends BaseMapperPlus<RsOverseasApproval, RsOverseasApprovalVo> {
/**
* 查询出国审批记录列表
*
* @param query 查询条件
* @return 出国审批记录集合
*/
List<RsOverseasApprovalVo> selectOverseasApprovalList(RsOverseasApprovalQuery query);
/**
* 查询出国审批记录详细信息
*
* @param approvalId 出国审批记录主键
* @return 出国审批记录
*/
RsOverseasApprovalVo selectOverseasApprovalById(Long approvalId);
/**
* 根据申请ID查询审批记录列表
*
* @param applicationId 申请ID
* @return 出国审批记录集合
*/
List<RsOverseasApprovalVo> selectOverseasApprovalsByApplicationId(Long applicationId);
/**
* 根据审批人ID查询审批记录列表
*
* @param approverId 审批人ID
* @return 出国审批记录集合
*/
List<RsOverseasApprovalVo> selectOverseasApprovalsByApproverId(Long approverId);
/**
* 联合查询:待审批申请 + 审批记录
*
* @param query 查询条件
* @return 出国审批记录集合(包含待审批和已审批)
*/
List<RsOverseasApprovalVo> selectCombinedApprovalList(RsOverseasApprovalQuery query);
}
@@ -0,0 +1,56 @@
package org.dromara.rs.mapper;
import java.util.List;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.rs.domain.RsOverseasRecord;
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
import org.dromara.rs.domain.query.RsOverseasRecordQuery;
/**
* 出国记录Mapper接口
*
* @author weizzzz
* @date 2025-07-28
*/
public interface RsOverseasRecordMapper extends BaseMapperPlus<RsOverseasRecord, RsOverseasRecordVo> {
/**
* 查询出国记录列表
*
* @param query 查询条件
* @return 出国记录集合
*/
List<RsOverseasRecordVo> selectOverseasRecordList(RsOverseasRecordQuery query);
/**
* 查询出国记录详细信息
*
* @param recordId 出国记录主键
* @return 出国记录
*/
RsOverseasRecordVo selectOverseasRecordById(Long recordId);
/**
* 根据申请ID查询出国记录
*
* @param applicationId 申请ID
* @return 出国记录
*/
RsOverseasRecordVo selectOverseasRecordByApplicationId(Long applicationId);
/**
* 根据申请人ID查询出国记录列表
*
* @param applicantId 申请人ID
* @return 出国记录集合
*/
List<RsOverseasRecordVo> selectOverseasRecordsByApplicantId(Long applicantId);
/**
* 查询我的出国记录列表(联合查询:记录+已通过申请)
*
* @param query 查询条件
* @return 出国记录集合
*/
List<RsOverseasRecordVo> selectMyOverseasRecordList(RsOverseasRecordQuery query);
}
@@ -0,0 +1,42 @@
package org.dromara.rs.mapper;
import org.dromara.rs.domain.RsTrainingApplication;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 培训申请Mapper接口
*
* @author weizzzz
* @date 2025-07-16
*/
public interface RsTrainingApplicationMapper extends BaseMapperPlus<RsTrainingApplication, RsTrainingApplication> {
/**
* 查询培训申请列表
*
* @param application 培训申请
* @return 培训申请集合
*/
List<RsTrainingApplication> selectRsTrainingApplicationList(RsTrainingApplication application);
/**
* 查询培训申请列表(分页)
*
* @param app 培训申请
* @param offset 偏移量
* @param limit 限制数量
* @return 培训申请集合
*/
List<RsTrainingApplication> selectRsTrainingApplicationListPage(@Param("app") RsTrainingApplication app, @Param("offset") long offset, @Param("limit") long limit);
/**
* 查询培训申请详细信息
*
* @param applicationId 培训申请ID
* @return 培训申请
*/
RsTrainingApplication selectRsTrainingApplicationById(Long applicationId);
}
@@ -0,0 +1,23 @@
package org.dromara.rs.mapper;
import org.dromara.rs.domain.RsTrainingApproval;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import java.util.List;
/**
* 培训审批Mapper接口
*
* @author weizzzz
* @date 2025-07-16
*/
public interface RsTrainingApprovalMapper extends BaseMapperPlus<RsTrainingApproval, RsTrainingApproval> {
/**
* 查询带详细信息的审批记录列表(关联申请人和课程信息)
*
* @param approval 查询条件
* @return 审批记录列表
*/
List<RsTrainingApproval> selectRsTrainingApprovalWithDetailsList(RsTrainingApproval approval);
}
@@ -0,0 +1,14 @@
package org.dromara.rs.mapper;
import org.dromara.rs.domain.RsTrainingCourse;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 培训课程Mapper接口
*
* @author weizzzz
* @date 2025-07-16
*/
public interface RsTrainingCourseMapper extends BaseMapperPlus<RsTrainingCourse, RsTrainingCourse> {
}
@@ -0,0 +1,14 @@
package org.dromara.rs.mapper;
import org.dromara.rs.domain.RsTrainingRecord;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
/**
* 培训记录Mapper接口
*
* @author weizzzz
* @date 2025-07-16
*/
public interface RsTrainingRecordMapper extends BaseMapperPlus<RsTrainingRecord, RsTrainingRecord> {
}
@@ -0,0 +1,94 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.rs.domain.RsApplication;
/**
* 申请记录Service接口
*
* @author ruoyi
*/
public interface IRsApplicationService {
/**
* 查询申请记录列表
*
* @param application 申请记录
* @return 申请记录集合
*/
List<RsApplication> selectRsApplicationList(RsApplication application);
/**
* 查询申请记录详细信息
*
* @param applicationId 申请记录主键
* @return 申请记录
*/
RsApplication selectRsApplicationById(Long applicationId);
/**
* 新增申请记录
*
* @param application 申请记录
* @return 结果
*/
int insertRsApplication(RsApplication application);
/**
* 修改申请记录
*
* @param application 申请记录
* @return 结果
*/
int updateRsApplication(RsApplication application);
/**
* 批量删除申请记录
*
* @param applicationIds 需要删除的申请记录主键集合
* @return 结果
*/
int deleteRsApplicationByIds(Long[] applicationIds);
/**
* 删除申请记录信息
*
* @param applicationId 申请记录主键
* @return 结果
*/
int deleteRsApplicationById(Long applicationId);
/**
* 更新申请状态
*
* @param applicationId 申请记录ID
* @param status 状态
* @param hrNotes HR备注
* @return 结果
*/
int updateApplicationStatus(Long applicationId, String status, String hrNotes);
/**
* 检查是否已申请
*
* @param jobId 职位ID
* @param applicantId 应聘者ID
* @return 申请记录
*/
RsApplication checkApplied(Long jobId, Long applicantId);
/**
* 查询应聘者的所有申请
*
* @param applicantId 应聘者ID
* @return 申请记录集合
*/
List<RsApplication> selectApplicationsByApplicant(Long applicantId);
/**
* 为申请记录设置职位标题
*
* @param application 申请记录
*/
void setJobTitleForApplication(RsApplication application);
}
@@ -0,0 +1,77 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.rs.domain.RsJob;
import org.dromara.rs.domain.vo.RsJobVo;
/**
* 招聘职位Service接口
*
* @author ruoyi
*/
public interface IRsJobService {
/**
* 查询招聘职位列表
*
* @param rsJob 招聘职位
* @return 招聘职位集合
*/
List<RsJobVo> selectRsJobList(RsJob rsJob);
/**
* 查询招聘职位详细信息
*
* @param jobId 招聘职位主键
* @return 招聘职位
*/
RsJobVo selectRsJobById(Long jobId);
/**
* 增加浏览次数
*
* @param jobId 招聘职位ID
* @return 结果
*/
int incrementViewCount(Long jobId);
/**
* 新增招聘职位
*
* @param rsJob 招聘职位
* @return 结果
*/
int insertRsJob(RsJob rsJob);
/**
* 修改招聘职位
*
* @param rsJob 招聘职位
* @return 结果
*/
int updateRsJob(RsJob rsJob);
/**
* 批量删除招聘职位
*
* @param jobIds 需要删除的招聘职位主键集合
* @return 结果
*/
int deleteRsJobByIds(Long[] jobIds);
/**
* 删除招聘职位信息
*
* @param jobId 招聘职位主键
* @return 结果
*/
int deleteRsJobById(Long jobId);
/**
* 切换职位状态
*
* @param jobId 招聘职位主键
* @return 结果
*/
int toggleJobStatus(Long jobId);
}
@@ -0,0 +1,161 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.rs.domain.RsOverseasApplication;
import org.dromara.rs.domain.bo.RsOverseasApplicationBo;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.query.RsOverseasApplicationQuery;
import java.util.Collection;
/**
* 出国申请Service接口
*
* @author weizzzz
* @date 2025-07-28
*/
public interface IRsOverseasApplicationService {
/**
* 查询出国申请分页列表
*
* @param query 查询条件
* @return 出国申请分页集合
*/
TableDataInfo<RsOverseasApplicationVo> queryPageList(RsOverseasApplicationQuery query);
/**
* 查询我的出国申请分页列表(不包括已通过的申请)
*
* @param query 查询条件
* @return 出国申请分页集合
*/
TableDataInfo<RsOverseasApplicationVo> queryMyApplicationList(RsOverseasApplicationQuery query);
/**
* 查询出国申请列表
*
* @param query 查询条件
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> queryList(RsOverseasApplicationQuery query);
/**
* 查询出国申请详细信息
*
* @param applicationId 出国申请主键
* @return 出国申请
*/
RsOverseasApplicationVo queryById(Long applicationId);
/**
* 根据申请人ID查询出国申请列表
*
* @param applicantId 申请人ID
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> queryByApplicantId(Long applicantId);
/**
* 根据状态查询出国申请列表
*
* @param status 申请状态
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> queryByStatus(String status);
/**
* 根据当前审批人查询待审批申请列表
*
* @param currentApprover 当前审批人
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> queryPendingByApprover(String currentApprover);
/**
* 根据审批级别查询待审批申请列表
*
* @param approvalLevel 审批级别
* @return 出国申请集合
*/
List<RsOverseasApplicationVo> queryPendingByLevel(String approvalLevel);
/**
* 新增出国申请
*
* @param bo 出国申请业务对象
* @return 是否成功
*/
Boolean insertByBo(RsOverseasApplicationBo bo);
/**
* 修改出国申请
*
* @param bo 出国申请业务对象
* @return 是否成功
*/
Boolean updateByBo(RsOverseasApplicationBo bo);
/**
* 提交出国申请(更新状态为待审核)
*
* @param applicationId 申请ID
* @return 是否成功
*/
Boolean submitApplication(Long applicationId);
/**
* 取消出国申请
*
* @param applicationId 申请ID
* @param reason 取消原因
* @return 是否成功
*/
Boolean cancelApplication(Long applicationId, String reason);
/**
* 审批出国申请
*
* @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 主键集合
* @param isValid 是否有效性校验
*/
void validatedByIds(List<Long> ids, Boolean isValid);
/**
* 批量删除出国申请
*
* @param ids 主键集合
* @return 是否成功
*/
Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid);
/**
* 修复已审批通过但未创建记录的申请
*
* @return 修复的记录数量
*/
int fixApprovedApplicationsWithoutRecords();
/**
* 重新提交已被拒绝的申请
*
* @param applicationId 申请ID
* @param applicationData 申请更新数据
* @return 是否成功
*/
Boolean resubmitApplication(Long applicationId, RsOverseasApplicationBo applicationData);
}
@@ -0,0 +1,117 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.rs.domain.bo.RsOverseasApprovalBo;
import org.dromara.rs.domain.vo.RsOverseasApprovalVo;
import org.dromara.rs.domain.query.RsOverseasApprovalQuery;
/**
* 出国审批记录Service接口
*
* @author weizzzz
* @date 2025-07-28
*/
public interface IRsOverseasApprovalService {
/**
* 查询出国审批记录分页列表
*
* @param query 查询条件
* @return 出国审批记录分页集合
*/
TableDataInfo<RsOverseasApprovalVo> queryPageList(RsOverseasApprovalQuery query);
/**
* 查询出国审批记录列表
*
* @param query 查询条件
* @return 出国审批记录集合
*/
List<RsOverseasApprovalVo> queryList(RsOverseasApprovalQuery query);
/**
* 查询出国审批记录详细信息
*
* @param approvalId 出国审批记录主键
* @return 出国审批记录
*/
RsOverseasApprovalVo queryById(Long approvalId);
/**
* 根据申请ID查询审批记录列表
*
* @param applicationId 申请ID
* @return 出国审批记录集合
*/
List<RsOverseasApprovalVo> queryByApplicationId(Long applicationId);
/**
* 根据申请ID查询审批历史记录
*
* @param applicationId 申请ID
* @return 出国审批历史记录集合
*/
List<RsOverseasApprovalVo> selectApprovalHistoryByApplicationId(Long applicationId);
/**
* 根据审批人ID查询审批记录列表
*
* @param approverId 审批人ID
* @return 出国审批记录集合
*/
List<RsOverseasApprovalVo> queryByApproverId(Long approverId);
/**
* 新增出国审批记录
*
* @param bo 出国审批记录业务对象
* @return 是否成功
*/
Boolean insertByBo(RsOverseasApprovalBo bo);
/**
* 修改出国审批记录
*
* @param bo 出国审批记录业务对象
* @return 是否成功
*/
Boolean updateByBo(RsOverseasApprovalBo bo);
/**
* 校验删除数据
*
* @param ids 主键集合
* @param isValid 是否有效性校验
*/
void validatedByIds(List<Long> ids, Boolean isValid);
/**
* 批量删除出国审批记录
*
* @param ids 主键集合
* @return 是否成功
*/
Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid);
/**
* 修复审批记录测试数据
*/
void fixApprovalTestData();
/**
* 联合查询分页列表:待审批申请 + 审批记录
*
* @param query 查询条件
* @return 出国审批记录分页集合(包含待审批和已审批)
*/
TableDataInfo<RsOverseasApprovalVo> queryCombinedPageList(RsOverseasApprovalQuery query);
/**
* 联合查询列表:待审批申请 + 审批记录
*
* @param query 查询条件
* @return 出国审批记录集合(包含待审批和已审批)
*/
List<RsOverseasApprovalVo> queryCombinedList(RsOverseasApprovalQuery query);
}
@@ -0,0 +1,115 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.rs.domain.bo.RsOverseasRecordBo;
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
import org.dromara.rs.domain.query.RsOverseasRecordQuery;
/**
* 出国记录Service接口
*
* @author weizzzz
* @date 2025-07-28
*/
public interface IRsOverseasRecordService {
/**
* 查询出国记录分页列表
*
* @param query 查询条件
* @return 出国记录分页集合
*/
TableDataInfo<RsOverseasRecordVo> queryPageList(RsOverseasRecordQuery query);
/**
* 查询出国记录列表
*
* @param query 查询条件
* @return 出国记录集合
*/
List<RsOverseasRecordVo> queryList(RsOverseasRecordQuery query);
/**
* 查询出国记录详细信息
*
* @param recordId 出国记录主键
* @return 出国记录
*/
RsOverseasRecordVo queryById(Long recordId);
/**
* 根据申请ID查询出国记录
*
* @param applicationId 申请ID
* @return 出国记录
*/
RsOverseasRecordVo queryByApplicationId(Long applicationId);
/**
* 根据申请人ID查询出国记录列表
*
* @param applicantId 申请人ID
* @return 出国记录集合
*/
List<RsOverseasRecordVo> queryByApplicantId(Long applicantId);
/**
* 查询我的出国记录分页列表(联合查询:记录+已通过申请)
*
* @param query 查询条件
* @return 出国记录分页集合
*/
TableDataInfo<RsOverseasRecordVo> queryMyRecordPageList(RsOverseasRecordQuery query);
/**
* 新增出国记录
*
* @param bo 出国记录业务对象
* @return 是否成功
*/
Boolean insertByBo(RsOverseasRecordBo bo);
/**
* 修改出国记录
*
* @param bo 出国记录业务对象
* @return 是否成功
*/
Boolean updateByBo(RsOverseasRecordBo bo);
/**
* 基于申请信息创建出国记录
*
* @param applicationId 申请ID
* @return 是否成功
*/
Boolean createRecordFromApplication(Long applicationId);
/**
* 更新出国记录完成状态
*
* @param recordId 记录ID
* @param completionStatus 完成状态
* @param achievement 出国成果
* @param reportContent 出国报告
* @return 是否成功
*/
Boolean updateCompletionStatus(Long recordId, String completionStatus, String achievement, String reportContent);
/**
* 校验删除数据
*
* @param ids 主键集合
* @param isValid 是否有效性校验
*/
void validatedByIds(List<Long> ids, Boolean isValid);
/**
* 批量删除出国记录
*
* @param ids 主键集合
* @return 是否成功
*/
Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid);
}
@@ -0,0 +1,118 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.rs.domain.RsTrainingApplication;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 培训申请服务接口
*
* @author weizzzz
* @date 2025-07-16
*/
public interface IRsTrainingApplicationService {
/**
* 查询培训申请列表
*
* @param application 培训申请
* @param pageQuery 分页参数
* @return 培训申请分页列表
*/
TableDataInfo<RsTrainingApplication> selectPageRsTrainingApplicationList(RsTrainingApplication application, PageQuery pageQuery);
/**
* 查询培训申请列表
*
* @param application 培训申请
* @return 培训申请列表
*/
List<RsTrainingApplication> selectRsTrainingApplicationList(RsTrainingApplication application);
/**
* 查询指定课程的培训申请列表
*
* @param courseId 课程ID
* @param application 培训申请
* @param pageQuery 分页参数
* @return 培训申请分页列表
*/
TableDataInfo<RsTrainingApplication> selectPageApplicationsByCourse(Long courseId, RsTrainingApplication application, PageQuery pageQuery);
/**
* 查询我的培训申请列表
*
* @param userId 用户ID
* @param application 培训申请
* @param pageQuery 分页参数
* @return 培训申请分页列表
*/
TableDataInfo<RsTrainingApplication> selectPageMyApplications(Long userId, RsTrainingApplication application, PageQuery pageQuery);
/**
* 查询培训申请详细信息
*
* @param applicationId 培训申请ID
* @return 培训申请
*/
RsTrainingApplication selectRsTrainingApplicationById(Long applicationId);
/**
* 新增培训申请
*
* @param application 培训申请
* @return 结果
*/
int insertRsTrainingApplication(RsTrainingApplication application);
/**
* 修改培训申请
*
* @param application 培训申请
* @return 结果
*/
int updateRsTrainingApplication(RsTrainingApplication application);
/**
* 批量删除培训申请
*
* @param applicationIds 需要删除的培训申请ID数组
* @return 结果
*/
int deleteRsTrainingApplicationByIds(Long[] applicationIds);
/**
* 删除培训申请信息
*
* @param applicationId 培训申请ID
* @return 结果
*/
int deleteRsTrainingApplicationById(Long applicationId);
/**
* 取消培训申请
*
* @param applicationId 培训申请ID
* @return 结果
*/
int cancelApplication(Long applicationId);
/**
* 更新申请状态
*
* @param applicationId 培训申请ID
* @param status 状态
* @return 结果
*/
int updateApplicationStatus(Long applicationId, String status);
/**
* 重新提交已被拒绝的申请
*
* @param applicationId 培训申请ID
* @param application 培训申请更新数据
* @return 结果
*/
int resubmitApplication(Long applicationId, RsTrainingApplication application);
}
@@ -0,0 +1,109 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.rs.domain.RsTrainingApproval;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 培训审批服务接口
*
* @author weizzzz
* @date 2025-07-16
*/
public interface IRsTrainingApprovalService {
/**
* 查询培训审批列表
*
* @param approval 培训审批
* @param pageQuery 分页参数
* @return 培训审批分页列表
*/
TableDataInfo<RsTrainingApproval> selectPageRsTrainingApprovalList(RsTrainingApproval approval, PageQuery pageQuery);
/**
* 查询培训审批列表
*
* @param approval 培训审批
* @return 培训审批列表
*/
List<RsTrainingApproval> selectRsTrainingApprovalList(RsTrainingApproval approval);
/**
* 查询指定申请的审批历史
*
* @param applicationId 申请ID
* @return 培训审批列表
*/
List<RsTrainingApproval> selectApprovalHistoryByApplicationId(Long applicationId);
/**
* 查询待我审批的列表
*
* @param approverId 审批人ID
* @param approval 培训审批
* @param pageQuery 分页参数
* @return 培训审批分页列表
*/
TableDataInfo<RsTrainingApproval> selectPagePendingApprovals(Long approverId, RsTrainingApproval approval, PageQuery pageQuery);
/**
* 查询培训审批详细信息
*
* @param approvalId 培训审批ID
* @return 培训审批
*/
RsTrainingApproval selectRsTrainingApprovalById(Long approvalId);
/**
* 新增培训审批
*
* @param approval 培训审批
* @return 结果
*/
int insertRsTrainingApproval(RsTrainingApproval approval);
/**
* 修改培训审批
*
* @param approval 培训审批
* @return 结果
*/
int updateRsTrainingApproval(RsTrainingApproval approval);
/**
* 查询带详细信息的审批记录列表(关联申请人和课程信息)
*
* @param approval 培训审批
* @param pageQuery 分页参数
* @return 培训审批分页列表
*/
TableDataInfo<RsTrainingApproval> selectPageRsTrainingApprovalWithDetailsList(RsTrainingApproval approval, PageQuery pageQuery);
/**
* 批量删除培训审批
*
* @param approvalIds 需要删除的培训审批ID数组
* @return 结果
*/
int deleteRsTrainingApprovalByIds(Long[] approvalIds);
/**
* 删除培训审批信息
*
* @param approvalId 培训审批ID
* @return 结果
*/
int deleteRsTrainingApprovalById(Long approvalId);
/**
* 处理审批
*
* @param applicationId 申请ID
* @param status 审批状态
* @param opinion 审批意见
* @return 结果
*/
int processApproval(Long applicationId, String status, String opinion);
}
@@ -0,0 +1,80 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.rs.domain.RsTrainingCourse;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 培训课程服务接口
*
* @author weizzzz
* @date 2025-07-16
*/
public interface IRsTrainingCourseService {
/**
* 查询培训课程列表
*
* @param course 培训课程
* @param pageQuery 分页参数
* @return 培训课程分页列表
*/
TableDataInfo<RsTrainingCourse> selectPageRsTrainingCourseList(RsTrainingCourse course, PageQuery pageQuery);
/**
* 查询培训课程列表
*
* @param course 培训课程
* @return 培训课程列表
*/
List<RsTrainingCourse> selectRsTrainingCourseList(RsTrainingCourse course);
/**
* 查询培训课程详细信息
*
* @param courseId 培训课程ID
* @return 培训课程
*/
RsTrainingCourse selectRsTrainingCourseById(Long courseId);
/**
* 新增培训课程
*
* @param course 培训课程
* @return 结果
*/
int insertRsTrainingCourse(RsTrainingCourse course);
/**
* 修改培训课程
*
* @param course 培训课程
* @return 结果
*/
int updateRsTrainingCourse(RsTrainingCourse course);
/**
* 批量删除培训课程
*
* @param courseIds 需要删除的培训课程ID数组
* @return 结果
*/
int deleteRsTrainingCourseByIds(Long[] courseIds);
/**
* 删除培训课程信息
*
* @param courseId 培训课程ID
* @return 结果
*/
int deleteRsTrainingCourseById(Long courseId);
/**
* 切换培训课程状态
*
* @param courseId 培训课程ID
* @return 结果
*/
int toggleCourseStatus(Long courseId);
}
@@ -0,0 +1,108 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.rs.domain.RsTrainingRecord;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
/**
* 培训记录服务接口
*
* @author weizzzz
* @date 2025-07-16
*/
public interface IRsTrainingRecordService {
/**
* 查询培训记录列表
*
* @param record 培训记录
* @param pageQuery 分页参数
* @return 培训记录分页列表
*/
TableDataInfo<RsTrainingRecord> selectPageRsTrainingRecordList(RsTrainingRecord record, PageQuery pageQuery);
/**
* 查询培训记录列表
*
* @param record 培训记录
* @return 培训记录列表
*/
List<RsTrainingRecord> selectRsTrainingRecordList(RsTrainingRecord record);
/**
* 查询我的培训记录列表
*
* @param userId 用户ID
* @param record 培训记录
* @param pageQuery 分页参数
* @return 培训记录分页列表
*/
TableDataInfo<RsTrainingRecord> selectPageMyRecords(Long userId, RsTrainingRecord record, PageQuery pageQuery);
/**
* 查询培训记录详细信息
*
* @param recordId 培训记录ID
* @return 培训记录
*/
RsTrainingRecord selectRsTrainingRecordById(Long recordId);
/**
* 新增培训记录
*
* @param record 培训记录
* @return 结果
*/
int insertRsTrainingRecord(RsTrainingRecord record);
/**
* 修改培训记录
*
* @param record 培训记录
* @return 结果
*/
int updateRsTrainingRecord(RsTrainingRecord record);
/**
* 批量删除培训记录
*
* @param recordIds 需要删除的培训记录ID数组
* @return 结果
*/
int deleteRsTrainingRecordByIds(Long[] recordIds);
/**
* 删除培训记录信息
*
* @param recordId 培训记录ID
* @return 结果
*/
int deleteRsTrainingRecordById(Long recordId);
/**
* 更新培训记录完成状态
*
* @param recordId 记录ID
* @param completionStatus 完成状态
* @return 结果
*/
int updateCompletionStatus(Long recordId, String completionStatus);
/**
* 上传培训证书
*
* @param recordId 记录ID
* @param certificatePath 证书路径
* @param certificateNumber 证书编号
* @return 结果
*/
int uploadCertificate(Long recordId, String certificatePath, String certificateNumber);
/**
* 根据申请ID查询培训记录
* @param applicationId 申请ID
* @return 培训记录
*/
RsTrainingRecord selectByApplicationId(Long applicationId);
}
@@ -0,0 +1,125 @@
package org.dromara.rs.service.impl;
import java.util.List;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.rs.domain.RsApplication;
import org.dromara.rs.domain.RsJob;
import org.dromara.rs.mapper.RsApplicationMapper;
import org.dromara.rs.service.IRsApplicationService;
import org.dromara.rs.service.IRsJobService;
/**
* 申请记录Service业务层处理
*
* @author ruoyi
*/
@Service
public class RsApplicationServiceImpl implements IRsApplicationService {
@Autowired
private RsApplicationMapper applicationMapper;
@Autowired
private IRsJobService jobService;
/**
* 查询申请记录列表
*/
@Override
public List<RsApplication> selectRsApplicationList(RsApplication application) {
return applicationMapper.selectRsApplicationList(application);
}
/**
* 查询申请记录详细信息
*/
@Override
public RsApplication selectRsApplicationById(Long applicationId) {
return applicationMapper.selectRsApplicationById(applicationId);
}
/**
* 新增申请记录
*/
@Override
public int insertRsApplication(RsApplication application) {
return applicationMapper.insertRsApplication(application);
}
/**
* 修改申请记录
*/
@Override
public int updateRsApplication(RsApplication application) {
return applicationMapper.updateRsApplication(application);
}
/**
* 批量删除申请记录
*/
@Override
public int deleteRsApplicationByIds(Long[] applicationIds) {
return applicationMapper.deleteRsApplicationByIds(applicationIds);
}
/**
* 删除申请记录信息
*/
@Override
public int deleteRsApplicationById(Long applicationId) {
return applicationMapper.deleteRsApplicationById(applicationId);
}
/**
* 更新申请状态
*/
@Override
public int updateApplicationStatus(Long applicationId, String status, String hrNotes) {
RsApplication application = new RsApplication();
application.setApplicationId(applicationId);
application.setStatus(status);
application.setHrNotes(hrNotes);
return applicationMapper.updateRsApplication(application);
}
/**
* 检查是否已申请
*/
@Override
public RsApplication checkApplied(Long jobId, Long applicantId) {
RsApplication query = new RsApplication();
query.setJobId(jobId);
query.setApplicantId(applicantId);
List<RsApplication> applications = applicationMapper.selectRsApplicationList(query);
return applications != null && !applications.isEmpty() ? applications.get(0) : null;
}
/**
* 查询应聘者的所有申请
*/
@Override
public List<RsApplication> selectApplicationsByApplicant(Long applicantId) {
RsApplication query = new RsApplication();
query.setApplicantId(applicantId);
return applicationMapper.selectRsApplicationList(query);
}
/**
* 为申请记录设置职位标题
*/
@Override
public void setJobTitleForApplication(RsApplication application) {
if (application.getJobId() != null) {
try {
RsJob job = jobService.selectRsJobById(application.getJobId());
if (job != null) {
application.setJobTitle(job.getTitle());
}
} catch (Exception e) {
// 记录错误但不抛出异常,避免影响申请提交
System.err.println("获取职位信息失败: " + e.getMessage());
}
}
}
}
@@ -0,0 +1,118 @@
package org.dromara.rs.service.impl;
import java.util.Arrays;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.dromara.rs.mapper.RsJobMapper;
import org.dromara.rs.domain.RsJob;
import org.dromara.rs.domain.vo.RsJobVo;
import org.dromara.rs.service.IRsJobService;
import org.dromara.common.core.utils.StringUtils;
/**
* 招聘职位Service业务层处理
*
* @author ruoyi
*/
@Service
public class RsJobServiceImpl implements IRsJobService {
@Autowired
private RsJobMapper rsJobMapper;
/**
* 查询招聘职位列表
*
* @param rsJob 招聘职位
* @return 招聘职位
*/
@Override
public List<RsJobVo> selectRsJobList(RsJob rsJob) {
return rsJobMapper.selectRsJobList(rsJob);
}
/**
* 查询招聘职位详细信息
*
* @param jobId 招聘职位主键
* @return 招聘职位
*/
@Override
public RsJobVo selectRsJobById(Long jobId) {
return rsJobMapper.selectRsJobById(jobId);
}
/**
* 增加浏览次数
*
* @param jobId 招聘职位ID
* @return 结果
*/
@Override
public int incrementViewCount(Long jobId) {
return rsJobMapper.incrementViewCount(jobId);
}
/**
* 新增招聘职位
*
* @param rsJob 招聘职位
* @return 结果
*/
@Override
public int insertRsJob(RsJob rsJob) {
return rsJobMapper.insert(rsJob);
}
/**
* 修改招聘职位
*
* @param rsJob 招聘职位
* @return 结果
*/
@Override
public int updateRsJob(RsJob rsJob) {
return rsJobMapper.updateById(rsJob);
}
/**
* 批量删除招聘职位
*
* @param jobIds 需要删除的招聘职位主键
* @return 结果
*/
@Override
public int deleteRsJobByIds(Long[] jobIds) {
return rsJobMapper.deleteBatchIds(Arrays.asList(jobIds));
}
/**
* 删除招聘职位信息
*
* @param jobId 招聘职位主键
* @return 结果
*/
@Override
public int deleteRsJobById(Long jobId) {
return rsJobMapper.deleteById(jobId);
}
/**
* 切换职位状态
*
* @param jobId 招聘职位主键
* @return 结果
*/
@Override
public int toggleJobStatus(Long jobId) {
RsJob rsJob = rsJobMapper.selectById(jobId);
if (rsJob != null) {
// 切换状态 0->1, 1->0
rsJob.setIsActive("0".equals(rsJob.getIsActive()) ? "1" : "0");
return rsJobMapper.updateById(rsJob);
}
return 0;
}
}
@@ -0,0 +1,546 @@
package org.dromara.rs.service.impl;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.rs.mapper.RsOverseasApplicationMapper;
import org.dromara.rs.mapper.RsOverseasApprovalMapper;
import org.dromara.rs.domain.RsOverseasApplication;
import org.dromara.rs.domain.RsOverseasApproval;
import org.dromara.rs.domain.bo.RsOverseasApplicationBo;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.query.RsOverseasApplicationQuery;
import org.dromara.rs.service.IRsOverseasApplicationService;
import org.dromara.rs.service.IRsOverseasRecordService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
/**
* 出国申请Service业务层处理
*
* @author weizzzz
* @date 2025-07-28
*/
@Service
public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationService {
private static final Logger log = LoggerFactory.getLogger(RsOverseasApplicationServiceImpl.class);
@Autowired
private RsOverseasApplicationMapper baseMapper;
@Autowired
private RsOverseasApprovalMapper approvalMapper;
@Autowired
private IRsOverseasRecordService overseasRecordService;
/**
* 查询出国申请分页列表
*/
@Override
public TableDataInfo<RsOverseasApplicationVo> queryPageList(RsOverseasApplicationQuery query) {
// 使用自定义查询方法,包含状态文本等扩展字段
List<RsOverseasApplicationVo> list = baseMapper.selectOverseasApplicationList(query);
// 手动分页处理
int pageNum = query.getPageNum();
int pageSize = query.getPageSize();
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, list.size());
List<RsOverseasApplicationVo> pageList;
if (start > list.size()) {
pageList = Arrays.asList();
} else {
pageList = list.subList(start, end);
}
// 构建分页对象
Page<RsOverseasApplicationVo> page = new Page<>(pageNum, pageSize);
page.setRecords(pageList);
page.setTotal(list.size());
return TableDataInfo.build(page);
}
/**
* 查询我的出国申请分页列表(排除已通过的申请)
* 已通过的申请会自动创建出国记录,在出国记录选项卡中显示
*/
@Override
public TableDataInfo<RsOverseasApplicationVo> queryMyApplicationList(RsOverseasApplicationQuery query) {
// 显示用户的申请记录,排除已通过的申请(已通过的申请在出国记录中显示)
// 查询条件:草稿、待审核、已拒绝、已取消、已完成等状态(不包括已通过状态)
List<RsOverseasApplicationVo> list = baseMapper.selectMyOverseasApplicationList(query);
// 手动分页处理
int pageNum = query.getPageNum();
int pageSize = query.getPageSize();
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, list.size());
List<RsOverseasApplicationVo> pageList;
if (start > list.size()) {
pageList = Arrays.asList();
} else {
pageList = list.subList(start, end);
}
// 构建分页对象
Page<RsOverseasApplicationVo> page = new Page<>(pageNum, pageSize);
page.setRecords(pageList);
page.setTotal(list.size());
return TableDataInfo.build(page);
}
/**
* 查询出国申请列表
*/
@Override
public List<RsOverseasApplicationVo> queryList(RsOverseasApplicationQuery query) {
return baseMapper.selectOverseasApplicationList(query);
}
/**
* 查询出国申请详细信息
*/
@Override
public RsOverseasApplicationVo queryById(Long applicationId) {
return baseMapper.selectOverseasApplicationById(applicationId);
}
/**
* 根据申请人ID查询出国申请列表
*/
@Override
public List<RsOverseasApplicationVo> queryByApplicantId(Long applicantId) {
return baseMapper.selectOverseasApplicationsByApplicantId(applicantId);
}
/**
* 根据状态查询出国申请列表
*/
@Override
public List<RsOverseasApplicationVo> queryByStatus(String status) {
return baseMapper.selectOverseasApplicationsByStatus(status);
}
/**
* 根据当前审批人查询待审批申请列表
*/
@Override
public List<RsOverseasApplicationVo> queryPendingByApprover(String currentApprover) {
return baseMapper.selectPendingApplicationsByApprover(currentApprover);
}
/**
* 根据审批级别查询待审批申请列表
*/
@Override
public List<RsOverseasApplicationVo> queryPendingByLevel(String approvalLevel) {
return baseMapper.selectPendingApplicationsByLevel(approvalLevel);
}
/**
* 新增出国申请(直接进入待审核状态)
*/
@Override
@Transactional
public Boolean insertByBo(RsOverseasApplicationBo bo) {
RsOverseasApplication add = buildEntityFromBo(bo);
// 设置申请时间
add.setApplyTime(new Date());
// 直接设置为待审核状态(适应数据库DEFAULT '0'设计)
add.setStatus("0");
// 设置初始审批级别为部门主管
add.setApprovalLevel("1");
// 设置当前审批环节描述
add.setCurrentApprover("待部门主管审批");
// 自动计算出国天数
if (add.getStartDate() != null && add.getEndDate() != null) {
long diffInMillies = add.getEndDate().getTime() - add.getStartDate().getTime();
int days = (int) (diffInMillies / (1000 * 60 * 60 * 24)) + 1;
add.setDuration(days);
}
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setApplicationId(add.getApplicationId());
}
return flag;
}
/**
* 修改出国申请
*/
@Override
@Transactional
public Boolean updateByBo(RsOverseasApplicationBo bo) {
RsOverseasApplication update = buildEntityFromBo(bo);
// 重新计算出国天数
if (update.getStartDate() != null && update.getEndDate() != null) {
long diffInMillies = update.getEndDate().getTime() - update.getStartDate().getTime();
int days = (int) (diffInMillies / (1000 * 60 * 60 * 24)) + 1;
update.setDuration(days);
}
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 提交出国申请(更新状态为待审核)
*/
@Override
@Transactional
public Boolean submitApplication(Long applicationId) {
RsOverseasApplication application = baseMapper.selectById(applicationId);
if (application == null) {
throw new RuntimeException("申请不存在");
}
// 只有草稿状态(status为null)的申请才能通过此方法提交
if (application.getStatus() != null && !"".equals(application.getStatus())) {
throw new RuntimeException("只有草稿状态的申请才能提交");
}
// 设置状态为待审核
application.setStatus("0");
application.setApplyTime(new Date());
// 设置初始审批级别为1(单级审批制度)
application.setApprovalLevel("1");
// 设置当前审批环节描述(单级审批制度)
application.setCurrentApprover("待审批");
return baseMapper.updateById(application) > 0;
}
/**
* 取消出国申请
*/
@Override
@Transactional
public Boolean cancelApplication(Long applicationId, String reason) {
RsOverseasApplication application = baseMapper.selectById(applicationId);
if (application == null) {
throw new RuntimeException("申请不存在");
}
if ("4".equals(application.getStatus())) {
throw new RuntimeException("已完成的申请不能取消");
}
application.setStatus("3");
application.setApplicantRemark(reason);
return baseMapper.updateById(application) > 0;
}
/**
* 审批出国申请
*/
@Override
@Transactional
public Boolean approveApplication(Long applicationId, Long approverId, String approverName,
String approvalStatus, String approvalOpinion) {
RsOverseasApplication application = baseMapper.selectById(applicationId);
if (application == null) {
throw new RuntimeException("申请不存在");
}
if (!"0".equals(application.getStatus())) {
throw new RuntimeException("只有待审核状态的申请才能审批");
}
// 创建审批记录
RsOverseasApproval approval = new RsOverseasApproval();
approval.setApplicationId(applicationId);
approval.setApproverId(approverId);
approval.setApproverName(approverName);
approval.setApprovalLevel(application.getApprovalLevel());
approval.setApprovalStatus(approvalStatus);
approval.setApprovalOpinion(approvalOpinion);
approval.setApprovalTime(new Date());
// 手动设置多租户和删除标志字段
if (approval.getTenantId() == null || approval.getTenantId().isEmpty()) {
approval.setTenantId("000000");
}
if (approval.getDeleted() == null || approval.getDeleted().isEmpty()) {
approval.setDeleted("0");
}
approvalMapper.insert(approval);
// 更新申请状态
if ("1".equals(approvalStatus)) {
// 审批通过
String nextLevel = getNextApprovalLevel(application.getApprovalLevel());
if (nextLevel != null) {
// 进入下一级审批
application.setApprovalLevel(nextLevel);
application.setCurrentApprover("待指定");
} else {
// 所有级别审批完成 - 先尝试创建出国记录,再更新申请状态
log.info("开始最终审批处理,申请ID: {}, 当前状态: {}, 申请人: {}",
applicationId, application.getStatus(), application.getApplicantName());
try {
// 如果审批通过且为最终级别,先创建出国记录
log.info("调用创建出国记录服务,申请ID: {}", applicationId);
Boolean recordCreated = overseasRecordService.createRecordFromApplication(applicationId);
if (!recordCreated) {
log.error("创建出国记录失败,申请ID: {}", applicationId);
throw new RuntimeException("创建出国记录失败,请联系管理员");
}
log.info("审批通过,成功创建出国记录,申请ID: {}", applicationId);
// 记录创建成功后,才更新申请状态为通过
log.info("准备更新申请状态为通过,申请ID: {}", applicationId);
application.setStatus("1");
application.setApprovalTime(new Date());
} catch (Exception e) {
log.error("审批通过后创建出国记录失败,申请ID: {}, 错误: {}", applicationId, e.getMessage(), e);
// 如果创建记录失败,不更新申请状态,保持待审核状态
throw new RuntimeException("审批处理失败:创建出国记录时发生错误 - " + e.getMessage());
}
}
} else if ("2".equals(approvalStatus)) {
// 审批拒绝
application.setStatus("2");
application.setApprovalTime(new Date());
}
// 更新申请状态到数据库
boolean updateResult = baseMapper.updateById(application) > 0;
return updateResult;
}
/**
* 获取下一级审批级别
* 修改为单级审批制度:审批通过后直接完成,不进入下一级
*/
private String getNextApprovalLevel(String currentLevel) {
// 单级审批:直接返回null表示审批完成
return null;
}
/**
* 修复已审批通过但未创建记录的申请
*
* @return 修复的记录数量
*/
@Override
@Transactional
public int fixApprovedApplicationsWithoutRecords() {
log.info("开始修复已审批通过但未创建记录的申请");
// 查询所有审批通过的申请
List<RsOverseasApplication> approvedApplications = baseMapper.selectList(
new LambdaQueryWrapper<RsOverseasApplication>()
.eq(RsOverseasApplication::getStatus, "1") // 已通过
);
int fixedCount = 0;
for (RsOverseasApplication application : approvedApplications) {
try {
// 检查是否已存在记录
RsOverseasRecordVo existingRecord = overseasRecordService.queryByApplicationId(application.getApplicationId());
if (existingRecord == null) {
// 不存在记录,尝试创建
log.info("为已通过申请创建记录,申请ID: {}, 申请人: {}",
application.getApplicationId(), application.getApplicantName());
boolean created = overseasRecordService.createRecordFromApplication(application.getApplicationId());
if (created) {
fixedCount++;
log.info("成功为已通过申请创建记录,申请ID: {}", application.getApplicationId());
}
} else {
log.debug("申请ID {} 已存在记录,无需修复", application.getApplicationId());
}
} catch (Exception e) {
log.error("修复申请记录失败,申请ID: {}, 错误: {}", application.getApplicationId(), e.getMessage(), e);
}
}
log.info("修复完成,共修复 {} 条记录", fixedCount);
return fixedCount;
}
/**
* 重新提交已被拒绝的申请
*
* @param applicationId 申请ID
* @param applicationData 申请更新数据
* @return 是否成功
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean resubmitApplication(Long applicationId, RsOverseasApplicationBo applicationData) {
// 查询原申请
RsOverseasApplication existingApp = baseMapper.selectById(applicationId);
if (existingApp == null) {
throw new RuntimeException("申请不存在");
}
// 只有状态为已拒绝的申请才能重新提交
if (!"2".equals(existingApp.getStatus())) {
throw new RuntimeException("只有已拒绝状态的申请才能重新提交");
}
log.info("【重新提交申请】原申请状态: {}, 申请ID: {}", existingApp.getStatus(), applicationId);
// 更新申请数据
if (applicationData.getDestination() != null) {
existingApp.setDestination(applicationData.getDestination());
}
if (applicationData.getPurpose() != null) {
existingApp.setPurpose(applicationData.getPurpose());
}
if (applicationData.getPurposeDetail() != null) {
existingApp.setPurposeDetail(applicationData.getPurposeDetail());
}
if (applicationData.getDestinationCity() != null) {
existingApp.setDestinationCity(applicationData.getDestinationCity());
}
if (applicationData.getStartDate() != null) {
existingApp.setStartDate(applicationData.getStartDate());
}
if (applicationData.getEndDate() != null) {
existingApp.setEndDate(applicationData.getEndDate());
}
if (applicationData.getBudget() != null) {
existingApp.setBudget(applicationData.getBudget());
}
if (applicationData.getItinerary() != null) {
existingApp.setItinerary(applicationData.getItinerary());
}
if (applicationData.getApplicantRemark() != null) {
existingApp.setApplicantRemark(applicationData.getApplicantRemark());
}
// 自动计算出国天数
if (existingApp.getStartDate() != null && existingApp.getEndDate() != null) {
long diffInMillies = existingApp.getEndDate().getTime() - existingApp.getStartDate().getTime();
int days = (int) (diffInMillies / (1000 * 60 * 60 * 24)) + 1;
existingApp.setDuration(days);
}
// 重置为待审核状态
existingApp.setStatus("0");
existingApp.setApplyTime(new Date()); // 更新申请时间
// 设置初始审批级别为1(单级审批制度)
existingApp.setApprovalLevel("1");
existingApp.setCurrentApprover("待审批");
log.info("【重新提交申请】更新为待审核状态,申请ID: {}", applicationId);
return baseMapper.updateById(existingApp) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(RsOverseasApplication entity) {
// 校验业务数据
if (entity.getStartDate() != null && entity.getEndDate() != null) {
if (entity.getStartDate().after(entity.getEndDate())) {
throw new RuntimeException("开始日期不能晚于结束日期");
}
}
if (entity.getBudget() != null && entity.getBudget().doubleValue() <= 0) {
throw new RuntimeException("预算金额必须大于0");
}
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<RsOverseasApplication> buildQueryWrapper(RsOverseasApplicationQuery query) {
LambdaQueryWrapper<RsOverseasApplication> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(query.getApplicantName()), RsOverseasApplication::getApplicantName, query.getApplicantName());
lqw.like(StringUtils.isNotBlank(query.getDepartment()), RsOverseasApplication::getDepartment, query.getDepartment());
lqw.eq(StringUtils.isNotBlank(query.getPurpose()), RsOverseasApplication::getPurpose, query.getPurpose());
lqw.like(StringUtils.isNotBlank(query.getDestination()), RsOverseasApplication::getDestination, query.getDestination());
lqw.eq(StringUtils.isNotBlank(query.getStatus()), RsOverseasApplication::getStatus, query.getStatus());
lqw.eq(StringUtils.isNotBlank(query.getApprovalLevel()), RsOverseasApplication::getApprovalLevel, query.getApprovalLevel());
lqw.like(StringUtils.isNotBlank(query.getCurrentApprover()), RsOverseasApplication::getCurrentApprover, query.getCurrentApprover());
lqw.eq(query.getApplicantId() != null, RsOverseasApplication::getApplicantId, query.getApplicantId());
lqw.ge(query.getBudgetMin() != null, RsOverseasApplication::getBudget, query.getBudgetMin());
lqw.le(query.getBudgetMax() != null, RsOverseasApplication::getBudget, query.getBudgetMax());
lqw.ge(query.getApplyTimeBegin() != null, RsOverseasApplication::getApplyTime, query.getApplyTimeBegin());
lqw.le(query.getApplyTimeEnd() != null, RsOverseasApplication::getApplyTime, query.getApplyTimeEnd());
lqw.ge(query.getStartDateBegin() != null, RsOverseasApplication::getStartDate, query.getStartDateBegin());
lqw.le(query.getStartDateEnd() != null, RsOverseasApplication::getStartDate, query.getStartDateEnd());
return lqw;
}
/**
* 从BO构建Entity
*/
private RsOverseasApplication buildEntityFromBo(RsOverseasApplicationBo bo) {
RsOverseasApplication entity = new RsOverseasApplication();
entity.setApplicationId(bo.getApplicationId());
entity.setApplicantId(bo.getApplicantId());
entity.setApplicantName(bo.getApplicantName());
entity.setDepartment(bo.getDepartment());
entity.setContactPhone(bo.getContactPhone());
entity.setPurpose(bo.getPurpose());
entity.setPurposeDetail(bo.getPurposeDetail());
entity.setDestination(bo.getDestination());
entity.setDestinationCity(bo.getDestinationCity());
entity.setStartDate(bo.getStartDate());
entity.setEndDate(bo.getEndDate());
entity.setDuration(bo.getDuration());
entity.setBudget(bo.getBudget());
entity.setInvitation(bo.getInvitation());
entity.setItinerary(bo.getItinerary());
entity.setAccommodationPlan(bo.getAccommodationPlan());
entity.setTransportationPlan(bo.getTransportationPlan());
entity.setApplicantRemark(bo.getApplicantRemark());
return entity;
}
/**
* 校验删除数据
*/
@Override
public void validatedByIds(List<Long> ids, Boolean isValid) {
if (isValid) {
// 校验是否存在关联数据
for (Long id : ids) {
RsOverseasApplication application = baseMapper.selectById(id);
if (application != null && "1".equals(application.getStatus())) {
throw new RuntimeException("已通过的申请不能删除");
}
}
}
}
/**
* 批量删除出国申请
*/
@Override
@Transactional
public Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid) {
validatedByIds(ids, isValid);
return baseMapper.deleteBatchIds(ids) > 0;
}
}
@@ -0,0 +1,325 @@
package org.dromara.rs.service.impl;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.rs.mapper.RsOverseasApprovalMapper;
import org.dromara.rs.domain.RsOverseasApproval;
import org.dromara.rs.domain.bo.RsOverseasApprovalBo;
import org.dromara.rs.domain.vo.RsOverseasApprovalVo;
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业务层处理
*
* @author weizzzz
* @date 2025-07-28
*/
@Service
public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService {
private static final Logger log = LoggerFactory.getLogger(RsOverseasApprovalServiceImpl.class);
@Autowired
private RsOverseasApprovalMapper baseMapper;
/**
* 查询出国审批记录分页列表
*/
@Override
public TableDataInfo<RsOverseasApprovalVo> queryPageList(RsOverseasApprovalQuery query) {
// 使用自定义SQL查询,包含申请信息的联合查询
List<RsOverseasApprovalVo> list = baseMapper.selectOverseasApprovalList(query);
// 手动分页处理
int pageNum = query.getPageNum();
int pageSize = query.getPageSize();
int total = list.size();
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, total);
List<RsOverseasApprovalVo> pageList = total > 0 && start < total ?
list.subList(start, end) : new ArrayList<>();
// 构建分页对象
Page<RsOverseasApprovalVo> page = new Page<>(pageNum, pageSize);
page.setRecords(pageList);
page.setTotal(total);
return TableDataInfo.build(page);
}
/**
* 查询出国审批记录列表
*/
@Override
public List<RsOverseasApprovalVo> queryList(RsOverseasApprovalQuery query) {
return baseMapper.selectOverseasApprovalList(query);
}
/**
* 查询出国审批记录详细信息
*/
@Override
public RsOverseasApprovalVo queryById(Long approvalId) {
return baseMapper.selectOverseasApprovalById(approvalId);
}
/**
* 根据申请ID查询审批记录列表
*/
@Override
public List<RsOverseasApprovalVo> queryByApplicationId(Long applicationId) {
return baseMapper.selectOverseasApprovalsByApplicationId(applicationId);
}
/**
* 根据审批人ID查询审批记录列表
*/
@Override
public List<RsOverseasApprovalVo> queryByApproverId(Long approverId) {
return baseMapper.selectOverseasApprovalsByApproverId(approverId);
}
/**
* 新增出国审批记录
*/
@Override
@Transactional
public Boolean insertByBo(RsOverseasApprovalBo bo) {
RsOverseasApproval add = buildEntityFromBo(bo);
validEntityBeforeSave(add);
return baseMapper.insert(add) > 0;
}
/**
* 修改出国审批记录
*/
@Override
@Transactional
public Boolean updateByBo(RsOverseasApprovalBo bo) {
RsOverseasApproval update = buildEntityFromBo(bo);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(RsOverseasApproval entity) {
// 校验审批级别
if (entity.getApprovalLevel() != null) {
if (!"1".equals(entity.getApprovalLevel()) &&
!"2".equals(entity.getApprovalLevel()) &&
!"3".equals(entity.getApprovalLevel())) {
throw new RuntimeException("审批级别只能是1(部门主管)、2(分管领导)、3(校领导)");
}
}
// 校验审批状态
if (entity.getApprovalStatus() != null) {
if (!"0".equals(entity.getApprovalStatus()) &&
!"1".equals(entity.getApprovalStatus()) &&
!"2".equals(entity.getApprovalStatus())) {
throw new RuntimeException("审批状态只能是0(待审核)、1(通过)、2(拒绝)");
}
}
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<RsOverseasApproval> buildQueryWrapper(RsOverseasApprovalQuery query) {
LambdaQueryWrapper<RsOverseasApproval> lqw = Wrappers.lambdaQuery();
lqw.eq(query.getApplicationId() != null, RsOverseasApproval::getApplicationId, query.getApplicationId());
lqw.eq(query.getApproverId() != null, RsOverseasApproval::getApproverId, query.getApproverId());
lqw.like(StringUtils.isNotBlank(query.getApproverName()), RsOverseasApproval::getApproverName, query.getApproverName());
lqw.eq(StringUtils.isNotBlank(query.getApprovalLevel()), RsOverseasApproval::getApprovalLevel, query.getApprovalLevel());
// 如果指定了审批状态,使用指定的状态;否则只查询已完成审批的记录(排除待审核状态)
if (StringUtils.isNotBlank(query.getApprovalStatus())) {
lqw.eq(RsOverseasApproval::getApprovalStatus, query.getApprovalStatus());
} else {
// 默认只查询已完成审批的记录:1-通过,2-拒绝(排除0-待审核)
lqw.in(RsOverseasApproval::getApprovalStatus, "1", "2");
}
lqw.ge(query.getApprovalTimeBegin() != null, RsOverseasApproval::getApprovalTime, query.getApprovalTimeBegin());
lqw.le(query.getApprovalTimeEnd() != null, RsOverseasApproval::getApprovalTime, query.getApprovalTimeEnd());
return lqw;
}
/**
* 从BO构建Entity
*/
private RsOverseasApproval buildEntityFromBo(RsOverseasApprovalBo bo) {
RsOverseasApproval entity = new RsOverseasApproval();
entity.setApprovalId(bo.getApprovalId());
entity.setApplicationId(bo.getApplicationId());
entity.setApproverId(bo.getApproverId());
entity.setApproverName(bo.getApproverName());
entity.setApprovalLevel(bo.getApprovalLevel());
entity.setApprovalStatus(bo.getApprovalStatus());
entity.setApprovalOpinion(bo.getApprovalOpinion());
entity.setApprovalTime(bo.getApprovalTime());
return entity;
}
/**
* 校验删除数据
*/
@Override
public void validatedByIds(List<Long> ids, Boolean isValid) {
if (isValid) {
// 校验是否存在关联数据
for (Long id : ids) {
RsOverseasApproval approval = baseMapper.selectById(id);
if (approval != null && "1".equals(approval.getApprovalStatus())) {
throw new RuntimeException("已通过的审批记录不能删除");
}
}
}
}
/**
* 批量删除出国审批记录
*/
@Override
@Transactional
public Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid) {
if (isValid) {
// 执行删除前的业务校验逻辑
// 如果校验不通过,抛出业务异常或返回false
}
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 修复审批记录测试数据
*/
@Override
@Transactional
public void fixApprovalTestData() {
log.info("开始修复审批记录测试数据...");
try {
// 1. 清理现有测试数据(谨慎操作,只清理测试租户数据)
baseMapper.delete(new LambdaQueryWrapper<RsOverseasApproval>()
.eq(RsOverseasApproval::getTenantId, "000000")
.eq(RsOverseasApproval::getDeleted, "0"));
// 2. 创建并插入标准测试数据
List<RsOverseasApproval> testApprovals = createTestApprovalData();
for (RsOverseasApproval approval : testApprovals) {
baseMapper.insert(approval);
}
log.info("审批记录测试数据修复完成,共插入 {} 条记录", testApprovals.size());
} catch (Exception e) {
log.error("修复审批记录测试数据失败", e);
throw new RuntimeException("数据修复失败:" + e.getMessage());
}
}
/**
* 创建测试审批数据
*/
private List<RsOverseasApproval> createTestApprovalData() {
List<RsOverseasApproval> approvals = new ArrayList<>();
// 创建5条标准测试审批记录,对应申请ID 1-5
Long[] applicationIds = {1L, 2L, 3L, 4L, 5L};
String[] approverNames = {"管理员", "张主任", "李领导", "王校长", "管理员"};
String[] approvalLevels = {"1", "1", "2", "3", "1"};
String[] approvalStatuses = {"1", "1", "1", "1", "1"}; // 全部通过
String[] opinions = {
"申请材料齐全,出国目的明确,同意申请",
"培训项目对教学有益,同意参加",
"国际会议有助于学科发展,批准参加",
"研究交流项目重要,全力支持",
"培训有助于专业发展,批准参加"
};
for (int i = 0; i < applicationIds.length; i++) {
RsOverseasApproval approval = new RsOverseasApproval();
approval.setApplicationId(applicationIds[i]);
approval.setApproverId(1L);
approval.setApproverName(approverNames[i]);
approval.setApprovalLevel(approvalLevels[i]);
approval.setApprovalStatus(approvalStatuses[i]);
approval.setApprovalOpinion(opinions[i]);
approval.setApprovalTime(Date.from(LocalDateTime.now().minusDays(i + 1).atZone(ZoneId.systemDefault()).toInstant()));
approval.setTenantId("000000");
approval.setDeleted("0");
approval.setCreateBy(1L);
approval.setCreateTime(Date.from(LocalDateTime.now().minusDays(i + 1).atZone(ZoneId.systemDefault()).toInstant()));
approvals.add(approval);
}
return approvals;
}
/**
* 根据申请ID查询审批历史记录
*/
@Override
public List<RsOverseasApprovalVo> selectApprovalHistoryByApplicationId(Long applicationId) {
LambdaQueryWrapper<RsOverseasApproval> lqw = new LambdaQueryWrapper<>();
lqw.eq(RsOverseasApproval::getApplicationId, applicationId);
lqw.orderByAsc(RsOverseasApproval::getApprovalLevel);
lqw.orderByDesc(RsOverseasApproval::getApprovalTime); // 按审批时间降序,最新的在前面
return baseMapper.selectVoList(lqw);
}
/**
* 联合查询分页列表:待审批申请 + 审批记录
*/
@Override
public TableDataInfo<RsOverseasApprovalVo> queryCombinedPageList(RsOverseasApprovalQuery query) {
List<RsOverseasApprovalVo> list = baseMapper.selectCombinedApprovalList(query);
// 手动分页处理
int total = list.size();
int pageNum = query.getPageNum() != null ? query.getPageNum() : 1;
int pageSize = query.getPageSize() != null ? query.getPageSize() : 10;
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, total);
if (start >= total) {
return TableDataInfo.build(new ArrayList<>());
}
List<RsOverseasApprovalVo> pageList = list.subList(start, end);
// 创建一个简单的分页结果
TableDataInfo<RsOverseasApprovalVo> result = TableDataInfo.build(pageList);
result.setTotal((long) total);
return result;
}
/**
* 联合查询列表:待审批申请 + 审批记录
*/
@Override
public List<RsOverseasApprovalVo> queryCombinedList(RsOverseasApprovalQuery query) {
return baseMapper.selectCombinedApprovalList(query);
}
}
@@ -0,0 +1,333 @@
package org.dromara.rs.service.impl;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.rs.mapper.RsOverseasRecordMapper;
import org.dromara.rs.mapper.RsOverseasApplicationMapper;
import org.dromara.rs.domain.RsOverseasRecord;
import org.dromara.rs.domain.RsOverseasApplication;
import org.dromara.rs.domain.bo.RsOverseasRecordBo;
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.query.RsOverseasRecordQuery;
import org.dromara.rs.service.IRsOverseasRecordService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 出国记录Service业务层处理
*
* @author weizzzz
* @date 2025-07-28
*/
@Service
public class RsOverseasRecordServiceImpl implements IRsOverseasRecordService {
private static final Logger log = LoggerFactory.getLogger(RsOverseasRecordServiceImpl.class);
@Autowired
private RsOverseasRecordMapper baseMapper;
@Autowired
private RsOverseasApplicationMapper applicationMapper;
/**
* 查询出国记录分页列表
*/
@Override
public TableDataInfo<RsOverseasRecordVo> queryPageList(RsOverseasRecordQuery query) {
Page<RsOverseasRecordVo> result = baseMapper.selectVoPage(query.build(), this.buildQueryWrapper(query));
return TableDataInfo.build(result);
}
/**
* 查询出国记录列表
*/
@Override
public List<RsOverseasRecordVo> queryList(RsOverseasRecordQuery query) {
return baseMapper.selectOverseasRecordList(query);
}
/**
* 查询出国记录详细信息
*/
@Override
public RsOverseasRecordVo queryById(Long recordId) {
return baseMapper.selectOverseasRecordById(recordId);
}
/**
* 根据申请ID查询出国记录
*/
@Override
public RsOverseasRecordVo queryByApplicationId(Long applicationId) {
return baseMapper.selectOverseasRecordByApplicationId(applicationId);
}
/**
* 根据申请人ID查询出国记录列表
*/
@Override
public List<RsOverseasRecordVo> queryByApplicantId(Long applicantId) {
return baseMapper.selectOverseasRecordsByApplicantId(applicantId);
}
/**
* 查询我的出国记录分页列表(联合查询:记录+已通过申请)
*/
@Override
public TableDataInfo<RsOverseasRecordVo> queryMyRecordPageList(RsOverseasRecordQuery query) {
// 使用联合查询方法,包含已通过审批的申请数据
List<RsOverseasRecordVo> list = baseMapper.selectMyOverseasRecordList(query);
// 手动分页处理
int pageNum = query.getPageNum();
int pageSize = query.getPageSize();
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, list.size());
List<RsOverseasRecordVo> pageList;
if (start > list.size()) {
pageList = Arrays.asList();
} else {
pageList = list.subList(start, end);
}
// 构建分页对象
Page<RsOverseasRecordVo> page = new Page<>(pageNum, pageSize);
page.setRecords(pageList);
page.setTotal(list.size());
return TableDataInfo.build(page);
}
/**
* 新增出国记录
*/
@Override
@Transactional
public Boolean insertByBo(RsOverseasRecordBo bo) {
RsOverseasRecord add = buildEntityFromBo(bo);
validEntityBeforeSave(add);
return baseMapper.insert(add) > 0;
}
/**
* 修改出国记录
*/
@Override
@Transactional
public Boolean updateByBo(RsOverseasRecordBo bo) {
RsOverseasRecord update = buildEntityFromBo(bo);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 基于申请信息创建出国记录
*/
@Override
@Transactional
public Boolean createRecordFromApplication(Long applicationId) {
log.info("开始为申请ID {} 创建出国记录", applicationId);
try {
RsOverseasApplicationVo application = applicationMapper.selectOverseasApplicationById(applicationId);
if (application == null) {
log.error("申请不存在,申请ID: {}", applicationId);
throw new RuntimeException("申请不存在");
}
// 修复逻辑:允许待审核状态(0)或已通过状态(1)创建记录
// 因为在审批通过流程中,会先创建记录再更新状态为通过
log.info("申请状态检查,申请ID: {}, 当前状态: {}, 申请人: {}",
applicationId, application.getStatus(), application.getApplicantName());
if (!"0".equals(application.getStatus()) && !"1".equals(application.getStatus())) {
log.error("申请状态不正确,只有待审核或已通过的申请才能创建记录,申请ID: {}, 当前状态: {}", applicationId, application.getStatus());
throw new RuntimeException("申请状态不正确,无法创建出国记录");
}
// 检查是否已存在记录
RsOverseasRecordVo existingRecord = baseMapper.selectOverseasRecordByApplicationId(applicationId);
if (existingRecord != null) {
log.warn("申请ID {} 已存在记录,跳过创建,记录ID: {}", applicationId, existingRecord.getRecordId());
return true;
}
RsOverseasRecord record = new RsOverseasRecord();
record.setApplicationId(applicationId);
record.setApplicantId(application.getApplicantId());
record.setApplicantName(application.getApplicantName());
record.setDepartment(application.getDepartment());
record.setContactPhone(application.getContactPhone());
record.setPurpose(application.getPurpose());
record.setPurposeDetail(application.getPurposeDetail());
record.setDestination(application.getDestination());
record.setDestinationCity(application.getDestinationCity());
record.setActualStartDate(application.getStartDate());
record.setActualEndDate(application.getEndDate());
record.setActualDuration(application.getDuration());
record.setActualBudget(application.getBudget());
record.setInvitation(application.getInvitation());
record.setItinerary(application.getItinerary());
record.setAccommodationPlan(application.getAccommodationPlan());
record.setTransportationPlan(application.getTransportationPlan());
// 修复:审批通过的申请创建记录时应该设置为已完成状态
// 因为审批通过意味着出国活动已经完成
record.setCompletionStatus("1"); // 设置为已完成
log.info("准备插入出国记录,申请ID: {}, 申请人: {}, 目的地: {}, 状态: 已完成",
applicationId, application.getApplicantName(), application.getDestination());
boolean insertResult = baseMapper.insert(record) > 0;
if (insertResult) {
log.info("成功创建出国记录,申请ID: {}, 记录ID: {}, 申请人: {}",
applicationId, record.getRecordId(), application.getApplicantName());
} else {
log.error("插入出国记录失败,申请ID: {}, 申请人: {}", applicationId, application.getApplicantName());
}
return insertResult;
} catch (Exception e) {
log.error("创建出国记录异常,申请ID: {}, 错误: {}", applicationId, e.getMessage(), e);
throw e; // 重新抛出异常,让上层处理
}
}
/**
* 更新出国记录完成状态
*/
@Override
@Transactional
public Boolean updateCompletionStatus(Long recordId, String completionStatus,
String achievement, String reportContent) {
RsOverseasRecord record = baseMapper.selectById(recordId);
if (record == null) {
throw new RuntimeException("记录不存在");
}
record.setCompletionStatus(completionStatus);
record.setAchievement(achievement);
record.setReportContent(reportContent);
// 如果是完成状态,更新对应申请状态为已完成
if ("1".equals(completionStatus)) {
RsOverseasApplication application = applicationMapper.selectById(record.getApplicationId());
if (application != null) {
application.setStatus("4"); // 已完成
applicationMapper.updateById(application);
}
}
return baseMapper.updateById(record) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(RsOverseasRecord entity) {
if (entity.getActualStartDate() != null && entity.getActualEndDate() != null) {
if (entity.getActualStartDate().after(entity.getActualEndDate())) {
throw new RuntimeException("实际开始日期不能晚于结束日期");
}
// 自动计算实际天数
long diffInMillies = entity.getActualEndDate().getTime() - entity.getActualStartDate().getTime();
int days = (int) (diffInMillies / (1000 * 60 * 60 * 24)) + 1;
entity.setActualDuration(days);
}
if (entity.getActualBudget() != null && entity.getActualBudget().doubleValue() < 0) {
throw new RuntimeException("实际花费金额不能为负数");
}
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<RsOverseasRecord> buildQueryWrapper(RsOverseasRecordQuery query) {
LambdaQueryWrapper<RsOverseasRecord> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(query.getApplicantName()), RsOverseasRecord::getApplicantName, query.getApplicantName());
lqw.like(StringUtils.isNotBlank(query.getDepartment()), RsOverseasRecord::getDepartment, query.getDepartment());
lqw.eq(StringUtils.isNotBlank(query.getPurpose()), RsOverseasRecord::getPurpose, query.getPurpose());
lqw.like(StringUtils.isNotBlank(query.getDestination()), RsOverseasRecord::getDestination, query.getDestination());
lqw.eq(StringUtils.isNotBlank(query.getCompletionStatus()), RsOverseasRecord::getCompletionStatus, query.getCompletionStatus());
lqw.eq(query.getApplicantId() != null, RsOverseasRecord::getApplicantId, query.getApplicantId());
lqw.eq(query.getApplicationId() != null, RsOverseasRecord::getApplicationId, query.getApplicationId());
lqw.ge(query.getActualStartDateBegin() != null, RsOverseasRecord::getActualStartDate, query.getActualStartDateBegin());
lqw.le(query.getActualStartDateEnd() != null, RsOverseasRecord::getActualStartDate, query.getActualStartDateEnd());
lqw.ge(query.getActualEndDateBegin() != null, RsOverseasRecord::getActualEndDate, query.getActualEndDateBegin());
lqw.le(query.getActualEndDateEnd() != null, RsOverseasRecord::getActualEndDate, query.getActualEndDateEnd());
return lqw;
}
/**
* 从BO构建Entity
*/
private RsOverseasRecord buildEntityFromBo(RsOverseasRecordBo bo) {
RsOverseasRecord entity = new RsOverseasRecord();
entity.setRecordId(bo.getRecordId());
entity.setApplicationId(bo.getApplicationId());
entity.setApplicantId(bo.getApplicantId());
entity.setApplicantName(bo.getApplicantName());
entity.setDepartment(bo.getDepartment());
entity.setContactPhone(bo.getContactPhone());
entity.setPurpose(bo.getPurpose());
entity.setPurposeDetail(bo.getPurposeDetail());
entity.setDestination(bo.getDestination());
entity.setDestinationCity(bo.getDestinationCity());
entity.setActualStartDate(bo.getActualStartDate());
entity.setActualEndDate(bo.getActualEndDate());
entity.setActualDuration(bo.getActualDuration());
entity.setActualBudget(bo.getActualBudget());
entity.setInvitation(bo.getInvitation());
entity.setItinerary(bo.getItinerary());
entity.setAccommodationPlan(bo.getAccommodationPlan());
entity.setTransportationPlan(bo.getTransportationPlan());
entity.setCompletionStatus(bo.getCompletionStatus());
entity.setAchievement(bo.getAchievement());
entity.setReportContent(bo.getReportContent());
entity.setCertificatePath(bo.getCertificatePath());
return entity;
}
/**
* 校验删除数据
*/
@Override
public void validatedByIds(List<Long> ids, Boolean isValid) {
if (isValid) {
// 校验是否存在关联数据
for (Long id : ids) {
RsOverseasRecord record = baseMapper.selectById(id);
if (record != null && "1".equals(record.getCompletionStatus())) {
throw new RuntimeException("已完成的记录不能删除");
}
}
}
}
/**
* 批量删除出国记录
*/
@Override
@Transactional
public Boolean deleteWithValidByIds(List<Long> ids, Boolean isValid) {
validatedByIds(ids, isValid);
return baseMapper.deleteBatchIds(ids) > 0;
}
}
@@ -0,0 +1,439 @@
package org.dromara.rs.service.impl;
import java.util.Arrays;
import java.util.List;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.rs.mapper.RsTrainingApplicationMapper;
import org.dromara.rs.mapper.RsTrainingCourseMapper;
import org.dromara.rs.domain.RsTrainingApplication;
import org.dromara.rs.domain.RsTrainingCourse;
import org.dromara.rs.service.IRsTrainingApplicationService;
import org.dromara.rs.service.IRsTrainingRecordService;
import org.dromara.rs.domain.RsTrainingRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 培训申请服务实现类
*
* @author weizzzz
* @date 2025-07-16
*/
@Service
public class RsTrainingApplicationServiceImpl implements IRsTrainingApplicationService {
private static final Logger log = LoggerFactory.getLogger(RsTrainingApplicationServiceImpl.class);
@Autowired
private RsTrainingApplicationMapper trainingApplicationMapper;
@Autowired
private RsTrainingCourseMapper trainingCourseMapper;
@Autowired
private IRsTrainingRecordService trainingRecordService;
/**
* 查询培训申请列表
*
* @param application 培训申请
* @param pageQuery 分页参数
* @return 培训申请分页列表
*/
@Override
public TableDataInfo<RsTrainingApplication> selectPageRsTrainingApplicationList(RsTrainingApplication application, PageQuery pageQuery) {
// 创建分页对象
Page<RsTrainingApplication> page = pageQuery.build();
// 获取分页参数
long current = page.getCurrent();
long size = page.getSize();
// 计算偏移量
long offset = (current - 1) * size;
// 设置租户ID
if (application.getTenantId() == null || application.getTenantId().isEmpty()) {
application.setTenantId("000000");
}
// 查询总数
long total = trainingApplicationMapper.selectCount(buildQueryWrapper(application));
// 使用自定义分页查询获取列表数据
List<RsTrainingApplication> list = trainingApplicationMapper.selectRsTrainingApplicationListPage(application, offset, size);
// 处理自定义培训课程(courseId=0)的情况
for (RsTrainingApplication app : list) {
if (app.getCourseId() != null && app.getCourseId() == 0 && (app.getCourseName() == null || app.getCourseName().isEmpty())) {
app.setCourseName("自定义培训");
}
}
// 构建分页对象
page.setRecords(list);
page.setTotal(total);
return TableDataInfo.build(page);
}
/**
* 查询培训申请列表
*
* @param application 培训申请
* @return 培训申请集合
*/
@Override
public List<RsTrainingApplication> selectRsTrainingApplicationList(RsTrainingApplication application) {
// 使用自定义的Mapper方法查询,已经关联了课程表获取课程名称
List<RsTrainingApplication> list = trainingApplicationMapper.selectRsTrainingApplicationList(application);
// 处理自定义培训课程(courseId=0)的情况
for (RsTrainingApplication app : list) {
if (app.getCourseId() != null && app.getCourseId() == 0 && (app.getCourseName() == null || app.getCourseName().isEmpty())) {
app.setCourseName("自定义培训");
}
}
return list;
}
/**
* 查询指定课程的培训申请列表
*
* @param courseId 课程ID
* @param application 培训申请
* @param pageQuery 分页参数
* @return 培训申请分页列表
*/
@Override
public TableDataInfo<RsTrainingApplication> selectPageApplicationsByCourse(Long courseId, RsTrainingApplication application, PageQuery pageQuery) {
LambdaQueryWrapper<RsTrainingApplication> lqw = buildQueryWrapper(application);
lqw.eq(RsTrainingApplication::getCourseId, courseId);
return TableDataInfo.build(trainingApplicationMapper.selectPage(pageQuery.build(), lqw));
}
/**
* 查询我的培训申请列表
*
* @param userId 用户ID
* @param application 培训申请
* @param pageQuery 分页参数
* @return 培训申请分页列表
*/
@Override
public TableDataInfo<RsTrainingApplication> selectPageMyApplications(Long userId, RsTrainingApplication application, PageQuery pageQuery) {
LambdaQueryWrapper<RsTrainingApplication> lqw = buildQueryWrapper(application);
lqw.eq(RsTrainingApplication::getUserId, userId);
return TableDataInfo.build(trainingApplicationMapper.selectPage(pageQuery.build(), lqw));
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<RsTrainingApplication> buildQueryWrapper(RsTrainingApplication application) {
LambdaQueryWrapper<RsTrainingApplication> lqw = new LambdaQueryWrapper<>();
// 排除已审批完成的数据:1-已通过,3-进行中,4-已完成
// 只保留:0-待审核,2-已拒绝
lqw.notIn(RsTrainingApplication::getStatus, "1", "3", "4");
lqw.like(application.getUserName() != null, RsTrainingApplication::getUserName, application.getUserName());
lqw.eq(application.getCourseId() != null, RsTrainingApplication::getCourseId, application.getCourseId());
lqw.eq(application.getUserId() != null, RsTrainingApplication::getUserId, application.getUserId());
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());
return lqw;
}
/**
* 查询培训申请详细信息
*
* @param applicationId 培训申请ID
* @return 培训申请
*/
@Override
public RsTrainingApplication selectRsTrainingApplicationById(Long applicationId) {
// 使用自定义的Mapper方法查询,已经关联了课程表获取课程名称
RsTrainingApplication application = trainingApplicationMapper.selectRsTrainingApplicationById(applicationId);
// 处理自定义培训课程(courseId=0)的情况
if (application != null && application.getCourseId() != null && application.getCourseId() == 0
&& (application.getCourseName() == null || application.getCourseName().isEmpty())) {
application.setCourseName("自定义培训");
}
return application;
}
/**
* 新增培训申请
*
* @param application 培训申请
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int insertRsTrainingApplication(RsTrainingApplication application) {
try {
// 确保applicationId不为空
if (application.getApplicationId() == null) {
application.setApplicationId(System.currentTimeMillis());
}
// 设置默认值,防止空指针异常
if (application.getUserId() == null) {
application.setUserId(1L); // 默认用户ID
}
if (application.getUserName() == null || application.getUserName().isEmpty()) {
application.setUserName("默认用户");
}
// 处理自定义培训课程的情况(courseId = 0 或 null)
if (application.getCourseId() == null || application.getCourseId() == 0) {
// 对于自定义培训课程,不需要验证课程和更新课程报名人数
application.setCourseId(0L); // 确保courseId为0L而不是null
// 确保courseName不为空
if (application.getCourseName() == null || application.getCourseName().isEmpty()) {
application.setCourseName("自定义培训");
}
application.setCreateTime(DateUtils.getNowDate());
application.setApplyTime(DateUtils.getNowDate());
application.setStatus("0"); // 待审核状态
application.setTenantId("000000"); // 设置默认租户ID
return trainingApplicationMapper.insert(application);
}
// 1. 验证课程是否存在并可申请
RsTrainingCourse course = trainingCourseMapper.selectById(application.getCourseId());
if (course == null) {
return 0;
}
// 2. 检查是否已达到最大参与人数
if (course.getCurrentParticipants() >= course.getMaxParticipants()) {
return 0;
}
// 3. 更新课程当前报名人数
course.setCurrentParticipants(course.getCurrentParticipants() + 1);
trainingCourseMapper.updateById(course);
// 4. 保存申请信息
application.setCreateTime(DateUtils.getNowDate());
application.setApplyTime(DateUtils.getNowDate());
application.setStatus("0"); // 待审核状态
application.setTenantId("000000"); // 设置默认租户ID
return trainingApplicationMapper.insert(application);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
/**
* 修改培训申请
*
* @param application 培训申请
* @return 结果
*/
@Override
public int updateRsTrainingApplication(RsTrainingApplication application) {
application.setUpdateTime(DateUtils.getNowDate());
return trainingApplicationMapper.updateById(application);
}
/**
* 批量删除培训申请
*
* @param applicationIds 需要删除的培训申请ID数组
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteRsTrainingApplicationByIds(Long[] applicationIds) {
// 查询所有要删除的申请
List<RsTrainingApplication> applications = trainingApplicationMapper.selectBatchIds(Arrays.asList(applicationIds));
// 对每个申请,减少对应课程的当前报名人数
for (RsTrainingApplication app : applications) {
RsTrainingCourse course = trainingCourseMapper.selectById(app.getCourseId());
if (course != null && course.getCurrentParticipants() > 0) {
course.setCurrentParticipants(course.getCurrentParticipants() - 1);
trainingCourseMapper.updateById(course);
}
}
return trainingApplicationMapper.deleteBatchIds(Arrays.asList(applicationIds));
}
/**
* 删除培训申请信息
*
* @param applicationId 培训申请ID
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteRsTrainingApplicationById(Long applicationId) {
// 查询申请
RsTrainingApplication application = trainingApplicationMapper.selectById(applicationId);
if (application != null) {
// 减少课程的当前报名人数
RsTrainingCourse course = trainingCourseMapper.selectById(application.getCourseId());
if (course != null && course.getCurrentParticipants() > 0) {
course.setCurrentParticipants(course.getCurrentParticipants() - 1);
trainingCourseMapper.updateById(course);
}
}
return trainingApplicationMapper.deleteById(applicationId);
}
/**
* 取消培训申请
*
* @param applicationId 培训申请ID
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int cancelApplication(Long applicationId) {
// 查询申请
RsTrainingApplication application = trainingApplicationMapper.selectById(applicationId);
if (application != null) {
// 只有待审核状态的申请才能取消
if ("0".equals(application.getStatus())) {
// 更新状态为已取消
application.setStatus("3");
application.setUpdateTime(DateUtils.getNowDate());
// 减少课程的当前报名人数
RsTrainingCourse course = trainingCourseMapper.selectById(application.getCourseId());
if (course != null && course.getCurrentParticipants() > 0) {
course.setCurrentParticipants(course.getCurrentParticipants() - 1);
trainingCourseMapper.updateById(course);
}
return trainingApplicationMapper.updateById(application);
}
}
return 0;
}
/**
* 更新申请状态
*
* @param applicationId 培训申请ID
* @param status 状态
* @return 结果
*/
@Override
public int updateApplicationStatus(Long applicationId, String status) {
RsTrainingApplication application = trainingApplicationMapper.selectById(applicationId);
if (application != null) {
application.setStatus(status);
application.setUpdateTime(DateUtils.getNowDate());
// 如果是拒绝,记录被拒绝时间
if ("2".equals(status)) {
application.setRejectTime(DateUtils.getNowDate());
}
int result = trainingApplicationMapper.updateById(application);
// 同步到培训记录表:状态为"1已通过""3进行中""4已完成"时自动写入
if ("1".equals(status) || "3".equals(status) || "4".equals(status)) {
log.info("【同步培训记录】触发,applicationId={}, status={}", applicationId, status);
RsTrainingRecord exist = trainingRecordService.selectByApplicationId(applicationId);
if (exist == null) {
log.info("【同步培训记录】插入新记录,userId={}, courseId={}", application.getUserId(), application.getCourseId());
RsTrainingRecord record = new RsTrainingRecord();
record.setApplicationId(application.getApplicationId());
record.setCourseId(application.getCourseId());
record.setUserId(application.getUserId());
record.setUserName(application.getUserName());
record.setTenantId(application.getTenantId());
record.setDepartment(application.getDepartment());
record.setPosition(application.getPosition());
record.setReason(application.getReason());
record.setStatus(application.getStatus());
record.setApplyTime(application.getApplyTime());
record.setCurrentApprover(application.getCurrentApprover());
record.setLocation(application.getLocation());
record.setDuration(application.getDuration());
record.setStartTime(application.getStartTime());
record.setEndTime(application.getEndTime());
record.setTenantId(application.getTenantId());
record.setCreateDept(application.getCreateDept());
record.setCourseName(application.getCourseName());
trainingRecordService.insertRsTrainingRecord(record);
} else {
log.info("【同步培训记录】已存在,不重复插入,applicationId={}", applicationId);
}
}
return result;
}
return 0;
}
/**
* 重新提交已被拒绝的申请
*
* @param applicationId 培训申请ID
* @param applicationData 培训申请更新数据
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int resubmitApplication(Long applicationId, RsTrainingApplication applicationData) {
// 查询原申请
RsTrainingApplication existingApp = trainingApplicationMapper.selectById(applicationId);
if (existingApp == null) {
return 0;
}
// 只有状态为已拒绝的申请才能重新提交
if (!"2".equals(existingApp.getStatus())) {
return 0;
}
log.info("【重新提交申请】原申请状态: {}, 申请ID: {}", existingApp.getStatus(), applicationId);
// 更新申请数据
if (applicationData.getReason() != null) {
existingApp.setReason(applicationData.getReason());
}
if (applicationData.getLocation() != null) {
existingApp.setLocation(applicationData.getLocation());
}
if (applicationData.getDuration() != null) {
existingApp.setDuration(applicationData.getDuration());
}
if (applicationData.getStartTime() != null) {
existingApp.setStartTime(applicationData.getStartTime());
}
if (applicationData.getEndTime() != null) {
existingApp.setEndTime(applicationData.getEndTime());
}
if (applicationData.getCourseName() != null) {
existingApp.setCourseName(applicationData.getCourseName());
}
// 重置为待审核状态
existingApp.setStatus("0");
existingApp.setUpdateTime(DateUtils.getNowDate());
existingApp.setApplyTime(DateUtils.getNowDate()); // 更新申请时间
log.info("【重新提交申请】更新为待审核状态");
return trainingApplicationMapper.updateById(existingApp);
}
}
@@ -0,0 +1,252 @@
package org.dromara.rs.service.impl;
import java.util.Arrays;
import java.util.List;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.rs.mapper.RsTrainingApprovalMapper;
import org.dromara.rs.mapper.RsTrainingApplicationMapper;
import org.dromara.rs.domain.RsTrainingApproval;
import org.dromara.rs.domain.RsTrainingApplication;
import org.dromara.rs.service.IRsTrainingApprovalService;
import org.dromara.rs.service.IRsTrainingApplicationService;
/**
* 培训审批服务实现类
*
* @author weizzzz
* @date 2025-07-16
*/
@Service
public class RsTrainingApprovalServiceImpl implements IRsTrainingApprovalService {
@Autowired
private RsTrainingApprovalMapper trainingApprovalMapper;
@Autowired
private RsTrainingApplicationMapper trainingApplicationMapper;
@Autowired
private IRsTrainingApplicationService trainingApplicationService;
/**
* 查询培训审批列表
*
* @param approval 培训审批
* @param pageQuery 分页参数
* @return 培训审批分页列表
*/
@Override
public TableDataInfo<RsTrainingApproval> selectPageRsTrainingApprovalList(RsTrainingApproval approval, PageQuery pageQuery) {
com.baomidou.mybatisplus.extension.plugins.pagination.Page<RsTrainingApproval> page = trainingApprovalMapper.selectPage(pageQuery.build(), buildQueryWrapper(approval));
return TableDataInfo.build(page);
}
/**
* 查询培训审批列表
*
* @param approval 培训审批
* @return 培训审批列表
*/
@Override
public List<RsTrainingApproval> selectRsTrainingApprovalList(RsTrainingApproval approval) {
return trainingApprovalMapper.selectList(buildQueryWrapper(approval));
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<RsTrainingApproval> buildQueryWrapper(RsTrainingApproval approval) {
LambdaQueryWrapper<RsTrainingApproval> lqw = new LambdaQueryWrapper<>();
lqw.eq(approval.getApplicationId() != null, RsTrainingApproval::getApplicationId, approval.getApplicationId());
lqw.eq(approval.getApproverId() != null, RsTrainingApproval::getApproverId, approval.getApproverId());
lqw.eq(approval.getApprovalLevel() != null, RsTrainingApproval::getApprovalLevel, approval.getApprovalLevel());
lqw.eq(approval.getStatus() != null, RsTrainingApproval::getStatus, approval.getStatus());
return lqw;
}
/**
* 查询指定申请的审批历史
*
* @param applicationId 申请ID
* @return 培训审批列表
*/
@Override
public List<RsTrainingApproval> selectApprovalHistoryByApplicationId(Long applicationId) {
LambdaQueryWrapper<RsTrainingApproval> lqw = new LambdaQueryWrapper<>();
lqw.eq(RsTrainingApproval::getApplicationId, applicationId);
// 逻辑删除在MyBatis-Plus中自动处理,不需要显式添加条件
lqw.orderByAsc(RsTrainingApproval::getApprovalLevel);
lqw.orderByDesc(RsTrainingApproval::getApprovalTime); // 按审批时间降序,最新的在前面
System.out.println("查询审批历史SQL条件 - applicationId: " + applicationId);
return trainingApprovalMapper.selectList(lqw);
}
/**
* 查询待我审批的列表
*
* @param approverId 审批人ID
* @param approval 培训审批
* @param pageQuery 分页参数
* @return 培训审批分页列表
*/
@Override
public TableDataInfo<RsTrainingApproval> selectPagePendingApprovals(Long approverId, RsTrainingApproval approval, PageQuery pageQuery) {
LambdaQueryWrapper<RsTrainingApproval> lqw = buildQueryWrapper(approval);
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);
}
/**
* 查询培训审批详细信息
*
* @param approvalId 培训审批ID
* @return 培训审批
*/
@Override
public RsTrainingApproval selectRsTrainingApprovalById(Long approvalId) {
return trainingApprovalMapper.selectById(approvalId);
}
/**
* 新增培训审批
*
* @param approval 培训审批
* @return 结果
*/
@Override
public int insertRsTrainingApproval(RsTrainingApproval approval) {
approval.setCreateTime(DateUtils.getNowDate());
return trainingApprovalMapper.insert(approval);
}
/**
* 修改培训审批
*
* @param approval 培训审批
* @return 结果
*/
@Override
public int updateRsTrainingApproval(RsTrainingApproval approval) {
approval.setUpdateTime(DateUtils.getNowDate());
return trainingApprovalMapper.updateById(approval);
}
/**
* 查询带详细信息的审批记录列表(关联申请人和课程信息)
*
* @param approval 培训审批
* @param pageQuery 分页参数
* @return 培训审批分页列表
*/
@Override
public TableDataInfo<RsTrainingApproval> selectPageRsTrainingApprovalWithDetailsList(RsTrainingApproval approval, PageQuery pageQuery) {
// 创建分页对象
com.baomidou.mybatisplus.extension.plugins.pagination.Page<RsTrainingApproval> page = pageQuery.build();
// 获取分页参数
long current = page.getCurrent();
long size = page.getSize();
// 计算偏移量
long offset = (current - 1) * size;
// 设置租户ID
if (approval.getTenantId() == null || approval.getTenantId().isEmpty()) {
approval.setTenantId("000000");
}
// 查询总数
long total = trainingApprovalMapper.selectCount(buildQueryWrapper(approval));
// 使用自定义分页查询获取列表数据
List<RsTrainingApproval> list = trainingApprovalMapper.selectRsTrainingApprovalWithDetailsList(approval);
// 手动分页处理
int start = (int) offset;
int end = Math.min(start + (int) size, list.size());
if (start > list.size()) {
list = Arrays.asList();
} else {
list = list.subList(start, end);
}
// 构建分页对象
page.setRecords(list);
page.setTotal(total);
return TableDataInfo.build(page);
}
/**
* 批量删除培训审批
*
* @param approvalIds 需要删除的培训审批ID数组
* @return 结果
*/
@Override
public int deleteRsTrainingApprovalByIds(Long[] approvalIds) {
return trainingApprovalMapper.deleteBatchIds(Arrays.asList(approvalIds));
}
/**
* 删除培训审批信息
*
* @param approvalId 培训审批ID
* @return 结果
*/
@Override
public int deleteRsTrainingApprovalById(Long approvalId) {
return trainingApprovalMapper.deleteById(approvalId);
}
/**
* 处理审批
*
* @param applicationId 申请ID
* @param status 审批状态
* @param opinion 审批意见
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int processApproval(Long applicationId, String status, String opinion) {
// 获取申请信息
RsTrainingApplication application = trainingApplicationMapper.selectById(applicationId);
if (application == null) {
return 0;
}
// 获取当前用户信息
Long userId = LoginHelper.getUserId();
String userName = LoginHelper.getUsername();
// 创建审批记录 - MyBatis-Plus自动生成雪花ID
RsTrainingApproval approval = new RsTrainingApproval();
// 不需要手动设置ID,MyBatis-Plus会自动生成雪花ID
approval.setApplicationId(applicationId);
approval.setApproverId(userId);
approval.setApproverName(userName);
approval.setApprovalLevel(1); // 假设默认是第一级审批
approval.setStatus(status); // 审批状态:1-通过,2-拒绝
approval.setOpinion(opinion); // 审批意见
approval.setApprovalTime(DateUtils.getNowDate());
approval.setCreateTime(DateUtils.getNowDate());
// 插入审批记录 - 无论通过还是拒绝,都创建独立审批记录
int result = trainingApprovalMapper.insert(approval);
System.out.println("创建审批记录 - applicationId: " + applicationId + ", status: " + status + ", result: " + result);
// 更新申请状态
trainingApplicationService.updateApplicationStatus(applicationId, status);
return result;
}
}
@@ -0,0 +1,138 @@
package org.dromara.rs.service.impl;
import java.util.Arrays;
import java.util.List;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.dromara.rs.mapper.RsTrainingCourseMapper;
import org.dromara.rs.domain.RsTrainingCourse;
import org.dromara.rs.service.IRsTrainingCourseService;
/**
* 培训课程服务实现类
*
* @author weizzzz
* @date 2025-07-16
*/
@Service
public class RsTrainingCourseServiceImpl implements IRsTrainingCourseService {
@Autowired
private RsTrainingCourseMapper trainingCourseMapper;
/**
* 查询培训课程列表
*
* @param course 培训课程
* @param pageQuery 分页参数
* @return 培训课程分页列表
*/
@Override
public TableDataInfo<RsTrainingCourse> selectPageRsTrainingCourseList(RsTrainingCourse course, PageQuery pageQuery) {
com.baomidou.mybatisplus.extension.plugins.pagination.Page<RsTrainingCourse> page = trainingCourseMapper.selectPage(pageQuery.build(), buildQueryWrapper(course));
return TableDataInfo.build(page);
}
/**
* 查询培训课程列表
*
* @param course 培训课程
* @return 培训课程列表
*/
@Override
public List<RsTrainingCourse> selectRsTrainingCourseList(RsTrainingCourse course) {
return trainingCourseMapper.selectList(buildQueryWrapper(course));
}
/**
* 查询培训课程详细信息
*
* @param courseId 培训课程ID
* @return 培训课程
*/
@Override
public RsTrainingCourse selectRsTrainingCourseById(Long courseId) {
return trainingCourseMapper.selectById(courseId);
}
/**
* 新增培训课程
*
* @param course 培训课程
* @return 结果
*/
@Override
public int insertRsTrainingCourse(RsTrainingCourse course) {
course.setCreateTime(DateUtils.getNowDate());
return trainingCourseMapper.insert(course);
}
/**
* 修改培训课程
*
* @param course 培训课程
* @return 结果
*/
@Override
public int updateRsTrainingCourse(RsTrainingCourse course) {
course.setUpdateTime(DateUtils.getNowDate());
return trainingCourseMapper.updateById(course);
}
/**
* 批量删除培训课程
*
* @param courseIds 需要删除的培训课程ID数组
* @return 结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteRsTrainingCourseByIds(Long[] courseIds) {
return trainingCourseMapper.deleteBatchIds(Arrays.asList(courseIds));
}
/**
* 删除培训课程信息
*
* @param courseId 培训课程ID
* @return 结果
*/
@Override
public int deleteRsTrainingCourseById(Long courseId) {
return trainingCourseMapper.deleteById(courseId);
}
/**
* 切换培训课程状态
*
* @param courseId 培训课程ID
* @return 结果
*/
@Override
public int toggleCourseStatus(Long courseId) {
RsTrainingCourse course = trainingCourseMapper.selectById(courseId);
if (course != null) {
// 切换状态:0正常 -> 1停用,或 1停用 -> 0正常
course.setStatus("0".equals(course.getStatus()) ? "1" : "0");
course.setUpdateTime(DateUtils.getNowDate());
return trainingCourseMapper.updateById(course);
}
return 0;
}
/**
* 构建查询条件
*/
private com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<RsTrainingCourse> buildQueryWrapper(RsTrainingCourse course) {
com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<RsTrainingCourse> lqw = new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
lqw.like(course.getTitle() != null, RsTrainingCourse::getTitle, course.getTitle());
lqw.eq(course.getCategory() != null, RsTrainingCourse::getCategory, course.getCategory());
lqw.eq(course.getStatus() != null, RsTrainingCourse::getStatus, course.getStatus());
return lqw;
}
}
@@ -0,0 +1,187 @@
package org.dromara.rs.service.impl;
import java.util.Arrays;
import java.util.List;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.rs.mapper.RsTrainingRecordMapper;
import org.dromara.rs.domain.RsTrainingRecord;
import org.dromara.rs.service.IRsTrainingRecordService;
/**
* 培训记录服务实现类
*
* @author weizzzz
* @date 2025-07-16
*/
@Service
public class RsTrainingRecordServiceImpl implements IRsTrainingRecordService {
@Autowired
private RsTrainingRecordMapper trainingRecordMapper;
/**
* 查询培训记录列表
*
* @param record 培训记录
* @param pageQuery 分页参数
* @return 培训记录分页列表
*/
@Override
public TableDataInfo<RsTrainingRecord> selectPageRsTrainingRecordList(RsTrainingRecord record, PageQuery pageQuery) {
return TableDataInfo.build(trainingRecordMapper.selectPage(pageQuery.build(), buildQueryWrapper(record)));
}
/**
* 查询培训记录列表
*
* @param record 培训记录
* @return 培训记录列表
*/
@Override
public List<RsTrainingRecord> selectRsTrainingRecordList(RsTrainingRecord record) {
return trainingRecordMapper.selectList(buildQueryWrapper(record));
}
/**
* 查询我的培训记录列表
*
* @param userId 用户ID
* @param record 培训记录
* @param pageQuery 分页参数
* @return 培训记录分页列表
*/
@Override
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));
}
/**
* 查询培训记录详细信息
*
* @param recordId 培训记录ID
* @return 培训记录
*/
@Override
public RsTrainingRecord selectRsTrainingRecordById(Long recordId) {
return trainingRecordMapper.selectById(recordId);
}
/**
* 新增培训记录
*
* @param record 培训记录
* @return 结果
*/
@Override
public int insertRsTrainingRecord(RsTrainingRecord record) {
record.setCreateTime(DateUtils.getNowDate());
return trainingRecordMapper.insert(record);
}
/**
* 修改培训记录
*
* @param record 培训记录
* @return 结果
*/
@Override
public int updateRsTrainingRecord(RsTrainingRecord record) {
record.setUpdateTime(DateUtils.getNowDate());
return trainingRecordMapper.updateById(record);
}
/**
* 批量删除培训记录
*
* @param recordIds 需要删除的培训记录ID数组
* @return 结果
*/
@Override
public int deleteRsTrainingRecordByIds(Long[] recordIds) {
return trainingRecordMapper.deleteBatchIds(Arrays.asList(recordIds));
}
/**
* 删除培训记录信息
*
* @param recordId 培训记录ID
* @return 结果
*/
@Override
public int deleteRsTrainingRecordById(Long recordId) {
return trainingRecordMapper.deleteById(recordId);
}
/**
* 更新培训记录完成状态
*
* @param recordId 记录ID
* @param completionStatus 完成状态
* @return 结果
*/
@Override
public int updateCompletionStatus(Long recordId, String completionStatus) {
RsTrainingRecord record = trainingRecordMapper.selectById(recordId);
if (record != null) {
record.setCompletionStatus(completionStatus);
// 如果状态改为已完成,自动设置完成时间
if ("1".equals(completionStatus)) {
record.setCompletionTime(DateUtils.getNowDate());
}
record.setUpdateTime(DateUtils.getNowDate());
return trainingRecordMapper.updateById(record);
}
return 0;
}
/**
* 上传培训证书
*
* @param recordId 记录ID
* @param certificatePath 证书路径
* @param certificateNumber 证书编号
* @return 结果
*/
@Override
public int uploadCertificate(Long recordId, String certificatePath, String certificateNumber) {
RsTrainingRecord record = trainingRecordMapper.selectById(recordId);
if (record != null) {
record.setCertificatePath(certificatePath);
record.setCertificateNumber(certificateNumber);
record.setUpdateTime(DateUtils.getNowDate());
return trainingRecordMapper.updateById(record);
}
return 0;
}
/**
* 构建查询条件
*/
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 RsTrainingRecord selectByApplicationId(Long applicationId) {
LambdaQueryWrapper<RsTrainingRecord> lqw = new LambdaQueryWrapper<>();
lqw.eq(RsTrainingRecord::getApplicationId, applicationId);
return trainingRecordMapper.selectOne(lqw);
}
}
@@ -0,0 +1,32 @@
package org.dromara.rs.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.datasource.init.ScriptUtils;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* SQL脚本执行工具
*/
@Component
public class SqlScriptRunner {
@Autowired
private DataSource dataSource;
/**
* 执行SQL脚本
*
* @param scriptPath 脚本路径(相对于classpath)
* @throws SQLException SQL执行异常
*/
public void executeScript(String scriptPath) throws SQLException {
try (Connection connection = dataSource.getConnection()) {
ScriptUtils.executeSqlScript(connection, new ClassPathResource(scriptPath));
}
}
}
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsApplicationMapper">
<resultMap type="org.dromara.rs.domain.RsApplication" id="RsApplicationResult">
<id property="applicationId" column="application_id"/>
<result property="jobId" column="job_id"/>
<result property="applicantId" column="applicant_id"/>
<result property="applyTime" column="apply_time"/>
<result property="status" column="status"/>
<result property="selfDescription" column="self_description"/>
<result property="jobTitle" column="job_title"/>
<result property="applicantName" column="applicant_name"/>
<result property="gender" column="gender"/>
<result property="contactPhone" column="contact_phone"/>
<result property="contactEmail" column="contact_email"/>
<result property="age" column="age"/>
<result property="education" column="education"/>
<result property="educationBackground" column="education_background"/>
<result property="experience" column="experience"/>
<result property="hrNotes" column="hr_notes"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectRsApplicationVo">
select a.application_id, a.job_id, a.applicant_id, a.apply_time, a.status,
a.self_description, a.job_title, a.applicant_name, a.gender,
a.contact_phone, a.contact_email, a.age, a.education, a.education_background,
a.experience, a.hr_notes, a.create_by, a.create_time, a.update_by, a.update_time
from rs_application a
</sql>
<select id="selectRsApplicationList" parameterType="org.dromara.rs.domain.RsApplication" resultMap="RsApplicationResult">
<include refid="selectRsApplicationVo"/>
<where>
<if test="jobId != null ">and a.job_id = #{jobId}</if>
<if test="applicantId != null ">and a.applicant_id = #{applicantId}</if>
<if test="applyTime != null ">and a.apply_time = #{applyTime}</if>
<if test="status != null and status != ''">and a.status = #{status}</if>
<if test="jobTitle != null and jobTitle != ''">and a.job_title like concat('%', #{jobTitle}, '%')</if>
<if test="applicantName != null and applicantName != ''">and a.applicant_name like concat('%', #{applicantName}, '%')</if>
<if test="contactPhone != null and contactPhone != ''">and a.contact_phone like concat('%', #{contactPhone}, '%')</if>
<if test="contactEmail != null and contactEmail != ''">and a.contact_email like concat('%', #{contactEmail}, '%')</if>
</where>
</select>
<select id="selectRsApplicationById" parameterType="Long" resultMap="RsApplicationResult">
<include refid="selectRsApplicationVo"/>
where a.application_id = #{applicationId}
</select>
<select id="selectCountByJobId" parameterType="Long" resultType="Integer">
select count(1) from rs_application where job_id = #{jobId}
</select>
<select id="selectByJobIdAndApplicantId" resultMap="RsApplicationResult">
<include refid="selectRsApplicationVo"/>
where a.job_id = #{jobId} and a.applicant_id = #{applicantId}
</select>
<select id="selectByApplicantId" resultMap="RsApplicationResult">
<include refid="selectRsApplicationVo"/>
where a.applicant_id = #{applicantId}
</select>
<insert id="insertRsApplication" parameterType="org.dromara.rs.domain.RsApplication" useGeneratedKeys="true" keyProperty="applicationId">
insert into rs_application
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="jobId != null">job_id,</if>
<if test="applicantId != null">applicant_id,</if>
<if test="applyTime != null">apply_time,</if>
<if test="status != null and status != ''">status,</if>
<if test="selfDescription != null and selfDescription != ''">self_description,</if>
<if test="jobTitle != null and jobTitle != ''">job_title,</if>
<if test="applicantName != null and applicantName != ''">applicant_name,</if>
<if test="gender != null and gender != ''">gender,</if>
<if test="contactPhone != null and contactPhone != ''">contact_phone,</if>
<if test="contactEmail != null and contactEmail != ''">contact_email,</if>
<if test="age != null">age,</if>
<if test="education != null and education != ''">education,</if>
<if test="educationBackground != null and educationBackground != ''">education_background,</if>
<if test="experience != null and experience != ''">experience,</if>
<if test="hrNotes != null">hr_notes,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="jobId != null">#{jobId},</if>
<if test="applicantId != null">#{applicantId},</if>
<if test="applyTime != null">#{applyTime},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="selfDescription != null and selfDescription != ''">#{selfDescription},</if>
<if test="jobTitle != null and jobTitle != ''">#{jobTitle},</if>
<if test="applicantName != null and applicantName != ''">#{applicantName},</if>
<if test="gender != null and gender != ''">#{gender},</if>
<if test="contactPhone != null and contactPhone != ''">#{contactPhone},</if>
<if test="contactEmail != null and contactEmail != ''">#{contactEmail},</if>
<if test="age != null">#{age},</if>
<if test="education != null and education != ''">#{education},</if>
<if test="educationBackground != null and educationBackground != ''">#{educationBackground},</if>
<if test="experience != null and experience != ''">#{experience},</if>
<if test="hrNotes != null">#{hrNotes},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<update id="updateRsApplication" parameterType="org.dromara.rs.domain.RsApplication">
update rs_application
<trim prefix="SET" suffixOverrides=",">
<if test="jobId != null">job_id = #{jobId},</if>
<if test="applicantId != null">applicant_id = #{applicantId},</if>
<if test="applyTime != null">apply_time = #{applyTime},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="selfDescription != null and selfDescription != ''">self_description = #{selfDescription},</if>
<if test="jobTitle != null and jobTitle != ''">job_title = #{jobTitle},</if>
<if test="applicantName != null and applicantName != ''">applicant_name = #{applicantName},</if>
<if test="gender != null and gender != ''">gender = #{gender},</if>
<if test="contactPhone != null and contactPhone != ''">contact_phone = #{contactPhone},</if>
<if test="contactEmail != null and contactEmail != ''">contact_email = #{contactEmail},</if>
<if test="age != null">age = #{age},</if>
<if test="education != null and education != ''">education = #{education},</if>
<if test="educationBackground != null and educationBackground != ''">education_background = #{educationBackground},</if>
<if test="experience != null and experience != ''">experience = #{experience},</if>
<if test="hrNotes != null">hr_notes = #{hrNotes},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where application_id = #{applicationId}
</update>
<delete id="deleteRsApplicationById" parameterType="Long">
delete from rs_application where application_id = #{applicationId}
</delete>
<delete id="deleteRsApplicationByIds" parameterType="String">
delete from rs_application where application_id in
<foreach item="applicationId" collection="array" open="(" separator="," close=")">
#{applicationId}
</foreach>
</delete>
</mapper>
@@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsJobMapper">
<resultMap type="RsJob" id="RsJobResult">
<id property="jobId" column="job_id"/>
<result property="title" column="title"/>
<result property="department" column="department"/>
<result property="salaryRange" column="salary_range"/>
<result property="location" column="location"/>
<result property="description" column="description"/>
<result property="requirements" column="requirements"/>
<result property="isActive" column="is_active"/>
<result property="viewCount" column="view_count"/>
<result property="tenantId" column="tenant_id"/>
<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="remark" column="remark"/>
</resultMap>
<resultMap id="RsJobVoResult" type="org.dromara.rs.domain.vo.RsJobVo" extends="RsJobResult">
<result property="applicantCount" column="applicant_count"/>
</resultMap>
<sql id="selectRsJobVo">
select j.job_id, j.title, j.department, j.salary_range, j.location, j.description, j.requirements,
j.is_active, j.view_count, j.tenant_id, j.create_by, j.create_time, j.update_by, j.update_time, j.remark,
(select count(1) from rs_application a where a.job_id = j.job_id and a.deleted = '0' and a.tenant_id = j.tenant_id) as applicant_count
from rs_job j
</sql>
<select id="selectRsJobList" parameterType="RsJob" resultMap="RsJobVoResult">
<include refid="selectRsJobVo"/>
<where>
j.deleted = '0'
<if test="title != null and title != ''">
AND j.title like concat('%', #{title}, '%')
</if>
<if test="department != null and department != ''">
AND j.department = #{department}
</if>
<if test="isActive != null and isActive != ''">
AND j.is_active = #{isActive}
</if>
<if test="tenantId != null and tenantId != ''">
AND j.tenant_id = #{tenantId}
</if>
</where>
order by j.create_time desc
</select>
<select id="selectRsJobById" parameterType="Long" resultMap="RsJobVoResult">
<include refid="selectRsJobVo"/>
where j.job_id = #{jobId} and j.deleted = '0'
</select>
<update id="incrementViewCount" parameterType="Long">
update rs_job set view_count = view_count + 1 where job_id = #{jobId}
</update>
</mapper>
@@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsOverseasApplicationMapper">
<resultMap type="RsOverseasApplication" id="RsOverseasApplicationResult">
<id property="applicationId" column="application_id"/>
<result property="applicantId" column="applicant_id"/>
<result property="applicantName" column="applicant_name"/>
<result property="department" column="department"/>
<result property="position" column="position"/>
<result property="contactPhone" column="contact_phone"/>
<result property="contactEmail" column="contact_email"/>
<result property="purpose" column="purpose"/>
<result property="purposeDetail" column="purpose_detail"/>
<result property="destination" column="destination"/>
<result property="destinationCity" column="destination_city"/>
<result property="startDate" column="start_date"/>
<result property="endDate" column="end_date"/>
<result property="duration" column="duration"/>
<result property="budget" column="budget"/>
<result property="invitation" column="invitation"/>
<result property="itinerary" column="itinerary"/>
<result property="accommodationPlan" column="accommodation_plan"/>
<result property="transportationPlan" column="transportation_plan"/>
<result property="status" column="status"/>
<result property="currentApprover" column="current_approver"/>
<result property="approvalLevel" column="approval_level"/>
<result property="applyTime" column="apply_time"/>
<result property="approvalTime" column="approval_time"/>
<result property="adminRemark" column="admin_remark"/>
<result property="applicantRemark" column="applicant_remark"/>
<result property="tenantId" column="tenant_id"/>
<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="deleted" column="deleted"/>
</resultMap>
<resultMap id="RsOverseasApplicationVoResult" type="org.dromara.rs.domain.vo.RsOverseasApplicationVo" extends="RsOverseasApplicationResult">
<result property="statusText" column="status_text"/>
<result property="purposeText" column="purpose_text"/>
<result property="approvalLevelText" column="approval_level_text"/>
</resultMap>
<sql id="selectOverseasApplicationVo">
select oa.application_id, oa.applicant_id, oa.applicant_name, oa.department, oa.position,
oa.contact_phone, oa.contact_email, oa.purpose, oa.purpose_detail, oa.destination, oa.destination_city,
oa.start_date, oa.end_date, oa.duration, oa.budget, oa.invitation, oa.itinerary,
oa.accommodation_plan, oa.transportation_plan, oa.status, oa.current_approver, oa.approval_level,
oa.apply_time, oa.approval_time, oa.admin_remark, oa.applicant_remark, oa.tenant_id,
oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted,
CASE
WHEN oa.status IS NULL OR oa.status = '' THEN '草稿'
WHEN oa.status = '0' THEN '待审核'
WHEN oa.status = '1' THEN '已通过'
WHEN oa.status = '2' THEN '已拒绝'
WHEN oa.status = '3' THEN '已取消'
WHEN oa.status = '4' THEN '已完成'
ELSE '未知状态'
END as status_text,
CASE oa.purpose
WHEN 'academic_exchange' THEN '学术交流'
WHEN 'conference' THEN '学术会议'
WHEN 'training' THEN '培训学习'
WHEN 'cooperation' THEN '合作项目'
WHEN 'research' THEN '科研访问'
WHEN 'other' THEN '其他'
ELSE oa.purpose
END as purpose_text,
CASE oa.approval_level
WHEN '1' THEN '部门主管'
WHEN '2' THEN '分管领导'
WHEN '3' THEN '校领导'
ELSE '未知级别'
END as approval_level_text
from rs_overseas_application oa
</sql>
<select id="selectOverseasApplicationList" parameterType="org.dromara.rs.domain.query.RsOverseasApplicationQuery" resultMap="RsOverseasApplicationVoResult">
<include refid="selectOverseasApplicationVo"/>
<where>
oa.deleted = '0'
<if test="applicantName != null and applicantName != ''">
AND oa.applicant_name like concat('%', #{applicantName}, '%')
</if>
<if test="department != null and department != ''">
AND oa.department like concat('%', #{department}, '%')
</if>
<if test="purpose != null and purpose != ''">
AND oa.purpose = #{purpose}
</if>
<if test="destination != null and destination != ''">
AND oa.destination like concat('%', #{destination}, '%')
</if>
<if test="status != null and status != ''">
AND oa.status = #{status}
</if>
<if test="approvalLevel != null and approvalLevel != ''">
AND oa.approval_level = #{approvalLevel}
</if>
<if test="currentApprover != null and currentApprover != ''">
AND oa.current_approver like concat('%', #{currentApprover}, '%')
</if>
<if test="applicantId != null">
AND oa.applicant_id = #{applicantId}
</if>
<if test="budgetMin != null">
AND oa.budget >= #{budgetMin}
</if>
<if test="budgetMax != null">
AND oa.budget &lt;= #{budgetMax}
</if>
<if test="applyTimeBegin != null">
AND oa.apply_time >= #{applyTimeBegin}
</if>
<if test="applyTimeEnd != null">
AND oa.apply_time &lt;= #{applyTimeEnd}
</if>
<if test="startDateBegin != null">
AND oa.start_date >= #{startDateBegin}
</if>
<if test="startDateEnd != null">
AND oa.start_date &lt;= #{startDateEnd}
</if>
<if test="tenantId != null and tenantId != ''">
AND oa.tenant_id = #{tenantId}
</if>
</where>
order by oa.apply_time desc, oa.create_time desc
</select>
<select id="selectMyOverseasApplicationList" parameterType="org.dromara.rs.domain.query.RsOverseasApplicationQuery" resultMap="RsOverseasApplicationVoResult">
<include refid="selectOverseasApplicationVo"/>
<where>
oa.deleted = '0'
<!-- 排除已通过的申请(status='1'),已通过的申请显示在出国记录中 -->
AND oa.status != '1'
<if test="applicantName != null and applicantName != ''">
AND oa.applicant_name like concat('%', #{applicantName}, '%')
</if>
<if test="department != null and department != ''">
AND oa.department like concat('%', #{department}, '%')
</if>
<if test="purpose != null and purpose != ''">
AND oa.purpose = #{purpose}
</if>
<if test="destination != null and destination != ''">
AND oa.destination like concat('%', #{destination}, '%')
</if>
<if test="status != null and status != ''">
AND oa.status = #{status}
</if>
<if test="approvalLevel != null and approvalLevel != ''">
AND oa.approval_level = #{approvalLevel}
</if>
<if test="currentApprover != null and currentApprover != ''">
AND oa.current_approver like concat('%', #{currentApprover}, '%')
</if>
<if test="applicantId != null">
AND oa.applicant_id = #{applicantId}
</if>
<if test="budgetMin != null">
AND oa.budget >= #{budgetMin}
</if>
<if test="budgetMax != null">
AND oa.budget &lt;= #{budgetMax}
</if>
<if test="applyTimeBegin != null">
AND oa.apply_time >= #{applyTimeBegin}
</if>
<if test="applyTimeEnd != null">
AND oa.apply_time &lt;= #{applyTimeEnd}
</if>
<if test="startDateBegin != null">
AND oa.start_date >= #{startDateBegin}
</if>
<if test="startDateEnd != null">
AND oa.start_date &lt;= #{startDateEnd}
</if>
<if test="tenantId != null and tenantId != ''">
AND oa.tenant_id = #{tenantId}
</if>
</where>
order by oa.apply_time desc, oa.create_time desc
</select>
<select id="selectOverseasApplicationById" parameterType="Long" resultMap="RsOverseasApplicationVoResult">
<include refid="selectOverseasApplicationVo"/>
where oa.application_id = #{applicationId} and oa.deleted = '0'
</select>
<select id="selectOverseasApplicationsByApplicantId" parameterType="Long" resultMap="RsOverseasApplicationVoResult">
<include refid="selectOverseasApplicationVo"/>
where oa.applicant_id = #{applicantId} and oa.deleted = '0'
order by oa.apply_time desc
</select>
<select id="selectOverseasApplicationsByStatus" parameterType="String" resultMap="RsOverseasApplicationVoResult">
<include refid="selectOverseasApplicationVo"/>
where oa.status = #{status} and oa.deleted = '0'
order by oa.apply_time desc
</select>
<select id="selectPendingApplicationsByApprover" parameterType="String" resultMap="RsOverseasApplicationVoResult">
<include refid="selectOverseasApplicationVo"/>
where oa.current_approver = #{currentApprover} and oa.status = '0' and oa.deleted = '0'
order by oa.apply_time asc
</select>
<select id="selectPendingApplicationsByLevel" parameterType="String" resultMap="RsOverseasApplicationVoResult">
<include refid="selectOverseasApplicationVo"/>
where oa.approval_level = #{approvalLevel} and oa.status = '0' and oa.deleted = '0'
order by oa.apply_time asc
</select>
</mapper>
@@ -0,0 +1,285 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsOverseasApprovalMapper">
<resultMap type="RsOverseasApproval" id="RsOverseasApprovalResult">
<id property="approvalId" column="approval_id"/>
<result property="applicationId" column="application_id"/>
<result property="approverId" column="approver_id"/>
<result property="approverName" column="approver_name"/>
<result property="approvalLevel" column="approval_level"/>
<result property="approvalStatus" column="approval_status"/>
<result property="approvalOpinion" column="approval_opinion"/>
<result property="approvalTime" column="approval_time"/>
<result property="tenantId" column="tenant_id"/>
<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="deleted" column="deleted"/>
</resultMap>
<resultMap id="RsOverseasApprovalVoResult" type="org.dromara.rs.domain.vo.RsOverseasApprovalVo" extends="RsOverseasApprovalResult">
<result property="approvalLevelText" column="approval_level_text"/>
<result property="approvalStatusText" column="approval_status_text"/>
<!-- 申请信息相关字段 -->
<result property="applicantId" column="applicant_id"/>
<result property="applicantName" column="applicant_name"/>
<result property="department" column="department"/>
<result property="purpose" column="purpose"/>
<result property="purposeText" column="purpose_text"/>
<result property="destination" column="destination"/>
<result property="startDate" column="start_date"/>
<result property="endDate" column="end_date"/>
<result property="duration" column="duration"/>
<result property="recordType" column="record_type"/>
</resultMap>
<sql id="selectOverseasApprovalVo">
select oa.approval_id, oa.application_id, oa.approver_id, oa.approver_name, oa.approval_level,
oa.approval_status, oa.approval_opinion, oa.approval_time, oa.tenant_id,
oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted,
<!-- 申请信息字段 -->
app.applicant_id, app.applicant_name, app.department, app.purpose, app.destination,
app.start_date, app.end_date, app.duration,
<!-- 文本转换 -->
CASE oa.approval_level
WHEN '1' THEN '部门主管'
WHEN '2' THEN '分管领导'
WHEN '3' THEN '校领导'
ELSE '未知级别'
END as approval_level_text,
CASE oa.approval_status
WHEN '0' THEN '待审核'
WHEN '1' THEN '通过'
WHEN '2' THEN '拒绝'
ELSE '未知状态'
END as approval_status_text,
CASE app.purpose
WHEN 'academic_exchange' THEN '学术交流'
WHEN 'training' THEN '培训学习'
WHEN 'conference' THEN '考察访问'
WHEN 'meeting' THEN '会议参加'
WHEN 'other' THEN '其他'
WHEN '1' THEN '学术交流'
WHEN '2' THEN '培训学习'
WHEN '3' THEN '考察访问'
WHEN '4' THEN '会议参加'
WHEN '5' THEN '其他'
ELSE '未知目的'
END as purpose_text
from rs_overseas_approval oa
left join rs_overseas_application app on oa.application_id = app.application_id
</sql>
<select id="selectOverseasApprovalList" parameterType="org.dromara.rs.domain.query.RsOverseasApprovalQuery" resultMap="RsOverseasApprovalVoResult">
<include refid="selectOverseasApprovalVo"/>
<where>
oa.deleted = '0'
AND (app.deleted IS NULL OR app.deleted = '0')
<if test="applicationId != null">
AND oa.application_id = #{applicationId}
</if>
<if test="approverId != null">
AND oa.approver_id = #{approverId}
</if>
<if test="approverName != null and approverName != ''">
AND oa.approver_name like concat('%', #{approverName}, '%')
</if>
<if test="applicantName != null and applicantName != ''">
AND app.applicant_name like concat('%', #{applicantName}, '%')
</if>
<if test="approvalLevel != null and approvalLevel != ''">
AND oa.approval_level = #{approvalLevel}
</if>
<!-- 审批状态过滤:如果指定了状态则使用指定的,否则显示所有状态 -->
<if test="approvalStatus != null and approvalStatus != ''">
AND oa.approval_status = #{approvalStatus}
</if>
<if test="approvalTimeBegin != null">
AND oa.approval_time >= #{approvalTimeBegin}
</if>
<if test="approvalTimeEnd != null">
AND oa.approval_time &lt;= #{approvalTimeEnd}
</if>
<if test="tenantId != null and tenantId != ''">
AND oa.tenant_id = #{tenantId}
</if>
</where>
order by oa.approval_time desc, oa.create_time desc
</select>
<select id="selectOverseasApprovalById" parameterType="Long" resultMap="RsOverseasApprovalVoResult">
<include refid="selectOverseasApprovalVo"/>
where oa.approval_id = #{approvalId} and oa.deleted = '0'
</select>
<select id="selectOverseasApprovalsByApplicationId" parameterType="Long" resultMap="RsOverseasApprovalVoResult">
<include refid="selectOverseasApprovalVo"/>
where oa.application_id = #{applicationId} and oa.deleted = '0'
order by oa.approval_level asc, oa.approval_time asc
</select>
<select id="selectOverseasApprovalsByApproverId" parameterType="Long" resultMap="RsOverseasApprovalVoResult">
<include refid="selectOverseasApprovalVo"/>
where oa.approver_id = #{approverId} and oa.deleted = '0'
order by oa.approval_time desc
</select>
<!-- 联合查询:待审批申请 + 审批记录 -->
<select id="selectCombinedApprovalList" parameterType="org.dromara.rs.domain.query.RsOverseasApprovalQuery" resultMap="RsOverseasApprovalVoResult">
<!-- 查询已完成的审批记录 -->
SELECT
oa.approval_id,
oa.application_id,
oa.approver_id,
oa.approver_name,
oa.approval_level,
oa.approval_status,
oa.approval_opinion,
oa.approval_time,
oa.tenant_id,
oa.create_by,
oa.create_time,
oa.update_by,
oa.update_time,
oa.deleted,
-- 申请信息字段
app.applicant_id,
app.applicant_name,
app.department,
app.purpose,
app.destination,
app.start_date,
app.end_date,
app.duration,
-- 文本转换
CASE oa.approval_level
WHEN '1' THEN '部门主管'
WHEN '2' THEN '分管领导'
WHEN '3' THEN '校领导'
ELSE '未知级别'
END as approval_level_text,
CASE oa.approval_status
WHEN '0' THEN '待审核'
WHEN '1' THEN '通过'
WHEN '2' THEN '拒绝'
ELSE '未知状态'
END as approval_status_text,
CASE app.purpose
WHEN 'academic_exchange' THEN '学术交流'
WHEN 'training' THEN '培训学习'
WHEN 'conference' THEN '考察访问'
WHEN 'meeting' THEN '会议参加'
WHEN 'other' THEN '其他'
WHEN '1' THEN '学术交流'
WHEN '2' THEN '培训学习'
WHEN '3' THEN '考察访问'
WHEN '4' THEN '会议参加'
WHEN '5' THEN '其他'
ELSE '未知目的'
END as purpose_text,
'completed' as record_type
FROM rs_overseas_approval oa
LEFT JOIN rs_overseas_application app ON oa.application_id = app.application_id
WHERE oa.deleted = '0'
AND (app.deleted IS NULL OR app.deleted = '0')
AND oa.approval_status IN ('1', '2') -- 只查询已完成的审批记录
<if test="applicationId != null">
AND oa.application_id = #{applicationId}
</if>
<if test="approverId != null">
AND oa.approver_id = #{approverId}
</if>
<if test="approverName != null and approverName != ''">
AND oa.approver_name like concat('%', #{approverName}, '%')
</if>
<if test="applicantName != null and applicantName != ''">
AND app.applicant_name like concat('%', #{applicantName}, '%')
</if>
<if test="approvalLevel != null and approvalLevel != ''">
AND oa.approval_level = #{approvalLevel}
</if>
<if test="tenantId != null and tenantId != ''">
AND oa.tenant_id = #{tenantId}
</if>
UNION ALL
-- 查询待审批的申请(还没有审批记录的)
SELECT
NULL as approval_id,
app.application_id,
NULL as approver_id,
app.current_approver as approver_name,
app.approval_level,
'0' as approval_status,
NULL as approval_opinion,
NULL as approval_time,
app.tenant_id,
app.create_by,
app.create_time,
app.update_by,
app.update_time,
app.deleted,
-- 申请信息字段
app.applicant_id,
app.applicant_name,
app.department,
app.purpose,
app.destination,
app.start_date,
app.end_date,
app.duration,
-- 文本转换
CASE app.approval_level
WHEN '1' THEN '部门主管'
WHEN '2' THEN '分管领导'
WHEN '3' THEN '校领导'
ELSE '未知级别'
END as approval_level_text,
'待审核' as approval_status_text,
CASE app.purpose
WHEN 'academic_exchange' THEN '学术交流'
WHEN 'training' THEN '培训学习'
WHEN 'conference' THEN '考察访问'
WHEN 'meeting' THEN '会议参加'
WHEN 'other' THEN '其他'
WHEN '1' THEN '学术交流'
WHEN '2' THEN '培训学习'
WHEN '3' THEN '考察访问'
WHEN '4' THEN '会议参加'
WHEN '5' THEN '其他'
ELSE '未知目的'
END as purpose_text,
'pending' as record_type
FROM rs_overseas_application app
WHERE app.deleted = '0'
AND app.status = '0' -- 待审核状态
AND NOT EXISTS (
SELECT 1 FROM rs_overseas_approval oa
WHERE oa.application_id = app.application_id
AND oa.deleted = '0'
)
<if test="applicationId != null">
AND app.application_id = #{applicationId}
</if>
<if test="applicantName != null and applicantName != ''">
AND app.applicant_name like concat('%', #{applicantName}, '%')
</if>
<if test="approvalLevel != null and approvalLevel != ''">
AND app.approval_level = #{approvalLevel}
</if>
<if test="tenantId != null and tenantId != ''">
AND app.tenant_id = #{tenantId}
</if>
ORDER BY
CASE WHEN approval_status = '0' THEN 0 ELSE 1 END, -- 待审核排在前面
approval_time DESC,
create_time DESC
</select>
</mapper>
@@ -0,0 +1,206 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsOverseasRecordMapper">
<resultMap type="RsOverseasRecord" id="RsOverseasRecordResult">
<id property="recordId" column="record_id"/>
<result property="applicationId" column="application_id"/>
<result property="applicantId" column="applicant_id"/>
<result property="applicantName" column="applicant_name"/>
<result property="department" column="department"/>
<result property="position" column="position"/>
<result property="contactPhone" column="contact_phone"/>
<result property="contactEmail" column="contact_email"/>
<result property="purpose" column="purpose"/>
<result property="purposeDetail" column="purpose_detail"/>
<result property="destination" column="destination"/>
<result property="destinationCity" column="destination_city"/>
<result property="actualStartDate" column="actual_start_date"/>
<result property="actualEndDate" column="actual_end_date"/>
<result property="actualDuration" column="actual_duration"/>
<result property="actualBudget" column="actual_budget"/>
<result property="invitation" column="invitation"/>
<result property="itinerary" column="itinerary"/>
<result property="accommodationPlan" column="accommodation_plan"/>
<result property="transportationPlan" column="transportation_plan"/>
<result property="completionStatus" column="completion_status"/>
<result property="achievement" column="achievement"/>
<result property="reportContent" column="report_content"/>
<result property="certificatePath" column="certificate_path"/>
<result property="tenantId" column="tenant_id"/>
<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="deleted" column="deleted"/>
</resultMap>
<resultMap id="RsOverseasRecordVoResult" type="org.dromara.rs.domain.vo.RsOverseasRecordVo" extends="RsOverseasRecordResult">
<result property="dataSource" column="data_source"/>
</resultMap>
<sql id="selectOverseasRecordVo">
select ors.record_id, ors.application_id, ors.applicant_id, ors.applicant_name, ors.department,
ors.position, ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city,
ors.actual_start_date, ors.actual_end_date, ors.actual_duration, ors.actual_budget,
ors.invitation, ors.itinerary, ors.accommodation_plan, ors.transportation_plan,
ors.completion_status, ors.achievement, ors.report_content, ors.certificate_path,
ors.tenant_id, ors.create_dept, ors.create_by, ors.create_time, ors.update_by, ors.update_time, ors.deleted,
'record' as data_source
from rs_overseas_record ors
</sql>
<!-- 联合查询:出国记录 + 已完成审批的申请 -->
<sql id="selectOverseasRecordWithApprovedApplicationsVo">
-- 查询已创建的出国记录
select
ors.record_id, ors.application_id, ors.applicant_id, ors.applicant_name, ors.department,
ors.position, ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city,
ors.actual_start_date as actual_start_date, ors.actual_end_date as actual_end_date, ors.actual_duration as actual_duration,
ors.actual_budget as actual_budget,
ors.invitation, ors.itinerary, ors.accommodation_plan, ors.transportation_plan,
ors.completion_status, ors.achievement, ors.report_content, ors.certificate_path,
ors.tenant_id, ors.create_dept, ors.create_by, ors.create_time, ors.update_by, ors.update_time, ors.deleted,
'record' as data_source
from rs_overseas_record ors
where ors.deleted = '0'
UNION ALL
-- 查询已通过审批但未创建记录的申请(作为出国记录显示)
select
NULL as record_id,
oa.application_id, oa.applicant_id, oa.applicant_name, oa.department,
oa.position, oa.contact_phone, oa.contact_email, oa.purpose, oa.purpose_detail, oa.destination, oa.destination_city,
oa.start_date as actual_start_date, oa.end_date as actual_end_date, oa.duration as actual_duration,
oa.budget as actual_budget,
oa.invitation, oa.itinerary, oa.accommodation_plan, oa.transportation_plan,
'1' as completion_status, NULL as achievement, NULL as report_content, NULL as certificate_path,
oa.tenant_id, NULL as create_dept, oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted,
'approved_application' as data_source
from rs_overseas_application oa
where oa.deleted = '0'
and oa.status = '1'
and not exists (
select 1 from rs_overseas_record ors
where ors.application_id = oa.application_id
and ors.deleted = '0'
and ors.tenant_id = '000000'
)
and oa.tenant_id = '000000'
</sql>
<select id="selectOverseasRecordList" parameterType="org.dromara.rs.domain.query.RsOverseasRecordQuery" resultMap="RsOverseasRecordVoResult">
SELECT * FROM (
<include refid="selectOverseasRecordWithApprovedApplicationsVo"/>
) combined_data
<where>
1=1
<if test="applicantName != null and applicantName != ''">
AND combined_data.applicant_name like concat('%', #{applicantName}, '%')
</if>
<if test="department != null and department != ''">
AND combined_data.department like concat('%', #{department}, '%')
</if>
<if test="purpose != null and purpose != ''">
AND combined_data.purpose = #{purpose}
</if>
<if test="destination != null and destination != ''">
AND combined_data.destination like concat('%', #{destination}, '%')
</if>
<if test="completionStatus != null and completionStatus != ''">
AND combined_data.completion_status = #{completionStatus}
</if>
<if test="applicantId != null">
AND combined_data.applicant_id = #{applicantId}
</if>
<if test="applicationId != null">
AND combined_data.application_id = #{applicationId}
</if>
<if test="actualStartDateBegin != null">
AND combined_data.actual_start_date >= #{actualStartDateBegin}
</if>
<if test="actualStartDateEnd != null">
AND combined_data.actual_start_date &lt;= #{actualStartDateEnd}
</if>
<if test="actualEndDateBegin != null">
AND combined_data.actual_end_date >= #{actualEndDateBegin}
</if>
<if test="actualEndDateEnd != null">
AND combined_data.actual_end_date &lt;= #{actualEndDateEnd}
</if>
<if test="tenantId != null and tenantId != ''">
AND combined_data.tenant_id = #{tenantId}
</if>
</where>
order by combined_data.actual_start_date desc, combined_data.create_time desc
</select>
<select id="selectOverseasRecordById" parameterType="Long" resultMap="RsOverseasRecordVoResult">
<include refid="selectOverseasRecordVo"/>
where ors.record_id = #{recordId} and ors.deleted = 0
</select>
<select id="selectOverseasRecordByApplicationId" parameterType="Long" resultMap="RsOverseasRecordVoResult">
<include refid="selectOverseasRecordVo"/>
where ors.application_id = #{applicationId} and ors.deleted = 0
limit 1
</select>
<select id="selectOverseasRecordsByApplicantId" parameterType="Long" resultMap="RsOverseasRecordVoResult">
<include refid="selectOverseasRecordVo"/>
where ors.applicant_id = #{applicantId} and ors.deleted = 0
order by ors.actual_start_date desc
</select>
<!-- 查询我的出国记录列表(联合查询:记录+已通过申请) -->
<select id="selectMyOverseasRecordList" parameterType="org.dromara.rs.domain.query.RsOverseasRecordQuery" resultMap="RsOverseasRecordVoResult">
SELECT * FROM (
<include refid="selectOverseasRecordWithApprovedApplicationsVo"/>
) combined_data
<where>
1=1
<if test="applicantId != null">
AND combined_data.applicant_id = #{applicantId}
</if>
<if test="applicantName != null and applicantName != ''">
AND combined_data.applicant_name like concat('%', #{applicantName}, '%')
</if>
<if test="department != null and department != ''">
AND combined_data.department like concat('%', #{department}, '%')
</if>
<if test="purpose != null and purpose != ''">
AND combined_data.purpose = #{purpose}
</if>
<if test="destination != null and destination != ''">
AND combined_data.destination like concat('%', #{destination}, '%')
</if>
<if test="completionStatus != null and completionStatus != ''">
AND combined_data.completion_status = #{completionStatus}
</if>
<if test="applicationId != null">
AND combined_data.application_id = #{applicationId}
</if>
<if test="actualStartDateBegin != null">
AND combined_data.actual_start_date >= #{actualStartDateBegin}
</if>
<if test="actualStartDateEnd != null">
AND combined_data.actual_start_date &lt;= #{actualStartDateEnd}
</if>
<if test="actualEndDateBegin != null">
AND combined_data.actual_end_date >= #{actualEndDateBegin}
</if>
<if test="actualEndDateEnd != null">
AND combined_data.actual_end_date &lt;= #{actualEndDateEnd}
</if>
<if test="tenantId != null and tenantId != ''">
AND combined_data.tenant_id = #{tenantId}
</if>
</where>
order by combined_data.actual_start_date desc, combined_data.create_time desc
</select>
</mapper>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsTrainingApplicationMapper">
<resultMap type="org.dromara.rs.domain.RsTrainingApplication" id="RsTrainingApplicationResult">
<id property="applicationId" column="application_id"/>
<result property="courseId" column="course_id"/>
<result property="userId" column="user_id"/>
<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="tenantId" column="tenant_id"/>
<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="courseName" column="course_name"/>
<result property="rejectTime" column="reject_time"/>
</resultMap>
<sql id="selectRsTrainingApplicationVo">
SELECT
a.application_id, a.course_id, a.course_name as custom_course_name, a.user_id, a.user_name, a.department, a.position, a.reason,
a.status, a.apply_time, a.current_approver, a.location, a.duration, a.start_time, a.end_time,
a.tenant_id, a.create_dept, a.reject_time, a.create_by, a.create_time, a.update_by, a.update_time,
CASE
WHEN a.course_id = 0 THEN a.course_name
ELSE c.title
END as course_name
FROM rs_training_application a
LEFT JOIN rs_training_course c ON a.course_id = c.course_id
</sql>
<select id="selectRsTrainingApplicationList" parameterType="org.dromara.rs.domain.RsTrainingApplication" resultMap="RsTrainingApplicationResult">
<include refid="selectRsTrainingApplicationVo"/>
<where>
<!-- 排除已审批完成的数据:1-已通过,3-进行中,4-已完成 -->
<!-- 只保留:0-待审核,2-已拒绝 -->
AND a.status NOT IN ('1', '3', '4')
<if test="courseId != null ">and a.course_id = #{courseId}</if>
<if test="userId != null ">and a.user_id = #{userId}</if>
<if test="userName != null and userName != ''">and a.user_name like concat('%', #{userName}, '%')</if>
<if test="department != null and department != ''">and a.department = #{department}</if>
<if test="position != null and position != ''">and a.position = #{position}</if>
<if test="status != null ">and a.status = #{status}</if>
<if test="applyTime != null ">and a.apply_time = #{applyTime}</if>
<if test="currentApprover != null and currentApprover != ''">and a.current_approver = #{currentApprover}</if>
<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>
</where>
ORDER BY a.create_time DESC
</select>
<select id="selectRsTrainingApplicationListPage" resultMap="RsTrainingApplicationResult">
<include refid="selectRsTrainingApplicationVo"/>
<where>
<!-- 排除已审批完成的数据:1-已通过,3-进行中,4-已完成 -->
<!-- 只保留:0-待审核,2-已拒绝 -->
AND a.status NOT IN ('1', '3', '4')
<if test="app.courseId != null ">and a.course_id = #{app.courseId}</if>
<if test="app.userId != null ">and a.user_id = #{app.userId}</if>
<if test="app.userName != null and app.userName != ''">and a.user_name like concat('%', #{app.userName}, '%')</if>
<if test="app.department != null and app.department != ''">and a.department = #{app.department}</if>
<if test="app.position != null and app.position != ''">and a.position = #{app.position}</if>
<if test="app.status != null ">and a.status = #{app.status}</if>
<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>
</where>
ORDER BY a.create_time DESC
LIMIT #{offset}, #{limit}
</select>
<select id="selectRsTrainingApplicationById" parameterType="Long" resultMap="RsTrainingApplicationResult">
<include refid="selectRsTrainingApplicationVo"/>
where a.application_id = #{applicationId}
</select>
</mapper>
@@ -0,0 +1,124 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsTrainingApprovalMapper">
<resultMap type="RsTrainingApproval" id="RsTrainingApprovalResult">
<id property="approvalId" column="approval_id"/>
<result property="applicationId" column="application_id"/>
<result property="approverId" column="approver_id"/>
<result property="approverName" column="approver_name"/>
<result property="approvalLevel" column="approval_level"/>
<result property="status" column="status"/>
<result property="approvalTime" column="approval_time"/>
<result property="opinion" column="opinion"/>
<result property="tenantId" column="tenant_id"/>
<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="remark" column="remark"/>
</resultMap>
<!-- 带关联信息的结果映射 -->
<resultMap type="RsTrainingApproval" id="RsTrainingApprovalWithDetailsResult">
<id property="approvalId" column="approval_id"/>
<result property="applicationId" column="application_id"/>
<result property="approverId" column="approver_id"/>
<result property="approverName" column="approver_name"/>
<result property="approvalLevel" column="approval_level"/>
<result property="status" column="status"/>
<result property="approvalTime" column="approval_time"/>
<result property="opinion" column="opinion"/>
<result property="tenantId" column="tenant_id"/>
<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="remark" column="remark"/>
<!-- 关联字段 -->
<result property="applicationUserName" column="application_user_name"/>
<result property="courseTitle" column="course_title"/>
</resultMap>
<sql id="selectRsTrainingApprovalVo">
select approval_id, application_id, approver_id, approver_name, approval_level,
status, approval_time, opinion, tenant_id, create_by,
create_time, update_by, update_time, remark
from rs_training_approval
</sql>
<select id="selectRsTrainingApprovalList" parameterType="RsTrainingApproval" resultMap="RsTrainingApprovalResult">
<include refid="selectRsTrainingApprovalVo"/>
<where>
deleted = '0'
<if test="applicationId != null">
AND application_id = #{applicationId}
</if>
<if test="approverId != null">
AND approver_id = #{approverId}
</if>
<if test="approverName != null and approverName != ''">
AND approver_name like concat('%', #{approverName}, '%')
</if>
<if test="approvalLevel != null">
AND approval_level = #{approvalLevel}
</if>
<if test="status != null and status != ''">
AND status = #{status}
</if>
<if test="tenantId != null and tenantId != ''">
AND tenant_id = #{tenantId}
</if>
</where>
order by approval_level asc, create_time desc
</select>
<!-- 查询带详细信息的审批记录列表(关联申请人和课程信息) -->
<select id="selectRsTrainingApprovalWithDetailsList" parameterType="RsTrainingApproval" resultMap="RsTrainingApprovalWithDetailsResult">
SELECT
a.approval_id, a.application_id, a.approver_id, a.approver_name,
a.approval_level, a.status, a.approval_time, a.opinion,
a.tenant_id, a.create_by, a.create_time, a.update_by, a.update_time, a.remark,
app.user_name as application_user_name,
COALESCE(app.course_name, c.title) as course_title
FROM rs_training_approval a
LEFT JOIN rs_training_application app ON a.application_id = app.application_id AND app.deleted = '0'
LEFT JOIN rs_training_course c ON app.course_id = c.course_id AND c.deleted = '0'
<where>
a.deleted = '0'
<if test="applicationId != null">
AND a.application_id = #{applicationId}
</if>
<if test="approverId != null">
AND a.approver_id = #{approverId}
</if>
<if test="approverName != null and approverName != ''">
AND a.approver_name like concat('%', #{approverName}, '%')
</if>
<if test="approvalLevel != null">
AND a.approval_level = #{approvalLevel}
</if>
<if test="status != null and status != ''">
AND a.status IN
<foreach collection="status.split(',')" item="statusItem" open="(" separator="," close=")">
#{statusItem}
</foreach>
</if>
<if test="tenantId != null and tenantId != ''">
AND a.tenant_id = #{tenantId}
</if>
<!-- 支持按申请人姓名搜索 -->
<if test="applicationUserName != null and applicationUserName != ''">
AND app.user_name like concat('%', #{applicationUserName}, '%')
</if>
<!-- 支持按课程名称搜索 -->
<if test="courseTitle != null and courseTitle != ''">
AND (app.course_name like concat('%', #{courseTitle}, '%') OR c.title like concat('%', #{courseTitle}, '%'))
</if>
</where>
ORDER BY a.approval_time DESC
</select>
</mapper>
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsTrainingCourseMapper">
<resultMap type="RsTrainingCourse" id="RsTrainingCourseResult">
<id property="courseId" column="course_id"/>
<result property="title" column="title"/>
<result property="category" column="category"/>
<result property="description" column="description"/>
<result property="startTime" column="start_time"/>
<result property="endTime" column="end_time"/>
<result property="duration" column="duration"/>
<result property="location" column="location"/>
<result property="maxParticipants" column="max_participants"/>
<result property="currentParticipants" column="current_participants"/>
<result property="status" column="status"/>
<result property="tenantId" column="tenant_id"/>
<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="remark" column="remark"/>
<result property="applicationCount" column="application_count"/>
</resultMap>
<sql id="selectRsTrainingCourseVo">
select c.course_id, c.title, c.category, c.description, c.start_time, c.end_time, c.duration,
c.location, c.max_participants, c.current_participants, c.status, c.tenant_id, c.create_by,
c.create_time, c.update_by, c.update_time, c.remark,
(select count(1) from rs_training_application a where a.course_id = c.course_id and a.deleted = '0' and a.tenant_id = c.tenant_id) as application_count
from rs_training_course c
</sql>
<select id="selectRsTrainingCourseList" parameterType="RsTrainingCourse" resultMap="RsTrainingCourseResult">
<include refid="selectRsTrainingCourseVo"/>
<where>
c.deleted = '0'
<if test="title != null and title != ''">
AND c.title like concat('%', #{title}, '%')
</if>
<if test="category != null and category != ''">
AND c.category = #{category}
</if>
<if test="startTime != null">
AND c.start_time = #{startTime}
</if>
<if test="endTime != null">
AND c.end_time = #{endTime}
</if>
<if test="status != null and status != ''">
AND c.status = #{status}
</if>
<if test="tenantId != null and tenantId != ''">
AND c.tenant_id = #{tenantId}
</if>
</where>
order by c.create_time desc
</select>
</mapper>
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsTrainingRecordMapper">
<resultMap type="RsTrainingRecord" id="RsTrainingRecordResult">
<id property="recordId" column="record_id"/>
<result property="applicationId" column="application_id"/>
<result property="courseId" column="course_id"/>
<result property="userId" column="user_id"/>
<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="completionTime" column="completion_time"/>
<result property="score" column="score"/>
<result property="certificatePath" column="certificate_path"/>
<result property="certificateNumber" column="certificate_number"/>
<result property="feedback" column="feedback"/>
<result property="tenantId" column="tenant_id"/>
<result property="createDept" column="create_dept"/>
<result property="courseName" column="course_name"/>
</resultMap>
<sql id="selectRsTrainingRecordVo">
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
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>
<select id="selectRsTrainingRecordList" parameterType="RsTrainingRecord" resultMap="RsTrainingRecordResult">
<include refid="selectRsTrainingRecordVo"/>
<where>
r.deleted = '0'
<if test="courseId != null">and r.course_id = #{courseId}</if>
<if test="courseName != null and courseName != ''">and r.course_name like concat('%', #{courseName}, '%')</if>
<if test="userId != null">and r.user_id = #{userId}</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="status != null and status != ''">and r.status = #{status}</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>
</where>
ORDER BY r.create_time DESC
</select>
<insert id="insertRsTrainingRecord" parameterType="org.dromara.rs.domain.RsTrainingRecord">
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
) VALUES (
#{applicationId}, #{courseId}, #{userId}, #{userName}, #{department}, #{position}, #{reason}, #{status},
#{applyTime}, #{currentApprover}, #{location}, #{duration}, #{startTime}, #{endTime}, #{tenantId}, #{createDept}, #{courseName}
)
</insert>
</mapper>
@@ -0,0 +1,142 @@
-- 招聘模块表结构
-- 创建于 2025-07-02
-- 职位表
DROP TABLE IF EXISTS `rs_job`;
CREATE TABLE `rs_job` (
`job_id` bigint(20) NOT NULL COMMENT '职位ID',
`title` varchar(100) NOT NULL COMMENT '职位名称',
`department` varchar(50) NOT NULL COMMENT '部门',
`salary_range` varchar(50) DEFAULT NULL COMMENT '薪资范围',
`location` varchar(100) DEFAULT NULL COMMENT '工作地点',
`description` text COMMENT '职位描述',
`requirements` text COMMENT '任职要求',
`is_active` char(1) DEFAULT '0' COMMENT '是否在招(0是 1否)',
`publish_time` datetime DEFAULT NULL COMMENT '发布时间',
`view_count` int(11) DEFAULT '0' COMMENT '浏览次数',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门',
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
PRIMARY KEY (`job_id`),
KEY `idx_department` (`department`),
KEY `idx_is_active` (`is_active`)
) ENGINE=InnoDB COMMENT='招聘职位表';
-- 应聘者表
DROP TABLE IF EXISTS `rs_applicant`;
CREATE TABLE `rs_applicant` (
`applicant_id` bigint(20) NOT NULL COMMENT '应聘者ID',
`user_id` bigint(20) DEFAULT NULL COMMENT '关联用户ID',
`name` varchar(50) NOT NULL COMMENT '姓名',
`phone` varchar(20) NOT NULL COMMENT '手机号码',
`email` varchar(100) NOT NULL COMMENT '电子邮箱',
`resume_file_path` varchar(255) DEFAULT NULL COMMENT '简历文件路径',
`self_intro` text COMMENT '自我介绍',
`current_status` varchar(20) DEFAULT 'active' COMMENT '当前状态(active-可申请 hired-已录用 rejected-已拒绝)',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
PRIMARY KEY (`applicant_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_phone` (`phone`)
) ENGINE=InnoDB COMMENT='应聘者信息表';
-- 教育经历表
DROP TABLE IF EXISTS `rs_education`;
CREATE TABLE `rs_education` (
`edu_id` bigint(20) NOT NULL COMMENT '教育经历ID',
`applicant_id` bigint(20) NOT NULL COMMENT '应聘者ID',
`school_name` varchar(100) NOT NULL COMMENT '学校名称',
`major` varchar(100) DEFAULT NULL COMMENT '专业',
`degree` varchar(50) DEFAULT NULL COMMENT '学位',
`start_date` date DEFAULT NULL COMMENT '开始日期',
`end_date` date DEFAULT NULL COMMENT '结束日期',
`gpa` varchar(20) DEFAULT NULL COMMENT '绩点',
`description` text COMMENT '描述',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
PRIMARY KEY (`edu_id`),
KEY `idx_applicant_id` (`applicant_id`)
) ENGINE=InnoDB COMMENT='教育经历表';
-- 工作经历表
DROP TABLE IF EXISTS `rs_work_experience`;
CREATE TABLE `rs_work_experience` (
`exp_id` bigint(20) NOT NULL COMMENT '工作经历ID',
`applicant_id` bigint(20) NOT NULL COMMENT '应聘者ID',
`company_name` varchar(100) NOT NULL COMMENT '公司名称',
`position` varchar(100) NOT NULL COMMENT '职位',
`start_date` date DEFAULT NULL COMMENT '开始日期',
`end_date` date DEFAULT NULL COMMENT '结束日期',
`is_current` char(1) DEFAULT '0' COMMENT '是否当前工作(0是 1否)',
`description` text COMMENT '工作描述',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
PRIMARY KEY (`exp_id`),
KEY `idx_applicant_id` (`applicant_id`)
) ENGINE=InnoDB COMMENT='工作经历表';
-- 申请记录表
DROP TABLE IF EXISTS `rs_application`;
CREATE TABLE `rs_application` (
`application_id` bigint(20) NOT NULL COMMENT '申请ID',
`job_id` bigint(20) NOT NULL COMMENT '职位ID',
`applicant_id` bigint(20) NOT NULL COMMENT '应聘者ID',
`apply_time` datetime NOT NULL COMMENT '申请时间',
`status` varchar(20) DEFAULT 'pending' COMMENT '状态(pending-待处理 interviewed-已面试 offered-已录用 rejected-已拒绝)',
`hr_notes` text COMMENT 'HR备注',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
PRIMARY KEY (`application_id`),
KEY `idx_job_id` (`job_id`),
KEY `idx_applicant_id` (`applicant_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB COMMENT='申请记录表';
-- 附件表
DROP TABLE IF EXISTS `rs_attachment`;
CREATE TABLE `rs_attachment` (
`attachment_id` bigint(20) NOT NULL COMMENT '附件ID',
`application_id` bigint(20) NOT NULL COMMENT '申请ID',
`file_name` varchar(100) NOT NULL COMMENT '文件名',
`file_path` varchar(255) NOT NULL COMMENT '文件路径',
`file_type` varchar(50) DEFAULT NULL COMMENT '文件类型',
`file_size` bigint(20) DEFAULT NULL COMMENT '文件大小(字节)',
`tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号',
`create_by` bigint(20) DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` bigint(20) DEFAULT NULL COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`deleted` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 1代表删除)',
PRIMARY KEY (`attachment_id`),
KEY `idx_application_id` (`application_id`)
) ENGINE=InnoDB COMMENT='申请附件表';
-- 插入初始测试数据
INSERT INTO `rs_job` (`job_id`, `title`, `department`, `salary_range`, `location`, `description`, `requirements`, `is_active`, `create_time`, `create_by`, `update_time`, `update_by`, `remark`, `deleted`, `tenant_id`)
VALUES
(1, '高级Java开发工程师', '教学', '15k-25k', '杭州', '负责校园教学管理系统的开发和维护,参与核心模块设计与实现,优化系统性能,解决技术难题。\n作为团队核心成员,参与技术方案评审,指导初级开发人员,确保项目按时高质量交付。', '计算机相关专业本科及以上学历,3年以上Java开发经验\n精通Java核心技术,熟悉Spring Boot、MyBatis等主流框架\n熟练掌握MySQL数据库设计与优化\n有教育行业相关系统开发经验者优先\n良好的沟通能力和团队协作精神', '0', NOW(), 1, NOW(), 1, '示例职位', '0', '000000'),
(2, '前端工程师', '教学', '12k-18k', '杭州', '负责校园教学管理系统前端开发,使用Vue3+TypeScript技术栈,参与UI设计与实现', '计算机相关专业本科及以上学历,2年以上前端开发经验\n精通HTML5/CSS3/JavaScript,熟悉Vue3/React等主流框架\n熟练使用TypeScript进行开发\n有教育行业相关系统开发经验者优先', '0', NOW(), 1, NOW(), 1, '示例职位', '0', '000000'),
(3, '行政助理', '行政', '5k-8k', '杭州', '协助部门主管处理日常行政事务,包括文件整理、会议安排、接待来访等工作', '大专及以上学历,行政管理相关专业优先\n熟悉办公软件,有较强的文字处理能力\n工作认真负责,有较强的组织协调能力\n有相关工作经验者优先', '0', NOW(), 1, NOW(), 1, '示例职位', '0', '000000');
@@ -0,0 +1,11 @@
-- -----------------------------
-- 删除培训申请表中的提交次数字段
-- -----------------------------
-- 删除 rs_training_application 表中的 submit_count 字段
-- 注意:MySQL 5.7及以下版本不支持 DROP COLUMN IF EXISTS 语法
-- 需要先检查字段是否存在,然后删除
ALTER TABLE rs_training_application DROP COLUMN submit_count;
-- 验证字段已删除
DESCRIBE rs_training_application;
@@ -0,0 +1,62 @@
-- -----------------------------
-- 培训审批数据一致性检查和修复
-- -----------------------------
-- 检查审批记录表是否为空
SELECT COUNT(*) AS approval_count FROM rs_training_approval WHERE deleted = '0';
-- 检查审批记录与申请记录是否一致
SELECT
COUNT(*) AS inconsistent_count
FROM
rs_training_approval a
LEFT JOIN
rs_training_application app ON a.application_id = app.application_id
WHERE
a.deleted = '0'
AND (app.application_id IS NULL OR app.deleted = '1');
-- 插入测试数据(如果没有审批记录)
INSERT INTO rs_training_approval (
approval_id, application_id, approver_id, approver_name,
approval_level, status, opinion, approval_time, tenant_id,
create_by, create_time, deleted
)
SELECT
FLOOR(RAND() * 1000000) + 100000, -- 随机审批ID
app.application_id, -- 使用真实申请ID
app.create_by, -- 使用申请的创建者ID作为审批人ID
(SELECT user_name FROM sys_user WHERE user_id = app.create_by LIMIT 1), -- 获取用户名
1, -- 审批级别
app.status, -- 使用申请状态
'系统自动生成的测试审批记录', -- 审批意见
app.update_time, -- 使用申请更新时间作为审批时间
app.tenant_id, -- 租户ID
app.create_by, -- 创建者
NOW(), -- 创建时间
'0' -- 未删除
FROM
rs_training_application app
WHERE
app.deleted = '0'
AND app.status IN ('1', '2') -- 已通过或已拒绝
AND NOT EXISTS (
SELECT 1
FROM rs_training_approval a
WHERE a.application_id = app.application_id AND a.deleted = '0'
)
LIMIT 5; -- 限制生成5条测试数据
-- 查询生成的测试数据
SELECT
a.approval_id, a.application_id, a.approver_name, a.status, a.opinion, a.approval_time,
app.user_name AS applicant_name, app.reason
FROM
rs_training_approval a
JOIN
rs_training_application app ON a.application_id = app.application_id
WHERE
a.deleted = '0'
AND a.create_time > DATE_SUB(NOW(), INTERVAL 1 HOUR) -- 最近一小时创建的记录
ORDER BY
a.create_time DESC;
@@ -0,0 +1,42 @@
-- ----------------------------
-- 培训管理模块测试数据
-- ----------------------------
-- ----------------------------
-- 培训课程表测试数据
-- ----------------------------
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`)
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');
-- ----------------------------
-- 培训申请表测试数据
-- ----------------------------
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`)
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');
-- ----------------------------
-- 培训审批表测试数据
-- ----------------------------
INSERT INTO `rs_training_approval` (`approval_id`, `application_id`, `approver_id`, `approver_name`, `approval_level`, `status`, `opinion`, `approval_time`, `tenant_id`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`, `deleted`)
VALUES
(30001, 20001, 101, '王主任', 1, '1', '同意参加,安排好工作交接', '2025-07-21 09:30:00', '000000', 101, NOW(), 101, NOW(), NULL, '0'),
(30002, 20003, 102, '孙院长', 1, '1', '批准参加,对提升教学质量有帮助', '2025-07-23 10:15:00', '000000', 102, NOW(), 102, 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');
@@ -0,0 +1,43 @@
-- -----------------------------
-- 培训审批数据修复脚本
-- -----------------------------
-- 检查comment列是否存在,如果存在则迁移数据到opinion列
SET @columnExists = 0;
SELECT COUNT(*) INTO @columnExists
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = 'rs_training_approval'
AND COLUMN_NAME = 'comment';
-- 如果comment列存在,将数据从comment迁移到opinion
SET @sqlStmt = IF(@columnExists > 0,
'UPDATE rs_training_approval SET opinion = comment WHERE opinion IS NULL AND comment IS NOT NULL',
'SELECT "comment列不存在,无需迁移" AS message');
PREPARE stmt FROM @sqlStmt;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
-- 添加测试数据(如果表为空)
INSERT INTO rs_training_approval (
approval_id, application_id, approver_id, approver_name,
approval_level, status, opinion, approval_time, tenant_id,
create_by, create_time, deleted
)
SELECT
FLOOR(RAND() * 1000000) + 100000, -- 随机ID
app.application_id, -- 关联到真实的申请ID
1, -- 管理员ID
'系统管理员', -- 管理员名称
1, -- 第一级审批
'1', -- 已通过状态
'系统自动生成的测试审批记录', -- 审批意见
NOW(), -- 当前时间
app.tenant_id, -- 使用申请的租户ID
app.create_by, -- 使用申请的创建者
NOW(), -- 当前时间
'0' -- 未删除
FROM rs_training_application app
WHERE NOT EXISTS (SELECT 1 FROM rs_training_approval)
LIMIT 1;
@@ -0,0 +1,45 @@
-- 培训管理权限设置
-- 创建于 2025-07-23
-- 培训课程管理权限
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`) VALUES
(2111, '培训课程查询', 2110, 1, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:course:query', '#', 1, NOW(), '培训课程查询权限'),
(2112, '培训课程新增', 2110, 2, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:course:add', '#', 1, NOW(), '培训课程新增权限'),
(2113, '培训课程修改', 2110, 3, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:course:edit', '#', 1, NOW(), '培训课程修改权限'),
(2114, '培训课程删除', 2110, 4, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:course:remove', '#', 1, NOW(), '培训课程删除权限'),
(2115, '培训课程导出', 2110, 5, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:course:export', '#', 1, NOW(), '培训课程导出权限');
-- 培训申请管理权限
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`) VALUES
(2121, '培训申请查询', 2110, 6, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:application:query', '#', 1, NOW(), '培训申请查询权限'),
(2122, '培训申请新增', 2110, 7, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:application:add', '#', 1, NOW(), '培训申请新增权限'),
(2123, '培训申请修改', 2110, 8, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:application:edit', '#', 1, NOW(), '培训申请修改权限'),
(2124, '培训申请删除', 2110, 9, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:application:remove', '#', 1, NOW(), '培训申请删除权限'),
(2125, '培训申请导出', 2110, 10, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:application:export', '#', 1, NOW(), '培训申请导出权限'),
(2126, '培训申请列表', 2110, 11, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:application:list', '#', 1, NOW(), '培训申请列表权限');
-- 培训审批管理权限
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`) VALUES
(2131, '培训审批查询', 2110, 12, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:approval:query', '#', 1, NOW(), '培训审批查询权限'),
(2132, '培训审批新增', 2110, 13, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:approval:add', '#', 1, NOW(), '培训审批新增权限'),
(2133, '培训审批修改', 2110, 14, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:approval:edit', '#', 1, NOW(), '培训审批修改权限'),
(2134, '培训审批删除', 2110, 15, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:approval:remove', '#', 1, NOW(), '培训审批删除权限'),
(2135, '培训审批导出', 2110, 16, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:approval:export', '#', 1, NOW(), '培训审批导出权限'),
(2136, '培训审批处理', 2110, 17, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:approval:process', '#', 1, NOW(), '培训审批处理权限'),
(2137, '培训审批列表', 2110, 18, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:approval:list', '#', 1, NOW(), '培训审批列表权限');
-- 培训记录管理权限
INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query_param`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `remark`) VALUES
(2141, '培训记录查询', 2110, 19, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:record:query', '#', 1, NOW(), '培训记录查询权限'),
(2142, '培训记录新增', 2110, 20, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:record:add', '#', 1, NOW(), '培训记录新增权限'),
(2143, '培训记录修改', 2110, 21, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:record:edit', '#', 1, NOW(), '培训记录修改权限'),
(2144, '培训记录删除', 2110, 22, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:record:remove', '#', 1, NOW(), '培训记录删除权限'),
(2145, '培训记录导出', 2110, 23, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:record:export', '#', 1, NOW(), '培训记录导出权限'),
(2146, '培训记录列表', 2110, 24, '', NULL, NULL, 1, 0, 'F', '0', '0', 'rs:training:record:list', '#', 1, NOW(), '培训记录列表权限');
-- 为管理员角色赋予培训管理所有权限
INSERT INTO `sys_role_menu` (`role_id`, `menu_id`) VALUES
(1, 2111), (1, 2112), (1, 2113), (1, 2114), (1, 2115),
(1, 2121), (1, 2122), (1, 2123), (1, 2124), (1, 2125), (1, 2126),
(1, 2131), (1, 2132), (1, 2133), (1, 2134), (1, 2135), (1, 2136), (1, 2137),
(1, 2141), (1, 2142), (1, 2143), (1, 2144), (1, 2145), (1, 2146);
@@ -0,0 +1,58 @@
-- -----------------------------
-- 培训审批历史记录完整性更新脚本
-- -----------------------------
-- 1. 在培训申请表中添加提交次数字段
ALTER TABLE rs_training_application ADD COLUMN IF NOT EXISTS submit_count int DEFAULT 1 COMMENT '提交次数';
-- 2. 检查并移除审批表可能存在的唯一约束(允许一个申请有多条审批记录)
ALTER TABLE rs_training_approval DROP INDEX IF EXISTS uk_application_approver;
ALTER TABLE rs_training_approval DROP INDEX IF EXISTS uk_application_level;
-- 3. 确保审批表中有足够的索引来高效查询
CREATE INDEX IF NOT EXISTS idx_training_approval_application ON rs_training_approval(application_id);
CREATE INDEX IF NOT EXISTS idx_training_approval_status ON rs_training_approval(status);
CREATE INDEX IF NOT EXISTS idx_training_approval_time ON rs_training_approval(approval_time);
-- 4. 添加审批记录表注释说明
ALTER TABLE rs_training_approval COMMENT = '培训审批表(记录所有审批动作,包括通过和拒绝记录)';
-- 5. 更新已有数据的提交次数为1(如果有)
UPDATE rs_training_application SET submit_count = 1 WHERE submit_count IS NULL;
-- 6. 检查并修复可能的数据不一致问题
-- 查找有申请状态为已拒绝但没有对应拒绝记录的数据
SELECT
app.application_id,
app.course_id,
app.user_name,
app.status,
COUNT(appr.approval_id) as approval_count
FROM
rs_training_application app
LEFT JOIN
rs_training_approval appr ON app.application_id = appr.application_id
WHERE
app.status = '2' AND app.deleted = '0'
GROUP BY
app.application_id
HAVING
approval_count = 0;
-- 查找有申请状态为已通过但没有对应通过记录的数据
SELECT
app.application_id,
app.course_id,
app.user_name,
app.status,
COUNT(appr.approval_id) as approval_count
FROM
rs_training_application app
LEFT JOIN
rs_training_approval appr ON app.application_id = appr.application_id
WHERE
app.status = '1' AND app.deleted = '0'
GROUP BY
app.application_id
HAVING
approval_count = 0;
@@ -0,0 +1,19 @@
-- -----------------------------
-- 培训审批历史记录更新
-- -----------------------------
-- 确保审批表中没有唯一约束,允许一个申请有多条审批记录
ALTER TABLE rs_training_approval DROP INDEX IF EXISTS uk_application_approver;
-- 添加审批意见列(如果不存在)
ALTER TABLE rs_training_approval ADD COLUMN IF NOT EXISTS opinion varchar(500) COMMENT '审批意见' AFTER status;
-- 添加comment列的别名以保持向后兼容(如果存在)
ALTER TABLE rs_training_approval CHANGE COLUMN comment opinion varchar(500) COMMENT '审批意见';
-- 更新审批记录表注释
ALTER TABLE rs_training_approval COMMENT = '培训审批表(记录所有审批动作,包括拒绝记录)';
-- 添加相关索引以提高查询效率
CREATE INDEX IF NOT EXISTS idx_training_approval_application ON rs_training_approval(application_id);
CREATE INDEX IF NOT EXISTS idx_training_approval_status ON rs_training_approval(status);
@@ -0,0 +1,17 @@
-- 更新培训课程表,添加create_dept字段
ALTER TABLE rs_training_course ADD COLUMN create_dept bigint(20) COMMENT '创建部门' AFTER tenant_id;
-- 更新培训申请表,添加create_dept字段
ALTER TABLE rs_training_application ADD COLUMN create_dept bigint(20) COMMENT '创建部门' AFTER tenant_id;
-- 更新培训申请表,添加location、duration、startTime和endTime字段
ALTER TABLE rs_training_application ADD COLUMN location varchar(200) COMMENT '培训地点' AFTER current_approver;
ALTER TABLE rs_training_application ADD COLUMN duration int(11) COMMENT '学时' AFTER location;
ALTER TABLE rs_training_application ADD COLUMN start_time datetime COMMENT '开始时间' AFTER duration;
ALTER TABLE rs_training_application ADD COLUMN end_time datetime COMMENT '结束时间' AFTER start_time;
-- 更新培训审批表,添加create_dept字段
ALTER TABLE rs_training_approval ADD COLUMN create_dept bigint(20) COMMENT '创建部门' AFTER tenant_id;
-- 更新培训记录表,添加create_dept字段
ALTER TABLE rs_training_record ADD COLUMN create_dept bigint(20) COMMENT '创建部门' AFTER tenant_id;
@@ -0,0 +1,103 @@
-- ----------------------------
-- 培训管理模块数据库表
-- ----------------------------
-- ----------------------------
-- 培训课程表
-- ----------------------------
drop table if exists rs_training_course;
create table rs_training_course (
course_id bigint(20) not null comment '课程ID',
title varchar(100) not null comment '课程名称',
category varchar(50) not null comment '课程类别',
description text comment '课程描述',
start_time datetime not null comment '开始时间',
end_time datetime not null comment '结束时间',
duration int(11) not null comment '学时数',
location varchar(200) comment '培训地点',
max_participants int(11) default 0 comment '最大参与人数',
current_participants int(11) default 0 comment '当前报名人数',
status char(1) default '0' comment '状态(0正常 1停用)',
tenant_id varchar(20) default '000000' comment '租户编号',
create_by bigint(20) comment '创建者',
create_time datetime comment '创建时间',
update_by bigint(20) comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) comment '备注',
deleted char(1) default '0' comment '删除标志',
primary key (course_id)
) engine=innodb comment = '培训课程表';
-- ----------------------------
-- 培训申请表
-- ----------------------------
drop table if exists rs_training_application;
create table rs_training_application (
application_id bigint(20) not null comment '申请ID',
course_id bigint(20) not null comment '课程ID',
user_id bigint(20) not null comment '申请人ID',
user_name varchar(50) not null comment '申请人姓名',
department varchar(50) not null comment '所属部门',
position varchar(50) comment '职位',
reason text comment '申请理由',
status char(1) default '0' comment '状态(0待审核 1已通过 2已拒绝 3进行中 4已完成)',
apply_time datetime comment '申请时间',
current_approver varchar(50) comment '当前审批人',
tenant_id varchar(20) default '000000' comment '租户编号',
create_by bigint(20) comment '创建者',
create_time datetime comment '创建时间',
update_by bigint(20) comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) comment '备注',
deleted char(1) default '0' comment '删除标志',
primary key (application_id)
) engine=innodb comment = '培训申请表';
-- ----------------------------
-- 培训审批表
-- ----------------------------
drop table if exists rs_training_approval;
create table rs_training_approval (
approval_id bigint(20) not null comment '审批ID',
application_id bigint(20) not null comment '申请ID',
approver_id bigint(20) not null comment '审批人ID',
approver_name varchar(50) not null comment '审批人姓名',
approval_level int(11) not null comment '审批级别(1部门主管 2分管领导 3校领导)',
status char(1) default '0' comment '状态(0待审核 1已通过 2已拒绝)',
opinion varchar(500) comment '审批意见',
approval_time datetime comment '审批时间',
tenant_id varchar(20) default '000000' comment '租户编号',
create_by bigint(20) comment '创建者',
create_time datetime comment '创建时间',
update_by bigint(20) comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) comment '备注',
deleted char(1) default '0' comment '删除标志',
primary key (approval_id)
) engine=innodb comment = '培训审批表';
-- ----------------------------
-- 培训记录表
-- ----------------------------
drop table if exists rs_training_record;
create table rs_training_record (
record_id bigint(20) not null comment '记录ID',
application_id bigint(20) not null comment '申请ID',
course_id bigint(20) not null comment '课程ID',
user_id bigint(20) not null comment '用户ID',
user_name varchar(50) not null comment '用户姓名',
completion_status char(1) default '0' comment '完成状态(0进行中 1已完成 2未完成)',
completion_time datetime comment '完成时间',
score decimal(5,2) comment '成绩',
certificate_path varchar(255) comment '证书路径',
certificate_number varchar(50) comment '证书编号',
feedback text comment '培训反馈',
tenant_id varchar(20) default '000000' comment '租户编号',
create_by bigint(20) comment '创建者',
create_time datetime comment '创建时间',
update_by bigint(20) comment '更新者',
update_time datetime comment '更新时间',
remark varchar(500) comment '备注',
deleted char(1) default '0' comment '删除标志',
primary key (record_id)
) engine=innodb comment = '培训记录表';
@@ -0,0 +1,5 @@
-- 添加自定义课程名称字段
-- 创建于 2025-07-24
-- 在rs_training_application表中添加course_name列,用于存储自定义培训名称
ALTER TABLE `rs_training_application` ADD COLUMN `course_name` varchar(100) DEFAULT NULL COMMENT '自定义课程名称' AFTER `course_id`;
Loaded 100 of 1097 files, more files were not shown because too many files have changed in this diff. Show more