wuzhen update

This commit is contained in:
wuzhen committed 2025-08-10 00:28:48 +08:00
commit 0037cb4e7d
1503 files changed
+74822 -196654

No files matched your search

+40
View File
@@ -909,3 +909,43 @@ const pendingDeleteAttachmentIds = ref<string[]>([]); // 编辑时标
- **✅ 必须** 在新页面开发时引用本规范并注明 *附件管理统一规范* 版本
> **版本**: 2025-01-27 附件管理统一规范 v1.0
---
## 5. 构建问题解决方案
### 5.1 JavaScript heap out of memory 解决方案(2025-01-27)
#### 问题描述
在执行 `npm run build:prod` 时出现内存溢出错误:
```
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
```
#### 根本原因
- 项目使用大量图标插件(unplugin-icons、svg-icons、element-plus图标等)
- 构建时需要加载大量图标文件到内存处理
- Node.js默认堆内存限制(~1.4GB)不足
#### 标准解决方案
修改 `package.json` 中的构建脚本,增加Node.js堆内存限制:
```json
{
"scripts": {
"build:prod": "node --max-old-space-size=8192 node_modules/vite/bin/vite.js build --mode production",
"build:dev": "node --max-old-space-size=8192 node_modules/vite/bin/vite.js build --mode development"
}
}
```
#### 参数说明
- `--max-old-space-size=8192`:设置堆内存限制为8GB
- 可根据项目规模和机器内存调整数值(4096=4GB, 8192=8GB)
#### 适用场景
- 大型前端项目构建
- 使用大量图标组件的项目
- 需要处理大量静态资源的项目
> **版本**: 2025-01-27 构建问题解决方案 v1.0
File diff suppressed because it is too large. Load diff
+334
View File
@@ -0,0 +1,334 @@
# Cursor Rules - 项目开发规范总览
## 🎯 项目概述
**项目名称**: 校园管理系统 (hzdyjs_xg)
**技术架构**: 前后端分离 + 移动端
**工作目录**: `/Users/wuzilong/work/00code/hzdyjs_xg`
### 项目结构
- **前端工程**: `web/` (Vue 3 + TypeScript + Element Plus)
- **后端工程**: `backend/` (Spring Boot 3.4.6 + MyBatis-Plus)
- **移动端工程**: `mobile/` (Vue 2.6.14 + uni-app + uview-ui 2.0.38 - 智慧校园移动端)
- **文档管理**: `docs/` (完整的项目文档体系)
---
## 📚 规范文件引用
**重要说明**: 本项目的开发规范已按技术栈模块化拆分,请根据开发场景参考对应规范:
### 🔗 专门规范文件
```
.cursor/
├── cursorrules-docs.md # 📋 文档管理规范
├── cursorrules-frontend.md # 🎨 前端开发规范 (Vue 3)
├── cursorrules-backend.md # ⚙️ 后端开发规范 (Spring Boot)
├── cursorrules-mobile.md # 📱 移动端开发规范 (Vue 2 + uni-app + uview-ui)
├── cursorrules-workflow.md # 🔄 工作流开发规范 (Warm-Flow)
└── cursorrules-api-test.md # 🧪 API测试规范
```
### 📖 规范文件详情
#### 📋 文档管理规范 (.cursor/cursorrules-docs.md)
- 文档目录组织(中文化架构)
- **🚨 强制性文档同步更新规则**
- 变更记录管理规范(按日期和用户分文件)
- 业务模块文档管理(为AI提供完整上下文)
- **📊 API文档拆分管理规范** (2025-07-01新增,文件夹重组):
- **📁 文件夹结构**: 所有API文档统一存放在 `docs/开发文档/API/` 文件夹下
- **📝 文件组织**: 按业务模块拆分:基础规范、认证登录、物业报修、满意度调查
- **🔤 命名规范**: 中文文件命名,便于快速识别和查找
- **🔗 模块化管理**: 独立维护,减少冲突,交叉引用基础规范
- **📚 主入口**: API文档总览.md 作为统一导航入口
- **📋 模板支持**: API文档模板.md 用于新API文档生成
- **🔄 自动生成规则**: 新增API模块时,优先在API文件夹下创建对应文档
- 测试文档管理规范
#### 🎨 前端开发规范 (.cursor/cursorrules-frontend.md)
- Vue 3.5.13 + TypeScript 5.8.3 技术栈
- Art Design Pro UI 设计规范
- 轮廓式按钮设计系统
- 组件开发标准和页面模板
- API接口定义和状态管理
- 项目架构和性能优化
#### ⚙️ 后端开发规范 (.cursor/cursorrules-backend.md)
- Spring Boot 3.4.6 技术栈
- **🔴 数据库规范(强制性)** - 雪花ID、逻辑删除、多租户
- **🔴 API接口规范(强制性)** - 统一响应格式、Token认证
- **🔐 菜单界面和接口权限配置规范**
- 标准代码模板 (Controller/Service/Mapper)
#### 📱 移动端开发规范 (.cursor/cursorrules-mobile.md)
- **Vue 2.6.14 + uni-app + uview-ui 2.0.38 技术栈(智慧校园移动端)**
- **基于Vue 2的稳定架构(兼容性优先)**
- **uview-ui 2.0.38 成熟UI组件库(Vue 2版本)**
- **现代CSS变量设计系统和响应式布局适配**
- **完整的用户体验优化(动画、手势识别、无障碍访问)**
- **智能缓存策略和网络请求优化**
- **多端发布支持(H5、微信小程序、App、鸿蒙)**
- **iconfont图标系统(支持uview-ui的u-icon组件)**
#### 🔄 工作流开发规范 (.cursor/cursorrules-workflow.md)
- **Warm-Flow工作流框架** - 基于dromara社区开源的轻量级工作流引擎
- **🔴 强制性VO类规范(避免MyBatis映射错误)** - 所有工作流业务VO类必须包含四个基础字段
- **工作流状态管理** - 7种标准业务状态(draft、waiting、complete等)
- **标准工作流模式** - 普通流程、排他网关、并行网关、会签等6种模式
- **前端工作流组件** - approvalButton、submitVerify、approvalRecord标准组件使用
- **工作流权限配置** - @ConditionalOnEnable条件开启和Sa-Token权限控制
- **请假工作流示例** - 完整的工作流开发实践模板
#### 🧪 API测试规范 (.cursor/cursorrules-api-test.md)
- **✅ AES+RSA双重加密登录方法(已完全解决)**
- API测试最佳实践和通用测试方法
- 自动化测试脚本和性能测试
- 测试记录模板
---
## 🚨 核心开发原则(必须遵循)
### 1. 📝 强制性文档同步更新
**⚠️ 超级重要**: 每次代码调整都必须立即同步更新相应文档!
#### 必须更新的文档
- **API变更** → 按业务模块更新对应的API文档(2025-07-01重新组织到API文件夹):
- 基础规范变更 → `docs/开发文档/API/API文档基础规范.md`
- 认证登录变更 → `docs/开发文档/API/用户认证登录API.md`
- 物业报修变更 → `docs/开发文档/API/物业报修管理API.md`
- 满意度调查变更 → `docs/开发文档/API/后勤满意度调查API.md`
- API文档总览 → `docs/开发文档/API/API文档总览.md`
- **数据库变更** → `docs/设计文档/database/`
- **业务逻辑变更** → `docs/业务模块/[模块名称]/`
- **配置变更** → `docs/中间件配置/` 或 `docs/部署文档/`
- **📱 移动端变更** → 移动端工程相关文档必须同步更新:
- **移动端API新增/变更** → 相应的API文档必须补充移动端实现章节
- **移动端页面开发** → `docs/开发文档/` 移动端相关文档
- **移动端功能变更** → `docs/业务模块/[模块名称]/` 移动端部分
- **移动端技术规范变更** → `.cursor/cursorrules-mobile.md`
#### 变更记录规范
- **文件格式**: `docs/变更管理/code-adjust-record-YYYY-MM-DD-{username}.md`
- **排序要求**: 按时间倒序排列,最新记录在最上面
- **时间填写**: 必须填写真实的当前时间(YYYY-MM-DD HH:mm:ss)
### 2. 🔴 数据库开发强制要求
- **标准基础字段**: 所有业务表必须包含统一的基础字段
- **雪花ID主键**: 统一使用 `bigint(20)` 作为主键
- **逻辑删除**: 禁止物理删除,使用 `deleted` 字段
- **多租户支持**: 必须包含 `tenant_id` 字段
- **参考规范**: `docs/设计文档/database/mysql-database-standard.md`
### 3. 🔴 API接口开发强制要求
- **统一响应格式**: 分页用TableDataInfo,非分页用R类型
- **Token认证**: Bearer格式,包含clientid、referer等必需Header
- **权限注解**: 每个接口必须有@SaCheckPermission注解
- **参数验证**: 使用Bean Validation进行参数校验
### 4. 🎯 技术要求
- **Node.js**: 20.15.x+, npm 10.7.x+
- **Java**: JDK 17/21
- **前端访问**: http://localhost (默认80端口)
- **后端服务**: http://localhost:8080
- **移动端**: Vue 2.6.14 + uni-app + uview-ui 2.0.38(智慧校园移动端)
---
## 🔄 Cursor AI 工作流程
### 📋 开发工作流程
```
1. **确定开发场景** → 自动应用对应技术栈规范
2. **编写/修改代码** → 遵循相应的编码规范
3. **立即更新文档** → 同步更新API文档和相关文档
4. **记录变更日志** → 在当日变更文件中记录
5. **验证和测试** → 确保功能正常和文档准确
```
### 🤖 Cursor AI 规范应用
- **前端开发**: 自动参考 `cursorrules-frontend.md`
- **后端开发**: 自动参考 `cursorrules-backend.md`
- **移动端开发**: 自动参考 `cursorrules-mobile.md`
- **工作流开发**: 自动参考 `cursorrules-workflow.md`
- **文档更新**: 自动参考 `cursorrules-docs.md`
- **API测试**: 自动参考 `cursorrules-api-test.md`
### 👍 点赞后知识总结机制
**⚠️ 重要提醒:当用户对Cursor回答点赞时,请主动询问是否需要进行总结归档**
#### 点赞触发规则
- **触发条件**: 用户对Cursor的回答点赞(👍)
- **自动提醒**: "检测到您对本次回答满意,是否需要将相关经验总结归档到项目规范中?"
- **总结目标**: 将成功的解决方案、最佳实践、经验教训整理到规范文档中
#### 总结内容建议
1. **技术解决方案**: 将有效的技术方案整理到对应的cursorrules文档
2. **开发流程优化**: 将改进的工作流程补充到开发规范中
3. **最佳实践**: 将成功经验添加到最佳实践文档
4. **问题解决方案**: 将常见问题和解决方案整理到知识库
5. **规范完善**: 根据实际应用效果完善和优化现有规范
#### 总结文档位置
- **技术规范**: 对应的`.cursor/cursorrules-*.md`文件
- **最佳实践**: `docs/开发文档/API开发最佳实践总结.md`
- **项目知识库**: `docs/知识库/`目录
- **变更记录**: `docs/变更管理/code-adjust-record-YYYY-MM-DD-{username}.md`
#### 总结流程
```
用户点赞 → Cursor主动询问 → 确认总结 → 分析价值点 → 整理到规范 → 更新相关文档
```
#### 价值目标
- 📚 **知识积累**: 将零散的经验系统化整理
- 🔄 **规范优化**: 基于实际应用持续改进规范
- 📖 **团队共享**: 将个人经验转化为团队资产
- 🚀 **效率提升**: 为后续类似问题提供标准解决方案
### 💡 API开发最佳实践
**⚠️ 基于场地预约管理系统开发实践制定的标准流程**
#### 完整开发流程(6个阶段)
1. **需求分析和文档创建** → 业务需求+技术架构文档
2. **数据库设计和初始化** → 表结构+字典+权限+示例数据
3. **后端模块开发** → 完整ruoyi模块(16个API接口)
4. **API文档编写** → 400+行详细API文档
5. **自动化测试脚本** → 450+行Python测试脚本
6. **变更记录和验证** → 完整的变更日志
#### 16个标准API接口模式
- **基础管理接口(6个)**: list、详情、新增、修改、删除、可用选项
- **业务功能接口(4个)**: 我的数据、待处理、规则检查、审批
- **辅助功能接口(6个)**: 导出、导入、统计、批量、启用、禁用
#### 🔴 VO类基础字段统一规范(新增 2025-07-01)
**⚠️ 超级重要:所有VO类必须包含统一的基础字段!**
**背景问题**:
- MyBatis-Plus自动查询:不包含继承字段
- 自定义SQL查询:明确包含字段,VO类必须有对应属性
- 导致运行时字段缺失错误:`Could not set property 'createBy'`
**统一解决方案**:
```java
// 所有VO类必须包含的四个基础字段
private Long createBy; // 创建者
private Date createTime; // 创建时间(含JSON格式化)
private Long updateBy; // 更新者
private Date updateTime; // 更新时间(含JSON格式化)
```
**强制要求**:
- ✅ 新增VO类必须包含四个基础字段
- ✅ 修复现有VO类时必须补全缺失字段
- ✅ 时间字段使用`@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")`
- ✅ 详细规范见:`.cursor/cursorrules-backend.md` → "VO类基础字段规范"
#### 质量保证标准
- ✅ **完整交付物**: 2个需求文档+3个SQL脚本+18个代码文件+1个API文档+1个测试脚本
- ✅ **开发时长**: 约3.5天完成完整API模块
- ✅ **文档同步**: 代码完成立即更新相关文档
- ✅ **测试覆盖**: 强制AES+RSA加密登录+完整功能测试
- ✅ **VO类规范**: 所有VO类包含统一基础字段,避免MyBatis映射错误
#### 参考文档
- **完整实践总结**: `docs/开发文档/API开发最佳实践总结.md`
- **成功案例**: `docs/开发文档/API/场地预约管理API.md`
- **测试脚本模板**: `test/场地预约API测试脚本.py`
- **VO类规范**: `.cursor/cursorrules-backend.md` → "VO类基础字段规范(强制性)"
---
## 🎯 快速开始
### 💡 开发提示
1. **查看具体规范**: 打开对应的 `.cursor/cursorrules-*.md` 文件
2. **遵循强制要求**: 特别注意标记为🔴和🚨的强制性规范
3. **使用标准模板**: 参考各规范文件中的代码模板
4. **及时更新文档**: 代码完成后立即更新相关文档
### 📚 重要文档位置
- **项目启动指南**: `工程本地启动指导.md`
- **API文档拆分架构** (按业务模块拆分,2025-07-01优化并重新组织到API文件夹):
- 📚 **API文档总览**: `docs/开发文档/API/API文档总览.md` (主入口导航)
- 📋 **基础规范**: `docs/开发文档/API/API文档基础规范.md` (通用规范)
- 🔐 **认证登录**: `docs/开发文档/API/用户认证登录API.md` (登录、用户信息)
- 🛠️ **物业报修**: `docs/开发文档/API/物业报修管理API.md` (报修工单管理)
- 📊 **满意度调查**: `docs/开发文档/API/后勤满意度调查API.md` (问卷答题)
- 📝 **API文档模板**: `docs/开发文档/API/API文档模板.md` (新API文档生成模板)
- **数据库设计**: `docs/设计文档/database/`
- **业务模块文档**: `docs/业务模块/`
- **测试脚本**: `test/` 目录
---
## 📞 规范说明
### 🔧 规范更新
- **规范文件更新**: 直接编辑对应的专门文件
- **跨模块变更**: 需要在相关文件中保持一致
- **重大变更**: 需要同步更新本主配置文件
### 📋 使用原则
1. **技术栈导向**: 根据当前开发的技术栈参考对应规范
2. **强制性要求**: 必须严格遵循标记为强制性的规范
3. **文档同步**: 代码变更必须同步更新文档
4. **质量保证**: 确保代码和文档的一致性
---
**🎯 目标**: 通过模块化规范管理和强制性文档同步,为Cursor AI提供准确的开发上下文,确保高质量的代码生成和完善的项目文档!
**📝 维护**: 拆分完成于 2025-07-01,所有原有内容完整保留
**📁 文件**: 5个专门规范文件 + 1个主配置文件
**🔄 加载**: Cursor自动识别和应用相关规范
# 原型文件存放规范(2025-07-01 新增)
- 所有后续生成的原型文件(如HTML原型、交互原型等),必须统一存放在 docs/原型文件/ 目录下的相应子目录中。
- 子目录应按端(如 web、移动端、小程序等)或业务模块分类,确保结构清晰、便于查找和维护。
- 禁止将原型文件存放在源码目录(如 web/src、mobile/src 等)或其他非文档目录。
- 迁移或生成原型文件时,需同步更新目录结构说明文档(如 docs/原型文件/README.md )。
# 移动端技术栈规范(2025-01-27 更新)
## 🚨 重要变更说明
**变更时间**: 2025-01-27
**变更内容**: 移动端技术栈从 Vue 3 + uview-plus 调整为 Vue 2 + uview-ui
**变更原因**: 基于项目实际技术配置和稳定性考虑
### 📱 移动端技术栈调整
#### 调整前(错误)
- Vue 3.4.21 + uview-plus 3.4.45
- 现代化架构但与实际项目不符
#### 调整后(正确)
- **Vue 2.6.14 + uview-ui 2.0.38**
- **基于uni-app的稳定架构**
- **与实际项目配置完全一致**
### 🔄 相关文档更新
- ✅ **主规范文件**: `.cursor/cursorrules.md` - 移动端技术栈描述已更新
- ✅ **移动端规范**: `.cursor/cursorrules-mobile.md` - 完整重写为Vue 2技术栈
- 📝 **变更记录**: 本次调整将记录在变更管理文档中
### 📋 开发者注意事项
1. **图标使用**: 支持 iconfont 图标系统 + uview-ui 的 u-icon 组件
2. **组件引用**: 使用 `<u-xxx>` 格式引用 uview-ui 组件
3. **页面配置**: easycom 自动注册:`"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"`
4. **样式导入**: `@import 'uview-ui/index.scss'`
### 🎯 技术选型原则
- **稳定性优先**: Vue 2 生态成熟稳定
- **兼容性考虑**: uview-ui 2.x 对 uni-app 兼容性更好
- **团队熟悉度**: 基于实际项目经验
- **维护成本**: 降低技术栈复杂度
+1 -2
View File
@@ -1,3 +1,2 @@
# Add directories or file patterns to ignore during indexing (e.g. foo/ or *.csv)
temp/
test/
+7 -7
View File
@@ -9,7 +9,7 @@
### 项目结构
- **前端工程**: `web/` (Vue 3 + TypeScript + Element Plus)
- **后端工程**: `backend/` (Spring Boot 3.4.6 + MyBatis-Plus)
- **移动端工程**: `mobile/` (Vue 3.4.21 + uniapp + uview-plus 3.4.45 - 现代化智慧校园)
- **移动端工程**: `mobile/` (Vue 2.6.14 + uni-app 2.x + uview-ui 2.0.38 - 智慧校园移动应用)
- **文档管理**: `docs/` (完整的项目文档体系)
---
@@ -62,8 +62,8 @@
- 标准代码模板 (Controller/Service/Mapper)
#### 📱 移动端开发规范 (.cursor/cursorrules-mobile.md)
- Vue 3.4.21 + uni-app + uview-plus 3.4.45 技术栈(现代化智慧校园)
- 纯净的Vue 3项目架构(无状态管理库,采用原生API)
- Vue 2.6.14 + uni-app 2.x + uview-ui 2.0.38 技术栈(智慧校园移动应用)
- 基于Vue 2的响应式项目架构(包含Vuex状态管理)
- 现代CSS变量设计系统和响应式布局适配
- 完整的用户体验优化(动画、手势识别、无障碍访问)
- 智能缓存策略和网络请求优化
@@ -130,7 +130,7 @@
- **Java**: JDK 17/21
- **前端访问**: http://localhost (默认80端口)
- **后端服务**: http://localhost:8080
- **移动端**: Vue 3.4.21 + uni-app + uview-plus 3.4.45(现代化智慧校园)
- **移动端**: Vue 2.6.14 + uni-app 2.x + uview-ui 2.0.38(智慧校园移动应用,端口8081)
---
@@ -292,12 +292,12 @@ private Date updateTime; // 更新时间(含JSON格式化)
- 禁止将原型文件存放在源码目录(如 web/src、mobile/src 等)或其他非文档目录。
- 迁移或生成原型文件时,需同步更新目录结构说明文档(如 docs/原型文件/README.md )。
# uview-plus 图标组件规范(2025-07-01 新增)
- 移动端UI开发中,图标组件统一使用 <u-icon />(uview-plus 3.x 官方标准),禁止使用 <up-icon /> 或 <uv-icon />。
# uview-ui 图标组件规范(2025-01-03 更新)
- 移动端UI开发中,图标组件统一使用 <u-icon />(uview-ui 2.x 官方标准),同时支持iconfont自定义图标。
- 推荐通过 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-ui 官方文档:https://www.uviewui.com/components/icon.html
+9 -1
View File
@@ -161,4 +161,12 @@ coverage/
*.crt
*.p12
*.keystore/xgxt/
excel/
excel/
# 吴自龙注释
test/*
script/*
stgl-api-test.js
update/*
本地临时文件目录/*
docs/变更管理/
@@ -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
# 连接超时时间
@@ -5,7 +5,7 @@ spring.servlet.multipart.location: /ruoyi/server/temp
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://47.121.125.41:9090/admin
instance:
service-host-type: IP
metadata:
@@ -22,7 +22,7 @@ snail-job:
# SnailJob 接入验证令牌 详见 script/sql/ry_job.sql `sj_group_config`表
token: "SJ_cKqBTPzCsWA3VyuCfFoccmuIEGXjr5KT"
server:
host: 127.0.0.1
host: 47.121.125.41
port: 17888
# 命名空间UUID 详见 script/sql/ry_job.sql `sj_namespace`表`unique_id`字段
namespace: ${spring.profiles.active}
@@ -54,7 +54,7 @@ spring:
# rewriteBatchedStatements=true 批处理优化 大幅提升批量插入更新删除性能(对数据库有性能损耗 使用批量操作应考虑性能问题)
url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&autoReconnect=true&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
username: root
password: 'hn02le.34lkdLKD'
password: hn02le.34lkdLKD
# # 从库数据源
# slave:
# lazy: true
@@ -83,7 +83,7 @@ spring:
# password: root
hikari:
# 最大连接池数量
maxPoolSize: 20
maxPoolSize: 30
# 最小空闲线程数量
minIdle: 10
# 配置获取连接等待超时的时间
@@ -101,13 +101,13 @@ spring:
spring.data:
redis:
# 地址
host: localhost
host: 47.99.164.66
# 端口,默认为6379
port: 6379
port: 16379
# 数据库索引
database: 0
database: 3
# redis 密码必须配置
password: ruoyi123
password: Hzdyjs@123456
# 连接超时时间
timeout: 10s
# 是否开启ssl
@@ -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;
@@ -38,7 +38,7 @@ spring.boot.admin.client:
# 增加客户端开关
enabled: true
# 设置 Spring Boot Admin Server 地址
url: http://localhost:9090/admin
url: http://127.0.0.1:9090/admin
instance:
service-host-type: IP
metadata:
@@ -2,9 +2,9 @@ spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
url: jdbc:mysql://47.99.164.66:3306/hzdyjs_xg?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
password: hn02le.34lkdLKD
hikari:
connection-timeout: 30000
validation-timeout: 5000
@@ -30,7 +30,7 @@ snail-job:
spring.boot.admin.client:
# 增加客户端开关
enabled: true
url: http://localhost:9090/admin
url: http://47.121.125.41:9090/admin
instance:
service-host-type: IP
metadata:
@@ -26,7 +26,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
<<<<<<< HEAD
import lombok.extern.slf4j.Slf4j;
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
import java.util.List;
import java.util.Map;
@@ -38,7 +41,10 @@ import java.util.HashMap;
* @author ruoyi
* @date 2025-01-01
*/
<<<<<<< HEAD
@Slf4j
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
@Validated
@Tag(name = "奖学金申请记录管理", description = "奖学金申请记录管理Controller")
@RestController
@@ -91,6 +97,7 @@ public class JcbtScholarshipApplicationController extends BaseController {
@PutMapping
@Operation(summary = "修改奖学金申请记录")
public R<Void> edit(@Parameter(description = "奖学金申请记录") @Validated(EditGroup.class) @RequestBody JcbtScholarshipApplicationBo bo) {
<<<<<<< HEAD
log.info("🔍 [DEBUG] Controller.edit 开始执行");
log.info("🔍 [DEBUG] 接收到的BO对象: {}", bo);
log.info("🔍 [DEBUG] 关键字段检查:");
@@ -111,6 +118,9 @@ public class JcbtScholarshipApplicationController extends BaseController {
log.error("❌ [ERROR] 异常消息: {}", e.getMessage());
throw e;
}
=======
return toAjax(applicationService.updateByBo(bo));
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
/**
@@ -357,6 +367,7 @@ public class JcbtScholarshipApplicationController extends BaseController {
}
return R.ok(result);
}
<<<<<<< HEAD
/**
* 调试接口:详细SQL日志输出测试
@@ -414,4 +425,6 @@ public class JcbtScholarshipApplicationController extends BaseController {
return R.ok(result);
}
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -1,7 +1,10 @@
package org.dromara.jcbt.domain;
import com.baomidou.mybatisplus.annotation.*;
<<<<<<< HEAD
import com.fasterxml.jackson.annotation.JsonIgnore;
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
@@ -20,6 +23,7 @@ import java.util.Date;
@TableName("jcbt_scholarship_application")
public class JcbtScholarshipApplication extends BaseEntity {
<<<<<<< HEAD
/**
* 重写父类的createDept字段,排除此字段因为数据库表中不存在create_dept列
* 通过重新声明字段并标记为不存在来避免MyBatis-Plus自动处理
@@ -33,6 +37,8 @@ public class JcbtScholarshipApplication extends BaseEntity {
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
@Serial
private static final long serialVersionUID = 1L;
@@ -43,12 +49,15 @@ public class JcbtScholarshipApplication extends BaseEntity {
private Long id;
/**
<<<<<<< HEAD
* 申请编号
*/
@TableField("application_no")
private String applicationNo;
/**
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
* 批次ID
*/
private Long batchId;
@@ -61,8 +70,12 @@ public class JcbtScholarshipApplication extends BaseEntity {
/**
* 学生学号
*/
<<<<<<< HEAD
@TableField("student_no")
private String studentNo;
=======
private String studentCode;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 学生姓名
@@ -80,9 +93,15 @@ public class JcbtScholarshipApplication extends BaseEntity {
private Long deptId;
/**
<<<<<<< HEAD
* 专业ID
*/
private Long majorId;
=======
* 学院名称
*/
private String deptName;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 班级ID
@@ -90,14 +109,40 @@ public class JcbtScholarshipApplication extends BaseEntity {
private Long classId;
/**
<<<<<<< HEAD
* 申请类型:RECOMMEND-教师推荐,AUTO-自动评定,DIRECT-直接指定
*/
@TableField("application_type")
=======
* 班级名称
*/
private String className;
/**
* 申请人类型:STUDENT-学生申请,TEACHER-教师代申请
*/
private String applicantType;
/**
* 申请人ID(班主任ID)
*/
private Long applicantId;
/**
* 申请人姓名(班主任姓名)
*/
private String applicantName;
/**
* 申请类型:RECOMMEND-教师推荐,AUTO-自动评定,DIRECT-直接指定
*/
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
private String applicationType;
/**
* 申请原因
*/
<<<<<<< HEAD
@TableField("apply_reason")
private String applyReason;
@@ -138,13 +183,113 @@ public class JcbtScholarshipApplication extends BaseEntity {
*/
@TableField("current_status")
private String currentStatus;
=======
private String applicationReason;
/**
* 申请说明
*/
private String applicationDescription;
/**
* 综合测评成绩
*/
private Double comprehensiveScore;
/**
* 学习成绩
*/
private Double academicScore;
/**
* 德育成绩
*/
private Double moralScore;
/**
* 体育成绩
*/
private Double physicalScore;
/**
* 美育成绩
*/
private Double aestheticScore;
/**
* 劳育成绩
*/
private Double laborScore;
/**
* 获奖情况
*/
private String awards;
/**
* 社会实践情况
*/
private String socialPractice;
/**
* 家庭经济情况
*/
private String familyEconomicStatus;
/**
* 申请状态:DRAFT-草稿,SUBMITTED-已提交,REVIEWING-审核中,APPROVED-已通过,REJECTED-已拒绝,WITHDRAWN-已撤回
*/
private String status;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 提交时间
*/
private Date submitTime;
<<<<<<< HEAD
=======
/**
* 审核时间
*/
private Date reviewTime;
/**
* 审核人ID
*/
private Long reviewerId;
/**
* 审核人姓名
*/
private String reviewerName;
/**
* 审核意见
*/
private String reviewComment;
/**
* 是否需要材料:0-否,1-是
*/
private Integer requireMaterials;
/**
* 材料上传状态:PENDING-待上传,PARTIAL-部分上传,COMPLETE-完整上传
*/
private String materialStatus;
/**
* 排序号
*/
private Integer sortOrder;
/**
* 备注
*/
private String remark;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 租户ID
@@ -98,6 +98,7 @@ public class JcbtScholarshipBatch extends BaseEntity {
private String noticeContent;
/**
<<<<<<< HEAD
* 租户ID
*/
@TableField("tenant_id")
@@ -110,6 +111,8 @@ public class JcbtScholarshipBatch extends BaseEntity {
private Integer deleted;
/**
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
* 关联的奖学金类型ID列表(非数据库字段)
*/
@TableField(exist = false)
@@ -56,7 +56,10 @@ public class JcbtScholarshipCompatibility extends BaseEntity {
/**
* 租户ID
*/
<<<<<<< HEAD
@TableField("tenant_id")
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
private Long tenantId;
/**
@@ -45,7 +45,11 @@ public class JcbtScholarshipApplicationBo extends BaseEntity {
*/
@NotBlank(message = "学生学号不能为空", groups = { AddGroup.class, EditGroup.class })
@Size(max = 50, message = "学生学号长度不能超过50个字符")
<<<<<<< HEAD
private String studentNo;
=======
private String studentCode;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 学生姓名
@@ -83,7 +87,20 @@ public class JcbtScholarshipApplicationBo extends BaseEntity {
@Size(max = 100, message = "班级名称长度不能超过100个字符")
private String className;
<<<<<<< HEAD
// 已删除 applicantType 和 applicantId 字段
=======
/**
* 申请人类型:STUDENT-学生申请,TEACHER-教师代申请
*/
@NotBlank(message = "申请人类型不能为空", groups = { AddGroup.class, EditGroup.class })
private String applicantType;
/**
* 申请人ID(班主任ID)
*/
private Long applicantId;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 申请人姓名(班主任姓名)
@@ -144,6 +161,7 @@ public class JcbtScholarshipApplicationBo extends BaseEntity {
private Double aestheticScore;
/**
<<<<<<< HEAD
* 劳动成绩
*/
@DecimalMin(value = "0", message = "劳动成绩不能为负数")
@@ -183,6 +201,34 @@ public class JcbtScholarshipApplicationBo extends BaseEntity {
/**
* 状态字段(前端使用,映射到currentStatus)
=======
* 劳育成绩
*/
@DecimalMin(value = "0", message = "劳育成绩不能为负数")
@DecimalMax(value = "100", message = "劳育成绩不能超过100分")
private Double laborScore;
/**
* 获奖情况
*/
@Size(max = 2000, message = "获奖情况长度不能超过2000个字符")
private String awards;
/**
* 社会实践情况
*/
@Size(max = 2000, message = "社会实践情况长度不能超过2000个字符")
private String socialPractice;
/**
* 家庭经济情况
*/
@Size(max = 1000, message = "家庭经济情况长度不能超过1000个字符")
private String familyEconomicStatus;
/**
* 申请状态:DRAFT-草稿,SUBMITTED-已提交,REVIEWING-审核中,APPROVED-已通过,REJECTED-已拒绝,WITHDRAWN-已撤回
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
*/
private String status;
@@ -190,6 +236,7 @@ public class JcbtScholarshipApplicationBo extends BaseEntity {
* 提交时间
*/
private Date submitTime;
<<<<<<< HEAD
/**
* 审核评语
@@ -229,4 +276,51 @@ public class JcbtScholarshipApplicationBo extends BaseEntity {
public void setApplicationStatus(String applicationStatus) {
this.currentStatus = applicationStatus;
}
=======
/**
* 审核时间
*/
private Date reviewTime;
/**
* 审核人ID
*/
private Long reviewerId;
/**
* 审核人姓名
*/
@Size(max = 100, message = "审核人姓名长度不能超过100个字符")
private String reviewerName;
/**
* 审核意见
*/
@Size(max = 1000, message = "审核意见长度不能超过1000个字符")
private String reviewComment;
/**
* 是否需要材料:0-否,1-是
*/
private Integer requireMaterials;
/**
* 材料上传状态:PENDING-待上传,PARTIAL-部分上传,COMPLETE-完整上传
*/
private String materialStatus;
/**
* 排序号
*/
@Min(value = 0, message = "排序号不能小于0")
private Integer sortOrder;
/**
* 备注
*/
@Size(max = 500, message = "备注长度不能超过500个字符")
private String remark;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -118,9 +118,12 @@ public class JcbtScholarshipBatchBo extends BaseEntity {
*/
private List<Long> scholarshipTypeIds;
<<<<<<< HEAD
/**
* 租户ID
*/
private Long tenantId;
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -60,9 +60,12 @@ public class JcbtScholarshipCompatibilityBo extends BaseEntity {
@Size(max = 500, message = "关系描述长度不能超过500个字符")
private String description;
<<<<<<< HEAD
/**
* 租户ID
*/
private Long tenantId;
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -49,6 +49,7 @@ public class JcbtScholarshipApplicationVo implements Serializable {
private String scholarshipTypeName;
/**
<<<<<<< HEAD
* 申请编号
*/
private String applicationNo;
@@ -57,6 +58,11 @@ public class JcbtScholarshipApplicationVo implements Serializable {
* 学生学号
*/
private String studentNo;
=======
* 学生学号
*/
private String studentCode;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 学生姓名
@@ -79,11 +85,14 @@ public class JcbtScholarshipApplicationVo implements Serializable {
private String deptName;
/**
<<<<<<< HEAD
* 专业ID
*/
private Long majorId;
/**
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
* 班级ID
*/
private Long classId;
@@ -93,7 +102,19 @@ public class JcbtScholarshipApplicationVo implements Serializable {
*/
private String className;
<<<<<<< HEAD
// 已删除 applicantType 和 applicantId 字段
=======
/**
* 申请人类型
*/
private String applicantType;
/**
* 申请人ID
*/
private Long applicantId;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 申请人姓名
@@ -106,6 +127,7 @@ public class JcbtScholarshipApplicationVo implements Serializable {
private String applicationType;
/**
<<<<<<< HEAD
* 当前状态
*/
private String currentStatus;
@@ -150,6 +172,66 @@ public class JcbtScholarshipApplicationVo implements Serializable {
* 在专业的排名
*/
private Integer rankingInMajor;
=======
* 申请原因
*/
private String applicationReason;
/**
* 申请详细描述
*/
private String applicationDescription;
/**
* 综合测评成绩
*/
private Double comprehensiveScore;
/**
* 学习成绩
*/
private Double academicScore;
/**
* 德育成绩
*/
private Double moralScore;
/**
* 体育成绩
*/
private Double physicalScore;
/**
* 美育成绩
*/
private Double aestheticScore;
/**
* 劳育成绩
*/
private Double laborScore;
/**
* 获奖情况
*/
private String awards;
/**
* 社会实践情况
*/
private String socialPractice;
/**
* 家庭经济状况
*/
private String familyEconomicStatus;
/**
* 申请状态
*/
private String status;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 提交时间
@@ -158,9 +240,41 @@ public class JcbtScholarshipApplicationVo implements Serializable {
private Date submitTime;
/**
<<<<<<< HEAD
* 租户编号
*/
private String tenantId;
=======
* 审核时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date reviewTime;
/**
* 审核人ID
*/
private Long reviewerId;
/**
* 审核人姓名
*/
private String reviewerName;
/**
* 审核意见
*/
private String reviewComment;
/**
* 是否需要材料
*/
private Integer requireMaterials;
/**
* 材料状态
*/
private String materialStatus;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 备注
@@ -154,9 +154,12 @@ public class JcbtScholarshipBatchVo implements Serializable {
*/
private List<Long> scholarshipTypeIds;
<<<<<<< HEAD
/**
* 租户ID
*/
private Long tenantId;
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -91,9 +91,12 @@ public class JcbtScholarshipCompatibilityVo implements Serializable {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
<<<<<<< HEAD
/**
* 租户ID
*/
private Long tenantId;
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -4,8 +4,11 @@ import org.dromara.jcbt.domain.JcbtScholarshipApplication;
import org.dromara.jcbt.domain.vo.JcbtScholarshipApplicationVo;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.apache.ibatis.annotations.Param;
<<<<<<< HEAD
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
import java.util.List;
@@ -18,6 +21,7 @@ import java.util.List;
public interface JcbtScholarshipApplicationMapper extends BaseMapperPlus<JcbtScholarshipApplication, JcbtScholarshipApplicationVo> {
/**
<<<<<<< HEAD
* 自定义分页查询(包含关联表信息)
*
* @param page 分页对象
@@ -28,12 +32,19 @@ public interface JcbtScholarshipApplicationMapper extends BaseMapperPlus<JcbtSch
/**
* 查询班主任负责班级的申请记录列表(已删除applicantId字段,需重新设计)
=======
* 查询班主任负责班级的申请记录列表
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
*
* @param applicantId 申请人ID(班主任ID)
* @param batchId 批次ID
* @return 申请记录列表
*/
<<<<<<< HEAD
// List<JcbtScholarshipApplicationVo> selectApplicationsByApplicant(@Param("applicantId") Long applicantId, @Param("batchId") Long batchId);
=======
List<JcbtScholarshipApplicationVo> selectApplicationsByApplicant(@Param("applicantId") Long applicantId, @Param("batchId") Long batchId);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
/**
* 查询班级的申请记录列表
@@ -54,6 +65,7 @@ public interface JcbtScholarshipApplicationMapper extends BaseMapperPlus<JcbtSch
List<JcbtScholarshipApplicationVo> selectPendingApplications(@Param("reviewerId") Long reviewerId, @Param("status") String status);
/**
<<<<<<< HEAD
* 根据ID查询申请记录详情(包含关联信息)
*
* @param id 主键ID
@@ -62,6 +74,8 @@ public interface JcbtScholarshipApplicationMapper extends BaseMapperPlus<JcbtSch
JcbtScholarshipApplicationVo selectByIdWithRelations(@Param("id") Long id);
/**
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
* 检查学生在指定批次中是否已有申请记录
*
* @param studentId 学生ID
@@ -103,6 +103,7 @@ public interface JcbtScholarshipQuotaMapper extends BaseMapperPlus<JcbtScholarsh
List<JcbtScholarshipQuotaVo> selectQuotaTreeByBatchAndType(@Param("batchId") Long batchId,
@Param("scholarshipTypeId") Long scholarshipTypeId);
<<<<<<< HEAD
/**
* 查询班级级别的名额分配
*
@@ -115,4 +116,6 @@ public interface JcbtScholarshipQuotaMapper extends BaseMapperPlus<JcbtScholarsh
@Param("scholarshipTypeId") Long scholarshipTypeId,
@Param("batchId") Long batchId);
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -204,11 +204,16 @@ public interface IJcbtScholarshipApplicationService {
*
* @param batchId 批次ID
* @param classId 班级ID
<<<<<<< HEAD
* @return 奖学金类型列表(包含名额信息)
=======
* @return 带名额信息的奖学金类型列表
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
*/
List<Map<String, Object>> getScholarshipTypesWithQuota(Long batchId, Long classId);
/**
<<<<<<< HEAD
* 测试班级名额查询(用于SQL日志调试)
*
* @param classId 班级ID
@@ -228,6 +233,8 @@ public interface IJcbtScholarshipApplicationService {
Map<String, Object> testSchoolQuotaQuery(Long batchId, Long scholarshipTypeId);
/**
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
* 确认选择班级(当用户从多个班级中选择一个时调用)
*
* @param teacherId 班主任ID
@@ -177,6 +177,7 @@ public interface IJcbtScholarshipQuotaService {
*/
String validateQuotaAllocation(JcbtScholarshipQuotaBo bo);
<<<<<<< HEAD
/**
* 查询班级级别的名额分配
*
@@ -187,4 +188,6 @@ public interface IJcbtScholarshipQuotaService {
*/
JcbtScholarshipQuotaVo selectClassQuota(Long classId, Long scholarshipTypeId, Long batchId);
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -37,9 +37,13 @@ import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
<<<<<<< HEAD
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
=======
import java.util.*;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
import lombok.extern.slf4j.Slf4j;
/**
@@ -71,7 +75,11 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
*/
@Override
public JcbtScholarshipApplicationVo queryById(Long id) {
<<<<<<< HEAD
return baseMapper.selectByIdWithRelations(id);
=======
return baseMapper.selectVoById(id);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
/**
@@ -80,8 +88,12 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
@Override
public TableDataInfo<JcbtScholarshipApplicationVo> queryPageList(JcbtScholarshipApplicationBo bo, PageQuery pageQuery) {
LambdaQueryWrapper<JcbtScholarshipApplication> lqw = buildQueryWrapper(bo);
<<<<<<< HEAD
// 使用自定义分页查询(包含关联表信息)
Page<JcbtScholarshipApplicationVo> result = baseMapper.selectJcbtScholarshipApplicationPage(pageQuery.build(), lqw);
=======
Page<JcbtScholarshipApplicationVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
return TableDataInfo.build(result);
}
@@ -99,11 +111,16 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
LambdaQueryWrapper<JcbtScholarshipApplication> lqw = Wrappers.lambdaQuery();
lqw.eq(ObjectUtil.isNotNull(bo.getBatchId()), JcbtScholarshipApplication::getBatchId, bo.getBatchId());
lqw.eq(ObjectUtil.isNotNull(bo.getScholarshipTypeId()), JcbtScholarshipApplication::getScholarshipTypeId, bo.getScholarshipTypeId());
<<<<<<< HEAD
lqw.eq(StringUtils.isNotBlank(bo.getStudentNo()), JcbtScholarshipApplication::getStudentNo, bo.getStudentNo());
=======
lqw.eq(StringUtils.isNotBlank(bo.getStudentCode()), JcbtScholarshipApplication::getStudentCode, bo.getStudentCode());
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
lqw.like(StringUtils.isNotBlank(bo.getStudentName()), JcbtScholarshipApplication::getStudentName, bo.getStudentName());
lqw.eq(ObjectUtil.isNotNull(bo.getStudentId()), JcbtScholarshipApplication::getStudentId, bo.getStudentId());
lqw.eq(ObjectUtil.isNotNull(bo.getDeptId()), JcbtScholarshipApplication::getDeptId, bo.getDeptId());
lqw.eq(ObjectUtil.isNotNull(bo.getClassId()), JcbtScholarshipApplication::getClassId, bo.getClassId());
<<<<<<< HEAD
// 已删除 applicantType 和 applicantId 字段
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), JcbtScholarshipApplication::getCurrentStatus, bo.getStatus());
// 支持前端 applicationStatus 查询参数
@@ -112,6 +129,14 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
JcbtScholarshipApplication::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
// 注意:排序在XML中处理,避免SQL语法错误
// lqw.orderByDesc(JcbtScholarshipApplication::getCreateTime);
=======
lqw.eq(StringUtils.isNotBlank(bo.getApplicantType()), JcbtScholarshipApplication::getApplicantType, bo.getApplicantType());
lqw.eq(ObjectUtil.isNotNull(bo.getApplicantId()), JcbtScholarshipApplication::getApplicantId, bo.getApplicantId());
lqw.eq(StringUtils.isNotBlank(bo.getStatus()), JcbtScholarshipApplication::getStatus, bo.getStatus());
lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
JcbtScholarshipApplication::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
lqw.orderByDesc(JcbtScholarshipApplication::getCreateTime);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
return lqw;
}
@@ -127,6 +152,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
throw new RuntimeException((String) eligibilityCheck.get("message"));
}
<<<<<<< HEAD
JcbtScholarshipApplication add = MapstructUtils.convert(bo, JcbtScholarshipApplication.class);
validEntityBeforeSave(add);
@@ -141,6 +167,23 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
}
if (StringUtils.isBlank(add.getApplicationType())) {
add.setApplicationType("TEACHER_RECOMMEND"); // 默认为教师推荐类型
=======
JcbtScholarshipApplication add = BeanUtil.toBean(bo, JcbtScholarshipApplication.class);
validEntityBeforeSave(add);
// 设置默认值
if (StringUtils.isBlank(add.getStatus())) {
add.setStatus("DRAFT");
}
if (StringUtils.isBlank(add.getApplicantType())) {
add.setApplicantType("TEACHER");
}
if (add.getRequireMaterials() == null) {
add.setRequireMaterials(0);
}
if (StringUtils.isBlank(add.getMaterialStatus())) {
add.setMaterialStatus("PENDING");
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
boolean flag = baseMapper.insert(add) > 0;
@@ -156,6 +199,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(JcbtScholarshipApplicationBo bo) {
<<<<<<< HEAD
log.info("🔍 [DEBUG] Service.updateByBo 开始执行");
log.info("🔍 [DEBUG] 接收到的BO对象: {}", bo);
log.info("🔍 [DEBUG] BO关键字段检查:");
@@ -193,12 +237,18 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
log.error("❌ [ERROR] 异常消息: {}", e.getMessage());
throw e;
}
=======
JcbtScholarshipApplication update = BeanUtil.toBean(bo, JcbtScholarshipApplication.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(JcbtScholarshipApplication entity) {
<<<<<<< HEAD
log.info("🔍 [DEBUG] validEntityBeforeSave 开始执行");
log.info("🔍 [DEBUG] 待校验的Entity: {}", entity);
@@ -228,6 +278,21 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
}
log.info("🔍 [DEBUG] validEntityBeforeSave 校验完成");
=======
// 数据校验逻辑
if (StringUtils.isBlank(entity.getStudentCode())) {
throw new RuntimeException("学生学号不能为空");
}
if (StringUtils.isBlank(entity.getStudentName())) {
throw new RuntimeException("学生姓名不能为空");
}
if (entity.getBatchId() == null) {
throw new RuntimeException("批次ID不能为空");
}
if (entity.getScholarshipTypeId() == null) {
throw new RuntimeException("奖学金类型ID不能为空");
}
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
/**
@@ -240,7 +305,11 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
// 删除前校验逻辑
for (Long id : ids) {
JcbtScholarshipApplicationVo application = queryById(id);
<<<<<<< HEAD
if (application != null && "APPROVED".equals(application.getCurrentStatus())) {
=======
if (application != null && "APPROVED".equals(application.getStatus())) {
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
throw new RuntimeException("已通过审核的申请不能删除");
}
}
@@ -255,6 +324,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
@Transactional(rollbackFor = Exception.class)
public Boolean batchSubmitApplicationsByTeacher(Long batchId, Long scholarshipTypeId, Long classId,
List<JcbtScholarshipApplicationBo> studentApplications) {
<<<<<<< HEAD
// 检查班级名额 - 使用和前端一致的查询方法
log.info("开始检查班级名额 - 批次ID: {}, 奖学金类型ID: {}, 班级ID: {}, 申请人数: {}",
batchId, scholarshipTypeId, classId, studentApplications.size());
@@ -288,6 +358,12 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
log.error("全校名额不足 - 可用名额: {}, 申请人数: {}", totalAvailable, studentApplications.size());
throw new RuntimeException("全校可用名额不足,无法提交申请");
}
=======
// 检查班级名额
JcbtScholarshipClassQuotaVo classQuota = classQuotaService.queryAvailableQuota(classId, scholarshipTypeId, batchId);
if (classQuota == null || classQuota.getAvailableQuota() < studentApplications.size()) {
throw new RuntimeException("班级可用名额不足,无法提交申请");
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
// 批量创建申请记录
@@ -295,13 +371,18 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
applicationBo.setBatchId(batchId);
applicationBo.setScholarshipTypeId(scholarshipTypeId);
applicationBo.setClassId(classId);
<<<<<<< HEAD
applicationBo.setApplicationType("TEACHER_RECOMMEND"); // 班主任代申请属于教师推荐类型
=======
applicationBo.setApplicantType("TEACHER");
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
applicationBo.setStatus("SUBMITTED");
applicationBo.setSubmitTime(new Date());
insertByBo(applicationBo);
}
<<<<<<< HEAD
// 更新名额使用情况
if (classQuota != null) {
// 有班级名额分配,更新班级名额
@@ -326,6 +407,12 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
}
}
}
=======
// 更新班级名额使用情况
classQuotaService.updateQuotaUsage(classQuota.getId(),
classQuota.getUsedQuota() + studentApplications.size(),
classQuota.getAvailableQuota() - studentApplications.size());
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
return true;
}
@@ -335,9 +422,13 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
*/
@Override
public List<JcbtScholarshipApplicationVo> queryApplicationsByApplicant(Long applicantId, Long batchId) {
<<<<<<< HEAD
// TODO: 重新设计查询逻辑,因为applicantId字段已删除
// 可能需要通过班级ID或其他方式查询班主任负责的申请记录
return new ArrayList<>();
=======
return baseMapper.selectApplicationsByApplicant(applicantId, batchId);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
/**
@@ -397,11 +488,19 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
if (application == null) {
throw new RuntimeException("申请记录不存在");
}
<<<<<<< HEAD
if (!"DRAFT".equals(application.getCurrentStatus())) {
throw new RuntimeException("只有草稿状态的申请才能提交");
}
application.setCurrentStatus("SUBMITTED");
=======
if (!"DRAFT".equals(application.getStatus())) {
throw new RuntimeException("只有草稿状态的申请才能提交");
}
application.setStatus("SUBMITTED");
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
application.setSubmitTime(new Date());
return baseMapper.updateById(application) > 0;
}
@@ -416,12 +515,21 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
if (application == null) {
throw new RuntimeException("申请记录不存在");
}
<<<<<<< HEAD
if ("APPROVED".equals(application.getCurrentStatus()) || "REJECTED".equals(application.getCurrentStatus())) {
throw new RuntimeException("已审核的申请不能撤回");
}
application.setCurrentStatus("WITHDRAWN");
// 已删除 remark 字段,撤回原因暂时不存储
=======
if ("APPROVED".equals(application.getStatus()) || "REJECTED".equals(application.getStatus())) {
throw new RuntimeException("已审核的申请不能撤回");
}
application.setStatus("WITHDRAWN");
application.setRemark(StringUtils.isNotBlank(reason) ? reason : "申请人主动撤回");
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
return baseMapper.updateById(application) > 0;
}
@@ -671,7 +779,11 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
List<Map<String, Object>> students = new ArrayList<>();
try {
<<<<<<< HEAD
// 使用自包含查询获取学生列表,关联班级表获取学院和专业信息
=======
// 使用自包含查询获取学生列表
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
String sql = """
SELECT
sm.id,
@@ -680,6 +792,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
sm.gender,
sm.phone,
sm.email,
<<<<<<< HEAD
sm.student_status,
cm.college_id,
cm.college_name,
@@ -693,6 +806,13 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
AND sm.deleted = 0
AND sm.student_status = '1'
AND (cm.deleted IS NULL OR cm.deleted = 0)
=======
sm.student_status
FROM xg_student_msg sm
WHERE sm.class_id = ?
AND sm.deleted = 0
AND sm.student_status = '1'
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
ORDER BY sm.student_no ASC
""";
@@ -716,6 +836,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
studentMap.put("phone", student.get("phone") != null ? student.get("phone").toString() : "");
studentMap.put("email", student.get("email") != null ? student.get("email").toString() : "");
studentMap.put("status", student.get("student_status") != null ? student.get("student_status").toString() : "1");
<<<<<<< HEAD
// 添加学院和专业信息
studentMap.put("collegeId", student.get("college_id") != null ? student.get("college_id").toString() : "");
@@ -725,6 +846,8 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
studentMap.put("className", student.get("class_name") != null ? student.get("class_name").toString() : "");
studentMap.put("grade", student.get("grade") != null ? student.get("grade").toString() : "");
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
students.add(studentMap);
}
log.info("成功获取班级学生列表 - 班级ID: {}, 学生数量: {}", classId, students.size());
@@ -809,8 +932,12 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
List<Map<String, Object>> scholarshipTypes = new ArrayList<>();
try {
<<<<<<< HEAD
log.info("=== 开始获取带名额信息的奖学金类型 ===");
log.info("输入参数 - 批次ID: {}, 班级ID: {}", batchId, classId);
=======
log.info("获取带名额信息的奖学金类型,批次ID: {}, 班级ID: {}", batchId, classId);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
// 获取该批次关联的所有奖学金类型ID(从批次类型关联表)
List<Long> batchTypeIds = batchTypeService.getScholarshipTypeIdsByBatchId(batchId);
@@ -818,6 +945,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
if (batchTypeIds != null && !batchTypeIds.isEmpty()) {
for (Long scholarshipTypeId : batchTypeIds) {
<<<<<<< HEAD
log.info("--- 处理奖学金类型ID: {} ---", scholarshipTypeId);
// 查询奖学金类型基本信息
@@ -827,6 +955,10 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
typeInfo.getId(), typeInfo.getTypeName(), typeInfo.getStatus(),
typeInfo.getAwardAmount(), typeInfo.getLevelType()) : "null");
=======
// 查询奖学金类型基本信息
JcbtScholarshipTypeVo typeInfo = scholarshipTypeService.queryById(scholarshipTypeId);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
if (typeInfo != null && typeInfo.getStatus() == 1) { // 只返回启用的类型
Map<String, Object> scholarshipType = new HashMap<>();
scholarshipType.put("id", scholarshipTypeId);
@@ -836,6 +968,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
scholarshipType.put("typeCategory", typeInfo.getTypeCategory());
scholarshipType.put("levelType", typeInfo.getLevelType());
<<<<<<< HEAD
// 查询该班级在该奖学金类型下的名额分配信息(从jcbt_scholarship_quota表查询)
log.info("开始查询班级名额 - SQL参数: classId={}, scholarshipTypeId={}, batchId={}", classId, scholarshipTypeId, batchId);
log.info("执行SQL: SELECT * FROM jcbt_scholarship_quota WHERE allocation_level = 'CLASS' AND target_dept_id = {} AND scholarship_type_id = {} AND batch_id = {} AND status = 'ACTIVE'", classId, scholarshipTypeId, batchId);
@@ -849,6 +982,14 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
if (classQuota != null) {
// 有班级名额分配
log.info("发现班级名额分配,处理班级名额数据");
=======
// 查询该班级在该奖学金类型下的名额分配信息
JcbtScholarshipClassQuotaVo classQuota = classQuotaService.queryAvailableQuota(classId, scholarshipTypeId, batchId);
log.debug("班级名额查询结果,类型ID: {}, 班级ID: {}, 名额信息: {}", scholarshipTypeId, classId, classQuota);
if (classQuota != null) {
// 有班级名额分配
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
scholarshipType.put("hasQuota", true);
int totalQuota = classQuota.getTotalQuota() != null ? classQuota.getTotalQuota() : 0;
int usedQuota = classQuota.getUsedQuota() != null ? classQuota.getUsedQuota() : 0;
@@ -859,6 +1000,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
scholarshipType.put("availableQuota", availableQuota);
scholarshipType.put("quotaStatus", availableQuota > 0 ? "available" : "full");
<<<<<<< HEAD
log.info("班级名额处理结果 - 类型: {}, 总名额: {}, 已用: {}, 可用: {}, 状态: {}",
typeInfo.getTypeName(), totalQuota, usedQuota, availableQuota, availableQuota > 0 ? "available" : "full");
} else {
@@ -878,6 +1020,13 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
}
}
=======
log.debug("类型: {}, 总名额: {}, 已用: {}, 可用: {}, 状态: {}",
typeInfo.getTypeName(), totalQuota, usedQuota, availableQuota, availableQuota > 0 ? "available" : "full");
} else {
// 没有班级名额分配,查询全校统计信息
List<JcbtScholarshipQuotaVo> quotaList = quotaService.queryListByBatchAndType(batchId, scholarshipTypeId);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
int totalQuota = quotaList.stream().mapToInt(q -> q.getTotalQuota() != null ? q.getTotalQuota() : 0).sum();
int usedQuota = quotaList.stream().mapToInt(q -> q.getUsedQuota() != null ? q.getUsedQuota() : 0).sum();
int availableQuota = Math.max(0, totalQuota - usedQuota);
@@ -888,16 +1037,23 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
scholarshipType.put("availableQuota", availableQuota);
scholarshipType.put("quotaStatus", totalQuota > 0 ? (availableQuota > 0 ? "available" : "full") : "unlimited");
<<<<<<< HEAD
log.info("全校名额统计结果 - 类型: {} (全校), 总名额: {}, 已用: {}, 可用: {}, 状态: {}",
=======
log.debug("类型: {} (全校), 总名额: {}, 已用: {}, 可用: {}, 状态: {}",
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
typeInfo.getTypeName(), totalQuota, usedQuota, availableQuota,
totalQuota > 0 ? (availableQuota > 0 ? "available" : "full") : "unlimited");
}
scholarshipTypes.add(scholarshipType);
<<<<<<< HEAD
log.info("奖学金类型 {} 处理完成,添加到结果列表", typeInfo.getTypeName());
} else {
log.warn("奖学金类型被跳过 - ID: {}, 原因: {}", scholarshipTypeId,
typeInfo == null ? "类型信息不存在" : "状态未启用(status=" + typeInfo.getStatus() + ")");
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
}
@@ -923,6 +1079,7 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
return levelA != null ? levelA.compareTo(levelB) : 0;
});
<<<<<<< HEAD
log.info("=== 最终返回结果 ===");
log.info("奖学金类型总数: {}", scholarshipTypes.size());
for (int i = 0; i < scholarshipTypes.size(); i++) {
@@ -932,18 +1089,26 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
}
} else {
log.warn("批次 {} 未关联任何奖学金类型", batchId);
=======
log.info("最终返回的奖学金类型数量: {}", scholarshipTypes.size());
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
return scholarshipTypes;
} catch (Exception e) {
<<<<<<< HEAD
log.error("=== 获取带名额信息的奖学金类型失败 ===");
log.error("错误参数 - 批次ID: {}, 班级ID: {}", batchId, classId);
log.error("错误详情: ", e);
=======
log.error("获取带名额信息的奖学金类型失败,批次ID: {}, 班级ID: {}", batchId, classId, e);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
// 如果调用失败,返回空数据
return new ArrayList<>();
}
}
<<<<<<< HEAD
/**
* 测试班级名额查询(用于SQL日志调试)
*/
@@ -1157,4 +1322,6 @@ public class JcbtScholarshipApplicationServiceImpl implements IJcbtScholarshipAp
return "JX" + dateStr + randomNum;
}
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -91,7 +91,11 @@ public class JcbtScholarshipBatchServiceImpl implements IJcbtScholarshipBatchSer
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(JcbtScholarshipBatchBo bo) {
<<<<<<< HEAD
JcbtScholarshipBatch add = MapstructUtils.convert(bo, JcbtScholarshipBatch.class);
=======
JcbtScholarshipBatch add = BeanUtil.toBean(bo, JcbtScholarshipBatch.class);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
@@ -112,7 +116,11 @@ public class JcbtScholarshipBatchServiceImpl implements IJcbtScholarshipBatchSer
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(JcbtScholarshipBatchBo bo) {
<<<<<<< HEAD
JcbtScholarshipBatch update = MapstructUtils.convert(bo, JcbtScholarshipBatch.class);
=======
JcbtScholarshipBatch update = BeanUtil.toBean(bo, JcbtScholarshipBatch.class);
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
validEntityBeforeSave(update);
boolean flag = baseMapper.updateById(update) > 0;
@@ -493,6 +493,7 @@ public class JcbtScholarshipQuotaServiceImpl implements IJcbtScholarshipQuotaSer
return Math.max(0, total - used - reserved);
}
<<<<<<< HEAD
/**
* 查询班级级别的名额分配
*
@@ -512,4 +513,6 @@ public class JcbtScholarshipQuotaServiceImpl implements IJcbtScholarshipQuotaSer
return result;
}
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
}
@@ -303,6 +303,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ORDER BY qt.level, qt.target_dept_id
</select>
<<<<<<< HEAD
<!-- 查询班级级别的名额分配 -->
<select id="selectClassQuota" resultMap="JcbtScholarshipQuotaResult">
<include refid="selectJcbtScholarshipQuotaVo"/>
@@ -313,4 +314,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and q.status = 'ACTIVE'
</select>
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
</mapper>
@@ -9,12 +9,17 @@
<result property="batchName" column="batch_name"/>
<result property="scholarshipTypeId" column="scholarship_type_id"/>
<result property="scholarshipTypeName" column="scholarship_type_name"/>
<<<<<<< HEAD
<result property="applicationNo" column="application_no"/>
<result property="studentNo" column="student_no"/>
=======
<result property="studentCode" column="student_code"/>
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
<result property="studentName" column="student_name"/>
<result property="studentId" column="student_id"/>
<result property="deptId" column="dept_id"/>
<result property="deptName" column="dept_name"/>
<<<<<<< HEAD
<result property="majorId" column="major_id"/>
<result property="classId" column="class_id"/>
<result property="className" column="class_name"/>
@@ -28,6 +33,35 @@
<result property="finalScore" column="final_score"/>
<result property="rankingInDept" column="ranking_in_dept"/>
<result property="rankingInMajor" column="ranking_in_major"/>
=======
<result property="classId" column="class_id"/>
<result property="className" column="class_name"/>
<result property="applicantType" column="applicant_type"/>
<result property="applicantId" column="applicant_id"/>
<result property="applicantName" column="applicant_name"/>
<result property="applicationType" column="application_type"/>
<result property="applicationReason" column="application_reason"/>
<result property="applicationDescription" column="application_description"/>
<result property="comprehensiveScore" column="comprehensive_score"/>
<result property="academicScore" column="academic_score"/>
<result property="moralScore" column="moral_score"/>
<result property="physicalScore" column="physical_score"/>
<result property="aestheticScore" column="aesthetic_score"/>
<result property="laborScore" column="labor_score"/>
<result property="awards" column="awards"/>
<result property="socialPractice" column="social_practice"/>
<result property="familyEconomicStatus" column="family_economic_status"/>
<result property="status" column="status"/>
<result property="submitTime" column="submit_time"/>
<result property="reviewTime" column="review_time"/>
<result property="reviewerId" column="reviewer_id"/>
<result property="reviewerName" column="reviewer_name"/>
<result property="reviewComment" column="review_comment"/>
<result property="requireMaterials" column="require_materials"/>
<result property="materialStatus" column="material_status"/>
<result property="sortOrder" column="sort_order"/>
<result property="remark" column="remark"/>
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
<result property="tenantId" column="tenant_id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
@@ -37,17 +71,31 @@
<!-- 基础查询SQL -->
<sql id="selectJcbtScholarshipApplicationVo">
<<<<<<< HEAD
select a.id, a.application_no, a.batch_id, b.batch_name, a.scholarship_type_id, st.type_name as scholarship_type_name,
a.student_no, a.student_name, a.student_id, a.dept_id, d.dept_name, a.major_id, a.class_id, c.class_name,
a.application_type, a.current_status, a.apply_reason, a.self_evaluation,
a.calculated_score, a.final_score, a.ranking_in_dept, a.ranking_in_major,
a.submit_time, a.tenant_id,
=======
select a.id, a.batch_id, b.batch_name, a.scholarship_type_id, st.type_name as scholarship_type_name,
a.student_code, a.student_name, a.student_id, a.dept_id, a.dept_name, a.class_id, a.class_name,
a.applicant_type, a.applicant_id, a.applicant_name, a.application_type,
a.application_reason, a.application_description,
a.comprehensive_score, a.academic_score, a.moral_score, a.physical_score, a.aesthetic_score, a.labor_score,
a.awards, a.social_practice, a.family_economic_status,
a.status, a.submit_time, a.review_time, a.reviewer_id, a.reviewer_name, a.review_comment,
a.require_materials, a.material_status, a.sort_order, a.remark, a.tenant_id,
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
a.create_by, a.create_time, a.update_by, a.update_time
from jcbt_scholarship_application a
left join jcbt_scholarship_batch b on a.batch_id = b.id
left join jcbt_scholarship_type st on a.scholarship_type_id = st.id
<<<<<<< HEAD
left join xg_class_msg c on a.class_id = c.id
left join sys_dept d on a.dept_id = d.dept_id
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
</sql>
<!-- 分页查询(标准模板) -->
@@ -59,11 +107,17 @@
AND ${ew.sqlSegment}
</if>
</where>
<<<<<<< HEAD
ORDER BY a.create_time DESC
</select>
<!-- 查询班主任负责班级的申请记录列表(已删除applicantId字段,需重新设计) -->
<!--
=======
</select>
<!-- 查询班主任负责班级的申请记录列表 -->
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
<select id="selectApplicationsByApplicant" resultMap="JcbtScholarshipApplicationResult">
<include refid="selectJcbtScholarshipApplicationVo"/>
<where>
@@ -75,7 +129,10 @@
</where>
ORDER BY a.create_time DESC
</select>
<<<<<<< HEAD
-->
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
<!-- 查询班级的申请记录列表 -->
<select id="selectApplicationsByClass" resultMap="JcbtScholarshipApplicationResult">
@@ -122,6 +179,7 @@
SELECT
a.scholarship_type_id,
st.type_name as scholarship_type_name,
<<<<<<< HEAD
a.current_status,
COUNT(*) as total_count,
a.dept_id,
@@ -129,6 +187,14 @@
FROM jcbt_scholarship_application a
LEFT JOIN jcbt_scholarship_type st ON a.scholarship_type_id = st.id
LEFT JOIN sys_dept d ON a.dept_id = d.dept_id
=======
a.status,
COUNT(*) as total_count,
a.dept_id,
a.dept_name
FROM jcbt_scholarship_application a
LEFT JOIN jcbt_scholarship_type st ON a.scholarship_type_id = st.id
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
<where>
a.deleted = 0
<if test="batchId != null">
@@ -138,8 +204,13 @@
AND a.dept_id = #{deptId}
</if>
</where>
<<<<<<< HEAD
GROUP BY a.scholarship_type_id, a.current_status, a.dept_id
ORDER BY a.scholarship_type_id, a.current_status
=======
GROUP BY a.scholarship_type_id, a.status, a.dept_id
ORDER BY a.scholarship_type_id, a.status
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
</select>
<!-- 批量更新申请状态 -->
@@ -158,10 +229,13 @@
AND deleted = 0
</update>
<<<<<<< HEAD
<!-- 根据ID查询申请记录详情(包含关联信息) -->
<select id="selectByIdWithRelations" resultMap="JcbtScholarshipApplicationResult">
<include refid="selectJcbtScholarshipApplicationVo"/>
WHERE a.id = #{id} AND a.deleted = 0
</select>
=======
>>>>>>> 4b9dbe40fb94a83d379f7d16211dec6d78c23755
</mapper>
+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;
}
}
Loaded 100 of 1503 files, more files were not shown because too many files have changed in this diff. Show more