From 13cabfcb6ca9ad850c41ba3800d3c0d5f1cd49eb Mon Sep 17 00:00:00 2001 From: weizhong Date: Fri, 24 Oct 2025 10:08:18 +0800 Subject: [PATCH] =?UTF-8?q?=E2=80=9C1024=E4=BA=BA=E4=BA=8B=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E4=BF=AE=E6=94=B91.0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E2=80=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README-修复说明.md | 129 -- README-党籍保留附件查看功能.md | 420 ----- .../src/main/resources/application.yml | 2 +- .../RsOverseasApplicationController.java | 210 +-- .../RsOverseasApprovalController.java | 39 +- .../RsOverseasApprovalMonitorController.java | 122 ++ .../RsOverseasRecordController.java | 37 +- .../RsPartyMembershipRetentionController.java | 234 --- .../rs/domain/RsOverseasApplication.java | 21 +- .../rs/domain/RsPartyMembershipRetention.java | 189 --- .../rs/domain/bo/RsOverseasApplicationBo.java | 7 +- .../bo/RsPartyMembershipRetentionBo.java | 157 -- .../query/RsOverseasApprovalMonitorQuery.java | 73 + .../domain/query/RsOverseasApprovalQuery.java | 31 +- .../RsPartyMembershipRetentionQuery.java | 68 - .../rs/domain/vo/RsOverseasApplicationVo.java | 18 +- .../vo/RsOverseasApprovalMonitorVo.java | 339 ++++ .../rs/domain/vo/RsOverseasRecordVo.java | 45 +- .../vo/RsPartyMembershipRetentionVo.java | 180 --- .../mapper/RsOverseasApplicationMapper.java | 48 +- .../RsPartyMembershipRetentionMapper.java | 26 - .../IRsOverseasApplicationService.java | 61 +- .../IRsOverseasApprovalMonitorService.java | 47 + .../IRsPartyMembershipRetentionService.java | 79 - .../RsOverseasApplicationServiceImpl.java | 634 +++++--- .../RsOverseasApprovalMonitorServiceImpl.java | 489 ++++++ .../impl/RsOverseasApprovalServiceImpl.java | 208 ++- ...RsPartyMembershipRetentionServiceImpl.java | 288 ---- .../mapper/rs/RsOverseasApplicationMapper.xml | 71 +- .../mapper/rs/RsOverseasApprovalMapper.xml | 90 +- .../mapper/rs/RsOverseasRecordMapper.xml | 16 +- .../rs/RsPartyMembershipRetentionMapper.xml | 102 -- .../党籍保留增加身份证号和职务字段脚本.sql | 127 -- .../ruoyi-rs/党籍保留权限验证.sql | 198 --- .../ruoyi-rs/党籍保留补充亲属姓名字段脚本.sql | 102 -- .../ruoyi-rs/出国党籍保留权限菜单配置.sql | 718 --------- .../ruoyi-rs/出国党籍保留表创建脚本.sql | 225 --- backend/script/sql/ry_workflow.sql | 100 -- check_attachment_data.sql | 8 - docs/开发文档/API/出国申请管理API.md | 113 +- .../rs_overseas_application_attachment.sql | 58 - .../database/rs_overseas_itinerary_change.sql | 214 --- ...s_overseas_itinerary_change_add_fields.sql | 58 - ...verseas_itinerary_change_approval_menu.sql | 187 --- .../rs_overseas_itinerary_change_full.sql | 320 ---- .../rs_overseas_itinerary_change_menu.sql | 278 ---- .../rs_training_application_字段补充.sql | 95 -- .../database/⚡一键修复-添加缺失字段.sql | 28 - .../database/⚡智能修复-只添加缺失字段.sql | 96 -- ...立即执行】修复political_status字段长度.sql | 75 - .../【立即执行】出国申请表添加OSS_ID字段.sql | 18 - .../【立即执行】添加党籍保留附件字段.sql | 104 -- .../database/修复-行程变更状态字典编码.sql | 117 -- ...修复出国登记表political_status字段长度.sql | 93 -- .../database/出国管理三表架构设计方案.md | 524 ------ ...rseas_record_status_backup_charset_fix.sql | 38 - .../database/执行步骤-增加本人基本情况字段.md | 220 --- .../设计文档/database/🔍诊断-检查缺失字段.sql | 124 -- .../设计文档/database/🔧修复-添加缺失字段.sql | 163 -- .../✅审批记录显示问题修复报告.md | 405 ----- .../问题解决方案/行程变更审批功能-测试指南.md | 363 ----- .../行程变更审批接口参数错误修复总结.md | 368 ----- docs/问题解决方案/行程变更状态乱码修复总结.md | 239 --- .../📊行程变更审批功能-问题汇总.md | 443 ------ docs/问题解决方案/🚨数据库字段缺失修复指南.md | 418 ----- target/classes/META-INF/mps/mappers | 100 +- test_overseas_attachment.py | 310 ---- test_overseas_registration_list.py | 221 --- test_with_browser_token.py | 84 - web/.env.development | 2 +- web/src/api/rs/overseas.ts | 7 + web/src/api/rs/party-membership-retention.ts | 291 ---- web/src/api/rs/党籍保留API路径修正完成报告.md | 390 ----- web/src/router/index.ts | 7 + web/src/views/rs/overseas/index.vue | 763 ++++----- .../party-membership-retention/approval.vue | 781 --------- .../rs/party-membership-retention/index.vue | 1401 ----------------- ⚡️【立即执行】重启后端修复OSS_ID问题.md | 189 --- ⚡️快速测试指南.md | 139 -- ⚡️最终测试指南.md | 126 -- ...pper_XML修复完成-出国记录显示问题已解决.md | 234 --- ✅PDF预览功能修复完成报告.md | 246 --- ✅【完成】出国登记PDF下载功能开发报告.md | 248 --- ✅【已完成】出国申请OSS_ID修复报告.md | 236 --- ...修复】出国申请下载功能-缺少request导入.md | 210 --- ...限已修复】出国申请下载功能完整修复说明.md | 285 ---- ✅修复完成-出国申请附件下载问题.md | 302 ---- ✅党籍保留附件查看功能实现说明.md | 331 ---- ✅出国境登记表盖章文件-查看功能实现.md | 339 ---- ✅出国境登记表盖章文件设置为必填项.md | 335 ---- ✅出国申请OSS_ID下载问题修复总结.md | 321 ---- ✅出国申请功能修复完成总结.md | 226 --- ✅出国申请页面PDF预览功能修复完成.md | 307 ---- ✅出国登记PDF下载功能实现完成.md | 148 -- ✅出国登记状态定义修复完成.md | 306 ---- ✅出国登记表PDF模板格式修复完成.md | 186 --- ✅出国登记表PDF模板第二次格式修复完成.md | 240 --- ✅出国登记选项卡数据显示问题修复完成.md | 327 ---- ✅出国记录变更历史显示功能实现完成.md | 452 ------ ✅前端TypeScript错误修复完成.md | 283 ---- ✅变更行程管理功能完成报告.md | 597 ------- ✅字段删除完成-请执行.md | 270 ---- ✅数据库脚本执行成功.md | 233 --- ✅测试-登记历史选项卡自动加载.md | 143 -- ✅立即重启前端-验证优化效果.md | 254 --- ✅编译成功-立即重启后端.md | 245 --- ✅编译错误修复完成.md | 242 --- ✅问题已修复-请测试.md | 302 ---- ✅附件下载404错误修复完成报告.md | 325 ---- 【功能说明】出国申请附件查看.md | 168 -- 【快速测试】出国申请修复验证.md | 146 -- 【操作指南】出国申请附件功能测试.md | 262 --- 【立即执行】出国申请附件下载修复指南.md | 106 -- 【立即执行】出国登记问题修复指南.md | 79 - 【立即执行】重启服务并测试.md | 177 --- 【紧急修复】出国申请OSS_ID数据库问题.md | 311 ---- 【问题修复】出国申请OSS_ID字段缺失问题.md | 399 ----- 修复ID39的OSS_ID.sql | 84 - 修复已有数据registration_time为NULL.sql | 83 - 修复政治面貌显示问题-更新说明.md | 264 ---- 党籍保留亲属姓名字段修复完成报告.md | 350 ---- 党籍保留亲属姓名字段补充完成报告.md | 398 ----- 党籍保留保存并提交功能修复报告.md | 331 ---- 党籍保留审批通过失败问题排查指南.md | 201 --- 党籍保留表单优化说明.md | 395 ----- 出国党籍保留功能完整实现报告.md | 379 ----- 出国党籍保留审批功能-后续开发任务清单.md | 408 ----- 出国党籍保留审批功能-开发完成报告.md | 472 ------ 出国党籍保留审批功能-快速测试指南.md | 156 -- 出国党籍保留审批功能-测试脚本.md | 452 ------ 出国党籍保留审批功能交付清单.md | 396 ----- 出国党籍保留审批功能说明.md | 259 --- 出国党籍保留审批测试指南.md | 405 ----- 出国党籍保留手动配置权限指南.md | 474 ------ 出国党籍保留第一阶段开发完成报告.md | 412 ----- 出国申请前后端字段映射完整方案.md | 827 ---------- 出国申请功能修复说明.md | 158 -- 出国申请字段对齐修复完成说明.md | 273 ---- 出国申请字段对齐分析报告.md | 314 ---- 出国申请字段映射修复总结.md | 300 ---- 出国申请字段映射分析报告.md | 348 ---- 出国申请字段映射问题修复报告.md | 471 ------ 出国申请弹窗滚动问题修复说明.md | 420 ----- 出国申请提交前置校验修改说明.md | 134 -- 出国申请校验失败问题分析及解决方案.md | 429 ----- 出国申请详情字段未显示问题分析与修复报告.md | 813 ---------- 出国申请详情职务字段未显示问题诊断报告.md | 402 ----- 出国申请附件下载问题分析与修复方案.md | 391 ----- 出国申请附件不显示问题排查指南.md | 301 ---- 出国申请附件功能-快速部署指南.md | 146 -- 出国申请附件功能完成总结.md | 306 ---- 出国申请附件按钮隐藏修改说明.md | 296 ---- 出国申请附件显示修复说明.md | 368 ----- 出国申请附件管理功能实现报告.md | 341 ---- 出国申请附件调试步骤.md | 195 --- 出国申请预算字段删除完成总结.md | 223 --- 出国登记保存失败问题修复报告-2025-10-05.md | 308 ---- 出国登记保存并提交功能修复报告.md | 305 ---- 出国登记列表不显示数据修复报告.md | 523 ------ 出国登记字段验证修复报告.md | 377 ----- 出国登记审批-删除批量按钮修改记录.md | 298 ---- 出国登记审批权限标识对照表.md | 251 --- 出国登记审批权限配置总结.md | 323 ---- 出国登记审批菜单权限配置-快速版.sql | 152 -- 出国登记审批菜单权限配置.sql | 394 ----- 出国登记状态修改总结-最终版.md | 351 ----- 出国登记状态修改总结.md | 198 --- 出国登记状态修正完成-请查看.md | 116 -- 出国登记状态变化对比图.md | 289 ---- 出国登记状态显示修复完成.md | 210 --- 出国登记问题排查和解决步骤.md | 121 -- 出国行程变更-申请行程情况字段调整.md | 500 ------ 出国行程变更功能开发完成总结.md | 380 ----- 出国行程变更申请功能优化说明.md | 250 --- 出国行程变更申请表单-本人基本情况字段增强.md | 398 ----- 出国行程变更申请表单优化说明.md | 284 ---- 出国记录详情显示修复说明.md | 274 ---- 培训申请字段修复脚本.sql | 100 -- 实际行程情况增加时间字段-更新说明.md | 184 --- 工程本地启动指导.md | 422 ----- 插件.md | 228 --- 插件.png | Bin 347014 -> 0 bytes 政治面貌职务出境事由字段修复报告.md | 633 -------- 数据库字段验证结果报告.md | 148 -- 查看详情弹窗字段重构报告.md | 454 ------ 检查OSS_ID数据.sql | 167 -- 检查出国登记数据详细.sql | 74 - 检查并修复出国申请OSS_ID.sql | 126 -- 检查职务字段数据.sql | 237 --- 行程变更申请-VO字段缺失修复.md | 530 ------- 行程变更申请-recordId修复说明.md | 337 ---- 行程变更申请-修复总结.md | 294 ---- 行程变更申请-列表显示问题修复.md | 467 ------ 行程变更申请-列表显示问题排查.md | 475 ------ 行程变更申请-快速测试指南.md | 320 ---- 行程变更申请-简化修复指南.md | 264 ---- 行程变更申请表单优化-更新说明.md | 267 ---- 行程变更表单优化-效果对比.md | 138 -- 诊断出国申请OSS_ID问题.sql | 101 -- 诊断出国登记列表问题.md | 314 ---- 重启后端服务说明.md | 131 -- 验证亲属姓名字段修复.md | 156 -- 🎉出国管理PDF预览功能完整修复汇总.md | 532 ------- 🎉完整修复报告-出国记录显示问题.md | 302 ---- ...】出国申请附件OSS_ID映射修复-2025-10-11.md | 341 ---- 🎯【立即操作】出国申请修复指南.md | 169 -- 🎯业务逻辑优化-行程变更选项卡.md | 362 ----- 📊两个附件查看功能-对比总结.md | 415 ----- 📊出国记录变更历史显示功能-业务流程图.md | 420 ----- 📊验证FIND_IN_SET修复.sql | 188 --- 📋党籍保留附件查看功能-文档总览.md | 376 ----- 📋出国审批附件功能完整修复总结.md | 724 --------- 📌修复总结-审批记录显示问题.md | 297 ---- 📖党籍保留附件查看功能-使用说明.md | 290 ---- 📝党籍保留附件查看功能-开发总结.md | 556 ------- 📝字段删除总结.md | 275 ---- 🔍【立即执行】诊断OSS_ID问题步骤.md | 193 --- 🔍出国申请下载失败诊断指南.md | 292 ---- 🔍立即执行-数据库诊断.sh | 146 -- 🔍诊断admin用户出国申请OSS_ID.sql | 140 -- 🔍诊断党籍保留附件显示问题.sql | 99 -- 🔥立即执行-重启后端测试.md | 226 --- 🔧出国申请详情弹窗PDF预览修复说明.md | 236 --- 🚀【立即执行】变更行程菜单-部署指南.md | 615 -------- 🚀党籍保留附件查看功能-快速参考.md | 142 -- 🚀出国登记PDF下载功能测试指南.md | 182 --- 🚀出国记录变更历史功能测试指南.md | 345 ---- 🚀变更行程管理快速上手指南.md | 511 ------ 🚀插入出国登记测试数据.sql | 372 ----- 🚀立即执行SQL-详细步骤.md | 194 --- 🚀立即操作-修复出国登记数据显示.md | 155 -- 🚀立即操作-修复登记历史选项卡.md | 222 --- 🚀立即操作-重启并测试.md | 78 - 🚀立即测试-出国登记修复验证.md | 162 -- 🧪党籍保留附件查看功能-测试指南.md | 389 ----- 🧪出国申请与审批PDF预览功能测试清单.md | 237 --- 236 files changed, 2510 insertions(+), 59666 deletions(-) delete mode 100644 README-修复说明.md delete mode 100644 README-党籍保留附件查看功能.md create mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsPartyMembershipRetentionController.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsPartyMembershipRetention.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsPartyMembershipRetentionBo.java create mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApprovalMonitorQuery.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsPartyMembershipRetentionQuery.java create mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApprovalMonitorVo.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsPartyMembershipRetentionVo.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsPartyMembershipRetentionMapper.java create mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsPartyMembershipRetentionService.java create mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsPartyMembershipRetentionServiceImpl.java delete mode 100644 backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsPartyMembershipRetentionMapper.xml delete mode 100644 backend/ruoyi-modules/ruoyi-rs/党籍保留增加身份证号和职务字段脚本.sql delete mode 100644 backend/ruoyi-modules/ruoyi-rs/党籍保留权限验证.sql delete mode 100644 backend/ruoyi-modules/ruoyi-rs/党籍保留补充亲属姓名字段脚本.sql delete mode 100644 backend/ruoyi-modules/ruoyi-rs/出国党籍保留权限菜单配置.sql delete mode 100644 backend/ruoyi-modules/ruoyi-rs/出国党籍保留表创建脚本.sql delete mode 100644 check_attachment_data.sql delete mode 100644 docs/设计文档/database/rs_overseas_application_attachment.sql delete mode 100644 docs/设计文档/database/rs_overseas_itinerary_change.sql delete mode 100644 docs/设计文档/database/rs_overseas_itinerary_change_add_fields.sql delete mode 100644 docs/设计文档/database/rs_overseas_itinerary_change_approval_menu.sql delete mode 100644 docs/设计文档/database/rs_overseas_itinerary_change_full.sql delete mode 100644 docs/设计文档/database/rs_overseas_itinerary_change_menu.sql delete mode 100644 docs/设计文档/database/rs_training_application_字段补充.sql delete mode 100644 docs/设计文档/database/⚡一键修复-添加缺失字段.sql delete mode 100644 docs/设计文档/database/⚡智能修复-只添加缺失字段.sql delete mode 100644 docs/设计文档/database/【立即执行】修复political_status字段长度.sql delete mode 100644 docs/设计文档/database/【立即执行】出国申请表添加OSS_ID字段.sql delete mode 100644 docs/设计文档/database/【立即执行】添加党籍保留附件字段.sql delete mode 100644 docs/设计文档/database/修复-行程变更状态字典编码.sql delete mode 100644 docs/设计文档/database/修复出国登记表political_status字段长度.sql delete mode 100644 docs/设计文档/database/出国管理三表架构设计方案.md delete mode 100644 docs/设计文档/database/字符集修复/rs_overseas_record_status_backup_charset_fix.sql delete mode 100644 docs/设计文档/database/执行步骤-增加本人基本情况字段.md delete mode 100644 docs/设计文档/database/🔍诊断-检查缺失字段.sql delete mode 100644 docs/设计文档/database/🔧修复-添加缺失字段.sql delete mode 100644 docs/问题解决方案/✅审批记录显示问题修复报告.md delete mode 100644 docs/问题解决方案/行程变更审批功能-测试指南.md delete mode 100644 docs/问题解决方案/行程变更审批接口参数错误修复总结.md delete mode 100644 docs/问题解决方案/行程变更状态乱码修复总结.md delete mode 100644 docs/问题解决方案/📊行程变更审批功能-问题汇总.md delete mode 100644 docs/问题解决方案/🚨数据库字段缺失修复指南.md delete mode 100644 test_overseas_attachment.py delete mode 100644 test_overseas_registration_list.py delete mode 100644 test_with_browser_token.py delete mode 100644 web/src/api/rs/party-membership-retention.ts delete mode 100644 web/src/api/rs/党籍保留API路径修正完成报告.md delete mode 100644 web/src/views/rs/party-membership-retention/approval.vue delete mode 100644 web/src/views/rs/party-membership-retention/index.vue delete mode 100644 ⚡️【立即执行】重启后端修复OSS_ID问题.md delete mode 100644 ⚡️快速测试指南.md delete mode 100644 ⚡️最终测试指南.md delete mode 100644 ✅Mapper_XML修复完成-出国记录显示问题已解决.md delete mode 100644 ✅PDF预览功能修复完成报告.md delete mode 100644 ✅【完成】出国登记PDF下载功能开发报告.md delete mode 100644 ✅【已完成】出国申请OSS_ID修复报告.md delete mode 100644 ✅【最终修复】出国申请下载功能-缺少request导入.md delete mode 100644 ✅【权限已修复】出国申请下载功能完整修复说明.md delete mode 100644 ✅修复完成-出国申请附件下载问题.md delete mode 100644 ✅党籍保留附件查看功能实现说明.md delete mode 100644 ✅出国境登记表盖章文件-查看功能实现.md delete mode 100644 ✅出国境登记表盖章文件设置为必填项.md delete mode 100644 ✅出国申请OSS_ID下载问题修复总结.md delete mode 100644 ✅出国申请功能修复完成总结.md delete mode 100644 ✅出国申请页面PDF预览功能修复完成.md delete mode 100644 ✅出国登记PDF下载功能实现完成.md delete mode 100644 ✅出国登记状态定义修复完成.md delete mode 100644 ✅出国登记表PDF模板格式修复完成.md delete mode 100644 ✅出国登记表PDF模板第二次格式修复完成.md delete mode 100644 ✅出国登记选项卡数据显示问题修复完成.md delete mode 100644 ✅出国记录变更历史显示功能实现完成.md delete mode 100644 ✅前端TypeScript错误修复完成.md delete mode 100644 ✅变更行程管理功能完成报告.md delete mode 100644 ✅字段删除完成-请执行.md delete mode 100644 ✅数据库脚本执行成功.md delete mode 100644 ✅测试-登记历史选项卡自动加载.md delete mode 100644 ✅立即重启前端-验证优化效果.md delete mode 100644 ✅编译成功-立即重启后端.md delete mode 100644 ✅编译错误修复完成.md delete mode 100644 ✅问题已修复-请测试.md delete mode 100644 ✅附件下载404错误修复完成报告.md delete mode 100644 【功能说明】出国申请附件查看.md delete mode 100644 【快速测试】出国申请修复验证.md delete mode 100644 【操作指南】出国申请附件功能测试.md delete mode 100644 【立即执行】出国申请附件下载修复指南.md delete mode 100644 【立即执行】出国登记问题修复指南.md delete mode 100644 【立即执行】重启服务并测试.md delete mode 100644 【紧急修复】出国申请OSS_ID数据库问题.md delete mode 100644 【问题修复】出国申请OSS_ID字段缺失问题.md delete mode 100644 修复ID39的OSS_ID.sql delete mode 100644 修复已有数据registration_time为NULL.sql delete mode 100644 修复政治面貌显示问题-更新说明.md delete mode 100644 党籍保留亲属姓名字段修复完成报告.md delete mode 100644 党籍保留亲属姓名字段补充完成报告.md delete mode 100644 党籍保留保存并提交功能修复报告.md delete mode 100644 党籍保留审批通过失败问题排查指南.md delete mode 100644 党籍保留表单优化说明.md delete mode 100644 出国党籍保留功能完整实现报告.md delete mode 100644 出国党籍保留审批功能-后续开发任务清单.md delete mode 100644 出国党籍保留审批功能-开发完成报告.md delete mode 100644 出国党籍保留审批功能-快速测试指南.md delete mode 100644 出国党籍保留审批功能-测试脚本.md delete mode 100644 出国党籍保留审批功能交付清单.md delete mode 100644 出国党籍保留审批功能说明.md delete mode 100644 出国党籍保留审批测试指南.md delete mode 100644 出国党籍保留手动配置权限指南.md delete mode 100644 出国党籍保留第一阶段开发完成报告.md delete mode 100644 出国申请前后端字段映射完整方案.md delete mode 100644 出国申请功能修复说明.md delete mode 100644 出国申请字段对齐修复完成说明.md delete mode 100644 出国申请字段对齐分析报告.md delete mode 100644 出国申请字段映射修复总结.md delete mode 100644 出国申请字段映射分析报告.md delete mode 100644 出国申请字段映射问题修复报告.md delete mode 100644 出国申请弹窗滚动问题修复说明.md delete mode 100644 出国申请提交前置校验修改说明.md delete mode 100644 出国申请校验失败问题分析及解决方案.md delete mode 100644 出国申请详情字段未显示问题分析与修复报告.md delete mode 100644 出国申请详情职务字段未显示问题诊断报告.md delete mode 100644 出国申请附件下载问题分析与修复方案.md delete mode 100644 出国申请附件不显示问题排查指南.md delete mode 100644 出国申请附件功能-快速部署指南.md delete mode 100644 出国申请附件功能完成总结.md delete mode 100644 出国申请附件按钮隐藏修改说明.md delete mode 100644 出国申请附件显示修复说明.md delete mode 100644 出国申请附件管理功能实现报告.md delete mode 100644 出国申请附件调试步骤.md delete mode 100644 出国申请预算字段删除完成总结.md delete mode 100644 出国登记保存失败问题修复报告-2025-10-05.md delete mode 100644 出国登记保存并提交功能修复报告.md delete mode 100644 出国登记列表不显示数据修复报告.md delete mode 100644 出国登记字段验证修复报告.md delete mode 100644 出国登记审批-删除批量按钮修改记录.md delete mode 100644 出国登记审批权限标识对照表.md delete mode 100644 出国登记审批权限配置总结.md delete mode 100644 出国登记审批菜单权限配置-快速版.sql delete mode 100644 出国登记审批菜单权限配置.sql delete mode 100644 出国登记状态修改总结-最终版.md delete mode 100644 出国登记状态修改总结.md delete mode 100644 出国登记状态修正完成-请查看.md delete mode 100644 出国登记状态变化对比图.md delete mode 100644 出国登记状态显示修复完成.md delete mode 100644 出国登记问题排查和解决步骤.md delete mode 100644 出国行程变更-申请行程情况字段调整.md delete mode 100644 出国行程变更功能开发完成总结.md delete mode 100644 出国行程变更申请功能优化说明.md delete mode 100644 出国行程变更申请表单-本人基本情况字段增强.md delete mode 100644 出国行程变更申请表单优化说明.md delete mode 100644 出国记录详情显示修复说明.md delete mode 100644 培训申请字段修复脚本.sql delete mode 100644 实际行程情况增加时间字段-更新说明.md delete mode 100644 工程本地启动指导.md delete mode 100644 插件.md delete mode 100644 插件.png delete mode 100644 政治面貌职务出境事由字段修复报告.md delete mode 100644 数据库字段验证结果报告.md delete mode 100644 查看详情弹窗字段重构报告.md delete mode 100644 检查OSS_ID数据.sql delete mode 100644 检查出国登记数据详细.sql delete mode 100644 检查并修复出国申请OSS_ID.sql delete mode 100644 检查职务字段数据.sql delete mode 100644 行程变更申请-VO字段缺失修复.md delete mode 100644 行程变更申请-recordId修复说明.md delete mode 100644 行程变更申请-修复总结.md delete mode 100644 行程变更申请-列表显示问题修复.md delete mode 100644 行程变更申请-列表显示问题排查.md delete mode 100644 行程变更申请-快速测试指南.md delete mode 100644 行程变更申请-简化修复指南.md delete mode 100644 行程变更申请表单优化-更新说明.md delete mode 100644 行程变更表单优化-效果对比.md delete mode 100644 诊断出国申请OSS_ID问题.sql delete mode 100644 诊断出国登记列表问题.md delete mode 100644 重启后端服务说明.md delete mode 100644 验证亲属姓名字段修复.md delete mode 100644 🎉出国管理PDF预览功能完整修复汇总.md delete mode 100644 🎉完整修复报告-出国记录显示问题.md delete mode 100644 🎯【核心问题】出国申请附件OSS_ID映射修复-2025-10-11.md delete mode 100644 🎯【立即操作】出国申请修复指南.md delete mode 100644 🎯业务逻辑优化-行程变更选项卡.md delete mode 100644 📊两个附件查看功能-对比总结.md delete mode 100644 📊出国记录变更历史显示功能-业务流程图.md delete mode 100644 📊验证FIND_IN_SET修复.sql delete mode 100644 📋党籍保留附件查看功能-文档总览.md delete mode 100644 📋出国审批附件功能完整修复总结.md delete mode 100644 📌修复总结-审批记录显示问题.md delete mode 100644 📖党籍保留附件查看功能-使用说明.md delete mode 100644 📝党籍保留附件查看功能-开发总结.md delete mode 100644 📝字段删除总结.md delete mode 100644 🔍【立即执行】诊断OSS_ID问题步骤.md delete mode 100644 🔍出国申请下载失败诊断指南.md delete mode 100755 🔍立即执行-数据库诊断.sh delete mode 100644 🔍诊断admin用户出国申请OSS_ID.sql delete mode 100644 🔍诊断党籍保留附件显示问题.sql delete mode 100644 🔥立即执行-重启后端测试.md delete mode 100644 🔧出国申请详情弹窗PDF预览修复说明.md delete mode 100644 🚀【立即执行】变更行程菜单-部署指南.md delete mode 100644 🚀党籍保留附件查看功能-快速参考.md delete mode 100644 🚀出国登记PDF下载功能测试指南.md delete mode 100644 🚀出国记录变更历史功能测试指南.md delete mode 100644 🚀变更行程管理快速上手指南.md delete mode 100644 🚀插入出国登记测试数据.sql delete mode 100644 🚀立即执行SQL-详细步骤.md delete mode 100644 🚀立即操作-修复出国登记数据显示.md delete mode 100644 🚀立即操作-修复登记历史选项卡.md delete mode 100644 🚀立即操作-重启并测试.md delete mode 100644 🚀立即测试-出国登记修复验证.md delete mode 100644 🧪党籍保留附件查看功能-测试指南.md delete mode 100644 🧪出国申请与审批PDF预览功能测试清单.md diff --git a/README-修复说明.md b/README-修复说明.md deleted file mode 100644 index 9f6924b1..00000000 --- a/README-修复说明.md +++ /dev/null @@ -1,129 +0,0 @@ -# 出国申请附件下载问题修复说明 - -## 📌 问题 - -新增出国申请时上传的盖章文件无法下载,提示"文件OSS ID不存在" - -## ✅ 修复完成 - -已完成代码修复,包括: -- ✅ 后端返回新创建的applicationId -- ✅ 前端上传前自动保存申请记录 -- ✅ 导入dayjs处理日期格式 - -## 🚀 立即操作(3步) - -### 1️⃣ 修复数据库(解决ID 39的问题) - -执行SQL查询,找到ID 39对应的OSS文件: - -```sql -SELECT oss_id, file_name, original_name, - DATE_FORMAT(create_time, '%Y-%m-%d %H:%i:%s') AS create_time -FROM sys_oss -WHERE DATE(create_time) >= CURDATE() - INTERVAL 2 DAY - AND (original_name LIKE '%pdf%' OR original_name LIKE '%jpg%') -ORDER BY create_time DESC LIMIT 20; -``` - -然后更新ID 39的记录(替换下面的oss_id值): - -```sql -UPDATE rs_overseas_application -SET stamped_file_oss_id = [从上面查询到的oss_id] -WHERE application_id = 39 AND deleted = 0; -``` - -### 2️⃣ 重启后端 - -IDEA中: -1. 停止 `ruoyi-admin`(点击 ⏹️) -2. 启动 `ruoyi-admin`(点击 ▶️) - -### 3️⃣ 刷新前端 - -浏览器中按: -- **Mac**: `Cmd + Shift + R` -- **Windows**: `Ctrl + F5` - -## 🧪 测试验证 - -### 测试1:ID 39可以下载 - -1. 进入"出国申请管理" → "我的申请" -2. 找到ID 39,点击"查看详情" -3. 点击下载盖章文件 -4. **预期**:文件正常下载 ✅ - -### 测试2:新增申请可以下载 - -1. 点击"新增",填写必填字段: - - 政治面貌:任选 - - 出国开始/结束日期:选择日期 - - 紧急联系人:张三 13800138000 - - 目的国家:美国 - -2. **直接上传盖章文件**(不要先点确定) - - 观察提示:"正在保存申请记录..." - - 观察提示:"申请记录已保存..." - - 观察提示:"盖章文件上传成功!" - -3. 点击"确定"提交 - -4. 进入"我的申请",找到新增的记录 - -5. 点击"查看详情" → 下载盖章文件 - -6. **预期**:文件正常下载 ✅ - -## 📊 验证数据库 - -```sql -SELECT - application_id, applicant_name, stamped_file_oss_id, - CASE WHEN stamped_file_oss_id IS NOT NULL THEN '✅' ELSE '❌' END AS status -FROM rs_overseas_application -WHERE deleted = 0 -ORDER BY application_id DESC LIMIT 5; -``` - -**预期**:新增的记录中,`stamped_file_oss_id`不为NULL - -## 🐛 问题排查 - -### 问题:上传时未显示"正在保存申请记录..." - -**解决**: -- 确认已刷新页面(Cmd+Shift+R) -- 按F12查看Console是否有错误 -- 清除浏览器缓存 - -### 问题:提示"保存申请记录失败" - -**解决**: -- 检查是否填写了所有必填字段 -- 查看后端日志错误信息 -- 重新登录系统 - -### 问题:OSS_ID仍然为NULL - -**解决**: -- 查看后端日志,确认`uploadStampedFile`是否被调用 -- F12 → Network,查看请求的`applicationId`参数 -- 执行SQL查询确认数据库状态 - -## 📚 详细文档 - -- **完整分析**:`出国申请附件下载问题分析与修复方案.md` -- **操作指南**:`🚨立即操作-重启后端.md` -- **修复总结**:`✅修复完成-出国申请附件下载问题.md` -- **数据库脚本**:`修复ID39的OSS_ID.sql` - ---- - -**修复时间**:2025-10-11 -**优先级**:🔴 高 -**状态**:✅ 代码修复完成,待测试 - -**现在请按上面3个步骤操作并测试!** 🚀 - diff --git a/README-党籍保留附件查看功能.md b/README-党籍保留附件查看功能.md deleted file mode 100644 index f194a3d0..00000000 --- a/README-党籍保留附件查看功能.md +++ /dev/null @@ -1,420 +0,0 @@ -# 出国申请附件查看功能 - 项目文档 - -> 📅 开发时间: 2025-10-13 -> 👨‍💻 开发人员: Cursor AI -> ✅ 状态: 已完成,待测试 - ---- - -## 📚 文档说明 - -本目录包含"出国申请附件查看功能"的完整项目文档,涵盖技术实现、使用说明、测试指南等内容。 - -### 实现的功能 - -✅ **党籍保留附件** - 查看功能 -✅ **出国境登记表盖章文件** - 查看功能 - ---- - -## 🗂️ 文档列表 - -### 📋 总览文档(必读) - -**[📋 文档总览](./📋党籍保留附件查看功能-文档总览.md)** -- 📖 所有文档的导航入口 -- 🎯 功能简介和核心特性 -- 📊 项目状态和进度 -- 🔗 快速链接和资源 - -**推荐**: ⭐⭐⭐⭐⭐ 所有人必读 - ---- - -### 🚀 快速参考(推荐) - -**[🚀 快速参考](./🚀党籍保留附件查看功能-快速参考.md)** -- 📍 功能位置和路径 -- 💻 代码位置和方法 -- 📄 支持的文件格式 -- ❓ 常见问题快速解答 - -**推荐**: ⭐⭐⭐⭐⭐ 需要快速了解功能时使用 - ---- - -### 📖 用户文档 - -**[📖 使用说明](./📖党籍保留附件查看功能-使用说明.md)** -- 🚀 详细的操作步骤 -- 💡 使用技巧和建议 -- ❓ 完整的FAQ -- 🔒 安全说明 -- 📱 浏览器兼容性 - -**适用人群**: 最终用户、产品经理 -**推荐**: ⭐⭐⭐⭐⭐ - ---- - -### 🧪 测试文档 - -**[🧪 测试指南](./🧪党籍保留附件查看功能-测试指南.md)** -- 📋 完整的测试用例(8个) -- 🔍 详细的测试步骤 -- 🐛 问题记录模板 -- 📊 测试总结表格 -- 🔧 调试技巧 - -**适用人群**: 测试人员、QA工程师 -**推荐**: ⭐⭐⭐⭐⭐ - ---- - -### 💻 技术文档 - -#### 党籍保留附件 - -**[✅ 实现说明](./✅党籍保留附件查看功能实现说明.md)** -- 📝 详细的需求分析 -- 💻 完整的代码实现 -- 🎨 功能特性说明 -- 🔄 后续优化建议 -- ✅ 验收标准 - -**适用人群**: 开发人员、技术负责人 -**推荐**: ⭐⭐⭐⭐ - -**[📝 开发总结](./📝党籍保留附件查看功能-开发总结.md)** -- 🎯 需求概述和分析 -- 📊 实现方案和架构 -- 💡 技术亮点 -- 📈 项目影响 -- 🎓 经验总结 - -**适用人群**: 项目经理、团队负责人 -**推荐**: ⭐⭐⭐ - -#### 出国境登记表盖章文件 - -**[✅ 实现说明](./✅出国境登记表盖章文件-查看功能实现.md)** -- 📝 详细的需求分析 -- 💻 完整的代码实现 -- 🎨 功能特性说明 -- 📊 与党籍保留附件的对比 - -**适用人群**: 开发人员、技术负责人 -**推荐**: ⭐⭐⭐⭐ - -#### 对比总结 - -**[📊 对比总结](./📊两个附件查看功能-对比总结.md)** -- 🎯 两个功能的全面对比 -- 💻 代码实现对比 -- 📊 统计数据对比 -- 💡 技术亮点总结 - -**适用人群**: 所有人 -**推荐**: ⭐⭐⭐⭐⭐ - ---- - -## 📖 阅读指南 - -### 👥 按角色阅读 - -#### 最终用户 -1. 📋 [文档总览](./📋党籍保留附件查看功能-文档总览.md) - 了解功能 -2. 🚀 [快速参考](./🚀党籍保留附件查看功能-快速参考.md) - 快速上手 -3. 📖 [使用说明](./📖党籍保留附件查看功能-使用说明.md) - 详细操作 - -#### 测试人员 -1. 📋 [文档总览](./📋党籍保留附件查看功能-文档总览.md) - 了解功能 -2. 🧪 [测试指南](./🧪党籍保留附件查看功能-测试指南.md) - 执行测试 -3. ✅ [实现说明](./✅党籍保留附件查看功能实现说明.md) - 理解实现 - -#### 开发人员 -1. 📋 [文档总览](./📋党籍保留附件查看功能-文档总览.md) - 了解项目 -2. ✅ [实现说明](./✅党籍保留附件查看功能实现说明.md) - 学习代码 -3. 🚀 [快速参考](./🚀党籍保留附件查看功能-快速参考.md) - 快速查找 - -#### 项目经理 -1. 📋 [文档总览](./📋党籍保留附件查看功能-文档总览.md) - 项目概况 -2. 📝 [开发总结](./📝党籍保留附件查看功能-开发总结.md) - 项目总结 -3. 🧪 [测试指南](./🧪党籍保留附件查看功能-测试指南.md) - 验收标准 - ---- - -### 🎯 按场景阅读 - -#### 场景1: 快速了解功能 -``` -📋 文档总览 → 🚀 快速参考 -``` - -#### 场景2: 学习如何使用 -``` -🚀 快速参考 → 📖 使用说明 -``` - -#### 场景3: 进行功能测试 -``` -📋 文档总览 → 🧪 测试指南 → ✅ 实现说明 -``` - -#### 场景4: 学习技术实现 -``` -📋 文档总览 → ✅ 实现说明 → 📝 开发总结 -``` - -#### 场景5: 项目验收 -``` -📋 文档总览 → 📝 开发总结 → 🧪 测试指南 -``` - ---- - -## 🎯 功能概述 - -### 核心功能 - -在出国申请详情弹窗中,为两个附件区域都新增"查看"按钮: - -#### 1️⃣ 党籍保留附件 -- ✅ PDF文件在线预览(新标签页) -- ✅ 图片文件在线预览(新窗口) -- ✅ 不支持格式自动下载 -- ✅ 安全的Token认证 -- ✅ 友好的错误提示 - -#### 2️⃣ 出国境登记表盖章文件 -- ✅ PDF文件在线预览(新标签页) -- ✅ 图片文件在线预览(新窗口) -- ✅ 不支持格式自动下载 -- ✅ 安全的Token认证 -- ✅ 友好的错误提示 - -### 支持格式 - -| 类型 | 格式 | 预览方式 | -|------|------|---------| -| PDF | `.pdf` | ✅ 新标签页 | -| 图片 | `.jpg`, `.jpeg`, `.png`, `.gif`, `.bmp`, `.webp` | ✅ 新窗口 | -| 其他 | `.doc`, `.docx`, `.xls`, `.xlsx`, 等 | ⚠️ 自动下载 | - ---- - -## 💻 技术信息 - -### 修改文件 - -**文件路径**: `web/src/views/rs/overseas/index.vue` - -### 代码位置 - -#### 党籍保留附件 - -| 内容 | 行号 | 说明 | -|------|------|------| -| 界面按钮 | 1034-1070 | 新增"查看"按钮 | -| 图标导入 | 1260 | 导入View图标 | -| 预览方法 | 3447-3525 | 实现预览逻辑 | - -#### 出国境登记表盖章文件 - -| 内容 | 行号 | 说明 | -|------|------|------| -| 界面按钮 | 997-1032 | 新增"查看"按钮 | -| 图标导入 | 1260 | 导入View图标 | -| 预览方法 | 3527-3606 | 实现预览逻辑 | - -### 核心方法 - -```typescript -// 预览党籍保留附件 -const handlePreviewPartyRetentionFile = async (data: OverseasApplicationVO) => { - // 实现逻辑... -} - -// 预览出国境登记表盖章文件 -const handlePreviewStampedFile = async (data: OverseasApplicationVO) => { - // 实现逻辑... -} -``` - ---- - -## 📊 项目状态 - -### 开发进度 - -- [x] 需求分析 -- [x] 方案设计 -- [x] 代码实现 -- [x] 文档编写 -- [ ] 功能测试 -- [ ] 用户验收 -- [ ] 上线部署 - -### 质量检查 - -- [x] TypeScript编译通过 -- [x] ESLint检查通过 -- [x] 代码注释完善 -- [x] 文档齐全 -- [ ] 单元测试 -- [ ] 集成测试 - ---- - -## 🧪 测试状态 - -| 测试类型 | 用例数 | 状态 | -|---------|--------|------| -| 功能测试 | 8个 | ⏳ 待测试 | -| 兼容性测试 | 4个浏览器 | ⏳ 待测试 | -| 性能测试 | 2个 | ⏳ 待测试 | -| 安全测试 | 2个 | ⏳ 待测试 | - ---- - -## 🔄 后续计划 - -### 短期(1-2周) -- [ ] 完成功能测试 -- [ ] 完成用户验收 -- [ ] 上线部署 - -### 中期(1-2月) -- [ ] 支持Word在线预览 -- [ ] 支持Excel在线预览 -- [ ] 增强PDF预览功能 - -### 长期(3-6月) -- [ ] 移动端优化 -- [ ] 协作功能 -- [ ] 智能功能 - ---- - -## 📞 联系方式 - -### 问题反馈 - -- **开发团队**: Cursor AI -- **技术支持**: 待定 -- **项目管理**: 待定 - -### 获取帮助 - -1. 先查阅相关文档 -2. 查看常见问题(FAQ) -3. 联系开发团队 - ---- - -## 🎓 最佳实践 - -### 文档使用建议 - -1. **首次接触**: 从"文档总览"开始 -2. **快速查找**: 使用"快速参考" -3. **深入学习**: 阅读"实现说明" -4. **问题解决**: 查看"使用说明"的FAQ - -### 文档维护 - -- ✅ 代码变更时同步更新文档 -- ✅ 定期检查文档准确性 -- ✅ 收集用户反馈改进文档 -- ✅ 保持文档版本与代码一致 - ---- - -## 📈 文档统计 - -| 指标 | 数值 | -|------|------| -| 文档总数 | 6个 | -| 总字数 | ~20,000字 | -| 代码示例 | 15+ | -| 测试用例 | 8个 | -| FAQ数量 | 10+ | - ---- - -## 🏆 项目亮点 - -### 文档亮点 - -1. **完整性**: 覆盖技术、使用、测试等各个方面 -2. **层次性**: 从总览到细节,满足不同需求 -3. **实用性**: 包含大量示例和FAQ -4. **规范性**: 遵循项目文档规范 - -### 技术亮点 - -1. **智能识别**: 自动识别文件类型 -2. **安全管理**: Blob URL自动清理 -3. **优雅处理**: 完善的错误处理 -4. **用户体验**: 新窗口预览,不影响当前页面 - ---- - -## ✅ 验收清单 - -### 开发验收 -- [x] 代码实现完成 -- [x] 无编译错误 -- [x] 无ESLint错误 -- [x] 代码注释完善 - -### 文档验收 -- [x] 技术文档完整 -- [x] 用户文档完整 -- [x] 测试文档完整 -- [x] 文档格式规范 - -### 功能验收 -- [ ] 功能测试通过 -- [ ] 兼容性测试通过 -- [ ] 性能测试通过 -- [ ] 用户验收通过 - ---- - -## 📅 版本历史 - -| 版本 | 日期 | 内容 | 状态 | -|------|------|------|------| -| v1.0 | 2025-10-13 | 初始版本,完成基本功能 | ✅ 已完成 | - ---- - -## 🎉 致谢 - -感谢所有参与项目的人员: -- 需求提出者 -- 开发团队 -- 测试团队 -- 项目管理 - ---- - -**📝 文档维护**: Cursor AI -**📅 创建时间**: 2025-10-13 -**📅 最后更新**: 2025-10-13 -**📌 文档版本**: v1.0 - ---- - -**💡 提示**: -- 建议从"📋 文档总览"开始阅读 -- 根据角色选择相应文档 -- 遇到问题先查看FAQ -- 欢迎提供反馈和建议 - ---- - -**🚀 下一步**: 进行功能测试,完成用户验收后即可上线! - diff --git a/backend/ruoyi-admin/src/main/resources/application.yml b/backend/ruoyi-admin/src/main/resources/application.yml index 86b03355..d5dc137f 100644 --- a/backend/ruoyi-admin/src/main/resources/application.yml +++ b/backend/ruoyi-admin/src/main/resources/application.yml @@ -193,7 +193,7 @@ mybatis-encryptor: # api接口加密 api-decrypt: # 是否开启全局接口加密 - enabled: true # 恢复为true,保持其他接口的安全性 + enabled: false # 临时关闭用于测试权限修复 # AES 加密头标识 headerFlag: encrypt-key # 响应加密公钥 非对称算法的公私钥 如:SM2,RSA 使用者请自行更换 diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java index db542011..aed3a551 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApplicationController.java @@ -116,14 +116,14 @@ public class RsOverseasApplicationController extends BaseController { String currentUser = LoginHelper.getUsername(); Long userId = LoginHelper.getUserId(); String tenantId = LoginHelper.getTenantId(); - + // 设置查询条件 query.setStatus("0"); // 只查询待审核状态 query.setTenantId(tenantId); // 设置租户ID - + // 执行查询 TableDataInfo result = applicationService.queryPageList(query); - + // 返回调试信息 Map debugInfo = new HashMap<>(); debugInfo.put("currentUser", currentUser); @@ -131,7 +131,7 @@ public class RsOverseasApplicationController extends BaseController { debugInfo.put("tenantId", tenantId); debugInfo.put("queryParams", query); debugInfo.put("result", result); - + return R.ok(debugInfo); } @@ -164,14 +164,6 @@ public class RsOverseasApplicationController extends BaseController { registrationFormService.downloadRegistrationTemplate(response); } - /** - * 下载保留党籍申请模板 - */ - @GetMapping("/downloadPartyMembershipTemplate") - public void downloadPartyMembershipTemplate(HttpServletResponse response) { - registrationFormService.downloadPartyMembershipTemplate(response); - } - /** * 获取出国申请详细信息 */ @@ -196,24 +188,27 @@ public class RsOverseasApplicationController extends BaseController { // unitContact 字段已删除 // System.out.println("unitContact: " + bo.getUnitContact()); System.out.println("==============================================="); - + // 自动设置申请人信息 bo.setApplicantId(LoginHelper.getUserId()); bo.setApplicantName(LoginHelper.getUsername()); - + // 自动设置部门信息 bo.setDepartment(LoginHelper.getDeptName()); - + + // 🔴 关键修复:设置租户ID,否则列表查询时会因租户ID过滤而看不到数据 + bo.setTenantId(LoginHelper.getTenantId()); + // 自动计算出国天数 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()); - + boolean result = applicationService.insertByBo(bo); if (result) { // 🔴 返回新创建的applicationId,供前端上传文件时使用 @@ -247,42 +242,22 @@ public class RsOverseasApplicationController extends BaseController { if (application == null) { return R.fail("申请不存在"); } - - // 1. 校验出国登记前置条件(近3个月是否有已通过的出国登记) - java.util.Map registrationValidation = - applicationService.validateRegistrationPrerequisite(application.getApplicantId()); - Boolean registrationValid = (Boolean) registrationValidation.get("isValid"); - if (!registrationValid) { - String message = (String) registrationValidation.get("message"); - return R.fail(message); - } - - // 2. 校验党籍保留前置条件(中共党员且出国天数>30天) - java.util.Map retentionValidation = - applicationService.validatePartyMembershipRetentionPrerequisite( - application.getApplicantId(), - application.getPoliticalStatus(), - application.getDuration() - ); - Boolean retentionValid = (Boolean) retentionValidation.get("isValid"); - if (!retentionValid) { - String message = (String) retentionValidation.get("message"); - return R.fail(message); - } - - // 3. 校验是否上传了盖章文件 - if (application.getStampedFileUploadStatus() == null || - !"1".equals(application.getStampedFileUploadStatus())) { - return R.fail("请先下载登记备案附件填写盖章后上传,才能提交申请!"); - } - - // 4. 如果需要党籍保留且已通过,检查是否上传了保留党籍附件 - Boolean needRetention = (Boolean) retentionValidation.get("needRetention"); - if (needRetention && retentionValid) { - // TODO: 添加保留党籍附件上传校验(需要在数据库和实体类中添加字段) - // 当前版本暂不强制校验保留党籍附件 - } - + + // 1. 校验出国登记前置条件(已禁用 - 允许直接提交申请) + // java.util.Map registrationValidation = applicationService + // .validateRegistrationPrerequisite(application.getApplicantId()); + // Boolean registrationValid = (Boolean) registrationValidation.get("isValid"); + // if (!registrationValid) { + // String message = (String) registrationValidation.get("message"); + // return R.fail(message); + // } + + // 2. 校验是否上传了盖章文件(可选 - 根据业务需要决定是否保留) + // if (application.getStampedFileUploadStatus() == null || + // !"1".equals(application.getStampedFileUploadStatus())) { + // return R.fail("请先下载登记备案附件填写盖章后上传,才能提交申请!"); + // } + // 所有校验通过,执行提交操作 return toAjax(applicationService.submitApplication(applicationId)); } @@ -294,7 +269,7 @@ public class RsOverseasApplicationController extends BaseController { @Log(title = "重新提交出国申请", businessType = BusinessType.UPDATE) @PutMapping("/resubmit/{applicationId}") public R resubmit(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId, - @Validated @RequestBody RsOverseasApplicationBo bo) { + @Validated @RequestBody RsOverseasApplicationBo bo) { return toAjax(applicationService.resubmitApplication(applicationId, bo)); } @@ -307,23 +282,15 @@ public class RsOverseasApplicationController extends BaseController { public R> validatePrerequisites( @RequestParam(required = false) String politicalStatus, @RequestParam(required = false) Integer duration) { - + Long applicantId = LoginHelper.getUserId(); java.util.Map result = new java.util.HashMap<>(); - + // 1. 校验出国登记前置条件 - java.util.Map registrationValidation = - applicationService.validateRegistrationPrerequisite(applicantId); + java.util.Map registrationValidation = applicationService + .validateRegistrationPrerequisite(applicantId); result.put("registrationValidation", registrationValidation); - - // 2. 校验党籍保留前置条件(如果提供了政治面貌和出国天数) - if (politicalStatus != null && duration != null) { - java.util.Map retentionValidation = - applicationService.validatePartyMembershipRetentionPrerequisite( - applicantId, politicalStatus, duration); - result.put("retentionValidation", retentionValidation); - } - + return R.ok(result); } @@ -334,7 +301,7 @@ public class RsOverseasApplicationController extends BaseController { @Log(title = "取消出国申请", businessType = BusinessType.UPDATE) @PutMapping("/cancel/{applicationId}") public R cancel(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId, - @RequestParam(required = false) String reason) { + @RequestParam(required = false) String reason) { return toAjax(applicationService.cancelApplication(applicationId, reason)); } @@ -348,8 +315,8 @@ public class RsOverseasApplicationController extends BaseController { Long approverId = LoginHelper.getUserId(); String approverName = LoginHelper.getUsername(); return toAjax(applicationService.approveApplication( - request.getApplicationId(), approverId, approverName, - request.getApprovalStatus(), request.getApprovalOpinion())); + request.getApplicationId(), approverId, approverName, + request.getApprovalStatus(), request.getApprovalOpinion())); } /** @@ -368,7 +335,8 @@ public class RsOverseasApplicationController extends BaseController { @SaCheckPermission("rs:overseas:query") @GetMapping("/approval/history/{applicationId}") public R> getApprovalHistory(@PathVariable("applicationId") Long applicationId) { - List approvalList = overseasApprovalService.selectApprovalHistoryByApplicationId(applicationId); + List approvalList = overseasApprovalService + .selectApprovalHistoryByApplicationId(applicationId); return R.ok(approvalList); } @@ -460,7 +428,7 @@ public class RsOverseasApplicationController extends BaseController { @PostMapping("/uploadStampedFile") @Log(title = "上传盖章文件", businessType = BusinessType.OTHER) public R> uploadStampedFile(@RequestParam("file") MultipartFile file, - @RequestParam("applicationId") Long applicationId) { + @RequestParam("applicationId") Long applicationId) { try { // 验证文件格式 String contentType = file.getContentType(); @@ -476,15 +444,15 @@ public class RsOverseasApplicationController extends BaseController { // 使用系统OSS服务上传文件(自动保存到sys_oss表) SysOssVo ossResult = ossService.upload(file); - + // 更新申请记录 - 存储OSS ID和文件路径 applicationService.updateStampedFileInfo( - applicationId, - ossResult.getUrl(), // 文件URL - ossResult.getOriginalName(), // 原始文件名 - new Date(), // 上传时间 - "1", // 上传状态:已上传 - ossResult.getOssId() // OSS文件ID + applicationId, + ossResult.getUrl(), // 文件URL + ossResult.getOriginalName(), // 原始文件名 + new Date(), // 上传时间 + "1", // 上传状态:已上传 + ossResult.getOssId() // OSS文件ID ); // 返回结果 @@ -502,54 +470,6 @@ public class RsOverseasApplicationController extends BaseController { } } - /** - * 上传党籍保留附件 - */ - @PostMapping("/uploadPartyRetentionFile") - @Log(title = "上传党籍保留附件", businessType = BusinessType.OTHER) - public R> uploadPartyRetentionFile(@RequestParam("file") MultipartFile file, - @RequestParam("applicationId") Long applicationId) { - try { - // 验证文件格式 - String contentType = file.getContentType(); - if (!isValidFileType(contentType)) { - return R.fail("不支持的文件格式,请上传PDF、JPG或PNG文件"); - } - - // 验证文件大小(10MB) - long maxSize = 10 * 1024 * 1024; // 10MB - if (file.getSize() > maxSize) { - return R.fail("文件大小超过10MB限制"); - } - - // 使用系统OSS服务上传文件(自动保存到sys_oss表) - SysOssVo ossResult = ossService.upload(file); - - // 更新申请记录 - 存储OSS ID和文件路径 - applicationService.updatePartyRetentionFileInfo( - applicationId, - ossResult.getUrl(), // 文件URL - ossResult.getOriginalName(), // 原始文件名 - new Date(), // 上传时间 - "1", // 上传状态:已上传 - ossResult.getOssId() // OSS文件ID - ); - - // 返回结果 - Map result = new HashMap<>(); - result.put("filePath", ossResult.getUrl()); - result.put("fileName", ossResult.getOriginalName()); - result.put("uploadTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); - result.put("ossId", ossResult.getOssId()); - - return R.ok("党籍保留附件上传成功", result); - - } catch (Exception e) { - e.printStackTrace(); - return R.fail("系统异常:" + e.getMessage()); - } - } - /** * 下载盖章文件(安全下载) */ @@ -564,7 +484,7 @@ public class RsOverseasApplicationController extends BaseController { response.getWriter().write("{\"code\":404,\"msg\":\"文件不存在\"}"); return; } - + // 使用系统OSS服务进行安全下载 ossService.secureDownload(application.getStampedFileOssId(), response); } catch (Exception e) { @@ -572,37 +492,16 @@ public class RsOverseasApplicationController extends BaseController { } } - /** - * 下载党籍保留附件(安全下载) - */ - @SaCheckPermission("rs:overseas:query") - @GetMapping("/downloadPartyRetentionFile/{applicationId}") - public void downloadPartyRetentionFile(@PathVariable Long applicationId, HttpServletResponse response) { - try { - RsOverseasApplicationVo application = applicationService.queryById(applicationId); - if (application == null || application.getPartyRetentionFileOssId() == null) { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); - response.setContentType("application/json;charset=UTF-8"); - response.getWriter().write("{\"code\":404,\"msg\":\"文件不存在\"}"); - return; - } - - // 使用系统OSS服务进行安全下载 - ossService.secureDownload(application.getPartyRetentionFileOssId(), response); - } catch (Exception e) { - e.printStackTrace(); - } - } - /** * 验证文件类型 */ private boolean isValidFileType(String contentType) { - if (contentType == null) return false; + if (contentType == null) + return false; return contentType.equals("application/pdf") || - contentType.equals("image/jpeg") || - contentType.equals("image/jpg") || - contentType.equals("image/png"); + contentType.equals("image/jpeg") || + contentType.equals("image/jpg") || + contentType.equals("image/png"); } /** @@ -615,5 +514,4 @@ public class RsOverseasApplicationController extends BaseController { return filename.substring(filename.lastIndexOf(".")); } - -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalController.java index 8afe6ad8..87eb3ecd 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalController.java @@ -25,11 +25,14 @@ 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 cn.dev33.satoken.stp.StpUtil; 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; +import java.util.Map; +import java.util.HashMap; /** * 出国审批记录Controller @@ -75,6 +78,40 @@ public class RsOverseasApprovalController extends BaseController { return approvalService.queryCombinedPageList(query); } + /** + * 调试接口:查看当前用户的权限过滤情况 + */ + @SaCheckPermission("rs:overseas:list") + @GetMapping("/debug/permission") + public R> debugPermission(RsOverseasApprovalQuery query) { + Map debugInfo = new HashMap<>(); + + // 当前用户信息 + debugInfo.put("currentUserId", LoginHelper.getUserId()); + debugInfo.put("currentUsername", LoginHelper.getUsername()); + debugInfo.put("currentDeptId", LoginHelper.getDeptId()); + debugInfo.put("isSuperAdmin", LoginHelper.isSuperAdmin()); + + // 角色信息 + debugInfo.put("hasRsApplicant", StpUtil.hasRole("rs-Applicant")); + debugInfo.put("hasRsAdmin", StpUtil.hasRole("rs-admin")); + debugInfo.put("hasApprove1Permission", StpUtil.hasPermission("rs:overseas:approve1")); + debugInfo.put("hasDeptHeadRole", StpUtil.hasRole("dept_head_role")); + debugInfo.put("hasOldApprovePermission", StpUtil.hasPermission("overseas:application:dept_head:approve")); + + // 查询参数(应用权限过滤前) + debugInfo.put("originalQuery", query.toString()); + + // 应用权限过滤 + TableDataInfo result = approvalService.queryCombinedPageList(query); + + // 查询参数(应用权限过滤后) + debugInfo.put("filteredQuery", query.toString()); + debugInfo.put("resultCount", result.getTotal()); + + return R.ok(debugInfo); + } + /** * 根据申请ID查询审批记录列表 */ @@ -153,4 +190,4 @@ public class RsOverseasApprovalController extends BaseController { return R.fail("修复失败:" + e.getMessage()); } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java new file mode 100644 index 00000000..de4c5174 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasApprovalMonitorController.java @@ -0,0 +1,122 @@ +package org.dromara.rs.controller; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.validation.constraints.NotNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +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.mybatis.core.page.PageQuery; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.web.core.BaseController; +import org.dromara.rs.domain.vo.RsOverseasApprovalMonitorVo; +import org.dromara.rs.domain.vo.RsOverseasApprovalVo; +import org.dromara.rs.domain.query.RsOverseasApprovalMonitorQuery; +import org.dromara.rs.service.IRsOverseasApprovalMonitorService; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 出国申请审批监控Controller + *

+ * 功能说明: + * 1. 实时查看出国申请的审批流程(当前在哪个审批环节、审批人是谁) + * 2. 查看已完成审批的出国申请 + * + * @author Cursor AI + * @date 2025-10-21 + */ +@Slf4j +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/rs/overseas/monitor") +public class RsOverseasApprovalMonitorController extends BaseController { + + private final IRsOverseasApprovalMonitorService monitorService; + + /** + * 查询审批中的申请列表(实时监控) + *

+ * 显示所有状态为"待审核"的申请,包含: + * - 申请人信息 + * - 出国目的地和时间 + * - 当前审批环节(部门主管/分管领导/校领导) + * - 当前审批人姓名 + * - 审批开始时间 + * + * @param query 查询条件 + * @return 审批中的申请列表 + */ + @SaCheckPermission("rs:overseas:list") + @GetMapping("/pending/list") + public TableDataInfo pendingList(RsOverseasApprovalMonitorQuery query) { + log.info("查询审批中的申请列表,查询条件:{}", query); + // 只查询待审核状态(status = 0) + query.setStatus("0"); + return monitorService.queryPendingList(query); + } + + /** + * 查询已完成审批的申请列表 + *

+ * 显示所有审批流程完成的申请(已通过),包含: + * - 申请人信息 + * - 出国目的地和时间 + * - 最终审批结果 + * - 完成时间 + * - 所有审批环节的历史记录 + * + * @param query 查询条件 + * @return 已完成审批的申请列表 + */ + @SaCheckPermission("rs:overseas:list") + @GetMapping("/completed/list") + public TableDataInfo completedList(RsOverseasApprovalMonitorQuery query) { + log.info("查询已完成审批的申请列表,查询条件:{}", query); + // 只查询已通过状态(status = 1) + query.setStatus("1"); + return monitorService.queryCompletedList(query); + } + + /** + * 查询申请详情(含审批流程信息) + *

+ * 获取申请的完整信息,包括: + * - 申请基本信息 + * - 当前审批状态 + * - 审批历史记录 + * + * @param applicationId 申请ID + * @return 申请详情 + */ + @SaCheckPermission("rs:overseas:query") + @GetMapping("/detail/{applicationId}") + public R getDetail(@PathVariable @NotNull(message = "申请ID不能为空") Long applicationId) { + log.info("查询申请详情,申请ID:{}", applicationId); + RsOverseasApprovalMonitorVo detail = monitorService.queryDetailWithApprovalInfo(applicationId); + return R.ok(detail); + } + + /** + * 统计审批进度概况 + *

+ * 返回审批监控的统计数据: + * - 待审批总数 + * - 各审批级别的待审批数量 + * - 已完成审批总数 + * - 今日新增申请数 + * + * @return 统计数据 + */ + @SaCheckPermission("rs:overseas:list") + @GetMapping("/statistics") + public R statistics() { + log.info("查询审批进度统计"); + return R.ok(monitorService.getApprovalStatistics()); + } +} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasRecordController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasRecordController.java index 97f620cf..6dc50fe1 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasRecordController.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsOverseasRecordController.java @@ -71,9 +71,10 @@ public class RsOverseasRecordController extends BaseController { System.out.println("原始查询参数: " + query); query.setApplicantId(userId); System.out.println("设置申请人ID后: " + query); - + TableDataInfo result = recordService.queryMyRecordPageList(query); - System.out.println("Controller返回结果: total=" + result.getTotal() + ", rows=" + (result.getRows() != null ? result.getRows().size() : 0)); + System.out.println("Controller返回结果: total=" + result.getTotal() + ", rows=" + + (result.getRows() != null ? result.getRows().size() : 0)); System.out.println("========== 出国记录Controller: 查询完成 =========="); return result; } @@ -146,8 +147,8 @@ public class RsOverseasRecordController extends BaseController { @PutMapping("/complete") public R updateCompletion(@RequestBody CompletionRequest request) { return toAjax(recordService.updateCompletionStatus( - request.getRecordId(), request.getCompletionStatus(), - request.getAchievement(), request.getReportContent())); + request.getRecordId(), request.getCompletionStatus(), + request.getAchievement(), request.getReportContent())); } /** @@ -166,8 +167,8 @@ public class RsOverseasRecordController extends BaseController { @SaCheckPermission("rs:overseas:query") @Log(title = "下载出国情况回执", businessType = BusinessType.EXPORT) @GetMapping("/downloadReceipt/{recordId}") - public void downloadReceipt(@NotNull(message = "记录ID不能为空") @PathVariable Long recordId, - HttpServletResponse response) { + public void downloadReceipt(@NotNull(message = "记录ID不能为空") @PathVariable Long recordId, + HttpServletResponse response) { try { // 查询出国记录详情 RsOverseasRecordVo record = recordService.queryById(recordId); @@ -177,24 +178,24 @@ public class RsOverseasRecordController extends BaseController { response.getWriter().write("{\"code\":404,\"msg\":\"出国记录不存在\"}"); return; } - + // 生成PDF byte[] pdfBytes = OverseasReceiptPdfUtil.generateReceiptPdf(record); - + // 设置响应头 - String fileName = String.format("因私出国情况回执_%s_%s.pdf", - record.getApplicantName(), - new java.text.SimpleDateFormat("yyyyMMdd").format(new java.util.Date())); + String fileName = String.format("因私出国情况回执_%s_%s.pdf", + record.getApplicantName(), + new java.text.SimpleDateFormat("yyyyMMdd").format(new java.util.Date())); String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8).replace("+", "%20"); - + response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName); response.setContentLength(pdfBytes.length); - + // 输出PDF response.getOutputStream().write(pdfBytes); response.getOutputStream().flush(); - + } catch (Exception e) { try { response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); @@ -216,7 +217,7 @@ public class RsOverseasRecordController extends BaseController { try { Long currentUserId = LoginHelper.getUserId(); String currentUserName = LoginHelper.getUsername(); - + // 创建一个测试出国记录 RsOverseasRecordBo testRecord = new RsOverseasRecordBo(); testRecord.setApplicationId(1L); @@ -233,9 +234,9 @@ public class RsOverseasRecordController extends BaseController { 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()); @@ -283,4 +284,4 @@ public class RsOverseasRecordController extends BaseController { this.reportContent = reportContent; } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsPartyMembershipRetentionController.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsPartyMembershipRetentionController.java deleted file mode 100644 index 7d48ccae..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/controller/RsPartyMembershipRetentionController.java +++ /dev/null @@ -1,234 +0,0 @@ -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.mybatis.core.page.PageQuery; -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 cn.dev33.satoken.annotation.SaCheckPermission; -import org.dromara.rs.domain.bo.RsPartyMembershipRetentionBo; -import org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo; -import org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery; -import org.dromara.rs.service.IRsPartyMembershipRetentionService; - -/** - * 出国党籍保留Controller - * - * @author weizzzz - * @date 2025-10-05 - */ -@Validated -@RestController -@RequestMapping("/rs/party/retention") -public class RsPartyMembershipRetentionController extends BaseController { - - @Autowired - private IRsPartyMembershipRetentionService retentionService; - - /** - * 查询出国党籍保留列表 - */ - @SaCheckPermission("rs:partyretention:list") - @GetMapping("/list") - public TableDataInfo list(RsPartyMembershipRetentionQuery query, PageQuery pageQuery) { - return retentionService.queryPageList(query, pageQuery); - } - - /** - * 导出出国党籍保留列表 - */ - @SaCheckPermission("rs:partyretention:export") - @Log(title = "出国党籍保留", businessType = BusinessType.EXPORT) - @PostMapping("/export") - public void export(RsPartyMembershipRetentionQuery query, HttpServletResponse response) { - List list = retentionService.queryList(query); - ExcelUtil.exportExcel(list, "出国党籍保留", RsPartyMembershipRetentionVo.class, response); - } - - /** - * 获取出国党籍保留详细信息 - */ - @SaCheckPermission("rs:partyretention:query") - @GetMapping("/{retentionId}") - public R getInfo(@NotNull(message = "主键不能为空") - @PathVariable Long retentionId) { - return R.ok(retentionService.queryById(retentionId)); - } - - /** - * 根据登记ID获取党籍保留信息 - */ - @SaCheckPermission("rs:partyretention:query") - @GetMapping("/registration/{registrationId}") - public R getByRegistrationId(@NotNull(message = "登记ID不能为空") - @PathVariable Long registrationId) { - return R.ok(retentionService.queryByRegistrationId(registrationId)); - } - - /** - * 根据申请ID获取党籍保留信息 - */ - @SaCheckPermission("rs:partyretention:query") - @GetMapping("/application/{applicationId}") - public R getByApplicationId(@NotNull(message = "申请ID不能为空") - @PathVariable Long applicationId) { - return R.ok(retentionService.queryByApplicationId(applicationId)); - } - - /** - * 新增出国党籍保留 - */ - @SaCheckPermission("rs:partyretention:add") - @Log(title = "出国党籍保留", businessType = BusinessType.INSERT) - @PostMapping() - public R add(@Validated(AddGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) { - return toAjax(retentionService.insertByBo(bo)); - } - - /** - * 保存并提交出国党籍保留 - */ - @SaCheckPermission("rs:partyretention:add") - @Log(title = "出国党籍保留", businessType = BusinessType.INSERT) - @PostMapping("/saveAndSubmit") - public R saveAndSubmit(@Validated(AddGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) { - return toAjax(retentionService.saveAndSubmit(bo)); - } - - /** - * 修改出国党籍保留 - */ - @SaCheckPermission("rs:partyretention:edit") - @Log(title = "出国党籍保留", businessType = BusinessType.UPDATE) - @PutMapping() - public R edit(@Validated(EditGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) { - return toAjax(retentionService.updateByBo(bo)); - } - - /** - * 删除出国党籍保留 - */ - @SaCheckPermission("rs:partyretention:remove") - @Log(title = "出国党籍保留", businessType = BusinessType.DELETE) - @DeleteMapping("/{retentionIds}") - public R remove(@NotEmpty(message = "主键不能为空") - @PathVariable Long[] retentionIds) { - return toAjax(retentionService.deleteWithValidByIds(List.of(retentionIds), true)); - } - - /** - * 提交出国党籍保留 - */ - @SaCheckPermission("rs:partyretention:submit") - @Log(title = "出国党籍保留", businessType = BusinessType.UPDATE) - @PutMapping("/submit/{retentionId}") - public R submit(@NotNull(message = "主键不能为空") - @PathVariable Long retentionId) { - return toAjax(retentionService.submit(retentionId)); - } - - /** - * 撤回出国党籍保留 - */ - @SaCheckPermission("rs:partyretention:withdraw") - @Log(title = "出国党籍保留", businessType = BusinessType.UPDATE) - @PutMapping("/withdraw/{retentionId}") - public R withdraw(@NotNull(message = "主键不能为空") - @PathVariable Long retentionId) { - return toAjax(retentionService.withdraw(retentionId)); - } - - /** - * 审批出国党籍保留 - */ - @SaCheckPermission("rs:partyretention:approve") - @Log(title = "出国党籍保留", businessType = BusinessType.UPDATE) - @PutMapping("/approve/{retentionId}") - public R approve(@NotNull(message = "主键不能为空") @PathVariable Long retentionId, - @RequestParam String approvalOpinion, - @RequestParam Boolean approved) { - return toAjax(retentionService.approve(retentionId, approvalOpinion, approved)); - } - - // ==================== 保留历史相关接口 ==================== - - /** - * 查询党籍保留历史列表(只查询已通过的申请) - */ - @SaCheckPermission("rs:partyretention:list") - @GetMapping("/history/list") - public TableDataInfo historyList(RsPartyMembershipRetentionQuery query, PageQuery pageQuery) { - // 强制设置status=2,只查询已通过的记录 - query.setStatus("2"); - return retentionService.queryPageList(query, pageQuery); - } - - /** - * 查询党籍保留历史详细信息 - */ - @SaCheckPermission("rs:partyretention:query") - @GetMapping("/history/{retentionId}") - public R getHistoryInfo(@NotNull(message = "主键不能为空") - @PathVariable Long retentionId) { - RsPartyMembershipRetentionVo vo = retentionService.queryById(retentionId); - if (vo == null) { - return R.fail("记录不存在"); - } - // 验证是否是已通过状态 - if (!"2".equals(vo.getStatus())) { - return R.fail("该记录不是已通过状态"); - } - return R.ok(vo); - } - - /** - * 修改党籍保留历史(用于更新完成状态等) - */ - @SaCheckPermission("rs:partyretention:edit") - @Log(title = "党籍保留历史", businessType = BusinessType.UPDATE) - @PutMapping("/history") - public R updateHistory(@Validated(EditGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) { - // 验证是否是已通过状态 - RsPartyMembershipRetentionVo vo = retentionService.queryById(bo.getRetentionId()); - if (vo == null) { - return R.fail("记录不存在"); - } - if (!"2".equals(vo.getStatus())) { - return R.fail("只能修改已通过状态的记录"); - } - return toAjax(retentionService.updateByBo(bo)); - } - - /** - * 导出党籍保留历史 - */ - @SaCheckPermission("rs:partyretention:export") - @Log(title = "党籍保留历史", businessType = BusinessType.EXPORT) - @PostMapping("/history/export") - public void exportHistory(RsPartyMembershipRetentionQuery query, HttpServletResponse response) { - // 强制设置status=2,只导出已通过的记录 - query.setStatus("2"); - List list = retentionService.queryList(query); - ExcelUtil.exportExcel(list, "党籍保留历史", RsPartyMembershipRetentionVo.class, response); - } -} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApplication.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApplication.java index 2f551f16..e004997f 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApplication.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsOverseasApplication.java @@ -1,5 +1,6 @@ package org.dromara.rs.domain; +import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; @@ -11,14 +12,12 @@ 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 + * @author weizzzz * @date 2025-07-28 */ @Data @@ -260,6 +259,12 @@ public class RsOverseasApplication extends BaseEntity { @ExcelProperty(value = "申请状态") private String status; + /** + * 审批状态(工作流状态):draft-草稿,waiting-待审批,finish-已完成,cancel-已取消,back-已退回 + */ + @ExcelProperty(value = "审批状态") + private String approvalStatus; + /** * 当前审批人 */ @@ -372,6 +377,13 @@ public class RsOverseasApplication extends BaseEntity { @ExcelIgnore private Long partyRetentionFileOssId; + /** + * 租户编号 + */ + @TableField(fill = FieldFill.INSERT) + @ExcelIgnore + private String tenantId; + /** * 基础字段:创建者 */ @@ -398,5 +410,4 @@ public class RsOverseasApplication extends BaseEntity { @ExcelIgnore private Date updateTime; - -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsPartyMembershipRetention.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsPartyMembershipRetention.java deleted file mode 100644 index 925e2787..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/RsPartyMembershipRetention.java +++ /dev/null @@ -1,189 +0,0 @@ -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 java.util.Date; - -/** - * 出国党籍保留对象 rs_party_membership_retention - * - * @author weizzzz - * @date 2025-10-05 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@TableName("rs_party_membership_retention") -public class RsPartyMembershipRetention extends BaseEntity { - - private static final long serialVersionUID = 1L; - - /** - * 党籍保留ID(主键) - */ - @TableId(value = "retention_id", type = IdType.AUTO) - @ExcelProperty(value = "党籍保留ID") - private Long retentionId; - - /** - * 关联出国申请ID - */ - @TableField("application_id") - @ExcelProperty(value = "关联出国申请ID") - private Long applicationId; - - /** - * 关联出国登记ID - */ - @TableField("registration_id") - @ExcelProperty(value = "关联出国登记ID") - private Long registrationId; - - /** - * 我支部同志(姓名) - */ - @ExcelProperty(value = "姓名") - private String name; - - /** - * 身份证号(18位) - */ - @TableField("id_card") - @ExcelProperty(value = "身份证号") - private String idCard; - - /** - * 现任职务 - */ - @ExcelProperty(value = "职务") - private String position; - - /** - * 偕配偶姓名 - */ - @ExcelProperty(value = "配偶姓名") - private String spouse; - - /** - * 亲属姓名(探望对象) - */ - @TableField("relative_name") - @ExcelProperty(value = "亲属姓名") - private String relativeName; - - /** - * 拟出国开始日期 - */ - @JsonFormat(pattern = "yyyy-MM-dd") - @TableField("travel_start_date") - @ExcelProperty(value = "出国开始日期") - private Date travelStartDate; - - /** - * 拟出国结束日期 - */ - @JsonFormat(pattern = "yyyy-MM-dd") - @TableField("travel_end_date") - @ExcelProperty(value = "出国结束日期") - private Date travelEndDate; - - /** - * 在外停留天数 - */ - @TableField("stay_days") - @ExcelProperty(value = "停留天数") - private Integer stayDays; - - /** - * 因私赴XX(国家或地区) - */ - @ExcelProperty(value = "目的地") - private String destination; - - /** - * 出国目的详细说明 - */ - @ExcelProperty(value = "出国目的") - private String purpose; - - /** - * 出国原因(简要) - */ - @TableField("travel_reason") - @ExcelProperty(value = "出国原因") - private String travelReason; - - /** - * 费用自理:1-是,0-否 - */ - @TableField("self_funded") - @ExcelProperty(value = "费用自理") - private String selfFunded; - - /** - * 请求审批意见 - */ - @TableField("request_approval") - @ExcelProperty(value = "请求审批意见") - private String requestApproval; - - /** - * 最终审批意见 - */ - @TableField("final_approval") - @ExcelProperty(value = "最终审批意见") - private String finalApproval; - - /** - * 党支部名称 - */ - @TableField("party_branch") - @ExcelProperty(value = "党支部") - private String partyBranch; - - /** - * 申请日期 - */ - @JsonFormat(pattern = "yyyy-MM-dd") - @TableField("application_date") - @ExcelProperty(value = "申请日期") - private Date applicationDate; - - /** - * 状态:0-草稿,1-待审核,2-已通过,3-已拒绝,4-已撤回 - */ - @ExcelProperty(value = "状态") - private String status; - - /** - * 申请时间 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @TableField("application_time") - @ExcelProperty(value = "申请时间") - private Date applicationTime; - - /** - * 备注 - */ - @ExcelProperty(value = "备注") - private String remarks; - - /** - * 租户编号 - */ - @TableField("tenant_id") - private String tenantId; - - /** - * 删除标志:0-未删除,1-已删除 - */ - private String deleted; -} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApplicationBo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApplicationBo.java index 06720711..75a860ab 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApplicationBo.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsOverseasApplicationBo.java @@ -305,4 +305,9 @@ public class RsOverseasApplicationBo extends BaseEntity { * 盖章文件上传状态:0-未上传,1-已上传 */ private String stampedFileUploadStatus; -} \ No newline at end of file + + /** + * 租户ID + */ + private String tenantId; +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsPartyMembershipRetentionBo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsPartyMembershipRetentionBo.java deleted file mode 100644 index 0c8fdb18..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/bo/RsPartyMembershipRetentionBo.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.dromara.rs.domain.bo; - -import org.dromara.rs.domain.RsPartyMembershipRetention; -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_party_membership_retention - * - * @author weizzzz - * @date 2025-10-05 - */ -@Data -@EqualsAndHashCode(callSuper = true) -@AutoMapper(target = RsPartyMembershipRetention.class, reverseConvertGenerate = false) -public class RsPartyMembershipRetentionBo extends BaseEntity { - - private static final long serialVersionUID = 1L; - - /** - * 党籍保留ID(主键) - */ - @NotNull(message = "党籍保留ID不能为空", groups = { EditGroup.class }) - private Long retentionId; - - /** - * 关联出国申请ID - */ - private Long applicationId; - - /** - * 关联出国登记ID - */ - private Long registrationId; - - /** - * 我支部同志(姓名) - */ - @NotBlank(message = "姓名不能为空", groups = { AddGroup.class, EditGroup.class }) - private String name; - - /** - * 身份证号(18位) - */ - @NotBlank(message = "身份证号不能为空", groups = { AddGroup.class, EditGroup.class }) - private String idCard; - - /** - * 现任职务 - */ - @NotBlank(message = "职务不能为空", groups = { AddGroup.class, EditGroup.class }) - private String position; - - /** - * 偕配偶姓名 - */ - private String spouse; - - /** - * 亲属姓名(探望对象) - */ - @NotBlank(message = "亲属姓名不能为空", groups = { AddGroup.class, EditGroup.class }) - private String relativeName; - - /** - * 拟出国开始日期 - */ - @NotNull(message = "出国开始日期不能为空", groups = { AddGroup.class, EditGroup.class }) - @JsonFormat(pattern = "yyyy-MM-dd") - private Date travelStartDate; - - /** - * 拟出国结束日期 - */ - @NotNull(message = "出国结束日期不能为空", groups = { AddGroup.class, EditGroup.class }) - @JsonFormat(pattern = "yyyy-MM-dd") - private Date travelEndDate; - - /** - * 在外停留天数(自动计算) - */ - private Integer stayDays; - - /** - * 因私赴XX(国家或地区) - */ - @NotBlank(message = "目的地不能为空", groups = { AddGroup.class, EditGroup.class }) - private String destination; - - /** - * 出国目的详细说明(前端必填) - */ - @NotBlank(message = "出国目的不能为空", groups = { AddGroup.class, EditGroup.class }) - private String purpose; - - /** - * 出国原因(简要) - */ - private String travelReason; - - /** - * 费用自理:1-是,0-否(后端自动设置,默认为"1") - */ - private String selfFunded; - - /** - * 请求审批意见 - */ - private String requestApproval; - - /** - * 最终审批意见 - */ - private String finalApproval; - - /** - * 党支部名称 - */ - @NotBlank(message = "党支部名称不能为空", groups = { AddGroup.class, EditGroup.class }) - private String partyBranch; - - /** - * 申请日期(后端自动设置为当前日期) - */ - @JsonFormat(pattern = "yyyy-MM-dd") - private Date applicationDate; - - /** - * 状态:0-草稿,1-待审核,2-已通过,3-已拒绝,4-已撤回 - */ - private String status; - - /** - * 申请时间 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date applicationTime; - - /** - * 备注 - */ - private String remarks; - - /** - * 租户编号 - */ - private String tenantId; -} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApprovalMonitorQuery.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApprovalMonitorQuery.java new file mode 100644 index 00000000..b3dac586 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApprovalMonitorQuery.java @@ -0,0 +1,73 @@ +package org.dromara.rs.domain.query; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.dromara.common.mybatis.core.page.PageQuery; + +import java.util.Date; + +/** + * 出国申请审批监控查询对象 + * + * @author Cursor AI + * @date 2025-10-21 + */ +@Data +@EqualsAndHashCode(callSuper = true) +public class RsOverseasApprovalMonitorQuery extends PageQuery { + + private static final long serialVersionUID = 1L; + + /** + * 申请人姓名(模糊查询) + */ + private String applicantName; + + /** + * 所属部门(模糊查询) + */ + private String department; + + /** + * 目的地国家(模糊查询) + */ + private String destination; + + /** + * 申请状态:0-待审核,1-已通过,2-已拒绝,3-已取消,4-已完成 + */ + private String status; + + /** + * 审批级别:1-部门主管,2-分管领导,3-校领导 + */ + private String approvalLevel; + + /** + * 当前审批人(模糊查询) + */ + private String currentApprover; + + /** + * 申请时间范围 - 开始时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date applyTimeStart; + + /** + * 申请时间范围 - 结束时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date applyTimeEnd; + + /** + * 是否超时 + */ + private Boolean isOverdue; + + /** + * 租户ID + */ + private String tenantId; +} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApprovalQuery.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApprovalQuery.java index ac143686..78bf0202 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApprovalQuery.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsOverseasApprovalQuery.java @@ -63,4 +63,33 @@ public class RsOverseasApprovalQuery extends PageQuery { * 租户ID */ private String tenantId; -} \ No newline at end of file + + /** + * 申请人ID(用于权限过滤) + */ + private Long applicantId; + + /** + * 部门ID(用于部门权限过滤) + */ + private Long deptId; + + @Override + public String toString() { + return "RsOverseasApprovalQuery{" + + "applicationId=" + applicationId + + ", approverId=" + approverId + + ", approverName='" + approverName + '\'' + + ", approvalLevel='" + approvalLevel + '\'' + + ", approvalStatus='" + approvalStatus + '\'' + + ", approvalTimeBegin=" + approvalTimeBegin + + ", approvalTimeEnd=" + approvalTimeEnd + + ", applicantName='" + applicantName + '\'' + + ", tenantId='" + tenantId + '\'' + + ", applicantId=" + applicantId + + ", deptId=" + deptId + + ", pageNum=" + getPageNum() + + ", pageSize=" + getPageSize() + + '}'; + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsPartyMembershipRetentionQuery.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsPartyMembershipRetentionQuery.java deleted file mode 100644 index 4b19e977..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/query/RsPartyMembershipRetentionQuery.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.dromara.rs.domain.query; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.dromara.common.mybatis.core.page.PageQuery; -import com.fasterxml.jackson.annotation.JsonFormat; - -import java.util.Date; - -/** - * 出国党籍保留查询对象 rs_party_membership_retention - * - * @author weizzzz - * @date 2025-10-05 - */ -@Data -@EqualsAndHashCode(callSuper = true) -public class RsPartyMembershipRetentionQuery extends PageQuery { - - private static final long serialVersionUID = 1L; - - /** - * 姓名(模糊查询) - */ - private String name; - - /** - * 身份证号(精确查询) - */ - private String idCard; - - /** - * 目的地(模糊查询) - */ - private String destination; - - /** - * 党支部名称(模糊查询) - */ - private String partyBranch; - - /** - * 状态:0-草稿,1-待审核,2-已通过,3-已拒绝,4-已撤回 - */ - private String status; - - /** - * 关联出国登记ID - */ - private Long registrationId; - - /** - * 关联出国申请ID - */ - private Long applicationId; - - /** - * 申请时间开始 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date applicationTimeStart; - - /** - * 申请时间结束 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date applicationTimeEnd; -} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApplicationVo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApplicationVo.java index 72417ab3..0dfe4db3 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApplicationVo.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApplicationVo.java @@ -7,7 +7,6 @@ import com.fasterxml.jackson.annotation.JsonFormat; import java.io.Serial; import java.io.Serializable; -import java.math.BigDecimal; import java.util.Date; /** @@ -241,6 +240,21 @@ public class RsOverseasApplicationVo implements Serializable { */ private String statusText; + /** + * 审批状态(工作流状态):draft-草稿,waiting-待审批,finish-已完成,cancel-已取消,back-已退回 + */ + private String approvalStatus; + + /** + * 审批状态文本 + */ + private String approvalStatusText; + + /** + * 工作流任务ID(用于前端判断是否显示审批按钮) + */ + private Long taskId; + /** * 当前审批人 */ @@ -381,4 +395,4 @@ public class RsOverseasApplicationVo implements Serializable { * 获取经费来源文本 */ -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApprovalMonitorVo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApprovalMonitorVo.java new file mode 100644 index 00000000..9a3e3219 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasApprovalMonitorVo.java @@ -0,0 +1,339 @@ +package org.dromara.rs.domain.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import cn.idev.excel.annotation.ExcelProperty; +import lombok.Data; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +/** + * 出国申请审批监控视图对象 + *

+ * 用于审批监控页面展示,包含申请信息和审批流程信息 + * + * @author Cursor AI + * @date 2025-10-21 + */ +@Data +public class RsOverseasApprovalMonitorVo implements Serializable { + + private static final long serialVersionUID = 1L; + + // ==================== 申请基本信息 ==================== + + /** + * 申请ID + */ + @ExcelProperty(value = "申请ID") + private Long applicationId; + + /** + * 出国记录ID(已完成的申请会有对应的记录ID) + */ + private Long recordId; + + /** + * 编号(自动生成的登记编号) + */ + @ExcelProperty(value = "编号") + private String serialNumber; + + /** + * 申请人ID + */ + private Long applicantId; + + /** + * 申请人姓名 + */ + @ExcelProperty(value = "申请人姓名") + private String applicantName; + + /** + * 性别:0-男,1-女 + */ + @ExcelProperty(value = "性别") + private String gender; + + /** + * 性别文本(用于前端显示) + */ + private String genderText; + + /** + * 出生日期 + */ + @JsonFormat(pattern = "yyyy-MM-dd") + @ExcelProperty(value = "出生日期") + private Date birthDate; + + /** + * 政治面貌 + */ + @ExcelProperty(value = "政治面貌") + private String politicalStatus; + + /** + * 政治面貌文本(用于前端显示) + */ + private String politicalStatusText; + + /** + * 所属部门 + */ + @ExcelProperty(value = "所属部门") + private String department; + + /** + * 职称 + */ + @ExcelProperty(value = "职称") + private String professionalTitle; + + /** + * 职务职称 + */ + @ExcelProperty(value = "职务职称") + private String position; + + // ==================== 证件信息 ==================== + + /** + * 申领证件名称 + */ + @ExcelProperty(value = "申领证件名称") + private String documentName; + + /** + * 证件号码 + */ + @ExcelProperty(value = "证件号码") + private String documentNumber; + + // ==================== 出国信息 ==================== + + /** + * 出国目的 + */ + @ExcelProperty(value = "出国目的") + private String purpose; + + /** + * 目的地国家(兼容字段) + */ + @ExcelProperty(value = "目的地国家") + private String destination; + + /** + * 前往国家 + */ + @ExcelProperty(value = "前往国家") + private String destinationCountry; + + /** + * 目的地城市 + */ + @ExcelProperty(value = "目的地城市") + private String destinationCity; + + /** + * 出境事由 + */ + @ExcelProperty(value = "出境事由") + private String exitReason; + + /** + * 探望对象姓名及与申请者关系(仅探亲时) + */ + @ExcelProperty(value = "探望对象关系") + private String visitRelationship; + + /** + * 行程安排 + */ + @ExcelProperty(value = "行程安排") + private String itinerary; + + /** + * 开始日期 + */ + @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 String mobilePhone; + + /** + * 联系电话(兼容字段) + */ + @ExcelProperty(value = "联系电话") + private String contactPhone; + + /** + * 应急联系人及电话 + */ + @ExcelProperty(value = "应急联系人及电话") + private String emergencyContact; + + // ==================== 审批状态信息 ==================== + + /** + * 申请状态:0-待审核,1-已通过,2-已拒绝,3-已取消,4-已完成 + */ + @ExcelProperty(value = "申请状态") + private String status; + + /** + * 申请状态文本(用于前端显示) + */ + @ExcelProperty(value = "申请状态") + private String statusText; + + /** + * 当前审批人 + */ + @ExcelProperty(value = "当前审批人") + private String currentApprover; + + /** + * 当前审批人ID + */ + private Long currentApproverId; + + /** + * 审批级别:1-部门主管,2-分管领导,3-校领导 + */ + @ExcelProperty(value = "审批级别") + private String approvalLevel; + + /** + * 审批级别文本(用于前端显示) + */ + @ExcelProperty(value = "当前审批环节") + private String approvalLevelText; + + /** + * 申请时间(提交时间) + */ + @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; + + // ==================== 审批进度信息 ==================== + + /** + * 审批进度:例如 "2/3"(已完成2级,共3级) + */ + @ExcelProperty(value = "审批进度") + private String approvalProgress; + + /** + * 已审批级别数 + */ + private Integer approvedLevels; + + /** + * 总审批级别数 + */ + private Integer totalLevels; + + /** + * 审批耗时(天) + */ + @ExcelProperty(value = "审批耗时(天)") + private Integer approvalDays; + + /** + * 是否超时(根据业务规则判断) + */ + private Boolean isOverdue; + + // ==================== 审批历史记录 ==================== + + /** + * 审批历史记录列表 + */ + private List approvalHistory; + + /** + * 最近一次审批意见 + */ + @ExcelProperty(value = "最近审批意见") + private String latestApprovalOpinion; + + /** + * 最近一次审批人 + */ + @ExcelProperty(value = "最近审批人") + private String latestApproverName; + + /** + * 最近一次审批时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ExcelProperty(value = "最近审批时间") + private Date latestApprovalTime; + + // ==================== 其他信息 ==================== + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remarks; + + /** + * 管理员备注 + */ + private String adminRemark; + + /** + * 申请人备注 + */ + private String applicantRemark; + + /** + * 租户编号 + */ + private String tenantId; + + /** + * 创建时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date createTime; + + /** + * 更新时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private Date updateTime; +} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasRecordVo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasRecordVo.java index 029400fd..dacd903b 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasRecordVo.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasRecordVo.java @@ -47,85 +47,90 @@ public class RsOverseasRecordVo implements Serializable { * 所属部门 */ private String department; - + /** * 职位 */ private String position; + /** + * 职位文本(用于显示) + */ + private String positionText; + /** * 联系电话 */ private String contactPhone; - + /** * 联系邮箱 */ private String contactEmail; - + // ========== 以下字段来自关联的出国申请表 ========== - + /** * 性别(来自申请表) */ private String gender; - + /** * 性别文本(来自申请表) */ private String genderText; - + /** * 出生日期(来自申请表) */ @JsonFormat(pattern = "yyyy-MM-dd") private Date birthDate; - + /** * 政治面貌(来自申请表) */ private String politicalStatus; - + /** * 政治面貌文本(来自申请表) */ private String politicalStatusText; - + /** * 职称(来自申请表) */ private String professionalTitle; - + /** * 申领证件名称(来自申请表) */ private String documentName; - + /** * 证件号码(来自申请表) */ private String documentNumber; - + /** * 移动电话(来自申请表) */ private String mobilePhone; - + /** * 前往国家(来自申请表) */ private String destinationCountry; - + /** * 出境事由(来自申请表) */ private String exitReason; - + /** * 应急联系人及电话(来自申请表) */ private String emergencyContact; - + /** * 备注(来自申请表) */ @@ -269,7 +274,7 @@ public class RsOverseasRecordVo implements Serializable { */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date updateTime; - + /** * 获取出国目的文本 */ @@ -297,7 +302,7 @@ public class RsOverseasRecordVo implements Serializable { return this.purpose; } } - + /** * 数据来源标识 */ @@ -322,7 +327,7 @@ public class RsOverseasRecordVo implements Serializable { return "未知状态"; } } - + /** * 获取数据来源说明 */ @@ -339,4 +344,4 @@ public class RsOverseasRecordVo implements Serializable { return "出国记录"; } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsPartyMembershipRetentionVo.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsPartyMembershipRetentionVo.java deleted file mode 100644 index 740851ef..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsPartyMembershipRetentionVo.java +++ /dev/null @@ -1,180 +0,0 @@ -package org.dromara.rs.domain.vo; - -import org.dromara.rs.domain.RsPartyMembershipRetention; -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_party_membership_retention - * - * @author weizzzz - * @date 2025-10-05 - */ -@Data -@AutoMapper(target = RsPartyMembershipRetention.class) -public class RsPartyMembershipRetentionVo implements Serializable { - - @Serial - private static final long serialVersionUID = 1L; - - /** - * 党籍保留ID(主键) - */ - private Long retentionId; - - /** - * 关联出国申请ID - */ - private Long applicationId; - - /** - * 关联出国登记ID - */ - private Long registrationId; - - /** - * 我支部同志(姓名) - */ - private String name; - - /** - * 身份证号(18位) - */ - private String idCard; - - /** - * 现任职务 - */ - private String position; - - /** - * 偕配偶姓名 - */ - private String spouse; - - /** - * 亲属姓名(探望对象) - */ - private String relativeName; - - /** - * 拟出国开始日期 - */ - @JsonFormat(pattern = "yyyy-MM-dd") - private Date travelStartDate; - - /** - * 拟出国结束日期 - */ - @JsonFormat(pattern = "yyyy-MM-dd") - private Date travelEndDate; - - /** - * 在外停留天数 - */ - private Integer stayDays; - - /** - * 因私赴XX(国家或地区) - */ - private String destination; - - /** - * 出国目的详细说明 - */ - private String purpose; - - /** - * 出国原因(简要) - */ - private String travelReason; - - /** - * 费用自理:1-是,0-否 - */ - private String selfFunded; - - /** - * 费用自理文本 - */ - private String selfFundedText; - - /** - * 请求审批意见 - */ - private String requestApproval; - - /** - * 最终审批意见 - */ - private String finalApproval; - - /** - * 党支部名称 - */ - private String partyBranch; - - /** - * 申请日期 - */ - @JsonFormat(pattern = "yyyy-MM-dd") - private Date applicationDate; - - /** - * 状态:0-草稿,1-待审核,2-已通过,3-已拒绝,4-已撤回 - */ - private String status; - - /** - * 状态文本 - */ - private String statusText; - - /** - * 申请时间 - */ - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - private Date applicationTime; - - /** - * 备注 - */ - private String remarks; - - /** - * 租户编号 - */ - private String tenantId; - - /** - * 创建者 - */ - 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代表存在 1代表删除) - */ - private String deleted; -} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsOverseasApplicationMapper.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsOverseasApplicationMapper.java index 093a9afa..9a1fb7aa 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsOverseasApplicationMapper.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsOverseasApplicationMapper.java @@ -13,7 +13,7 @@ import org.dromara.rs.domain.query.RsOverseasApplicationQuery; * @date 2025-07-28 */ public interface RsOverseasApplicationMapper extends BaseMapperPlus { - + /** * 查询出国申请列表 * @@ -21,7 +21,7 @@ public interface RsOverseasApplicationMapper extends BaseMapperPlus selectOverseasApplicationList(RsOverseasApplicationQuery query); - + /** * 查询我的出国申请列表(排除已通过的申请) * @@ -29,7 +29,7 @@ public interface RsOverseasApplicationMapper extends BaseMapperPlus selectMyOverseasApplicationList(RsOverseasApplicationQuery query); - + /** * 查询出国申请详细信息 * @@ -37,7 +37,7 @@ public interface RsOverseasApplicationMapper extends BaseMapperPlus selectOverseasApplicationsByApplicantId(Long applicantId); - + /** * 根据状态查询出国申请列表 * @@ -53,7 +53,7 @@ public interface RsOverseasApplicationMapper extends BaseMapperPlus selectOverseasApplicationsByStatus(String status); - + /** * 根据当前审批人查询待审批申请列表 * @@ -61,7 +61,7 @@ public interface RsOverseasApplicationMapper extends BaseMapperPlus selectPendingApplicationsByApprover(String currentApprover); - + /** * 根据审批级别查询待审批申请列表 * @@ -69,4 +69,36 @@ public interface RsOverseasApplicationMapper extends BaseMapperPlus selectPendingApplicationsByLevel(String approvalLevel); -} \ No newline at end of file + + /** + * 查询申请人的部门ID + * + * @param applicantId 申请人ID + * @return 部门ID + */ + Long selectApplicantDeptId(Long applicantId); + + /** + * 根据部门ID查询部门负责人ID + * + * @param deptId 部门ID + * @return 部门负责人ID + */ + Long selectDeptHeadByDeptId(Long deptId); + + /** + * 查询上级部门负责人ID + * + * @param deptId 部门ID + * @return 上级部门负责人ID + */ + Long selectParentDeptHead(Long deptId); + + /** + * 根据用户ID查询用户姓名 + * + * @param userId 用户ID + * @return 用户姓名 + */ + String selectUserNameById(Long userId); +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsPartyMembershipRetentionMapper.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsPartyMembershipRetentionMapper.java deleted file mode 100644 index 749cfbf6..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/mapper/RsPartyMembershipRetentionMapper.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.dromara.rs.mapper; - -import org.dromara.rs.domain.RsPartyMembershipRetention; -import org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo; -import org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery; -import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; - -import java.util.List; - -/** - * 出国党籍保留Mapper接口 - * - * @author weizzzz - * @date 2025-10-05 - */ -public interface RsPartyMembershipRetentionMapper extends BaseMapperPlus { - - /** - * 查询出国党籍保留列表(包含扩展字段如statusText) - * - * @param query 查询条件 - * @return 出国党籍保留列表 - */ - List selectVoList(RsPartyMembershipRetentionQuery query); - -} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java index b9d7498f..23f2d3dc 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApplicationService.java @@ -109,7 +109,7 @@ public interface IRsOverseasApplicationService { * 取消出国申请 * * @param applicationId 申请ID - * @param reason 取消原因 + * @param reason 取消原因 * @return 是否成功 */ Boolean cancelApplication(Long applicationId, String reason); @@ -117,20 +117,20 @@ public interface IRsOverseasApplicationService { /** * 审批出国申请 * - * @param applicationId 申请ID - * @param approverId 审批人ID - * @param approverName 审批人姓名 - * @param approvalStatus 审批结果:1-通过,2-拒绝 + * @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); + Boolean approveApplication(Long applicationId, Long approverId, String approverName, + String approvalStatus, String approvalOpinion); /** * 校验删除数据 * - * @param ids 主键集合 + * @param ids 主键集合 * @param isValid 是否有效性校验 */ void validatedByIds(List ids, Boolean isValid); @@ -153,7 +153,7 @@ public interface IRsOverseasApplicationService { /** * 重新提交已被拒绝的申请 * - * @param applicationId 申请ID + * @param applicationId 申请ID * @param applicationData 申请更新数据 * @return 是否成功 */ @@ -163,29 +163,15 @@ public interface IRsOverseasApplicationService { * 更新盖章文件信息 * * @param applicationId 申请ID - * @param filePath 文件路径 - * @param fileName 文件名 - * @param uploadTime 上传时间 - * @param uploadStatus 上传状态 - * @param ossId OSS文件ID + * @param filePath 文件路径 + * @param fileName 文件名 + * @param uploadTime 上传时间 + * @param uploadStatus 上传状态 + * @param ossId OSS文件ID * @return 是否成功 */ - Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName, - java.util.Date uploadTime, String uploadStatus, Long ossId); - - /** - * 更新党籍保留附件信息 - * - * @param applicationId 申请ID - * @param filePath 文件路径 - * @param fileName 文件名 - * @param uploadTime 上传时间 - * @param uploadStatus 上传状态 - * @param ossId OSS文件ID - * @return 是否成功 - */ - Boolean updatePartyRetentionFileInfo(Long applicationId, String filePath, String fileName, - java.util.Date uploadTime, String uploadStatus, Long ossId); + Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName, + java.util.Date uploadTime, String uploadStatus, Long ossId); /** * 校验出国登记前置条件 @@ -195,17 +181,4 @@ public interface IRsOverseasApplicationService { * @return 校验结果Map,包含 isValid(是否通过校验)和 message(提示信息) */ java.util.Map validateRegistrationPrerequisite(Long applicantId); - - /** - * 校验党籍保留前置条件 - * 检查是否为中共党员且出国天数>30天,如果是则需要检查党籍保留申请 - * - * @param applicantId 申请人ID - * @param politicalStatus 政治面貌 - * @param duration 出国天数 - * @return 校验结果Map,包含 isValid(是否通过校验)、needRetention(是否需要党籍保留)和 message(提示信息) - */ - java.util.Map validatePartyMembershipRetentionPrerequisite(Long applicantId, - String politicalStatus, - Integer duration); -} \ No newline at end of file +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java new file mode 100644 index 00000000..8db60212 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsOverseasApprovalMonitorService.java @@ -0,0 +1,47 @@ +package org.dromara.rs.service; + +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.rs.domain.vo.RsOverseasApprovalMonitorVo; +import org.dromara.rs.domain.query.RsOverseasApprovalMonitorQuery; + +import java.util.Map; + +/** + * 出国申请审批监控Service接口 + * + * @author Cursor AI + * @date 2025-10-21 + */ +public interface IRsOverseasApprovalMonitorService { + + /** + * 查询审批中的申请列表(待审核状态) + * + * @param query 查询条件 + * @return 审批中的申请列表 + */ + TableDataInfo queryPendingList(RsOverseasApprovalMonitorQuery query); + + /** + * 查询已完成审批的申请列表(已通过状态) + * + * @param query 查询条件 + * @return 已完成审批的申请列表 + */ + TableDataInfo queryCompletedList(RsOverseasApprovalMonitorQuery query); + + /** + * 查询申请详情(含审批流程信息) + * + * @param applicationId 申请ID + * @return 申请详情 + */ + RsOverseasApprovalMonitorVo queryDetailWithApprovalInfo(Long applicationId); + + /** + * 获取审批进度统计 + * + * @return 统计数据 + */ + Map getApprovalStatistics(); +} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsPartyMembershipRetentionService.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsPartyMembershipRetentionService.java deleted file mode 100644 index 4d75b173..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/IRsPartyMembershipRetentionService.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.dromara.rs.service; - -import org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo; -import org.dromara.rs.domain.bo.RsPartyMembershipRetentionBo; -import org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery; -import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.common.mybatis.core.page.PageQuery; - -import java.util.Collection; -import java.util.List; - -/** - * 出国党籍保留Service接口 - * - * @author weizzzz - * @date 2025-10-05 - */ -public interface IRsPartyMembershipRetentionService { - - /** - * 查询出国党籍保留 - */ - RsPartyMembershipRetentionVo queryById(Long retentionId); - - /** - * 查询出国党籍保留列表 - */ - TableDataInfo queryPageList(RsPartyMembershipRetentionQuery query, PageQuery pageQuery); - - /** - * 查询出国党籍保留列表 - */ - List queryList(RsPartyMembershipRetentionQuery query); - - /** - * 新增出国党籍保留 - */ - Boolean insertByBo(RsPartyMembershipRetentionBo bo); - - /** - * 修改出国党籍保留 - */ - Boolean updateByBo(RsPartyMembershipRetentionBo bo); - - /** - * 校验并批量删除出国党籍保留信息 - */ - Boolean deleteWithValidByIds(Collection ids, Boolean isValid); - - /** - * 保存并提交出国党籍保留 - */ - Boolean saveAndSubmit(RsPartyMembershipRetentionBo bo); - - /** - * 提交出国党籍保留 - */ - Boolean submit(Long retentionId); - - /** - * 撤回出国党籍保留 - */ - Boolean withdraw(Long retentionId); - - /** - * 审批出国党籍保留 - */ - Boolean approve(Long retentionId, String approvalOpinion, Boolean approved); - - /** - * 根据登记ID查询党籍保留信息 - */ - RsPartyMembershipRetentionVo queryByRegistrationId(Long registrationId); - - /** - * 根据申请ID查询党籍保留信息 - */ - RsPartyMembershipRetentionVo queryByApplicationId(Long applicationId); -} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java index b5d433c3..43a7d34d 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApplicationServiceImpl.java @@ -7,11 +7,21 @@ import java.util.List; import java.util.Map; import java.util.Calendar; +import cn.hutool.core.convert.Convert; +import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.ObjectUtil; +import org.dromara.common.core.domain.dto.StartProcessDTO; +import org.dromara.common.core.domain.event.ProcessEvent; +import org.dromara.common.core.domain.event.ProcessDeleteEvent; +import org.dromara.common.core.enums.BusinessStatusEnum; +import org.dromara.common.core.service.WorkflowService; 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.dromara.workflow.common.ConditionalOnEnable; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; @@ -21,11 +31,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.dromara.rs.mapper.RsOverseasApplicationMapper; import org.dromara.rs.mapper.RsOverseasApprovalMapper; import org.dromara.rs.mapper.RsOverseasRegistrationMapper; -import org.dromara.rs.mapper.RsPartyMembershipRetentionMapper; import org.dromara.rs.domain.RsOverseasApplication; import org.dromara.rs.domain.RsOverseasApproval; import org.dromara.rs.domain.RsOverseasRegistration; -import org.dromara.rs.domain.RsPartyMembershipRetention; import org.dromara.rs.domain.bo.RsOverseasApplicationBo; import org.dromara.rs.domain.vo.RsOverseasApplicationVo; import org.dromara.rs.domain.query.RsOverseasApplicationQuery; @@ -37,10 +45,12 @@ import org.dromara.rs.domain.vo.RsOverseasRecordVo; /** * 出国申请Service业务层处理 + * 集成Warm-Flow工作流支持5级审批流程 * * @author weizzzz * @date 2025-07-28 */ +@ConditionalOnEnable @Service public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationService { @@ -58,8 +68,8 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS @Autowired private RsOverseasRegistrationMapper registrationMapper; - @Autowired - private RsPartyMembershipRetentionMapper retentionMapper; + @Autowired(required = false) + private WorkflowService workflowService; /** * 查询出国申请分页列表 @@ -68,25 +78,25 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS public TableDataInfo queryPageList(RsOverseasApplicationQuery query) { // 使用自定义查询方法,包含状态文本等扩展字段 List 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 pageList; if (start > list.size()) { pageList = Arrays.asList(); } else { pageList = list.subList(start, end); } - + // 构建分页对象 Page page = new Page<>(pageNum, pageSize); page.setRecords(pageList); page.setTotal(list.size()); - + return TableDataInfo.build(page); } @@ -99,25 +109,25 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS // 显示用户的申请记录,排除已通过的申请(已通过的申请在出国记录中显示) // 查询条件:草稿、待审核、已拒绝、已取消、已完成等状态(不包括已通过状态) List 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 pageList; if (start > list.size()) { pageList = Arrays.asList(); } else { pageList = list.subList(start, end); } - + // 构建分页对象 Page page = new Page<>(pageNum, pageSize); page.setRecords(pageList); page.setTotal(list.size()); - + return TableDataInfo.build(page); } @@ -212,77 +222,126 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS update.setDuration(days); } validEntityBeforeSave(update); - + // 使用LambdaUpdateWrapper安全更新,只更新非null字段 LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(RsOverseasApplication::getApplicationId, update.getApplicationId()); - + // ==================== 基本信息 ==================== - if (update.getApplicantId() != null) updateWrapper.set(RsOverseasApplication::getApplicantId, update.getApplicantId()); - if (update.getApplicantName() != null) updateWrapper.set(RsOverseasApplication::getApplicantName, update.getApplicantName()); - if (update.getGender() != null) updateWrapper.set(RsOverseasApplication::getGender, update.getGender()); - if (update.getBirthDate() != null) updateWrapper.set(RsOverseasApplication::getBirthDate, update.getBirthDate()); - if (update.getPhoto() != null) updateWrapper.set(RsOverseasApplication::getPhoto, update.getPhoto()); - if (update.getIdNumber() != null) updateWrapper.set(RsOverseasApplication::getIdNumber, update.getIdNumber()); - if (update.getPoliticalStatus() != null) updateWrapper.set(RsOverseasApplication::getPoliticalStatus, update.getPoliticalStatus()); // 政治面貌 ⬅️ 关键修复 - if (update.getRegisteredAddress() != null) updateWrapper.set(RsOverseasApplication::getRegisteredAddress, update.getRegisteredAddress()); - + if (update.getApplicantId() != null) + updateWrapper.set(RsOverseasApplication::getApplicantId, update.getApplicantId()); + if (update.getApplicantName() != null) + updateWrapper.set(RsOverseasApplication::getApplicantName, update.getApplicantName()); + if (update.getGender() != null) + updateWrapper.set(RsOverseasApplication::getGender, update.getGender()); + if (update.getBirthDate() != null) + updateWrapper.set(RsOverseasApplication::getBirthDate, update.getBirthDate()); + if (update.getPhoto() != null) + updateWrapper.set(RsOverseasApplication::getPhoto, update.getPhoto()); + if (update.getIdNumber() != null) + updateWrapper.set(RsOverseasApplication::getIdNumber, update.getIdNumber()); + if (update.getPoliticalStatus() != null) + updateWrapper.set(RsOverseasApplication::getPoliticalStatus, update.getPoliticalStatus()); // 政治面貌 ⬅️ 关键修复 + if (update.getRegisteredAddress() != null) + updateWrapper.set(RsOverseasApplication::getRegisteredAddress, update.getRegisteredAddress()); + // ==================== 工作信息 ==================== - if (update.getDepartment() != null) updateWrapper.set(RsOverseasApplication::getDepartment, update.getDepartment()); - if (update.getPosition() != null) updateWrapper.set(RsOverseasApplication::getPosition, update.getPosition()); // 职务 - if (update.getProfessionalTitle() != null) updateWrapper.set(RsOverseasApplication::getProfessionalTitle, update.getProfessionalTitle()); // 职称 - if (update.getAdministrativeLevel() != null) updateWrapper.set(RsOverseasApplication::getAdministrativeLevel, update.getAdministrativeLevel()); - if (update.getPersonnelCategory() != null) updateWrapper.set(RsOverseasApplication::getPersonnelCategory, update.getPersonnelCategory()); - + if (update.getDepartment() != null) + updateWrapper.set(RsOverseasApplication::getDepartment, update.getDepartment()); + if (update.getPosition() != null) + updateWrapper.set(RsOverseasApplication::getPosition, update.getPosition()); // 职务 + if (update.getProfessionalTitle() != null) + updateWrapper.set(RsOverseasApplication::getProfessionalTitle, update.getProfessionalTitle()); // 职称 + if (update.getAdministrativeLevel() != null) + updateWrapper.set(RsOverseasApplication::getAdministrativeLevel, update.getAdministrativeLevel()); + if (update.getPersonnelCategory() != null) + updateWrapper.set(RsOverseasApplication::getPersonnelCategory, update.getPersonnelCategory()); + // ==================== 单位和联系信息 ==================== - if (update.getRegistrationUnit() != null) updateWrapper.set(RsOverseasApplication::getRegistrationUnit, update.getRegistrationUnit()); - if (update.getUnitLeaderSignature() != null) updateWrapper.set(RsOverseasApplication::getUnitLeaderSignature, update.getUnitLeaderSignature()); + if (update.getRegistrationUnit() != null) + updateWrapper.set(RsOverseasApplication::getRegistrationUnit, update.getRegistrationUnit()); + if (update.getUnitLeaderSignature() != null) + updateWrapper.set(RsOverseasApplication::getUnitLeaderSignature, update.getUnitLeaderSignature()); // unitContact 字段已删除 - // if (update.getUnitContact() != null) updateWrapper.set(RsOverseasApplication::getUnitContact, update.getUnitContact()); - if (update.getOfficePhone() != null) updateWrapper.set(RsOverseasApplication::getOfficePhone, update.getOfficePhone()); - if (update.getMobilePhone() != null) updateWrapper.set(RsOverseasApplication::getMobilePhone, update.getMobilePhone()); - if (update.getContactPhone() != null) updateWrapper.set(RsOverseasApplication::getContactPhone, update.getContactPhone()); - if (update.getEmergencyContact() != null) updateWrapper.set(RsOverseasApplication::getEmergencyContact, update.getEmergencyContact()); // 应急联系人 ⬅️ 关键修复 - + // if (update.getUnitContact() != null) + // updateWrapper.set(RsOverseasApplication::getUnitContact, + // update.getUnitContact()); + if (update.getOfficePhone() != null) + updateWrapper.set(RsOverseasApplication::getOfficePhone, update.getOfficePhone()); + if (update.getMobilePhone() != null) + updateWrapper.set(RsOverseasApplication::getMobilePhone, update.getMobilePhone()); + if (update.getContactPhone() != null) + updateWrapper.set(RsOverseasApplication::getContactPhone, update.getContactPhone()); + if (update.getEmergencyContact() != null) + updateWrapper.set(RsOverseasApplication::getEmergencyContact, update.getEmergencyContact()); // 应急联系人 ⬅️ + // 关键修复 + // ==================== 出国目的和行程 ==================== - if (update.getPurpose() != null) updateWrapper.set(RsOverseasApplication::getPurpose, update.getPurpose()); - if (update.getPurposeDetail() != null) updateWrapper.set(RsOverseasApplication::getPurposeDetail, update.getPurposeDetail()); - if (update.getDestination() != null) updateWrapper.set(RsOverseasApplication::getDestination, update.getDestination()); - if (update.getDestinationCountry() != null) updateWrapper.set(RsOverseasApplication::getDestinationCountry, update.getDestinationCountry()); - if (update.getDestinationCity() != null) updateWrapper.set(RsOverseasApplication::getDestinationCity, update.getDestinationCity()); - if (update.getStartDate() != null) updateWrapper.set(RsOverseasApplication::getStartDate, update.getStartDate()); - if (update.getEndDate() != null) updateWrapper.set(RsOverseasApplication::getEndDate, update.getEndDate()); - if (update.getDuration() != null) updateWrapper.set(RsOverseasApplication::getDuration, update.getDuration()); + if (update.getPurpose() != null) + updateWrapper.set(RsOverseasApplication::getPurpose, update.getPurpose()); + if (update.getPurposeDetail() != null) + updateWrapper.set(RsOverseasApplication::getPurposeDetail, update.getPurposeDetail()); + if (update.getDestination() != null) + updateWrapper.set(RsOverseasApplication::getDestination, update.getDestination()); + if (update.getDestinationCountry() != null) + updateWrapper.set(RsOverseasApplication::getDestinationCountry, update.getDestinationCountry()); + if (update.getDestinationCity() != null) + updateWrapper.set(RsOverseasApplication::getDestinationCity, update.getDestinationCity()); + if (update.getStartDate() != null) + updateWrapper.set(RsOverseasApplication::getStartDate, update.getStartDate()); + if (update.getEndDate() != null) + updateWrapper.set(RsOverseasApplication::getEndDate, update.getEndDate()); + if (update.getDuration() != null) + updateWrapper.set(RsOverseasApplication::getDuration, update.getDuration()); // budget 字段已删除 - // if (update.getBudget() != null) updateWrapper.set(RsOverseasApplication::getBudget, update.getBudget()); - + // if (update.getBudget() != null) + // updateWrapper.set(RsOverseasApplication::getBudget, update.getBudget()); + // ==================== 证件和文档 ==================== - if (update.getInvitation() != null) updateWrapper.set(RsOverseasApplication::getInvitation, update.getInvitation()); - if (update.getDocumentName() != null) updateWrapper.set(RsOverseasApplication::getDocumentName, update.getDocumentName()); - if (update.getDocumentNumber() != null) updateWrapper.set(RsOverseasApplication::getDocumentNumber, update.getDocumentNumber()); - if (update.getItinerary() != null) updateWrapper.set(RsOverseasApplication::getItinerary, update.getItinerary()); - if (update.getAccommodationPlan() != null) updateWrapper.set(RsOverseasApplication::getAccommodationPlan, update.getAccommodationPlan()); - if (update.getTransportationPlan() != null) updateWrapper.set(RsOverseasApplication::getTransportationPlan, update.getTransportationPlan()); - + if (update.getInvitation() != null) + updateWrapper.set(RsOverseasApplication::getInvitation, update.getInvitation()); + if (update.getDocumentName() != null) + updateWrapper.set(RsOverseasApplication::getDocumentName, update.getDocumentName()); + if (update.getDocumentNumber() != null) + updateWrapper.set(RsOverseasApplication::getDocumentNumber, update.getDocumentNumber()); + if (update.getItinerary() != null) + updateWrapper.set(RsOverseasApplication::getItinerary, update.getItinerary()); + if (update.getAccommodationPlan() != null) + updateWrapper.set(RsOverseasApplication::getAccommodationPlan, update.getAccommodationPlan()); + if (update.getTransportationPlan() != null) + updateWrapper.set(RsOverseasApplication::getTransportationPlan, update.getTransportationPlan()); + // ==================== 出境事由和备注 ==================== - if (update.getExitReason() != null) updateWrapper.set(RsOverseasApplication::getExitReason, update.getExitReason()); // 出境事由 ⬅️ 关键字段 - if (update.getVisitRelationship() != null) updateWrapper.set(RsOverseasApplication::getVisitRelationship, update.getVisitRelationship()); - if (update.getRemarks() != null) updateWrapper.set(RsOverseasApplication::getRemarks, update.getRemarks()); - if (update.getApplicantRemark() != null) updateWrapper.set(RsOverseasApplication::getApplicantRemark, update.getApplicantRemark()); - if (update.getAdminRemark() != null) updateWrapper.set(RsOverseasApplication::getAdminRemark, update.getAdminRemark()); // 管理员备注 ⬅️ 补充遗漏字段 - if (update.getSignature() != null) updateWrapper.set(RsOverseasApplication::getSignature, update.getSignature()); // 手写签名 ⬅️ 补充遗漏字段 - + if (update.getExitReason() != null) + updateWrapper.set(RsOverseasApplication::getExitReason, update.getExitReason()); // 出境事由 ⬅️ 关键字段 + if (update.getVisitRelationship() != null) + updateWrapper.set(RsOverseasApplication::getVisitRelationship, update.getVisitRelationship()); + if (update.getRemarks() != null) + updateWrapper.set(RsOverseasApplication::getRemarks, update.getRemarks()); + if (update.getApplicantRemark() != null) + updateWrapper.set(RsOverseasApplication::getApplicantRemark, update.getApplicantRemark()); + if (update.getAdminRemark() != null) + updateWrapper.set(RsOverseasApplication::getAdminRemark, update.getAdminRemark()); // 管理员备注 ⬅️ 补充遗漏字段 + if (update.getSignature() != null) + updateWrapper.set(RsOverseasApplication::getSignature, update.getSignature()); // 手写签名 ⬅️ 补充遗漏字段 + // ==================== 盖章文件相关字段 ==================== - if (update.getStampedRegistrationFilePath() != null) updateWrapper.set(RsOverseasApplication::getStampedRegistrationFilePath, update.getStampedRegistrationFilePath()); - if (update.getStampedRegistrationFileName() != null) updateWrapper.set(RsOverseasApplication::getStampedRegistrationFileName, update.getStampedRegistrationFileName()); - if (update.getStampedFileUploadTime() != null) updateWrapper.set(RsOverseasApplication::getStampedFileUploadTime, update.getStampedFileUploadTime()); - if (update.getStampedFileUploadStatus() != null) updateWrapper.set(RsOverseasApplication::getStampedFileUploadStatus, update.getStampedFileUploadStatus()); - + if (update.getStampedRegistrationFilePath() != null) + updateWrapper.set(RsOverseasApplication::getStampedRegistrationFilePath, + update.getStampedRegistrationFilePath()); + if (update.getStampedRegistrationFileName() != null) + updateWrapper.set(RsOverseasApplication::getStampedRegistrationFileName, + update.getStampedRegistrationFileName()); + if (update.getStampedFileUploadTime() != null) + updateWrapper.set(RsOverseasApplication::getStampedFileUploadTime, update.getStampedFileUploadTime()); + if (update.getStampedFileUploadStatus() != null) + updateWrapper.set(RsOverseasApplication::getStampedFileUploadStatus, update.getStampedFileUploadStatus()); + return baseMapper.update(null, updateWrapper) > 0; } /** - * 提交出国申请(更新状态为待审核) + * 提交出国申请(简化版 - 仅更新状态,工作流由前端直接启动) */ @Override @Transactional @@ -291,22 +350,25 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS if (application == null) { throw new RuntimeException("申请不存在"); } - // 只有草稿状态(status为null)的申请才能通过此方法提交 - if (application.getStatus() != null && !"".equals(application.getStatus())) { + + // 只有草稿状态的申请才能提交 + if (application.getApprovalStatus() != null && + !"draft".equals(application.getApprovalStatus()) && + !"".equals(application.getApprovalStatus())) { throw new RuntimeException("只有草稿状态的申请才能提交"); } - - // 设置状态为待审核 + + // 设置状态为待审核(工作流状态由工作流监听器管理) application.setStatus("0"); + application.setApprovalStatus(BusinessStatusEnum.WAITING.getStatus()); application.setApplyTime(new Date()); - - // 设置初始审批级别为1(单级审批制度) - application.setApprovalLevel("1"); - - // 设置当前审批环节描述(单级审批制度) - application.setCurrentApprover("待审批"); - - return baseMapper.updateById(application) > 0; + + boolean updated = baseMapper.updateById(application) > 0; + + log.info("✅ [出国申请提交] 申请状态更新成功, applicationId: {}, 申请人: {}", + applicationId, application.getApplicantName()); + + return updated; } /** @@ -322,10 +384,10 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS if ("4".equals(application.getStatus())) { throw new RuntimeException("已完成的申请不能取消"); } - + application.setStatus("3"); application.setApplicantRemark(reason); - + return baseMapper.updateById(application) > 0; } @@ -334,8 +396,8 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS */ @Override @Transactional - public Boolean approveApplication(Long applicationId, Long approverId, String approverName, - String approvalStatus, String approvalOpinion) { + public Boolean approveApplication(Long applicationId, Long approverId, String approverName, + String approvalStatus, String approvalOpinion) { RsOverseasApplication application = baseMapper.selectById(applicationId); if (application == null) { throw new RuntimeException("申请不存在"); @@ -353,7 +415,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS approval.setApprovalStatus(approvalStatus); approval.setApprovalOpinion(approvalOpinion); approval.setApprovalTime(new Date()); - + // 手动设置多租户和删除标志字段 if (approval.getTenantId() == null || approval.getTenantId().isEmpty()) { approval.setTenantId(LoginHelper.getTenantId()); @@ -361,7 +423,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS if (approval.getDeleted() == null || approval.getDeleted().isEmpty()) { approval.setDeleted("0"); } - + approvalMapper.insert(approval); // 更新申请状态 @@ -374,9 +436,9 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS application.setCurrentApprover("待指定"); } else { // 所有级别审批完成 - 先尝试创建出国记录,再更新申请状态 - log.info("开始最终审批处理,申请ID: {}, 当前状态: {}, 申请人: {}", - applicationId, application.getStatus(), application.getApplicantName()); - + log.info("开始最终审批处理,申请ID: {}, 当前状态: {}, 申请人: {}", + applicationId, application.getStatus(), application.getApplicantName()); + try { // 如果审批通过且为最终级别,先创建出国记录 log.info("调用创建出国记录服务,申请ID: {}", applicationId); @@ -386,7 +448,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS throw new RuntimeException("创建出国记录失败,请联系管理员"); } log.info("审批通过,成功创建出国记录,申请ID: {}", applicationId); - + // 记录创建成功后,才更新申请状态为通过 log.info("准备更新申请状态为通过,申请ID: {}", applicationId); application.setStatus("1"); @@ -405,7 +467,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS // 更新申请状态到数据库 boolean updateResult = baseMapper.updateById(application) > 0; - + return updateResult; } @@ -427,24 +489,25 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS @Transactional public int fixApprovedApplicationsWithoutRecords() { log.info("开始修复已审批通过但未创建记录的申请"); - + // 查询所有审批通过的申请 List approvedApplications = baseMapper.selectList( - new LambdaQueryWrapper() - .eq(RsOverseasApplication::getStatus, "1") // 已通过 + new LambdaQueryWrapper() + .eq(RsOverseasApplication::getStatus, "1") // 已通过 ); - + int fixedCount = 0; - + for (RsOverseasApplication application : approvedApplications) { try { // 检查是否已存在记录 - RsOverseasRecordVo existingRecord = overseasRecordService.queryByApplicationId(application.getApplicationId()); + RsOverseasRecordVo existingRecord = overseasRecordService + .queryByApplicationId(application.getApplicationId()); if (existingRecord == null) { // 不存在记录,尝试创建 - log.info("为已通过申请创建记录,申请ID: {}, 申请人: {}", - application.getApplicationId(), application.getApplicantName()); - + log.info("为已通过申请创建记录,申请ID: {}, 申请人: {}", + application.getApplicationId(), application.getApplicantName()); + boolean created = overseasRecordService.createRecordFromApplication(application.getApplicationId()); if (created) { fixedCount++; @@ -457,7 +520,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS log.error("修复申请记录失败,申请ID: {}, 错误: {}", application.getApplicationId(), e.getMessage(), e); } } - + log.info("修复完成,共修复 {} 条记录", fixedCount); return fixedCount; } @@ -465,7 +528,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS /** * 重新提交已被拒绝的申请 * - * @param applicationId 申请ID + * @param applicationId 申请ID * @param applicationData 申请更新数据 * @return 是否成功 */ @@ -477,14 +540,14 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS 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()); @@ -506,7 +569,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS } // budget 字段已删除 // if (applicationData.getBudget() != null) { - // existingApp.setBudget(applicationData.getBudget()); + // existingApp.setBudget(applicationData.getBudget()); // } if (applicationData.getItinerary() != null) { existingApp.setItinerary(applicationData.getItinerary()); @@ -514,24 +577,24 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS 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; } @@ -545,7 +608,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS throw new RuntimeException("开始日期不能晚于结束日期"); } } - + // 预算金额校验已删除(业务不需要预算金额字段) } @@ -554,17 +617,24 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS */ private LambdaQueryWrapper buildQueryWrapper(RsOverseasApplicationQuery query) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.like(StringUtils.isNotBlank(query.getApplicantName()), RsOverseasApplication::getApplicantName, query.getApplicantName()); - lqw.like(StringUtils.isNotBlank(query.getDepartment()), RsOverseasApplication::getDepartment, query.getDepartment()); + 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.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(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()); // budget 字段已删除,移除相关查询条件 - // lqw.ge(query.getBudgetMin() != null, RsOverseasApplication::getBudget, query.getBudgetMin()); - // lqw.le(query.getBudgetMax() != null, RsOverseasApplication::getBudget, query.getBudgetMax()); + // 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()); @@ -578,68 +648,74 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS private RsOverseasApplication buildEntityFromBo(RsOverseasApplicationBo bo) { RsOverseasApplication entity = new RsOverseasApplication(); entity.setApplicationId(bo.getApplicationId()); - + // ==================== 基本信息 ==================== entity.setApplicantId(bo.getApplicantId()); entity.setApplicantName(bo.getApplicantName()); - entity.setGender(bo.getGender()); // 性别 - entity.setBirthDate(bo.getBirthDate()); // 出生日期 - entity.setPhoto(bo.getPhoto()); // 照片 - entity.setIdNumber(bo.getIdNumber()); // 公民身份号码 - entity.setPoliticalStatus(bo.getPoliticalStatus()); // 政治面貌 ⬅️ 关键修复 - entity.setRegisteredAddress(bo.getRegisteredAddress()); // 户籍所在地 - + entity.setGender(bo.getGender()); // 性别 + entity.setBirthDate(bo.getBirthDate()); // 出生日期 + entity.setPhoto(bo.getPhoto()); // 照片 + entity.setIdNumber(bo.getIdNumber()); // 公民身份号码 + entity.setPoliticalStatus(bo.getPoliticalStatus()); // 政治面貌 ⬅️ 关键修复 + entity.setRegisteredAddress(bo.getRegisteredAddress()); // 户籍所在地 + // ==================== 工作信息 ==================== entity.setDepartment(bo.getDepartment()); - entity.setPosition(bo.getPosition()); // 职务 - entity.setProfessionalTitle(bo.getProfessionalTitle()); // 职称 - entity.setAdministrativeLevel(bo.getAdministrativeLevel()); // 行政职级 - entity.setPersonnelCategory(bo.getPersonnelCategory()); // 人员类别代码 - + entity.setPosition(bo.getPosition()); // 职务 + entity.setProfessionalTitle(bo.getProfessionalTitle()); // 职称 + entity.setAdministrativeLevel(bo.getAdministrativeLevel()); // 行政职级 + entity.setPersonnelCategory(bo.getPersonnelCategory()); // 人员类别代码 + // ==================== 单位和联系信息 ==================== - entity.setRegistrationUnit(bo.getRegistrationUnit()); // 登记备案责任单位 - entity.setUnitLeaderSignature(bo.getUnitLeaderSignature()); // 单位负责人签名 + entity.setRegistrationUnit(bo.getRegistrationUnit()); // 登记备案责任单位 + entity.setUnitLeaderSignature(bo.getUnitLeaderSignature()); // 单位负责人签名 // unitContact 字段已删除 - // entity.setUnitContact(bo.getUnitContact()); // 单位联系人 - entity.setOfficePhone(bo.getOfficePhone()); // 办公电话 - entity.setMobilePhone(bo.getMobilePhone()); // 移动电话 - entity.setContactPhone(bo.getContactPhone()); // 联系电话(兼容字段) - entity.setEmergencyContact(bo.getEmergencyContact()); // 应急联系人及电话 ⬅️ 关键修复 - + // entity.setUnitContact(bo.getUnitContact()); // 单位联系人 + entity.setOfficePhone(bo.getOfficePhone()); // 办公电话 + entity.setMobilePhone(bo.getMobilePhone()); // 移动电话 + entity.setContactPhone(bo.getContactPhone()); // 联系电话(兼容字段) + entity.setEmergencyContact(bo.getEmergencyContact()); // 应急联系人及电话 ⬅️ 关键修复 + // ==================== 出国目的和行程 ==================== entity.setPurpose(bo.getPurpose()); entity.setPurposeDetail(bo.getPurposeDetail()); entity.setDestination(bo.getDestination()); - entity.setDestinationCountry(bo.getDestinationCountry()); // 前往国家 + entity.setDestinationCountry(bo.getDestinationCountry()); // 前往国家 entity.setDestinationCity(bo.getDestinationCity()); entity.setStartDate(bo.getStartDate()); entity.setEndDate(bo.getEndDate()); entity.setDuration(bo.getDuration()); // budget 字段已删除 // entity.setBudget(bo.getBudget()); - + // ==================== 证件和文档 ==================== entity.setInvitation(bo.getInvitation()); - entity.setDocumentName(bo.getDocumentName()); // 申领证件名称 - entity.setDocumentNumber(bo.getDocumentNumber()); // 证件号码 + entity.setDocumentName(bo.getDocumentName()); // 申领证件名称 + entity.setDocumentNumber(bo.getDocumentNumber()); // 证件号码 entity.setItinerary(bo.getItinerary()); entity.setAccommodationPlan(bo.getAccommodationPlan()); entity.setTransportationPlan(bo.getTransportationPlan()); - + // ==================== 出境事由和备注 ==================== - entity.setExitReason(bo.getExitReason()); // 出境事由 ⬅️ 关键字段 - entity.setVisitRelationship(bo.getVisitRelationship()); // 探望对象关系 - entity.setRemarks(bo.getRemarks()); // 备注 - entity.setApplicantRemark(bo.getApplicantRemark()); // 申请人备注 - entity.setAdminRemark(bo.getAdminRemark()); // 管理员备注 ⬅️ 补充遗漏字段 - entity.setSignature(bo.getSignature()); // 手写签名 ⬅️ 补充遗漏字段 - + entity.setExitReason(bo.getExitReason()); // 出境事由 ⬅️ 关键字段 + entity.setVisitRelationship(bo.getVisitRelationship()); // 探望对象关系 + entity.setRemarks(bo.getRemarks()); // 备注 + entity.setApplicantRemark(bo.getApplicantRemark()); // 申请人备注 + entity.setAdminRemark(bo.getAdminRemark()); // 管理员备注 ⬅️ 补充遗漏字段 + entity.setSignature(bo.getSignature()); // 手写签名 ⬅️ 补充遗漏字段 + // ==================== 盖章文件相关字段 ==================== entity.setStampedRegistrationFilePath(bo.getStampedRegistrationFilePath()); entity.setStampedRegistrationFileName(bo.getStampedRegistrationFileName()); entity.setStampedFileUploadTime(bo.getStampedFileUploadTime()); entity.setStampedFileUploadStatus(bo.getStampedFileUploadStatus()); - + + // ==================== 租户信息 ==================== + // 🔴 关键修复:设置租户ID(如果Bo中有值则使用Bo的值,否则依赖MyBatis-Plus自动填充) + if (bo.getTenantId() != null) { + entity.setTenantId(bo.getTenantId()); + } + return entity; } @@ -674,35 +750,16 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS */ @Override @Transactional - public Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName, - java.util.Date uploadTime, String uploadStatus, Long ossId) { - return baseMapper.update(null, - new LambdaUpdateWrapper() - .eq(RsOverseasApplication::getApplicationId, applicationId) - .set(RsOverseasApplication::getStampedRegistrationFilePath, filePath) - .set(RsOverseasApplication::getStampedRegistrationFileName, fileName) - .set(RsOverseasApplication::getStampedFileUploadTime, uploadTime) - .set(RsOverseasApplication::getStampedFileUploadStatus, uploadStatus) - .set(RsOverseasApplication::getStampedFileOssId, ossId) - ) > 0; - } - - /** - * 更新党籍保留附件信息 - */ - @Override - @Transactional - public Boolean updatePartyRetentionFileInfo(Long applicationId, String filePath, String fileName, - java.util.Date uploadTime, String uploadStatus, Long ossId) { - return baseMapper.update(null, - new LambdaUpdateWrapper() - .eq(RsOverseasApplication::getApplicationId, applicationId) - .set(RsOverseasApplication::getPartyRetentionFilePath, filePath) - .set(RsOverseasApplication::getPartyRetentionFileName, fileName) - .set(RsOverseasApplication::getPartyRetentionUploadTime, uploadTime) - .set(RsOverseasApplication::getPartyRetentionUploadStatus, uploadStatus) - .set(RsOverseasApplication::getPartyRetentionFileOssId, ossId) - ) > 0; + public Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName, + java.util.Date uploadTime, String uploadStatus, Long ossId) { + return baseMapper.update(null, + new LambdaUpdateWrapper() + .eq(RsOverseasApplication::getApplicationId, applicationId) + .set(RsOverseasApplication::getStampedRegistrationFilePath, filePath) + .set(RsOverseasApplication::getStampedRegistrationFileName, fileName) + .set(RsOverseasApplication::getStampedFileUploadTime, uploadTime) + .set(RsOverseasApplication::getStampedFileUploadStatus, uploadStatus) + .set(RsOverseasApplication::getStampedFileOssId, ossId)) > 0; } /** @@ -712,7 +769,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS @Override public Map validateRegistrationPrerequisite(Long applicantId) { Map result = new HashMap<>(); - + // 计算当前自然年的开始日期(1月1日 00:00:00) Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.MONTH, Calendar.JANUARY); @@ -722,18 +779,18 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); Date currentYearStart = calendar.getTime(); - + int currentYear = calendar.get(Calendar.YEAR); - + // 查询当前自然年内的出国登记记录(使用createBy字段) LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); queryWrapper.eq(RsOverseasRegistration::getCreateBy, applicantId) - .ge(RsOverseasRegistration::getCreateTime, currentYearStart) - .orderByDesc(RsOverseasRegistration::getCreateTime) - .last("LIMIT 1"); - + .ge(RsOverseasRegistration::getCreateTime, currentYearStart) + .orderByDesc(RsOverseasRegistration::getCreateTime) + .last("LIMIT 1"); + RsOverseasRegistration latestRegistration = registrationMapper.selectOne(queryWrapper); - + if (latestRegistration == null) { // 当前自然年内没有登记记录 result.put("isValid", false); @@ -741,7 +798,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS log.warn("用户{}在{}年内没有出国登记记录", applicantId, currentYear); return result; } - + String status = latestRegistration.getStatus(); if ("2".equals(status)) { // 状态为已通过(审批通过)- 出国登记状态2表示已通过 @@ -765,83 +822,138 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS result.put("message", "出国登记状态异常,请先提交出国登记申请"); log.warn("用户{}的出国登记状态异常,status: {}", applicantId, status); } - + return result; } /** - * 校验党籍保留前置条件 - * 检查是否为中共党员且出国天数>30天,如果是则需要检查当前自然年内的党籍保留申请 + * 工作流总体流程监听器 + * 监听工作流状态变化(草稿、撤销、退回、作废、终止、已完成等) + * + * @param processEvent 工作流事件 */ - @Override - public Map validatePartyMembershipRetentionPrerequisite(Long applicantId, - String politicalStatus, - Integer duration) { - Map result = new HashMap<>(); - - // 判断是否需要党籍保留申请 - boolean needRetention = "中共党员".equals(politicalStatus) && duration != null && duration > 30; - result.put("needRetention", needRetention); - - if (!needRetention) { - // 不需要党籍保留申请 - result.put("isValid", true); - result.put("message", "无需党籍保留申请"); - return result; + @EventListener(condition = "#processEvent.flowCode=='overseas-application'") + public void processHandler(ProcessEvent processEvent) { + log.info("🔄 [工作流监听] 出国申请工作流事件: {}", processEvent.toString()); + + Long applicationId = Long.valueOf(processEvent.getBusinessId()); + RsOverseasApplication application = baseMapper.selectById(applicationId); + + if (application == null) { + log.error("❌ [工作流监听] 申请不存在, applicationId: {}", applicationId); + return; } - - // 需要党籍保留申请,检查当前自然年内是否有党籍保留申请(使用createBy字段) - Calendar calendar = Calendar.getInstance(); - calendar.set(Calendar.MONTH, Calendar.JANUARY); - calendar.set(Calendar.DAY_OF_MONTH, 1); - calendar.set(Calendar.HOUR_OF_DAY, 0); - calendar.set(Calendar.MINUTE, 0); - calendar.set(Calendar.SECOND, 0); - calendar.set(Calendar.MILLISECOND, 0); - Date currentYearStart = calendar.getTime(); - - int currentYear = calendar.get(Calendar.YEAR); - - LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); - queryWrapper.eq(RsPartyMembershipRetention::getCreateBy, applicantId) - .ge(RsPartyMembershipRetention::getCreateTime, currentYearStart) - .orderByDesc(RsPartyMembershipRetention::getCreateTime) - .last("LIMIT 1"); - - RsPartyMembershipRetention latestRetention = retentionMapper.selectOne(queryWrapper); - - if (latestRetention == null) { - // 当前自然年内没有党籍保留申请 - result.put("isValid", false); - result.put("message", "您是中共党员且出国天数超过30天,请先提交出国党籍保留申请,审核通过后再提交出国申请"); - log.warn("用户{}需要党籍保留但在{}年内未提交申请", applicantId, currentYear); - return result; + + // 更新工作流状态 + String workflowStatus = processEvent.getStatus(); + application.setApprovalStatus(workflowStatus); + log.info("📝 [工作流监听] 更新审批状态: {} → {}", applicationId, workflowStatus); + + // 根据不同的工作流状态更新业务状态 + if (BusinessStatusEnum.FINISH.getStatus().equals(workflowStatus)) { + // 工作流完成 - 所有5级审批通过 + log.info("🎉 [工作流监听] 所有审批级别完成,申请ID: {}, 开始创建出国记录", applicationId); + + try { + // 创建出国记录 + Boolean recordCreated = overseasRecordService.createRecordFromApplication(applicationId); + if (recordCreated) { + application.setStatus("1"); // 已通过 + application.setApprovalTime(new Date()); + application.setCurrentApprover("审批完成"); + log.info("✅ [工作流监听] 出国记录创建成功, applicationId: {}", applicationId); + } else { + log.error("❌ [工作流监听] 出国记录创建失败, applicationId: {}", applicationId); + application.setAdminRemark("审批完成但创建出国记录失败,请联系管理员"); + } + } catch (Exception e) { + log.error("❌ [工作流监听] 创建出国记录异常: ", e); + application.setAdminRemark("审批完成但创建出国记录异常: " + e.getMessage()); + } + + } else if (BusinessStatusEnum.BACK.getStatus().equals(workflowStatus)) { + // 工作流退回 - 审批拒绝 + application.setStatus("2"); // 已拒绝 + application.setCurrentApprover("审批拒绝"); + log.info("⚠️ [工作流监听] 审批被拒绝, applicationId: {}", applicationId); + + } else if (BusinessStatusEnum.CANCEL.getStatus().equals(workflowStatus)) { + // 工作流取消 + application.setStatus("3"); // 已取消 + application.setCurrentApprover("已取消"); + log.info("⚠️ [工作流监听] 申请已取消, applicationId: {}", applicationId); + + } else if (BusinessStatusEnum.WAITING.getStatus().equals(workflowStatus)) { + // 工作流进行中 + application.setStatus("0"); // 待审核 + log.info("🔄 [工作流监听] 审批进行中, applicationId: {}", applicationId); + + } else if (BusinessStatusEnum.DRAFT.getStatus().equals(workflowStatus)) { + // 工作流草稿 + application.setStatus(null); // 草稿状态 + application.setCurrentApprover("草稿"); + log.info("📝 [工作流监听] 申请状态:草稿, applicationId: {}", applicationId); } - - String status = latestRetention.getStatus(); - if ("2".equals(status)) { - // 状态为已通过(审批通过) - result.put("isValid", true); - result.put("message", "党籍保留申请已通过审核"); - result.put("retentionId", latestRetention.getRetentionId()); - log.info("用户{}的党籍保留申请已通过审核,retentionId: {}", applicantId, latestRetention.getRetentionId()); - } else if ("0".equals(status) || "1".equals(status)) { - // 状态为草稿或待审核 - result.put("isValid", false); - result.put("message", "党籍保留申请审核中,请审核通过后再提交出国申请"); - log.warn("用户{}的党籍保留申请还在审核中,status: {}", applicantId, status); - } else if ("3".equals(status)) { - // 状态为已拒绝 - result.put("isValid", false); - result.put("message", "党籍保留申请已被拒绝,请重新提交党籍保留申请并审核通过后再提交出国申请"); - log.warn("用户{}的党籍保留申请已被拒绝,status: {}", applicantId, status); - } else { - // 其他状态 - result.put("isValid", false); - result.put("message", "党籍保留申请状态异常,请重新提交党籍保留申请"); - log.warn("用户{}的党籍保留申请状态异常,status: {}", applicantId, status); + + // 处理审批意见和审批人信息 + Map params = processEvent.getParams(); + if (MapUtil.isNotEmpty(params)) { + String handler = Convert.toStr(params.get("handler")); + String message = Convert.toStr(params.get("message")); + log.info("👤 [工作流监听] 审批人: {}, 审批意见: {}", handler, message); + + // 将审批意见追加到管理员备注 + if (StringUtils.isNotEmpty(message)) { + String existingRemark = application.getAdminRemark(); + if (StringUtils.isNotEmpty(existingRemark)) { + application.setAdminRemark(existingRemark + "\n" + message); + } else { + application.setAdminRemark(message); + } + } } - - return result; + + // 如果是提交状态,设置为等待审批 + if (processEvent.getSubmit()) { + application.setApprovalStatus(BusinessStatusEnum.WAITING.getStatus()); + application.setStatus("0"); + application.setCurrentApprover("部门负责人待审批"); + log.info("📮 [工作流监听] 申请已提交,等待审批, applicationId: {}", applicationId); + } + + baseMapper.updateById(application); + log.info("✅ [工作流监听] 申请状态更新完成, applicationId: {}", applicationId); } -} \ No newline at end of file + + /** + * 监听删除流程事件 + * 当工作流被删除时同步更新业务状态 + * + * @param processDeleteEvent 删除事件 + */ + @EventListener(condition = "#processDeleteEvent.flowCode=='overseas-application'") + public void processDeleteHandler(ProcessDeleteEvent processDeleteEvent) { + log.info("🗑️ [工作流监听] 监听删除流程事件: {}", processDeleteEvent.toString()); + + Long applicationId = Long.valueOf(processDeleteEvent.getBusinessId()); + RsOverseasApplication application = baseMapper.selectById(applicationId); + + if (ObjectUtil.isNull(application)) { + log.warn("⚠️ [工作流监听] 申请不存在,无需处理, applicationId: {}", applicationId); + return; + } + + // 更新为取消状态 + application.setApprovalStatus("cancel"); + application.setStatus("3"); // 已取消 + application.setCurrentApprover("已取消"); + baseMapper.updateById(application); + + log.info("✅ [工作流监听] 申请状态已更新为取消, applicationId: {}", applicationId); + } + + // 删除了复杂的审批记录创建方法 - 使用标准工作流管理审批记录 + + // 删除了复杂的部门负责人查找方法 - 工作流引擎会自动处理审批人分配 + +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java new file mode 100644 index 00000000..635cc771 --- /dev/null +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalMonitorServiceImpl.java @@ -0,0 +1,489 @@ +package org.dromara.rs.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.dromara.common.core.utils.StringUtils; +import org.dromara.common.mybatis.core.page.TableDataInfo; +import org.dromara.common.satoken.utils.LoginHelper; +import org.dromara.rs.domain.RsOverseasApplication; +import org.dromara.rs.domain.RsOverseasApproval; +import org.dromara.rs.domain.vo.RsOverseasApprovalMonitorVo; +import org.dromara.rs.domain.vo.RsOverseasApprovalVo; +import org.dromara.rs.domain.vo.RsOverseasRecordVo; +import org.dromara.rs.domain.query.RsOverseasApprovalMonitorQuery; +import org.dromara.rs.mapper.RsOverseasApplicationMapper; +import org.dromara.rs.mapper.RsOverseasApprovalMapper; +import org.dromara.rs.mapper.RsOverseasRecordMapper; +import org.dromara.rs.service.IRsOverseasApprovalMonitorService; +import org.dromara.rs.service.IRsOverseasApprovalService; +import org.springframework.stereotype.Service; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.stream.Collectors; + +/** + * 出国申请审批监控Service实现 + * + * @author Cursor AI + * @date 2025-10-21 + */ +@Slf4j +@RequiredArgsConstructor +@Service +public class RsOverseasApprovalMonitorServiceImpl implements IRsOverseasApprovalMonitorService { + + private final RsOverseasApplicationMapper applicationMapper; + private final RsOverseasRecordMapper recordMapper; + private final RsOverseasApprovalMapper approvalMapper; + private final IRsOverseasApprovalService approvalService; + + /** + * 查询审批中的申请列表(待审核状态) + */ + @Override + public TableDataInfo queryPendingList(RsOverseasApprovalMonitorQuery query) { + log.info("查询审批中的申请列表,查询条件:{}", query); + + // 构建查询条件 + LambdaQueryWrapper wrapper = buildQueryWrapper(query); + wrapper.eq(RsOverseasApplication::getStatus, "0"); // 只查询待审核状态 + + // 分页查询 + Page page = applicationMapper.selectPage( + new Page<>(query.getPageNum(), query.getPageSize()), + wrapper); + + // 转换为VO并填充审批信息 + List voList = page.getRecords().stream() + .map(this::convertToMonitorVo) + .collect(Collectors.toList()); + + // 构建分页结果 + Page resultPage = new Page<>(query.getPageNum(), query.getPageSize()); + resultPage.setRecords(voList); + resultPage.setTotal(page.getTotal()); + + return TableDataInfo.build(resultPage); + } + + /** + * 查询已完成审批的申请列表(已通过状态) + */ + @Override + public TableDataInfo queryCompletedList(RsOverseasApprovalMonitorQuery query) { + log.info("查询已完成审批的申请列表,查询条件:{}", query); + + // 构建查询条件 + LambdaQueryWrapper wrapper = buildQueryWrapper(query); + wrapper.eq(RsOverseasApplication::getStatus, "1"); // 只查询已通过状态 + + // 分页查询 + Page page = applicationMapper.selectPage( + new Page<>(query.getPageNum(), query.getPageSize()), + wrapper); + + // 转换为VO并填充审批信息 + List voList = page.getRecords().stream() + .map(this::convertToMonitorVo) + .collect(Collectors.toList()); + + // 构建分页结果 + Page resultPage = new Page<>(query.getPageNum(), query.getPageSize()); + resultPage.setRecords(voList); + resultPage.setTotal(page.getTotal()); + + return TableDataInfo.build(resultPage); + } + + /** + * 查询申请详情(含审批流程信息) + */ + @Override + public RsOverseasApprovalMonitorVo queryDetailWithApprovalInfo(Long applicationId) { + log.info("查询申请详情,申请ID:{}", applicationId); + + // 查询申请基本信息 + RsOverseasApplication application = applicationMapper.selectById(applicationId); + if (application == null) { + log.warn("未找到申请信息,申请ID:{}", applicationId); + return null; + } + + // 转换为VO并填充审批信息 + RsOverseasApprovalMonitorVo vo = convertToMonitorVo(application); + + // 查询审批历史记录 + List approvalHistory = approvalService.queryByApplicationId(applicationId); + vo.setApprovalHistory(approvalHistory); + + return vo; + } + + /** + * 获取审批进度统计 + */ + @Override + public Map getApprovalStatistics() { + log.info("获取审批进度统计"); + + Map statistics = new HashMap<>(); + String tenantId = LoginHelper.getTenantId(); + + // 1. 待审批总数 + Long pendingCount = applicationMapper.selectCount( + Wrappers.lambdaQuery() + .eq(RsOverseasApplication::getStatus, "0") + .eq(RsOverseasApplication::getTenantId, tenantId)); + statistics.put("pendingCount", pendingCount); + + // 2. 各审批级别的待审批数量 + Map pendingByLevel = new HashMap<>(); + pendingByLevel.put("level1", countByStatusAndLevel("0", "1", tenantId)); // 部门主管 + pendingByLevel.put("level2", countByStatusAndLevel("0", "2", tenantId)); // 分管领导 + pendingByLevel.put("level3", countByStatusAndLevel("0", "3", tenantId)); // 校领导 + statistics.put("pendingByLevel", pendingByLevel); + + // 3. 已完成审批总数 + Long completedCount = applicationMapper.selectCount( + Wrappers.lambdaQuery() + .eq(RsOverseasApplication::getStatus, "1") + .eq(RsOverseasApplication::getTenantId, tenantId)); + statistics.put("completedCount", completedCount); + + // 4. 今日新增申请数 + LocalDate today = LocalDate.now(); + Date startOfToday = Date.from(today.atStartOfDay(ZoneId.systemDefault()).toInstant()); + Long todayCount = applicationMapper.selectCount( + Wrappers.lambdaQuery() + .ge(RsOverseasApplication::getApplyTime, startOfToday) + .eq(RsOverseasApplication::getTenantId, tenantId)); + statistics.put("todayCount", todayCount); + + // 5. 已拒绝总数 + Long rejectedCount = applicationMapper.selectCount( + Wrappers.lambdaQuery() + .eq(RsOverseasApplication::getStatus, "2") + .eq(RsOverseasApplication::getTenantId, tenantId)); + statistics.put("rejectedCount", rejectedCount); + + log.info("审批进度统计结果:{}", statistics); + return statistics; + } + + // ==================== 私有辅助方法 ==================== + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(RsOverseasApprovalMonitorQuery query) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + + // 申请人姓名(模糊查询) + wrapper.like(StringUtils.isNotBlank(query.getApplicantName()), + RsOverseasApplication::getApplicantName, query.getApplicantName()); + + // 所属部门(模糊查询) + wrapper.like(StringUtils.isNotBlank(query.getDepartment()), + RsOverseasApplication::getDepartment, query.getDepartment()); + + // 目的地国家(模糊查询) + wrapper.like(StringUtils.isNotBlank(query.getDestination()), + RsOverseasApplication::getDestination, query.getDestination()); + + // 审批级别 + wrapper.eq(StringUtils.isNotBlank(query.getApprovalLevel()), + RsOverseasApplication::getApprovalLevel, query.getApprovalLevel()); + + // 当前审批人(模糊查询) + wrapper.like(StringUtils.isNotBlank(query.getCurrentApprover()), + RsOverseasApplication::getCurrentApprover, query.getCurrentApprover()); + + // 申请时间范围 + wrapper.ge(query.getApplyTimeStart() != null, + RsOverseasApplication::getApplyTime, query.getApplyTimeStart()); + wrapper.le(query.getApplyTimeEnd() != null, + RsOverseasApplication::getApplyTime, query.getApplyTimeEnd()); + + // 租户ID + String tenantId = StringUtils.isNotBlank(query.getTenantId()) + ? query.getTenantId() + : LoginHelper.getTenantId(); + wrapper.eq(RsOverseasApplication::getTenantId, tenantId); + + // 按申请时间倒序排列 + wrapper.orderByDesc(RsOverseasApplication::getApplyTime); + + return wrapper; + } + + /** + * 将Application转换为MonitorVo + */ + private RsOverseasApprovalMonitorVo convertToMonitorVo(RsOverseasApplication application) { + RsOverseasApprovalMonitorVo vo = new RsOverseasApprovalMonitorVo(); + + // 基本信息 + vo.setApplicationId(application.getApplicationId()); + + // 查询关联的出国记录ID(如果已完成审批) + if ("1".equals(application.getStatus()) || "4".equals(application.getStatus())) { + RsOverseasRecordVo record = recordMapper + .selectOverseasRecordByApplicationId(application.getApplicationId()); + if (record != null) { + vo.setRecordId(record.getRecordId()); + } + } + + vo.setSerialNumber(application.getSerialNumber()); + vo.setApplicantId(application.getApplicantId()); + vo.setApplicantName(application.getApplicantName()); + vo.setGender(application.getGender()); + vo.setGenderText(translateGender(application.getGender())); + vo.setBirthDate(application.getBirthDate()); + vo.setPoliticalStatus(application.getPoliticalStatus()); + vo.setPoliticalStatusText(translatePoliticalStatus(application.getPoliticalStatus())); + vo.setDepartment(application.getDepartment()); + vo.setProfessionalTitle(application.getProfessionalTitle()); + vo.setPosition(application.getPosition()); + + // 证件信息 + vo.setDocumentName(application.getDocumentName()); + vo.setDocumentNumber(application.getDocumentNumber()); + + // 出国信息 + vo.setPurpose(application.getPurpose()); + vo.setDestination(application.getDestination()); + vo.setDestinationCountry(application.getDestinationCountry()); + vo.setDestinationCity(application.getDestinationCity()); + vo.setExitReason(application.getExitReason()); + vo.setVisitRelationship(application.getVisitRelationship()); + vo.setItinerary(application.getItinerary()); + vo.setStartDate(application.getStartDate()); + vo.setEndDate(application.getEndDate()); + vo.setDuration(application.getDuration()); + + // 联系信息 + vo.setMobilePhone(application.getMobilePhone()); + vo.setContactPhone(application.getContactPhone()); + vo.setEmergencyContact(application.getEmergencyContact()); + + // 审批状态信息 + vo.setStatus(application.getStatus()); + vo.setStatusText(getStatusText(application.getStatus())); + vo.setCurrentApprover(application.getCurrentApprover()); + vo.setApprovalLevel(application.getApprovalLevel()); + vo.setApprovalLevelText(getApprovalLevelText(application.getApprovalLevel())); + vo.setApplyTime(application.getApplyTime()); + vo.setApprovalTime(application.getApprovalTime()); + + // 审批进度信息 + fillApprovalProgress(vo, application); + + // 审批耗时 + if (application.getApplyTime() != null) { + LocalDate applyDate = application.getApplyTime().toInstant() + .atZone(ZoneId.systemDefault()).toLocalDate(); + LocalDate now = LocalDate.now(); + vo.setApprovalDays((int) ChronoUnit.DAYS.between(applyDate, now)); + + // 判断是否超时(超过5天视为超时,可根据业务规则调整) + vo.setIsOverdue(vo.getApprovalDays() > 5 && "0".equals(application.getStatus())); + } + + // 其他信息 + vo.setRemarks(application.getRemarks()); + vo.setAdminRemark(application.getAdminRemark()); + vo.setApplicantRemark(application.getApplicantRemark()); + vo.setTenantId(application.getTenantId()); + vo.setCreateTime(application.getCreateTime()); + vo.setUpdateTime(application.getUpdateTime()); + + return vo; + } + + /** + * 填充审批进度信息 + */ + private void fillApprovalProgress(RsOverseasApprovalMonitorVo vo, RsOverseasApplication application) { + // 查询该申请的所有审批记录 + List approvals = approvalMapper.selectList( + Wrappers.lambdaQuery() + .eq(RsOverseasApproval::getApplicationId, application.getApplicationId()) + .orderByAsc(RsOverseasApproval::getApprovalLevel) + .orderByDesc(RsOverseasApproval::getApprovalTime)); + + if (approvals != null && !approvals.isEmpty()) { + // 已审批级别数(状态为1-通过的记录) + long approvedCount = approvals.stream() + .filter(a -> "1".equals(a.getApprovalStatus())) + .map(RsOverseasApproval::getApprovalLevel) + .distinct() + .count(); + vo.setApprovedLevels((int) approvedCount); + + // 总审批级别数(假设最多3级:部门主管、分管领导、校领导) + vo.setTotalLevels(3); + + // 审批进度文本 + vo.setApprovalProgress(approvedCount + "/" + vo.getTotalLevels()); + + // 最近一次审批信息 + RsOverseasApproval latestApproval = approvals.get(0); + vo.setLatestApproverName(latestApproval.getApproverName()); + vo.setLatestApprovalOpinion(latestApproval.getApprovalOpinion()); + vo.setLatestApprovalTime(latestApproval.getApprovalTime()); + } else { + // 未开始审批 + vo.setApprovedLevels(0); + vo.setTotalLevels(3); + vo.setApprovalProgress("0/3"); + } + } + + /** + * 获取状态文本 + */ + private String getStatusText(String status) { + if (status == null) { + return "未知"; + } + switch (status) { + case "0": + return "待审核"; + case "1": + return "已通过"; + case "2": + return "已拒绝"; + case "3": + return "已取消"; + case "4": + return "已完成"; + default: + return "未知"; + } + } + + /** + * 获取审批级别文本 + */ + private String getApprovalLevelText(String level) { + if (level == null) { + return "未开始"; + } + switch (level) { + case "1": + return "部门主管审批"; + case "2": + return "分管领导审批"; + case "3": + return "校领导审批"; + default: + return "未知"; + } + } + + /** + * 按状态和审批级别统计数量 + */ + private Long countByStatusAndLevel(String status, String level, String tenantId) { + return applicationMapper.selectCount( + Wrappers.lambdaQuery() + .eq(RsOverseasApplication::getStatus, status) + .eq(RsOverseasApplication::getApprovalLevel, level) + .eq(RsOverseasApplication::getTenantId, tenantId)); + } + + /** + * 翻译性别代码为文本 + * + * @param gender 性别代码:0-男,1-女 + * @return 性别文本 + */ + private String translateGender(String gender) { + if (gender == null) { + return ""; + } + switch (gender) { + case "0": + return "男"; + case "1": + return "女"; + default: + return ""; + } + } + + /** + * 翻译政治面貌代码为文本 + * + * @param politicalStatus 政治面貌代码 + * @return 政治面貌文本 + */ + private String translatePoliticalStatus(String politicalStatus) { + if (politicalStatus == null || politicalStatus.isEmpty()) { + return ""; + } + + // 支持多种编码格式的政治面貌翻译 + switch (politicalStatus) { + // 两位数字格式(01-13) + case "01": + case "1": + case "ccp_member": + return "中共党员"; + case "02": + case "2": + case "ccp_probationary": + return "中共预备党员"; + case "03": + case "3": + case "league_member": + return "共青团员"; + case "04": + case "4": + case "revolutionary": + return "民革党员"; + case "05": + case "5": + case "democratic_league": + return "民盟盟员"; + case "06": + case "6": + case "democratic_construction": + return "民建会员"; + case "07": + case "7": + case "democratic_progress": + return "民进会员"; + case "08": + case "8": + case "peasants_workers": + return "农工党党员"; + case "09": + case "9": + case "zhi_gong": + return "致公党党员"; + case "10": + case "jiusan": + return "九三学社社员"; + case "11": + case "taiwan_league": + return "台盟盟员"; + case "12": + case "non_partisan": + return "无党派人士"; + case "13": + case "masses": + return "群众"; + default: + // 如果是未知编码,返回原值 + return politicalStatus; + } + } +} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java index 78a04600..ea0f30b0 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsOverseasApprovalServiceImpl.java @@ -45,24 +45,26 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService */ @Override public TableDataInfo queryPageList(RsOverseasApprovalQuery query) { + // 🔧 应用数据权限过滤 + applyDataPermissionFilter(query); + // 使用自定义SQL查询,包含申请信息的联合查询 List 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 pageList = total > 0 && start < total ? - list.subList(start, end) : new ArrayList<>(); - + + List pageList = total > 0 && start < total ? list.subList(start, end) : new ArrayList<>(); + // 构建分页对象 Page page = new Page<>(pageNum, pageSize); page.setRecords(pageList); page.setTotal(total); - + return TableDataInfo.build(page); } @@ -126,18 +128,18 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService private void validEntityBeforeSave(RsOverseasApproval entity) { // 校验审批级别 if (entity.getApprovalLevel() != null) { - if (!"1".equals(entity.getApprovalLevel()) && - !"2".equals(entity.getApprovalLevel()) && - !"3".equals(entity.getApprovalLevel())) { + 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())) { + if (!"0".equals(entity.getApprovalStatus()) && + !"1".equals(entity.getApprovalStatus()) && + !"2".equals(entity.getApprovalStatus())) { throw new RuntimeException("审批状态只能是0(待审核)、1(通过)、2(拒绝)"); } } @@ -148,35 +150,45 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService */ private LambdaQueryWrapper buildQueryWrapper(RsOverseasApprovalQuery query) { LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - + // ===== 业务逻辑层权限过滤 ===== // 根据用户角色进行数据权限过滤 if (!LoginHelper.isSuperAdmin()) { // 获取当前登录用户信息 Long currentUserId = LoginHelper.getUserId(); - + Long currentDeptId = LoginHelper.getDeptId(); + // 检查用户角色并应用相应的数据过滤 if (StpUtil.hasRole("rs-Applicant")) { // 申请人只能看到与自己相关的审批记录 - lqw.exists("SELECT 1 FROM rs_overseas_application app WHERE app.application_id = rs_overseas_approval.application_id AND app.applicant_id = " + currentUserId); + lqw.exists( + "SELECT 1 FROM rs_overseas_application app WHERE app.application_id = rs_overseas_approval.application_id AND app.applicant_id = " + + currentUserId); } else if (StpUtil.hasRole("rs-admin")) { // 人事管理员可以看到所有审批记录 // 不设置额外过滤条件,让管理员能看到所有审批 + } else if (isDeptApprover(currentUserId)) { + // 部门负责人只能看到本部门的审批记录 + lqw.exists( + "SELECT 1 FROM rs_overseas_application app INNER JOIN sys_user u ON app.applicant_id = u.user_id WHERE app.application_id = rs_overseas_approval.application_id AND u.dept_id = " + + currentDeptId); } else { // 默认情况:只能看到自己创建或处理的审批 lqw.and(wrapper -> wrapper.eq(RsOverseasApproval::getCreateBy, currentUserId) - .or().eq(RsOverseasApproval::getApproverId, currentUserId)); + .or().eq(RsOverseasApproval::getApproverId, currentUserId)); } } - + // 添加租户过滤 lqw.eq(RsOverseasApproval::getTenantId, LoginHelper.getTenantId()); - + 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()); - + 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()); @@ -185,12 +197,41 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService 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; } + /** + * 应用数据权限过滤 + */ + private void applyDataPermissionFilter(RsOverseasApprovalQuery query) { + Long currentUserId = StpUtil.getLoginIdAsLong(); + Long currentDeptId = LoginHelper.getDeptId(); + + log.info("权限过滤 - 用户ID: {}, 部门ID: {}", currentUserId, currentDeptId); + + // 🔧 优先检查部门负责人权限(避免被rs-admin角色覆盖) + boolean isDeptApprover = isDeptApprover(currentUserId); + if (isDeptApprover) { + log.info("用户 {} 是部门负责人,应用审批人过滤: {}", currentUserId, currentUserId); + query.setApproverId(currentUserId); // 只看分配给自己的审批 + query.setDeptId(currentDeptId); // 同时应用部门过滤 + return; + } + + // 人事管理员可以看到所有申请(仅当不是部门负责人时) + if (StpUtil.hasRole("rs-admin")) { + log.info("用户 {} 是人事管理员,可查看所有申请", currentUserId); + return; // 全局权限 + } + + // 普通用户只能看到自己的申请 + log.info("用户 {} 是普通用户,只能查看自己的申请", currentUserId); + query.setApplicantId(currentUserId); + } + /** * 从BO构建Entity */ @@ -269,12 +310,12 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService */ private List createTestApprovalData() { List 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"}; // 全部通过 + 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 = { "申请材料齐全,出国目的明确,同意申请", "培训项目对教学有益,同意参加", @@ -282,7 +323,7 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService "研究交流项目重要,全力支持", "培训有助于专业发展,批准参加" }; - + for (int i = 0; i < applicationIds.length; i++) { RsOverseasApproval approval = new RsOverseasApproval(); approval.setApplicationId(applicationIds[i]); @@ -291,15 +332,17 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService 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.setApprovalTime( + Date.from(LocalDateTime.now().minusDays(i + 1).atZone(ZoneId.systemDefault()).toInstant())); approval.setTenantId(LoginHelper.getTenantId()); approval.setDeleted("0"); approval.setCreateBy(1L); - approval.setCreateTime(Date.from(LocalDateTime.now().minusDays(i + 1).atZone(ZoneId.systemDefault()).toInstant())); - + approval.setCreateTime( + Date.from(LocalDateTime.now().minusDays(i + 1).atZone(ZoneId.systemDefault()).toInstant())); + approvals.add(approval); } - + return approvals; } @@ -312,36 +355,39 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService lqw.eq(RsOverseasApproval::getApplicationId, applicationId); lqw.orderByAsc(RsOverseasApproval::getApprovalLevel); lqw.orderByDesc(RsOverseasApproval::getApprovalTime); // 按审批时间降序,最新的在前面 - + return baseMapper.selectVoList(lqw); } - + /** * 联合查询分页列表:待审批申请 + 审批记录 */ @Override public TableDataInfo queryCombinedPageList(RsOverseasApprovalQuery query) { + // 添加部门权限过滤 + applyDeptPermissionFilter(query); + List 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 pageList = list.subList(start, end); - + // 创建一个简单的分页结果 TableDataInfo result = TableDataInfo.build(pageList); result.setTotal((long) total); return result; } - + /** * 联合查询列表:待审批申请 + 审批记录 */ @@ -349,4 +395,84 @@ public class RsOverseasApprovalServiceImpl implements IRsOverseasApprovalService public List queryCombinedList(RsOverseasApprovalQuery query) { return baseMapper.selectCombinedApprovalList(query); } -} \ No newline at end of file + + /** + * 判断当前用户是否为部门审批人 + * 检查用户是否具有部门负责人审批权限 + */ + private boolean isDeptApprover(Long userId) { + String currentUsername = LoginHelper.getUsername(); + + // 检查用户是否具有部门负责人审批权限 + // 方法1:检查是否有第1级审批权限 + boolean hasApprove1 = StpUtil.hasPermission("rs:overseas:approve1"); + log.info("用户 {} 权限检查 - rs:overseas:approve1: {}", currentUsername, hasApprove1); + if (hasApprove1) { + return true; + } + + // 方法2:检查是否有部门负责人角色(如果有定义的话) + boolean hasDeptHeadRole = StpUtil.hasRole("dept_head_role"); + log.info("用户 {} 角色检查 - dept_head_role: {}", currentUsername, hasDeptHeadRole); + if (hasDeptHeadRole) { + return true; + } + + // 方法3:检查是否有旧版权限标识符(兼容性) + boolean hasOldPermission = StpUtil.hasPermission("overseas:application:dept_head:approve"); + log.info("用户 {} 权限检查 - overseas:application:dept_head:approve: {}", currentUsername, hasOldPermission); + if (hasOldPermission) { + return true; + } + + log.info("用户 {} 不具有部门负责人权限", currentUsername); + return false; + } + + /** + * 应用部门权限过滤 + * 根据当前用户的角色和部门,设置查询条件 + */ + private void applyDeptPermissionFilter(RsOverseasApprovalQuery query) { + Long currentUserId = LoginHelper.getUserId(); + String currentUsername = LoginHelper.getUsername(); + Long currentDeptId = LoginHelper.getDeptId(); + boolean isSuperAdmin = LoginHelper.isSuperAdmin(); + + log.info("权限过滤开始 - 用户: {}, ID: {}, 部门ID: {}, 是否超级管理员: {}", + currentUsername, currentUserId, currentDeptId, isSuperAdmin); + + // 超级管理员不需要过滤 + if (isSuperAdmin) { + log.info("用户 {} 是超级管理员,跳过权限过滤", currentUsername); + return; + } + + // 申请人只能看到自己的申请 + if (StpUtil.hasRole("rs-Applicant")) { + log.info("用户 {} 具有rs-Applicant角色,设置申请人过滤: {}", currentUsername, currentUserId); + query.setApplicantId(currentUserId); + return; + } + + // 🔧 优先检查部门负责人权限(避免被rs-admin角色覆盖) + boolean isDeptApprover = isDeptApprover(currentUserId); + log.info("用户 {} 部门负责人检查结果: {}", currentUsername, isDeptApprover); + + if (isDeptApprover) { + log.info("用户 {} 是部门负责人,设置部门过滤: {}", currentUsername, currentDeptId); + query.setDeptId(currentDeptId); + return; + } + + // 人事管理员可以看到所有申请(仅当不是部门负责人时) + if (StpUtil.hasRole("rs-admin")) { + log.info("用户 {} 具有rs-admin角色,不设置过滤条件", currentUsername); + return; + } + + // 其他角色只能看到分配给自己的审批 + log.info("用户 {} 是普通审批人,设置审批人过滤: {}", currentUsername, currentUserId); + query.setApproverId(currentUserId); + } +} \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsPartyMembershipRetentionServiceImpl.java b/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsPartyMembershipRetentionServiceImpl.java deleted file mode 100644 index 167ef436..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/service/impl/RsPartyMembershipRetentionServiceImpl.java +++ /dev/null @@ -1,288 +0,0 @@ -package org.dromara.rs.service.impl; - -import java.util.Collection; -import java.util.Date; -import java.util.List; - -import org.dromara.common.core.utils.MapstructUtils; -import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.mybatis.core.page.TableDataInfo; -import org.dromara.common.mybatis.core.page.PageQuery; -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.RsPartyMembershipRetentionMapper; -import org.dromara.rs.domain.RsPartyMembershipRetention; -import org.dromara.rs.domain.bo.RsPartyMembershipRetentionBo; -import org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo; -import org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery; -import org.dromara.rs.service.IRsPartyMembershipRetentionService; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * 出国党籍保留Service业务层处理 - * - * @author weizzzz - * @date 2025-10-05 - */ -@Service -public class RsPartyMembershipRetentionServiceImpl implements IRsPartyMembershipRetentionService { - - private static final Logger log = LoggerFactory.getLogger(RsPartyMembershipRetentionServiceImpl.class); - - @Autowired - private RsPartyMembershipRetentionMapper baseMapper; - - /** - * 查询出国党籍保留 - */ - @Override - public RsPartyMembershipRetentionVo queryById(Long retentionId) { - return baseMapper.selectVoById(retentionId); - } - - /** - * 查询出国党籍保留列表(使用自定义SQL,包含statusText映射) - */ - @Override - public TableDataInfo queryPageList(RsPartyMembershipRetentionQuery query, PageQuery pageQuery) { - // 1. 调用自定义SQL查询(包含statusText映射) - List list = baseMapper.selectVoList(query); - - // 2. 手动分页处理 - int pageNum = pageQuery.getPageNum(); - int pageSize = pageQuery.getPageSize(); - int start = (pageNum - 1) * pageSize; - int end = Math.min(start + pageSize, list.size()); - - // 如果起始位置超出了列表大小,返回空结果 - List pageList = start > list.size() ? - List.of() : list.subList(start, end); - - // 3. 构建分页对象 - Page page = new Page<>(pageNum, pageSize); - page.setRecords(pageList); - page.setTotal(list.size()); - - return TableDataInfo.build(page); - } - - /** - * 查询出国党籍保留列表 - */ - @Override - public List queryList(RsPartyMembershipRetentionQuery query) { - return baseMapper.selectVoList(buildQueryWrapper(query)); - } - - /** - * 构建查询条件 - */ - private LambdaQueryWrapper buildQueryWrapper(RsPartyMembershipRetentionQuery query) { - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.like(StringUtils.isNotBlank(query.getName()), RsPartyMembershipRetention::getName, query.getName()); - lqw.like(StringUtils.isNotBlank(query.getDestination()), RsPartyMembershipRetention::getDestination, query.getDestination()); - lqw.like(StringUtils.isNotBlank(query.getPartyBranch()), RsPartyMembershipRetention::getPartyBranch, query.getPartyBranch()); - lqw.eq(StringUtils.isNotBlank(query.getStatus()), RsPartyMembershipRetention::getStatus, query.getStatus()); - lqw.eq(query.getRegistrationId() != null, RsPartyMembershipRetention::getRegistrationId, query.getRegistrationId()); - lqw.eq(query.getApplicationId() != null, RsPartyMembershipRetention::getApplicationId, query.getApplicationId()); - lqw.between(query.getApplicationTimeStart() != null && query.getApplicationTimeEnd() != null, - RsPartyMembershipRetention::getApplicationTime, query.getApplicationTimeStart(), query.getApplicationTimeEnd()); - lqw.orderByDesc(RsPartyMembershipRetention::getCreateTime); - return lqw; - } - - /** - * 新增出国党籍保留 - */ - @Override - @Transactional(rollbackFor = Exception.class) - public Boolean insertByBo(RsPartyMembershipRetentionBo bo) { - RsPartyMembershipRetention add = MapstructUtils.convert(bo, RsPartyMembershipRetention.class); - - // ⚠️ 重要:在validEntityBeforeSave之前设置默认值,确保不会因为NOT NULL约束报错 - - // 自动设置申请日期为当前日期(如果前端未提供) - if (add.getApplicationDate() == null) { - add.setApplicationDate(new Date()); - } - - // 自动设置费用自理为"是"(如果前端未提供) - 数据库字段为NOT NULL - if (StringUtils.isBlank(add.getSelfFunded())) { - add.setSelfFunded("1"); - } - - // 设置默认状态为草稿 - if (StringUtils.isBlank(add.getStatus())) { - add.setStatus("0"); - } - - // 执行数据校验和自动计算 - validEntityBeforeSave(add); - - boolean flag = baseMapper.insert(add) > 0; - if (flag) { - bo.setRetentionId(add.getRetentionId()); - } - return flag; - } - - /** - * 修改出国党籍保留 - */ - @Override - @Transactional(rollbackFor = Exception.class) - public Boolean updateByBo(RsPartyMembershipRetentionBo bo) { - RsPartyMembershipRetention update = MapstructUtils.convert(bo, RsPartyMembershipRetention.class); - validEntityBeforeSave(update); - return baseMapper.updateById(update) > 0; - } - - /** - * 保存实体之前的数据校验 - */ - private void validEntityBeforeSave(RsPartyMembershipRetention entity) { - // 计算停留天数 - if (entity.getTravelStartDate() != null && entity.getTravelEndDate() != null) { - long diff = entity.getTravelEndDate().getTime() - entity.getTravelStartDate().getTime(); - entity.setStayDays((int) (diff / (1000 * 60 * 60 * 24))); - } - } - - /** - * 校验并批量删除出国党籍保留信息 - */ - @Override - @Transactional(rollbackFor = Exception.class) - public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { - if (isValid) { - // 做一些业务上的校验,判断是否允许删除 - // 例如:已提交或已审批的不允许删除 - for (Long id : ids) { - RsPartyMembershipRetentionVo vo = baseMapper.selectVoById(id); - if (vo != null && !"0".equals(vo.getStatus())) { - throw new RuntimeException("只能删除草稿状态的记录"); - } - } - } - return baseMapper.deleteByIds(ids) > 0; - } - - /** - * 保存并提交出国党籍保留 - */ - @Override - @Transactional(rollbackFor = Exception.class) - public Boolean saveAndSubmit(RsPartyMembershipRetentionBo bo) { - // 先保存 - boolean saved; - if (bo.getRetentionId() != null) { - saved = updateByBo(bo); - } else { - saved = insertByBo(bo); - } - - // 再提交 - if (saved && bo.getRetentionId() != null) { - return submit(bo.getRetentionId()); - } - return false; - } - - /** - * 提交出国党籍保留 - */ - @Override - @Transactional(rollbackFor = Exception.class) - public Boolean submit(Long retentionId) { - RsPartyMembershipRetentionVo vo = baseMapper.selectVoById(retentionId); - if (vo == null) { - throw new RuntimeException("记录不存在"); - } - - if (!"0".equals(vo.getStatus()) && !"4".equals(vo.getStatus())) { - throw new RuntimeException("只有草稿或已撤回状态才能提交"); - } - - RsPartyMembershipRetention update = new RsPartyMembershipRetention(); - update.setRetentionId(retentionId); - update.setStatus("1"); // 待审核 - update.setApplicationTime(new Date()); - - return baseMapper.updateById(update) > 0; - } - - /** - * 撤回出国党籍保留 - */ - @Override - @Transactional(rollbackFor = Exception.class) - public Boolean withdraw(Long retentionId) { - RsPartyMembershipRetentionVo vo = baseMapper.selectVoById(retentionId); - if (vo == null) { - throw new RuntimeException("记录不存在"); - } - - if (!"1".equals(vo.getStatus())) { - throw new RuntimeException("只有待审核状态才能撤回"); - } - - RsPartyMembershipRetention update = new RsPartyMembershipRetention(); - update.setRetentionId(retentionId); - update.setStatus("4"); // 已撤回 - - return baseMapper.updateById(update) > 0; - } - - /** - * 审批出国党籍保留 - */ - @Override - @Transactional(rollbackFor = Exception.class) - public Boolean approve(Long retentionId, String approvalOpinion, Boolean approved) { - RsPartyMembershipRetentionVo vo = baseMapper.selectVoById(retentionId); - if (vo == null) { - throw new RuntimeException("记录不存在"); - } - - if (!"1".equals(vo.getStatus())) { - throw new RuntimeException("只有待审核状态才能审批"); - } - - RsPartyMembershipRetention update = new RsPartyMembershipRetention(); - update.setRetentionId(retentionId); - update.setStatus(approved ? "2" : "3"); // 2-已通过,3-已拒绝 - update.setFinalApproval(approvalOpinion); - - return baseMapper.updateById(update) > 0; - } - - /** - * 根据登记ID查询党籍保留信息 - */ - @Override - public RsPartyMembershipRetentionVo queryByRegistrationId(Long registrationId) { - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.eq(RsPartyMembershipRetention::getRegistrationId, registrationId); - lqw.orderByDesc(RsPartyMembershipRetention::getCreateTime); - lqw.last("LIMIT 1"); - return baseMapper.selectVoOne(lqw); - } - - /** - * 根据申请ID查询党籍保留信息 - */ - @Override - public RsPartyMembershipRetentionVo queryByApplicationId(Long applicationId) { - LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); - lqw.eq(RsPartyMembershipRetention::getApplicationId, applicationId); - lqw.orderByDesc(RsPartyMembershipRetention::getCreateTime); - lqw.last("LIMIT 1"); - return baseMapper.selectVoOne(lqw); - } -} diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml index 10652440..be18e4de 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApplicationMapper.xml @@ -44,6 +44,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -57,11 +58,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" - - - - - @@ -72,6 +68,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + + @@ -89,13 +87,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" oa.destination_country, oa.exit_reason, oa.visit_relationship, oa.remarks, oa.purpose, oa.purpose_detail, oa.destination, oa.destination_city, oa.start_date, oa.end_date, oa.duration, oa.invitation, oa.itinerary, - oa.accommodation_plan, oa.transportation_plan, oa.status, oa.current_approver, oa.approval_level, + oa.accommodation_plan, oa.transportation_plan, oa.status, oa.approval_status, oa.current_approver, oa.approval_level, oa.apply_time, oa.approval_time, oa.admin_remark, oa.applicant_remark, oa.emergency_contact, oa.signature, oa.stamped_registration_file_path, oa.stamped_registration_file_name, oa.stamped_file_upload_time, oa.stamped_file_upload_status, oa.stamped_file_oss_id, - oa.party_retention_file_path, oa.party_retention_file_name, - oa.party_retention_upload_time, oa.party_retention_upload_status, oa.party_retention_file_oss_id, oa.tenant_id, oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted, CASE WHEN oa.status IS NULL OR oa.status = '' THEN '草稿' @@ -106,6 +102,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHEN oa.status = '4' THEN '已完成' ELSE '未知状态' END as status_text, + CASE + WHEN oa.approval_status IS NULL OR oa.approval_status = '' THEN '草稿' + WHEN oa.approval_status = 'draft' THEN '草稿' + WHEN oa.approval_status = 'waiting' THEN '待审批' + WHEN oa.approval_status = 'finish' THEN '已完成' + WHEN oa.approval_status = 'cancel' THEN '已取消' + WHEN oa.approval_status = 'back' THEN '已退回' + ELSE '未知状态' + END as approval_status_text, CASE oa.purpose WHEN 'academic_exchange' THEN '学术交流' WHEN 'conference' THEN '学术会议' @@ -165,8 +170,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" WHEN '4' THEN '4-一般干部' WHEN '5' THEN '5-其他重要岗位人员' ELSE oa.personnel_category - END as personnel_category_text + END as personnel_category_text, + ft.id as task_id from rs_overseas_application oa + + LEFT JOIN ( + SELECT t.id, i.business_id + FROM flow_task t + INNER JOIN flow_instance i ON t.instance_id = i.id + WHERE t.del_flag = '0' + AND i.del_flag = '0' + AND t.node_type = 1 + ) ft ON CAST(ft.business_id AS SIGNED) = oa.application_id + SELECT u.dept_id + FROM sys_user u + WHERE u.user_id = #{applicantId} + AND u.deleted = '0' + + + + + + + + + + + \ No newline at end of file diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApprovalMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApprovalMapper.xml index 011013e9..3d7a5148 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApprovalMapper.xml +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasApprovalMapper.xml @@ -107,6 +107,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND oa.tenant_id = #{tenantId} + + + AND EXISTS ( + SELECT 1 FROM sys_user u + WHERE u.user_id = app.applicant_id + AND u.dept_id = #{deptId} + ) + + + + AND app.applicant_id = #{applicantId} + order by oa.approval_time desc, oa.create_time desc @@ -205,25 +217,37 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND oa.tenant_id = #{tenantId} + + + AND EXISTS ( + SELECT 1 FROM sys_user u + WHERE u.user_id = app.applicant_id + AND u.dept_id = #{deptId} + ) + + + + AND app.applicant_id = #{applicantId} + UNION ALL - -- 查询待审批的申请(还没有审批记录的) + -- 查询待审批的申请(有审批记录且状态为待审核的) SELECT - NULL as approval_id, + oa.approval_id, app.application_id, - NULL as approver_id, - app.current_approver as approver_name, - app.approval_level, - '0' as approval_status, - NULL as approval_opinion, - NULL as approval_time, - app.tenant_id, - app.create_by, - app.create_time, - app.update_by, - app.update_time, - app.deleted, + oa.approver_id, + oa.approver_name, + oa.approval_level, + oa.approval_status, + oa.approval_opinion, + oa.approval_time, + oa.tenant_id, + oa.create_by, + oa.create_time, + oa.update_by, + oa.update_time, + oa.deleted, -- 申请信息字段 app.applicant_id, app.applicant_name, @@ -234,7 +258,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" app.end_date, app.duration, -- 文本转换 - CASE app.approval_level + CASE oa.approval_level WHEN '1' THEN '部门主管' WHEN '2' THEN '分管领导' WHEN '3' THEN '校领导' @@ -255,14 +279,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ELSE '未知目的' END as purpose_text, 'pending' as record_type - FROM rs_overseas_application app - WHERE app.deleted = '0' - AND app.status = '0' -- 待审核状态 - AND NOT EXISTS ( - SELECT 1 FROM rs_overseas_approval oa - WHERE oa.application_id = app.application_id - AND oa.deleted = '0' - ) + FROM rs_overseas_approval oa + INNER JOIN rs_overseas_application app ON oa.application_id = app.application_id + WHERE oa.deleted = '0' + AND app.deleted = '0' + AND app.status = '0' -- 申请状态:待审核 + AND oa.approval_status = '0' -- 审批状态:待审核 AND app.application_id = #{applicationId} @@ -270,10 +292,28 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" AND app.applicant_name like concat('%', #{applicantName}, '%') - AND app.approval_level = #{approvalLevel} + AND oa.approval_level = #{approvalLevel} + + + AND oa.approver_id = #{approverId} + + + AND oa.approver_name like concat('%', #{approverName}, '%') - AND app.tenant_id = #{tenantId} + AND oa.tenant_id = #{tenantId} + + + + AND EXISTS ( + SELECT 1 FROM sys_user u + WHERE u.user_id = app.applicant_id + AND u.dept_id = #{deptId} + ) + + + + AND app.applicant_id = #{applicantId} ORDER BY diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasRecordMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasRecordMapper.xml index 32648189..27d09569 100644 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasRecordMapper.xml +++ b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasRecordMapper.xml @@ -40,6 +40,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" + @@ -73,7 +74,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select ors.record_id, ors.application_id, ors.applicant_id, ors.applicant_name, ors.department, - ors.position, ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city, + ors.position, + COALESCE(sp.post_name, ors.position) as position_text, + ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city, ors.actual_start_date, ors.actual_end_date, ors.actual_duration, ors.actual_budget, ors.invitation, ors.itinerary, ors.accommodation_plan, ors.transportation_plan, ors.completion_status, ors.achievement, ors.report_content, ors.certificate_path, @@ -131,6 +134,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 'record' as data_source from rs_overseas_record ors left join rs_overseas_application oa on ors.application_id = oa.application_id and oa.deleted = '0' + left join sys_post sp on (ors.position = CAST(sp.post_id AS CHAR) or ors.position = sp.post_code) and sp.status = '0' @@ -138,7 +142,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" -- 查询已创建的出国记录(关联申请表获取扩展字段) select ors.record_id, ors.application_id, ors.applicant_id, ors.applicant_name, ors.department, - ors.position, ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city, + ors.position, + COALESCE(sp1.post_name, ors.position) as position_text, + ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city, ors.actual_start_date as actual_start_date, ors.actual_end_date as actual_end_date, ors.actual_duration as actual_duration, ors.actual_budget as actual_budget, ors.invitation, ors.itinerary, ors.accommodation_plan, ors.transportation_plan, @@ -206,6 +212,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" 'record' as data_source from rs_overseas_record ors left join rs_overseas_application oa on ors.application_id = oa.application_id and oa.deleted = '0' + left join sys_post sp1 on (ors.position = CAST(sp1.post_id AS CHAR) or ors.position = sp1.post_code) and sp1.status = '0' where ors.deleted = '0' UNION ALL @@ -214,7 +221,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" select NULL as record_id, oa.application_id, oa.applicant_id, oa.applicant_name, oa.department, - oa.position, oa.contact_phone, oa.contact_email, oa.purpose, oa.purpose_detail, oa.destination, oa.destination_city, + oa.position, + COALESCE(sp2.post_name, oa.position) as position_text, + oa.contact_phone, oa.contact_email, oa.purpose, oa.purpose_detail, oa.destination, oa.destination_city, oa.start_date as actual_start_date, oa.end_date as actual_end_date, oa.duration as actual_duration, NULL as actual_budget, oa.invitation, oa.itinerary, oa.accommodation_plan, oa.transportation_plan, @@ -275,6 +284,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" '进行中' as completion_status_text, 'approved_application' as data_source from rs_overseas_application oa + left join sys_post sp2 on (oa.position = CAST(sp2.post_id AS CHAR) or oa.position = sp2.post_code) and sp2.status = '0' where oa.deleted = '0' and oa.status = '1' and not exists ( diff --git a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsPartyMembershipRetentionMapper.xml b/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsPartyMembershipRetentionMapper.xml deleted file mode 100644 index 377b2083..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsPartyMembershipRetentionMapper.xml +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - select r.retention_id, r.application_id, r.registration_id, r.name, r.id_card, r.position, r.spouse, r.relative_name, - r.travel_start_date, r.travel_end_date, r.stay_days, r.destination, r.purpose, - r.travel_reason, r.self_funded, r.request_approval, r.final_approval, - r.party_branch, r.application_date, r.status, r.application_time, r.remarks, - r.tenant_id, r.create_by, r.create_time, r.update_by, r.update_time, r.deleted, - CASE r.self_funded - WHEN '1' THEN '是' - WHEN '0' THEN '否' - ELSE '未知' - END as self_funded_text, - CASE r.status - WHEN '0' THEN '草稿' - WHEN '1' THEN '待审核' - WHEN '2' THEN '已通过' - WHEN '3' THEN '已拒绝' - WHEN '4' THEN '已撤回' - ELSE '未知状态' - END as status_text - from rs_party_membership_retention r - - - - - - - diff --git a/backend/ruoyi-modules/ruoyi-rs/党籍保留增加身份证号和职务字段脚本.sql b/backend/ruoyi-modules/ruoyi-rs/党籍保留增加身份证号和职务字段脚本.sql deleted file mode 100644 index e8cec897..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/党籍保留增加身份证号和职务字段脚本.sql +++ /dev/null @@ -1,127 +0,0 @@ --- ============================================ --- 出国党籍保留表 - 补充身份证号和职务字段 --- ============================================ --- 版本: v1.1 --- 日期: 2025-10-08 --- 说明: 补充前端表单需要的身份证号和职务字段 --- 修改内容: --- 1. 增加 id_card 字段(身份证号,varchar(18)) --- 2. 增加 position 字段(职务,varchar(100)) --- 3. 为身份证号字段添加索引(支持精确查询) --- ============================================ - -USE `hzdyjs_xg`; - --- ============================================ --- 第一部分:增加字段 --- ============================================ - --- 1. 增加身份证号字段(放在 name 字段之后) -ALTER TABLE `rs_party_membership_retention` -ADD COLUMN `id_card` varchar(18) NULL DEFAULT NULL COMMENT '身份证号(18位)' -AFTER `name`; - --- 2. 增加职务字段(放在 id_card 字段之后) -ALTER TABLE `rs_party_membership_retention` -ADD COLUMN `position` varchar(100) NULL DEFAULT NULL COMMENT '现任职务' -AFTER `id_card`; - --- ============================================ --- 第二部分:添加索引优化 --- ============================================ - --- 为身份证号添加索引(支持精确查询) -ALTER TABLE `rs_party_membership_retention` -ADD INDEX `idx_id_card` (`id_card`) USING BTREE COMMENT '身份证号索引(精确查询)'; - --- ============================================ --- 第三部分:验证字段添加结果 --- ============================================ - --- 验证表结构(查看新增字段) -SELECT - COLUMN_NAME AS '字段名', - COLUMN_TYPE AS '字段类型', - IS_NULLABLE AS '是否可空', - COLUMN_DEFAULT AS '默认值', - COLUMN_COMMENT AS '字段注释' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_party_membership_retention' - AND COLUMN_NAME IN ('id_card', 'position') -ORDER BY ORDINAL_POSITION; - --- 验证索引(查看新增索引) -SHOW INDEX FROM `rs_party_membership_retention` -WHERE Key_name = 'idx_id_card'; - --- 查看完整表结构 -DESC `rs_party_membership_retention`; - --- ============================================ --- 第四部分:更新测试数据(可选) --- ============================================ - --- 为现有测试数据补充身份证号和职务信息 -UPDATE `rs_party_membership_retention` -SET - `id_card` = '330106198501011234', - `position` = '副教授' -WHERE `retention_id` = 1 AND `name` = '张三'; - -UPDATE `rs_party_membership_retention` -SET - `id_card` = '310101198003155678', - `position` = '讲师' -WHERE `retention_id` = 2 AND `name` = '王五'; - -UPDATE `rs_party_membership_retention` -SET - `id_card` = '440106197206209012', - `position` = '教授' -WHERE `retention_id` = 3 AND `name` = '李四'; - --- ============================================ --- 第五部分:验证数据更新 --- ============================================ - --- 查看更新后的数据 -SELECT - retention_id AS 'ID', - name AS '姓名', - id_card AS '身份证号', - position AS '职务', - destination AS '目的地', - CASE status - WHEN '0' THEN '草稿' - WHEN '1' THEN '已提交' - WHEN '2' THEN '已批准' - WHEN '3' THEN '已拒绝' - WHEN '4' THEN '已撤回' - END AS '状态' -FROM `rs_party_membership_retention` -WHERE deleted = '0' -ORDER BY retention_id; - --- ============================================ --- 脚本执行完成 --- ============================================ --- 执行结果说明: --- ✅ 增加了 2 个新字段:id_card(身份证号)、position(职务) --- ✅ 增加了 1 个索引:idx_id_card(身份证号索引) --- ✅ 更新了 3 条测试数据 --- --- 字段详情: --- - id_card:varchar(18),可空,支持18位身份证号 --- - position:varchar(100),可空,支持多种职务描述 --- --- 索引说明: --- - idx_id_card:BTREE索引,支持身份证号精确查询 --- --- ⚠️ 注意事项: --- 1. 新增字段为可空(NULL),不影响现有数据 --- 2. 可以安全地多次执行本脚本(已使用IF NOT EXISTS) --- 3. 测试数据更新部分根据实际情况选择执行 --- 4. 生产环境执行前请先备份数据库 --- ============================================ - diff --git a/backend/ruoyi-modules/ruoyi-rs/党籍保留权限验证.sql b/backend/ruoyi-modules/ruoyi-rs/党籍保留权限验证.sql deleted file mode 100644 index ab7e7d0a..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/党籍保留权限验证.sql +++ /dev/null @@ -1,198 +0,0 @@ --- ============================================ --- 出国党籍保留权限验证脚本 --- ============================================ --- 用途:验证菜单权限配置是否正确 --- 执行时间:权限配置完成后立即执行 --- ============================================ - --- 1. 验证菜单是否正确创建(应该返回9条记录) -SELECT - menu_id AS '菜单ID', - menu_name AS '菜单名称', - menu_type AS '类型', - perms AS '权限标识', - parent_id AS '父菜单', - order_num AS '排序', - CASE status - WHEN '0' THEN '✅ 正常' - WHEN '1' THEN '❌ 停用' - END AS '状态', - CASE visible - WHEN '0' THEN '✅ 显示' - WHEN '1' THEN '❌ 隐藏' - END AS '可见性' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND deleted = '0' -ORDER BY menu_id; - --- 预期结果: --- 2010 - 出国党籍保留(主菜单) --- 2011-2018 - 8个功能按钮 - --- ============================================ - --- 2. 验证父菜单关系(确认挂载到人事管理下) -SELECT - m.menu_id AS '菜单ID', - m.menu_name AS '菜单名称', - p.menu_id AS '父菜单ID', - p.menu_name AS '父菜单名称', - CASE - WHEN p.menu_id = 2000 THEN '✅ 正确' - ELSE '⚠️ 父菜单错误' - END AS '验证结果' -FROM sys_menu m -LEFT JOIN sys_menu p ON m.parent_id = p.menu_id -WHERE m.menu_id = 2010 - AND m.deleted = '0'; - --- 预期:父菜单应该是"人事管理"(ID: 2000) - --- ============================================ - --- 3. 验证角色权限分配(管理员应该有9个权限) -SELECT - r.role_id AS '角色ID', - r.role_name AS '角色名称', - COUNT(DISTINCT rm.menu_id) AS '已分配权限数', - GROUP_CONCAT(m.menu_name ORDER BY m.menu_id) AS '权限列表', - CASE - WHEN COUNT(DISTINCT rm.menu_id) = 9 THEN '✅ 完整' - WHEN COUNT(DISTINCT rm.menu_id) > 0 THEN '⚠️ 不完整' - ELSE '❌ 未分配' - END AS '验证结果' -FROM sys_role r -LEFT JOIN sys_role_menu rm ON r.role_id = rm.role_id AND rm.menu_id BETWEEN 2010 AND 2018 -LEFT JOIN sys_menu m ON rm.menu_id = m.menu_id AND m.deleted = '0' -WHERE r.role_key = 'admin' - AND r.deleted = '0' -GROUP BY r.role_id, r.role_name; - --- 预期:管理员角色应该有9个权限 - --- ============================================ - --- 4. 验证权限标识是否规范 -SELECT - menu_id AS 'ID', - menu_name AS '菜单名称', - perms AS '权限标识', - CASE - WHEN perms LIKE 'rs:party:retention:%' THEN '✅ 规范' - WHEN perms LIKE 'rs:partyretention:%' THEN '⚠️ 旧格式' - ELSE '❌ 不规范' - END AS '格式验证', - CASE - WHEN menu_id = 2010 AND perms = 'rs:party:retention:list' THEN '✅' - WHEN menu_id = 2011 AND perms = 'rs:party:retention:query' THEN '✅' - WHEN menu_id = 2012 AND perms = 'rs:party:retention:add' THEN '✅' - WHEN menu_id = 2013 AND perms = 'rs:party:retention:edit' THEN '✅' - WHEN menu_id = 2014 AND perms = 'rs:party:retention:remove' THEN '✅' - WHEN menu_id = 2015 AND perms = 'rs:party:retention:export' THEN '✅' - WHEN menu_id = 2016 AND perms = 'rs:party:retention:submit' THEN '✅' - WHEN menu_id = 2017 AND perms = 'rs:party:retention:withdraw' THEN '✅' - WHEN menu_id = 2018 AND perms = 'rs:party:retention:approve' THEN '✅' - ELSE '⚠️' - END AS '权限匹配' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND deleted = '0' -ORDER BY menu_id; - --- ⚠️ 注意:如果权限标识不匹配Controller中的注解,需要统一调整 - --- ============================================ - --- 5. 验证菜单路径配置 -SELECT - menu_id AS 'ID', - menu_name AS '菜单名称', - path AS '路由地址', - component AS '组件路径', - CASE - WHEN menu_id = 2010 AND path = 'party-membership-retention' THEN '✅' - WHEN menu_id = 2010 AND component = 'rs/party-membership-retention/index' THEN '✅' - WHEN menu_id > 2010 AND path = '#' THEN '✅' - ELSE '⚠️' - END AS '配置验证' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND deleted = '0' -ORDER BY menu_id; - --- ============================================ - --- 6. 检查是否有ID冲突 -SELECT - menu_id AS '冲突ID', - menu_name AS '菜单名称', - parent_id AS '父菜单', - '⚠️ 发现ID冲突' AS '警告' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND menu_name NOT LIKE '%党籍保留%' - AND deleted = '0'; - --- 预期:应该返回空结果(无冲突) - --- ============================================ - --- 7. 验证字典数据(党籍保留状态) -SELECT - dict_label AS '状态名称', - dict_value AS '状态值', - list_class AS '标签样式', - dict_sort AS '排序', - CASE is_default - WHEN 'Y' THEN '✅ 默认' - ELSE '' - END AS '是否默认', - remark AS '说明' -FROM sys_dict_data -WHERE dict_type = 'rs_party_retention_status' - AND deleted = '0' -ORDER BY dict_sort; - --- 预期:应该有5个状态值(0-草稿,1-已提交,2-已批准,3-已拒绝,4-已撤回) - --- ============================================ - --- 8. 生成完整权限清单(供前端参考) -SELECT - menu_id AS 'ID', - menu_name AS '权限名称', - perms AS '权限标识(v-hasPermi)', - CONCAT('v-hasPermi="[''', perms, ''']"') AS '前端指令示例' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND deleted = '0' -ORDER BY menu_id; - --- ============================================ --- 验证完成 --- ============================================ -/* -执行说明: -1. 依次执行上面8个查询 -2. 检查每个查询结果是否符合预期 -3. 如果发现问题,记录具体的错误信息 -4. 根据错误信息进行修正 - -✅ 验证通过标准: -- 查询1:返回9条记录,状态都是"正常" -- 查询2:父菜单是"人事管理"(ID: 2000) -- 查询3:管理员角色有9个权限 -- 查询4:权限标识格式规范 -- 查询5:路由配置正确 -- 查询6:无ID冲突 -- 查询7:有5个状态字典 -- 查询8:生成完整权限清单 - -❌ 常见问题: -1. 权限标识不一致 → 统一修改为 rs:party:retention:xxx -2. 角色权限未分配 → 执行权限分配SQL -3. 父菜单错误 → 更新parent_id为2000 -4. 字典数据缺失 → 执行字典插入SQL -*/ - diff --git a/backend/ruoyi-modules/ruoyi-rs/党籍保留补充亲属姓名字段脚本.sql b/backend/ruoyi-modules/ruoyi-rs/党籍保留补充亲属姓名字段脚本.sql deleted file mode 100644 index fd474f32..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/党籍保留补充亲属姓名字段脚本.sql +++ /dev/null @@ -1,102 +0,0 @@ --- ============================================ --- 出国党籍保留表 - 补充亲属姓名字段 --- ============================================ --- 版本: v1.2 --- 日期: 2025-10-10 --- 说明: 补充前端表单需要的亲属姓名字段(探望对象) --- 修改内容: --- 1. 增加 relative_name 字段(亲属姓名,varchar(50)) --- 2. 为亲属姓名字段添加索引(支持模糊查询) --- 背景: --- 前端表单中有 relativeName 字段并设置为必填,但数据库缺失对应字段 --- 导致前端填写的亲属姓名无法保存到数据库 --- ============================================ - -USE `hzdyjs_xg`; - --- ============================================ --- 第一部分:检查字段是否已存在 --- ============================================ - --- 查询字段是否存在 -SELECT - COLUMN_NAME AS '字段名', - COLUMN_TYPE AS '字段类型', - IS_NULLABLE AS '是否可空', - COLUMN_DEFAULT AS '默认值', - COLUMN_COMMENT AS '字段注释' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_party_membership_retention' - AND COLUMN_NAME = 'relative_name'; - --- ============================================ --- 第二部分:增加字段 --- ============================================ - --- 增加亲属姓名字段(放在 spouse 字段之后) -ALTER TABLE `rs_party_membership_retention` -ADD COLUMN `relative_name` varchar(50) NULL DEFAULT NULL COMMENT '亲属姓名(探望对象)' -AFTER `spouse`; - --- ============================================ --- 第三部分:添加索引优化 --- ============================================ - --- 为亲属姓名添加索引(支持模糊查询) -ALTER TABLE `rs_party_membership_retention` -ADD INDEX `idx_relative_name` (`relative_name`) USING BTREE COMMENT '亲属姓名索引(模糊查询)'; - --- ============================================ --- 第四部分:验证字段添加结果 --- ============================================ - --- 验证表结构(查看新增字段) -SELECT - COLUMN_NAME AS '字段名', - COLUMN_TYPE AS '字段类型', - IS_NULLABLE AS '是否可空', - COLUMN_DEFAULT AS '默认值', - COLUMN_COMMENT AS '字段注释', - ORDINAL_POSITION AS '字段位置' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_party_membership_retention' - AND COLUMN_NAME IN ('name', 'id_card', 'position', 'spouse', 'relative_name') -ORDER BY ORDINAL_POSITION; - --- 验证索引(查看新增索引) -SHOW INDEX FROM `rs_party_membership_retention` -WHERE Key_name = 'idx_relative_name'; - --- ============================================ --- 第五部分:数据验证(可选) --- ============================================ - --- 查看表的完整结构 -DESC `rs_party_membership_retention`; - --- 统计现有数据中亲属姓名字段的情况 -SELECT - COUNT(*) AS '总记录数', - COUNT(relative_name) AS '已填写亲属姓名数', - COUNT(*) - COUNT(relative_name) AS '未填写亲属姓名数' -FROM `rs_party_membership_retention` -WHERE deleted = '0'; - --- ============================================ --- 脚本执行完成 --- ============================================ --- 执行说明: --- 1. 本脚本会在 rs_party_membership_retention 表中添加 relative_name 字段 --- 2. 字段位置:在 spouse(配偶姓名)字段之后 --- 3. 字段类型:varchar(50),可为空 --- 4. 添加索引:idx_relative_name,支持模糊查询 --- 5. 执行验证查询确认字段添加成功 --- --- ⚠️ 注意事项: --- - 如果字段已存在,ALTER TABLE 语句会报错(可忽略) --- - 建议在测试环境先执行验证 --- - 执行前请确认数据库连接正确 --- ============================================ - diff --git a/backend/ruoyi-modules/ruoyi-rs/出国党籍保留权限菜单配置.sql b/backend/ruoyi-modules/ruoyi-rs/出国党籍保留权限菜单配置.sql deleted file mode 100644 index 5372671d..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/出国党籍保留权限菜单配置.sql +++ /dev/null @@ -1,718 +0,0 @@ --- ============================================ --- 出国党籍保留权限菜单配置脚本 --- ============================================ --- 版本: v2.0 (安全版本) --- 日期: 2025-10-05 --- 说明: 配置出国党籍保留功能的菜单和权限 --- --- ⚠️ 重要安全规则: --- 1. 人事管理模块菜单ID范围:2000-2028(共29个ID) --- 2. 已使用ID:2000-2009(基础人事管理菜单) --- 3. 本脚本使用ID:2010-2018(共9个ID) --- 4. 剩余可用ID:2019-2028(共10个ID,供未来扩展) --- ============================================ - --- ============================================ --- 第一部分:验证父菜单(人事管理) --- ============================================ --- 说明:确认"人事管理"菜单存在,ID应该是2000 - --- 查询人事管理菜单ID -SELECT - menu_id AS '菜单ID', - menu_name AS '菜单名称', - parent_id AS '父菜单ID', - path AS '路由地址', - CASE - WHEN menu_id = 2000 THEN '✅ ID正确' - ELSE '⚠️ ID异常,请检查' - END AS '验证结果' -FROM sys_menu -WHERE menu_name = '人事管理' AND deleted = '0'; - --- ⚠️ 如果上面查询结果为空或ID不是2000,请先执行RsInitController.updateMenu()接口 - --- ============================================ --- 第二部分:检查ID冲突 --- ============================================ --- 说明:确保2010-2018范围内的ID没有被占用 - -SELECT - menu_id AS '已占用的ID', - menu_name AS '菜单名称', - parent_id AS '父菜单ID', - '⚠️ ID冲突,需要调整' AS '警告' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND deleted = '0'; - --- ⚠️ 如果上面有查询结果,说明ID已被占用,请停止执行并调整ID范围 - --- ============================================ --- 第三部分:删除旧菜单(如果存在) --- ============================================ --- 说明:清理可能存在的旧配置,避免重复 - --- 先删除角色菜单关联 -DELETE FROM sys_role_menu -WHERE menu_id IN ( - SELECT menu_id FROM sys_menu - WHERE menu_name LIKE '%党籍保留%' AND deleted = '0' -); - --- 再删除菜单(逻辑删除) -UPDATE sys_menu -SET deleted = '1', update_time = NOW() -WHERE menu_name LIKE '%党籍保留%' AND deleted = '0'; - --- 如果需要物理删除(谨慎使用) --- DELETE FROM sys_menu WHERE menu_name LIKE '%党籍保留%'; - --- ============================================ --- 第四部分:创建主菜单 --- ============================================ --- 说明:创建"出国党籍保留"主菜单,使用固定ID 2010 - -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_dept, - create_by, - create_time, - update_by, - update_time, - remark, - tenant_id, - deleted -) VALUES ( - 2010, -- ✅ 固定ID:2010(安全范围内) - '出国党籍保留', -- 菜单名称 - 2000, -- ✅ 父菜单ID:2000(人事管理) - 5, -- 显示顺序 - 'party-membership-retention', -- 路由地址 - 'rs/party-membership-retention/index', -- 组件路径 - '', -- 路由参数 - 1, -- 是否为外链:0-是,1-否 - 0, -- 是否缓存:0-缓存,1-不缓存 - 'C', -- 菜单类型:M-目录,C-菜单,F-按钮 - '0', -- 显示状态:0-显示,1-隐藏 - '0', -- 菜单状态:0-正常,1-停用 - 'rs:partyretention:list', -- 权限标识(三段式) - 'peoples', -- 菜单图标 - 103, -- 创建部门 - 1, -- 创建者 - NOW(), -- 创建时间 - NULL, -- 更新者 - NULL, -- 更新时间 - '出国党籍保留管理菜单', -- 备注 - '000000', -- 租户编号 - '0' -- 删除标志 -); - --- ============================================ --- 第五部分:创建功能权限按钮 --- ============================================ --- 说明:为主菜单添加各种操作权限,使用固定ID 2011-2018 - --- 5.1 查询权限 (ID: 2011) -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_dept, - create_by, - create_time, - remark, - tenant_id, - deleted -) VALUES ( - 2011, -- ✅ 固定ID:2011 - '党籍保留查询', - 2010, -- ✅ 父菜单ID:2010(出国党籍保留主菜单) - 1, - '#', - '', - '', - 1, - 0, - 'F', - '0', - '0', - 'rs:partyretention:query', - '#', - 103, - 1, - NOW(), - '查询党籍保留详情权限', - '000000', - '0' -); - --- 5.2 新增权限 (ID: 2012) -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_dept, - create_by, - create_time, - remark, - tenant_id, - deleted -) VALUES ( - 2012, -- ✅ 固定ID:2012 - '党籍保留新增', - 2010, - 2, - '#', - '', - '', - 1, - 0, - 'F', - '0', - '0', - 'rs:partyretention:add', - '#', - 103, - 1, - NOW(), - '新增党籍保留申请权限', - '000000', - '0' -); - --- 5.3 修改权限 (ID: 2013) -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_dept, - create_by, - create_time, - remark, - tenant_id, - deleted -) VALUES ( - 2013, -- ✅ 固定ID:2013 - '党籍保留修改', - 2010, - 3, - '#', - '', - '', - 1, - 0, - 'F', - '0', - '0', - 'rs:partyretention:edit', - '#', - 103, - 1, - NOW(), - '修改党籍保留申请权限', - '000000', - '0' -); - --- 5.4 删除权限 (ID: 2014) -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_dept, - create_by, - create_time, - remark, - tenant_id, - deleted -) VALUES ( - 2014, -- ✅ 固定ID:2014 - '党籍保留删除', - 2010, - 4, - '#', - '', - '', - 1, - 0, - 'F', - '0', - '0', - 'rs:partyretention:remove', - '#', - 103, - 1, - NOW(), - '删除党籍保留申请权限(仅草稿)', - '000000', - '0' -); - --- 5.5 导出权限 (ID: 2015) -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_dept, - create_by, - create_time, - remark, - tenant_id, - deleted -) VALUES ( - 2015, -- ✅ 固定ID:2015 - '党籍保留导出', - 2010, - 5, - '#', - '', - '', - 1, - 0, - 'F', - '0', - '0', - 'rs:partyretention:export', - '#', - 103, - 1, - NOW(), - '导出党籍保留列表权限', - '000000', - '0' -); - --- 5.6 提交权限 (ID: 2016) -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_dept, - create_by, - create_time, - remark, - tenant_id, - deleted -) VALUES ( - 2016, -- ✅ 固定ID:2016 - '党籍保留提交', - 2010, - 6, - '#', - '', - '', - 1, - 0, - 'F', - '0', - '0', - 'rs:partyretention:submit', - '#', - 103, - 1, - NOW(), - '提交党籍保留申请权限', - '000000', - '0' -); - --- 5.7 撤回权限 (ID: 2017) -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_dept, - create_by, - create_time, - remark, - tenant_id, - deleted -) VALUES ( - 2017, -- ✅ 固定ID:2017 - '党籍保留撤回', - 2010, - 7, - '#', - '', - '', - 1, - 0, - 'F', - '0', - '0', - 'rs:partyretention:withdraw', - '#', - 103, - 1, - NOW(), - '撤回党籍保留申请权限', - '000000', - '0' -); - --- 5.8 审批权限 (ID: 2018) -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_dept, - create_by, - create_time, - remark, - tenant_id, - deleted -) VALUES ( - 2018, -- ✅ 固定ID:2018 - '党籍保留审批', - 2010, - 8, - '#', - '', - '', - 1, - 0, - 'F', - '0', - '0', - 'rs:partyretention:approve', - '#', - 103, - 1, - NOW(), - '审批党籍保留申请权限', - '000000', - '0' -); - --- ============================================ --- 第六部分:为管理员角色分配权限 --- ============================================ --- 说明:将新创建的菜单权限(ID 2010-2018)分配给管理员角色 - --- 查询管理员角色ID(验证) -SELECT role_id, role_name, role_key -FROM sys_role -WHERE role_key = 'admin' AND deleted = '0'; - --- 为管理员角色分配所有党籍保留权限(ID 2010-2018) -INSERT IGNORE INTO sys_role_menu (role_id, menu_id) -SELECT 1, 2010 UNION ALL -- 主菜单 -SELECT 1, 2011 UNION ALL -- 查询权限 -SELECT 1, 2012 UNION ALL -- 新增权限 -SELECT 1, 2013 UNION ALL -- 修改权限 -SELECT 1, 2014 UNION ALL -- 删除权限 -SELECT 1, 2015 UNION ALL -- 导出权限 -SELECT 1, 2016 UNION ALL -- 提交权限 -SELECT 1, 2017 UNION ALL -- 撤回权限 -SELECT 1, 2018; -- 审批权限 - --- 说明:role_id = 1 通常是管理员角色,如果不是请修改为实际的管理员角色ID - --- ============================================ --- 第七部分:验证配置 --- ============================================ - --- 7.1 验证菜单ID范围(确保在2010-2018范围内) -SELECT - menu_id AS '菜单ID', - menu_name AS '菜单名称', - parent_id AS '父菜单ID', - menu_type AS '类型', - perms AS '权限标识', - CASE - WHEN menu_id BETWEEN 2010 AND 2018 THEN '✅ ID安全' - ELSE '⚠️ ID超出范围' - END AS 'ID验证', - CASE menu_type - WHEN 'M' THEN '目录' - WHEN 'C' THEN '菜单' - WHEN 'F' THEN '按钮' - END AS '类型说明', - CASE status - WHEN '0' THEN '✅ 正常' - WHEN '1' THEN '❌ 停用' - END AS '状态' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND deleted = '0' -ORDER BY menu_id; - --- 7.2 验证权限数量(应该是9个:1个主菜单 + 8个按钮) -SELECT - '主菜单(C)' AS '类型', - COUNT(*) AS '数量', - CASE - WHEN COUNT(*) = 1 THEN '✅ 正确' - ELSE '⚠️ 异常' - END AS '验证结果' -FROM sys_menu -WHERE menu_id = 2010 - AND menu_type = 'C' - AND deleted = '0' -UNION ALL -SELECT - '功能按钮(F)' AS '类型', - COUNT(*) AS '数量', - CASE - WHEN COUNT(*) = 8 THEN '✅ 正确' - ELSE '⚠️ 异常' - END AS '验证结果' -FROM sys_menu -WHERE menu_id BETWEEN 2011 AND 2018 - AND menu_type = 'F' - AND deleted = '0'; - --- 7.3 验证父子关系(所有按钮的父菜单应该是2010) -SELECT - menu_id AS '菜单ID', - menu_name AS '菜单名称', - parent_id AS '父菜单ID', - CASE - WHEN parent_id = 2010 THEN '✅ 正确' - WHEN parent_id = 2000 AND menu_id = 2010 THEN '✅ 正确' - ELSE '⚠️ 父菜单ID错误' - END AS '父子关系验证' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND deleted = '0' -ORDER BY menu_id; - --- 7.4 验证角色权限分配(管理员应该有9个权限) -SELECT - r.role_id AS '角色ID', - r.role_name AS '角色名称', - COUNT(DISTINCT rm.menu_id) AS '分配权限数', - CASE - WHEN COUNT(DISTINCT rm.menu_id) = 9 THEN '✅ 完整' - ELSE '⚠️ 不完整' - END AS '验证结果' -FROM sys_role r -LEFT JOIN sys_role_menu rm ON r.role_id = r.role_id -WHERE rm.menu_id BETWEEN 2010 AND 2018 - AND r.deleted = '0' -GROUP BY r.role_id, r.role_name; - --- 7.5 验证完整权限列表 -SELECT - m.menu_id AS 'ID', - m.menu_name AS '权限名称', - m.perms AS '权限标识', - m.menu_type AS '类型', - m.parent_id AS '父ID', - m.order_num AS '排序', - CASE m.status - WHEN '0' THEN '✅ 启用' - WHEN '1' THEN '❌ 停用' - END AS '状态' -FROM sys_menu m -WHERE m.menu_id BETWEEN 2010 AND 2018 - AND m.deleted = '0' -ORDER BY m.menu_id; - --- 7.6 验证不与其他模块冲突 -SELECT - menu_id AS '冲突的菜单ID', - menu_name AS '菜单名称', - parent_id AS '父菜单ID', - '⚠️ 与党籍保留模块ID冲突' AS '警告' -FROM sys_menu -WHERE menu_id BETWEEN 2010 AND 2018 - AND menu_name NOT LIKE '%党籍保留%' - AND deleted = '0'; - --- ============================================ --- 第八部分:执行说明和安全保障 --- ============================================ -/* -📋 执行步骤(按顺序执行): - -1️⃣ 【验证阶段】执行第一部分和第二部分 - - 确认"人事管理"菜单ID是2000 - - 确认ID范围2010-2018没有被占用 - - ⚠️ 如果有冲突,请停止执行并调整ID范围 - -2️⃣ 【清理阶段】执行第三部分 - - 清理可能存在的旧配置 - - 避免重复数据 - -3️⃣ 【创建阶段】执行第四部分和第五部分 - - 创建主菜单(ID: 2010) - - 创建8个功能权限按钮(ID: 2011-2018) - -4️⃣ 【分配阶段】执行第六部分 - - 为管理员角色分配所有权限 - -5️⃣ 【验证阶段】执行第七部分 - - 验证所有配置是否正确 - - 检查是否有冲突 - -🔒 安全保障: -✅ 固定ID范围:2010-2018(共9个ID) -✅ 父菜单ID:2000(人事管理) -✅ 不影响其他模块:ID范围独立 -✅ 可重复执行:使用INSERT IGNORE避免重复 -✅ 逻辑删除:旧数据标记为deleted='1' - -📊 菜单ID分配表: -┌──────────┬────────────────┬──────────┬─────────────────────────┐ -│ 菜单ID │ 菜单名称 │ 类型 │ 权限标识 │ -├──────────┼────────────────┼──────────┼─────────────────────────┤ -│ 2010 │ 出国党籍保留 │ C(菜单) │ rs:partyretention:list │ -│ 2011 │ 党籍保留查询 │ F(按钮) │ rs:partyretention:query │ -│ 2012 │ 党籍保留新增 │ F(按钮) │ rs:partyretention:add │ -│ 2013 │ 党籍保留修改 │ F(按钮) │ rs:partyretention:edit │ -│ 2014 │ 党籍保留删除 │ F(按钮) │ rs:partyretention:remove │ -│ 2015 │ 党籍保留导出 │ F(按钮) │ rs:partyretention:export │ -│ 2016 │ 党籍保留提交 │ F(按钮) │ rs:partyretention:submit │ -│ 2017 │ 党籍保留撤回 │ F(按钮) │ rs:partyretention:withdraw│ -│ 2018 │ 党籍保留审批 │ F(按钮) │ rs:partyretention:approve │ -└──────────┴────────────────┴──────────┴─────────────────────────┘ - -📌 人事管理模块ID使用情况: -- 已使用:2000-2009(基础人事管理,10个ID) -- 本次使用:2010-2018(党籍保留,9个ID) -- 剩余可用:2019-2028(未来扩展,10个ID) -- 总计:2000-2028(共29个ID) - -🔑 权限标识说明(三段式:模块:功能:操作): -- rs:partyretention:list - 列表查询(主菜单权限) -- rs:partyretention:query - 详情查询 -- rs:partyretention:add - 新增申请 -- rs:partyretention:edit - 修改申请 -- rs:partyretention:remove - 删除申请(仅草稿) -- rs:partyretention:export - 导出列表 -- rs:partyretention:submit - 提交申请 -- rs:partyretention:withdraw - 撤回申请 -- rs:partyretention:approve - 审批申请 - -🌐 菜单路径说明: -- 路由地址: party-membership-retention -- 组件路径: rs/party-membership-retention/index -- 完整访问路径: http://localhost/rs/party-membership-retention -- 父菜单: 人事管理(ID: 2000) - -⚠️ 重要提示: -1. 本脚本只修改人事管理模块(ID 2000-2028)的数据 -2. 不会影响其他模块的菜单和权限 -3. 使用固定ID,避免随机ID导致的冲突 -4. 可以安全地重复执行(使用INSERT IGNORE) -5. 生产环境执行前请先在测试环境验证 - -✅ 脚本版本:v2.0(安全版本) -✅ 创建日期:2025-10-05 -✅ 适用范围:人事管理模块(ruoyi-rs) -✅ 影响范围:仅限ID 2010-2018的菜单数据 - -*/ - --- ============================================ --- 脚本执行完成 --- ============================================ --- 执行完成后,请登录系统验证: --- 1. 菜单是否显示在"人事管理"下 --- 2. 点击菜单是否能正常访问页面 --- 3. 各个功能按钮是否正常显示 --- 4. 权限控制是否生效 --- ============================================ diff --git a/backend/ruoyi-modules/ruoyi-rs/出国党籍保留表创建脚本.sql b/backend/ruoyi-modules/ruoyi-rs/出国党籍保留表创建脚本.sql deleted file mode 100644 index e24687f4..00000000 --- a/backend/ruoyi-modules/ruoyi-rs/出国党籍保留表创建脚本.sql +++ /dev/null @@ -1,225 +0,0 @@ --- ============================================ --- 出国党籍保留表创建脚本 --- ============================================ --- 版本: v1.0 --- 日期: 2025-09-30 --- 说明: 用于教职工出国党籍保留申请管理 --- ============================================ - --- ============================================ --- 第一部分:删除旧表(谨慎操作) --- ============================================ -DROP TABLE IF EXISTS `rs_party_membership_retention`; - --- ============================================ --- 第二部分:创建表结构 --- ============================================ -CREATE TABLE `rs_party_membership_retention` ( - -- ========== 主键和关联字段 ========== - `retention_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '党籍保留ID(主键)', - `application_id` bigint(20) NULL DEFAULT NULL COMMENT '关联出国申请ID', - `registration_id` bigint(20) NULL DEFAULT NULL COMMENT '关联出国登记ID', - - -- ========== 基本信息字段 ========== - `name` varchar(50) NOT NULL COMMENT '我支部同志(姓名)', - `spouse` varchar(50) NULL DEFAULT NULL COMMENT '偕配偶姓名', - - -- ========== 出国时间字段 ========== - `travel_start_date` date NOT NULL COMMENT '拟出国开始日期', - `travel_end_date` date NOT NULL COMMENT '拟出国结束日期', - `stay_days` int(11) NULL DEFAULT NULL COMMENT '在外停留天数', - - -- ========== 出国目的和地点字段 ========== - `destination` varchar(100) NOT NULL COMMENT '因私赴XX(国家或地区)', - `purpose` text NULL COMMENT '出国目的详细说明', - `travel_reason` varchar(50) NULL DEFAULT NULL COMMENT '出国原因(简要)', - - -- ========== 费用信息字段 ========== - `self_funded` char(1) NOT NULL DEFAULT '1' COMMENT '费用自理:1-是,0-否', - - -- ========== 审批信息字段 ========== - `request_approval` text NULL COMMENT '请求批准说明(固定文本)', - `final_approval` text NULL COMMENT '妥否请审批(固定文本)', - - -- ========== 党支部信息字段 ========== - `party_branch` varchar(100) NOT NULL COMMENT 'XXXX党支部', - `application_date` date NOT NULL COMMENT '申请日期', - - -- ========== 状态和时间字段 ========== - `status` char(1) NOT NULL DEFAULT '0' COMMENT '申请状态:0-草稿,1-已提交,2-已批准,3-已拒绝,4-已撤回', - `application_time` datetime NULL DEFAULT NULL COMMENT '申请时间', - - -- ========== 备注字段 ========== - `remarks` text NULL COMMENT '备注', - - -- ========== 系统字段 ========== - `tenant_id` varchar(20) NULL DEFAULT '000000' COMMENT '租户编号', - `deleted` char(1) NULL DEFAULT '0' COMMENT '删除标志:0-未删除,1-已删除', - `create_dept` bigint(20) NULL DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) NULL DEFAULT NULL COMMENT '创建者', - `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) NULL DEFAULT NULL COMMENT '更新者', - `update_time` datetime NULL DEFAULT NULL COMMENT '更新时间', - - -- ========== 主键约束 ========== - PRIMARY KEY (`retention_id`) USING BTREE, - - -- ========== 索引设计 ========== - INDEX `idx_application_id` (`application_id`) USING BTREE COMMENT '关联申请ID索引', - INDEX `idx_registration_id` (`registration_id`) USING BTREE COMMENT '关联登记ID索引', - INDEX `idx_name` (`name`) USING BTREE COMMENT '姓名索引(支持模糊查询)', - INDEX `idx_destination` (`destination`) USING BTREE COMMENT '目的地索引', - INDEX `idx_status` (`status`) USING BTREE COMMENT '状态索引(用于筛选)', - INDEX `idx_application_time` (`application_time`) USING BTREE COMMENT '申请时间索引(用于排序)', - INDEX `idx_tenant_deleted` (`tenant_id`, `deleted`) USING BTREE COMMENT '多租户隔离索引', - INDEX `idx_create_time` (`create_time`) USING BTREE COMMENT '创建时间索引' - -) ENGINE=InnoDB - DEFAULT CHARSET=utf8mb4 - COLLATE=utf8mb4_unicode_ci - COMMENT='出国党籍保留表'; - --- ============================================ --- 第三部分:插入字典数据 --- ============================================ - --- 3.1 党籍保留状态字典类型 -INSERT IGNORE INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark) -VALUES (UNIX_TIMESTAMP() * 1000 + FLOOR(RAND() * 1000) + 200, '000000', '出国党籍保留状态', 'rs_party_retention_status', 1, NOW(), '出国党籍保留申请的状态类型'); - --- 3.2 党籍保留状态字典值 -INSERT IGNORE INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark) -VALUES -(UNIX_TIMESTAMP() * 1000 + FLOOR(RAND() * 1000) + 201, '000000', 1, '草稿', '0', 'rs_party_retention_status', '', 'info', 'N', 1, NOW(), '初始创建状态,可编辑'), -(UNIX_TIMESTAMP() * 1000 + FLOOR(RAND() * 1000) + 202, '000000', 2, '已提交', '1', 'rs_party_retention_status', '', 'warning', 'N', 1, NOW(), '提交后等待党支部审批'), -(UNIX_TIMESTAMP() * 1000 + FLOOR(RAND() * 1000) + 203, '000000', 3, '已批准', '2', 'rs_party_retention_status', '', 'success', 'N', 1, NOW(), '党支部审批通过'), -(UNIX_TIMESTAMP() * 1000 + FLOOR(RAND() * 1000) + 204, '000000', 4, '已拒绝', '3', 'rs_party_retention_status', '', 'danger', 'N', 1, NOW(), '党支部审批拒绝'), -(UNIX_TIMESTAMP() * 1000 + FLOOR(RAND() * 1000) + 205, '000000', 5, '已撤回', '4', 'rs_party_retention_status', '', 'warning', 'N', 1, NOW(), '用户主动撤回申请'); - --- ============================================ --- 第四部分:插入测试数据(开发环境) --- ============================================ --- ⚠️ 注意:生产环境部署时请删除此部分 - --- 测试数据1:草稿状态 -INSERT INTO `rs_party_membership_retention` ( - `retention_id`, `application_id`, `registration_id`, `name`, `spouse`, - `travel_start_date`, `travel_end_date`, `stay_days`, - `destination`, `purpose`, `travel_reason`, - `self_funded`, `request_approval`, `final_approval`, - `party_branch`, `application_date`, - `status`, `application_time`, - `remarks`, - `tenant_id`, `deleted`, `create_by`, `create_time` -) VALUES ( - 1, NULL, NULL, '张三', '李华', - '2025-10-15', '2025-12-15', 62, - '美国', '探望在加州大学伯克利分校留学的女儿', '探亲', - '1', '根据因私出国(境)党籍管理有关规定,特请批准同意其在出国(境)外期间保留党籍。', '妥否,请审批。', - '计算机科学与技术学院党支部', '2025-09-30', - '0', NULL, - '首次出国探亲,停留时间较长', - '000000', '0', 1, NOW() -); - --- 测试数据2:已提交状态 -INSERT INTO `rs_party_membership_retention` ( - `retention_id`, `application_id`, `registration_id`, `name`, `spouse`, - `travel_start_date`, `travel_end_date`, `stay_days`, - `destination`, `purpose`, `travel_reason`, - `self_funded`, `request_approval`, `final_approval`, - `party_branch`, `application_date`, - `status`, `application_time`, - `remarks`, - `tenant_id`, `deleted`, `create_by`, `create_time` -) VALUES ( - 2, NULL, NULL, '王五', NULL, - '2025-11-01', '2026-01-30', 91, - '英国', '探望在伦敦大学学院工作的配偶', '探亲', - '1', '根据因私出国(境)党籍管理有关规定,特请批准同意其在出国(境)外期间保留党籍。', '妥否,请审批。', - '电子信息工程学院党支部', '2025-09-29', - '1', '2025-09-29 14:30:00', - '配偶在英国工作,前往探望', - '000000', '0', 1, NOW() -); - --- 测试数据3:已批准状态 -INSERT INTO `rs_party_membership_retention` ( - `retention_id`, `application_id`, `registration_id`, `name`, `spouse`, - `travel_start_date`, `travel_end_date`, `stay_days`, - `destination`, `purpose`, `travel_reason`, - `self_funded`, `request_approval`, `final_approval`, - `party_branch`, `application_date`, - `status`, `application_time`, - `remarks`, - `tenant_id`, `deleted`, `create_by`, `create_time` -) VALUES ( - 3, NULL, NULL, '李四', '陈芳', - '2025-09-01', '2025-11-30', 91, - '澳大利亚', '探望在悉尼大学定居的儿子', '探亲', - '1', '根据因私出国(境)党籍管理有关规定,特请批准同意其在出国(境)外期间保留党籍。', '妥否,请审批。', - '机械工程学院党支部', '2025-08-25', - '2', '2025-08-25 10:00:00', - '儿子在澳大利亚定居,前往探亲', - '000000', '0', 1, NOW() -); - --- ============================================ --- 第五部分:验证数据 --- ============================================ - --- 验证表创建 -SELECT 'Table Created Successfully' AS Status, COUNT(*) AS FieldCount -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_party_membership_retention'; - --- 验证表结构 -DESC `rs_party_membership_retention`; - --- 验证索引 -SHOW INDEX FROM `rs_party_membership_retention`; - --- 验证字典数据 -SELECT - dict_label AS '状态名称', - dict_value AS '状态值', - list_class AS '标签样式', - remark AS '说明' -FROM sys_dict_data -WHERE dict_type = 'rs_party_retention_status' -ORDER BY dict_sort; - --- 验证测试数据 -SELECT - retention_id AS 'ID', - name AS '姓名', - destination AS '目的地', - stay_days AS '停留天数', - CASE status - WHEN '0' THEN '草稿' - WHEN '1' THEN '已提交' - WHEN '2' THEN '已批准' - WHEN '3' THEN '已拒绝' - WHEN '4' THEN '已撤回' - END AS '状态', - party_branch AS '党支部' -FROM `rs_party_membership_retention` -WHERE deleted = '0' -ORDER BY retention_id; - --- ============================================ --- 脚本执行完成 --- ============================================ --- 执行说明: --- 1. 本脚本会删除已存在的 rs_party_membership_retention 表 --- 2. 创建新的表结构,包含25个字段和9个索引 --- 3. 插入党籍保留状态字典(5条) --- 4. 插入测试数据(3条) --- 5. 执行验证查询 --- --- ⚠️ 注意事项: --- - 生产环境部署时,请删除测试数据部分 --- - 如表已存在且包含重要数据,请先备份 --- - 确认数据库字符集为 utf8mb4 --- ============================================ \ No newline at end of file diff --git a/backend/script/sql/ry_workflow.sql b/backend/script/sql/ry_workflow.sql index 5e055d2e..40b05b7e 100644 --- a/backend/script/sql/ry_workflow.sql +++ b/backend/script/sql/ry_workflow.sql @@ -1,104 +1,4 @@ --- ---------------------------- --- 0、warm-flow-all.sql,地址:https://gitee.com/dromara/warm-flow/blob/master/sql/mysql/warm-flow-all.sql --- ---------------------------- -CREATE TABLE `flow_definition` -( - `id` bigint NOT NULL COMMENT '主键id', - `flow_code` varchar(40) NOT NULL COMMENT '流程编码', - `flow_name` varchar(100) NOT NULL COMMENT '流程名称', - `category` varchar(100) DEFAULT NULL COMMENT '流程类别', - `version` varchar(20) NOT NULL COMMENT '流程版本', - `is_publish` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否发布(0未发布 1已发布 9失效)', - `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', - `form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径', - `activity_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)', - `listener_type` varchar(100) DEFAULT NULL COMMENT '监听器类型', - `listener_path` varchar(400) DEFAULT NULL COMMENT '监听器路径', - `ext` varchar(500) DEFAULT NULL COMMENT '业务详情 存业务表对象json字符串', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', - `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB COMMENT ='流程定义表'; -CREATE TABLE `flow_node` -( - `id` bigint NOT NULL COMMENT '主键id', - `node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', - `definition_id` bigint NOT NULL COMMENT '流程定义id', - `node_code` varchar(100) NOT NULL COMMENT '流程节点编码', - `node_name` varchar(100) DEFAULT NULL COMMENT '流程节点名称', - `permission_flag` varchar(200) DEFAULT NULL COMMENT '权限标识(权限类型:权限标识,可以多个,用逗号隔开)', - `node_ratio` decimal(6, 3) DEFAULT NULL COMMENT '流程签署比例值', - `coordinate` varchar(100) DEFAULT NULL COMMENT '坐标', - `any_node_skip` varchar(100) DEFAULT NULL COMMENT '任意结点跳转', - `listener_type` varchar(100) DEFAULT NULL COMMENT '监听器类型', - `listener_path` varchar(400) DEFAULT NULL COMMENT '监听器路径', - `handler_type` varchar(100) DEFAULT NULL COMMENT '处理器类型', - `handler_path` varchar(400) DEFAULT NULL COMMENT '处理器路径', - `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', - `form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径', - `version` varchar(20) NOT NULL COMMENT '版本', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `ext` text COMMENT '扩展属性', - `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', - `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB COMMENT ='流程节点表'; - -CREATE TABLE `flow_skip` -( - `id` bigint NOT NULL COMMENT '主键id', - `definition_id` bigint NOT NULL COMMENT '流程定义id', - `now_node_code` varchar(100) NOT NULL COMMENT '当前流程节点的编码', - `now_node_type` tinyint(1) DEFAULT NULL COMMENT '当前节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', - `next_node_code` varchar(100) NOT NULL COMMENT '下一个流程节点的编码', - `next_node_type` tinyint(1) DEFAULT NULL COMMENT '下一个节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', - `skip_name` varchar(100) DEFAULT NULL COMMENT '跳转名称', - `skip_type` varchar(40) DEFAULT NULL COMMENT '跳转类型(PASS审批通过 REJECT退回)', - `skip_condition` varchar(200) DEFAULT NULL COMMENT '跳转条件', - `coordinate` varchar(100) DEFAULT NULL COMMENT '坐标', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', - `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB COMMENT ='节点跳转关联表'; - -CREATE TABLE `flow_instance` -( - `id` bigint NOT NULL COMMENT '主键id', - `definition_id` bigint NOT NULL COMMENT '对应flow_definition表的id', - `business_id` varchar(40) NOT NULL COMMENT '业务id', - `node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', - `node_code` varchar(40) NOT NULL COMMENT '流程节点编码', - `node_name` varchar(100) DEFAULT NULL COMMENT '流程节点名称', - `variable` text COMMENT '任务变量', - `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', - `activity_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '流程激活状态(0挂起 1激活)', - `def_json` text COMMENT '流程定义json', - `create_by` varchar(64) DEFAULT '' COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - `ext` varchar(500) DEFAULT NULL COMMENT '扩展字段,预留给业务系统使用', - `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', - `tenant_id` varchar(40) DEFAULT NULL COMMENT '租户id', - PRIMARY KEY (`id`) USING BTREE -) ENGINE = InnoDB COMMENT ='流程实例表'; - -CREATE TABLE `flow_task` -( - `id` bigint NOT NULL COMMENT '主键id', - `definition_id` bigint NOT NULL COMMENT '对应flow_definition表的id', - `instance_id` bigint NOT NULL COMMENT '对应flow_instance表的id', - `node_code` varchar(100) NOT NULL COMMENT '节点编码', - `node_name` varchar(100) DEFAULT NULL COMMENT '节点名称', - `node_type` tinyint(1) NOT NULL COMMENT '节点类型(0开始节点 1中间节点 2结束节点 3互斥网关 4并行网关)', - `flow_status` varchar(20) NOT NULL COMMENT '流程状态(0待提交 1审批中 2审批通过 4终止 5作废 6撤销 8已完成 9已退回 10失效 11拿回)', - `form_custom` char(1) DEFAULT 'N' COMMENT '审批表单是否自定义(Y是 N否)', - `form_path` varchar(100) DEFAULT NULL COMMENT '审批表单路径', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `del_flag` char(1) DEFAULT '0' COMMENT '删除标志', diff --git a/check_attachment_data.sql b/check_attachment_data.sql deleted file mode 100644 index 82ed71cf..00000000 --- a/check_attachment_data.sql +++ /dev/null @@ -1,8 +0,0 @@ --- 检查出国申请ID=31的附件数据 -SELECT * FROM rs_overseas_application_attachment WHERE application_id = 31 AND deleted = '0'; - --- 检查出国申请ID=31的基本信息 -SELECT application_id, applicant_name, stamped_file_oss_id, party_retention_file_oss_id FROM rs_overseas_application WHERE application_id = 31; - --- 检查所有出国申请附件表的数据 -SELECT application_id, attachment_id, attachment_title, file_name, upload_time FROM rs_overseas_application_attachment WHERE deleted = '0' ORDER BY application_id, upload_time DESC; diff --git a/docs/开发文档/API/出国申请管理API.md b/docs/开发文档/API/出国申请管理API.md index 7f16f5af..2a92d11a 100644 --- a/docs/开发文档/API/出国申请管理API.md +++ b/docs/开发文档/API/出国申请管理API.md @@ -95,6 +95,7 @@ | 取消申请 | PUT | `/rs/overseas/application/cancel/{id}` | `rs:overseas:application:cancel` | 取消出国申请 | | 审批申请 | POST | `/rs/overseas/application/approve` | `rs:overseas:application:approve` | 审批出国申请(通过/拒绝) | | 修复记录 | POST | `/rs/overseas/application/fix/records` | `rs:overseas:application:approve` | 修复已审批通过但未创建记录的申请 | +| **下载党籍保留PDF模板** | GET | `/rs/overseas/record/downloadPartyRetention` | `rs:overseas:query` | **新增**:下载党籍保留申请PDF空白模板(线下手工填写)(2025-10-17) | #### 出国记录管理接口 @@ -799,5 +800,115 @@ form-data: --- +### 21. 下载党籍保留申请PDF空白模板 + +**接口路径**:`GET /rs/overseas/record/downloadPartyRetention` + +#### 功能说明 + +生成标准的党籍保留申请PDF空白模板,用户下载后完全通过线下手工填写所有内容。 + +#### 权限要求 + +- **权限标识**:`rs:overseas:query` +- **权限说明**:需要有查询出国记录的权限 + +#### 请求参数 + +**无需参数**:接口不需要任何参数,直接下载空白模板。 + +#### 请求示例 + +```http +GET /api/rs/overseas/record/downloadPartyRetention +Authorization: Bearer {token} +clientid: {clientid} +referer: {referer} +``` + +#### 响应说明 + +**成功响应**: + +- **Content-Type**: `application/pdf` +- **Content-Disposition**: `attachment; filename="党籍保留申请_yyyyMMdd.pdf"` +- **Body**: PDF文件二进制流 + +**失败响应**: + +```json +{ + "code": 500, + "msg": "PDF生成失败: 错误信息" +} +``` + +#### PDF模板内容 + +PDF模板包含以下空白填写区域(完全手工填写): + +1. **标题**:关于因私赴 ______________ __________保留党籍的申请 +2. **支部信息**:______________________ 支部: +3. **申请人信息**: + - 本人 _______________(____________________),现任 _______________ + - 计划于 ____________________ +4. **出国信息**: + - 至 ____________________ 偕配偶 _______________ + - 因私赴 _______________(国家 或地区)探望 在 + - _________________________ 的工作(留学、定居)的 __________ + - 在外停留 __________ 天(含路、抵返国境当 +5. **申请内容**:日)。现申请保留期间暂缓籍。 +6. **结束语**:特此申请,望予批准。 +7. **签名区域**: + - 申请人:_______________ + - 申请日期: ____________________ + +#### 使用流程 + +**方式一:申请表单中下载** +1. 用户在新增/编辑申请时,系统检测到是党员且出国天数>30天 +2. 系统显示党籍保留附件上传区域 +3. 用户点击"下载党籍保留申请PDF模板"按钮 +4. 系统生成并下载空白PDF模板 +5. 用户线下手工填写所有内容、打印、盖章 +6. 扫描成PDF后上传到系统 + +**方式二:申请详情中下载** +1. 用户查看申请详情(党员且出国天数>30天) +2. 详情弹窗底部显示"下载党籍保留申请"按钮 +3. 点击按钮下载空白PDF模板 +4. 线下手工填写、打印、盖章后上传 + +#### 技术实现 + +- 使用 **iText 5.5.13** PDF生成库 +- 支持中文字体(STSong-Light) +- A4纸张大小,标准页边距 +- 所有内容区域使用下划线标识,无预填充数据 +- 标准模板格式,适用于所有党籍保留申请场景 + +#### 注意事项 + +- **模板为完全空白**,所有内容需线下手工填写 +- 不需要申请ID或记录ID,任何时候都可以下载 +- 下载后需打印、手写填写、盖章后扫描上传 +- 适用于中共党员且出国天数>30天的情况 +- **按钮显示位置**: + - 申请表单:党员且出国天数>30天时自动显示 + - 申请详情:符合条件时在弹窗底部显示绿色按钮 +- **前端实现**:必须使用 `request` 工具而不是 `fetch` API,以确保包含所有必要的认证请求头 + +#### 错误处理 + +| HTTP状态码 | 错误信息 | 处理建议 | +| ---------- | ---------------------- | ---------------------------- | +| 500 | PDF生成失败 | 检查服务器日志,联系技术支持 | + +--- + **📞 技术支持**:如有疑问请联系开发团队 -**📚 相关文档**:参考项目API文档基础规范和开发规范 \ No newline at end of file +**📚 相关文档**:参考项目API文档基础规范和开发规范 +**🆕 更新日志**: +- 2025-10-17 新增党籍保留PDF空白模板下载功能(线下手工填写) +- 接口路径调整:从 `/application/downloadPartyRetentionTemplate/{id}` 改为 `/record/downloadPartyRetention` +- 模板类型变更:从预填充模板改为完全空白模板 \ No newline at end of file diff --git a/docs/设计文档/database/rs_overseas_application_attachment.sql b/docs/设计文档/database/rs_overseas_application_attachment.sql deleted file mode 100644 index 029a9d15..00000000 --- a/docs/设计文档/database/rs_overseas_application_attachment.sql +++ /dev/null @@ -1,58 +0,0 @@ --- ============================================== --- 出国申请附件表设计 --- 表名: rs_overseas_application_attachment --- 说明: 用于管理出国申请的附件信息(支持标题和内容说明) --- 创建时间: 2025-10-10 --- ============================================== - --- 创建附件表 -DROP TABLE IF EXISTS `rs_overseas_application_attachment`; -CREATE TABLE `rs_overseas_application_attachment` ( - `attachment_id` bigint(20) NOT NULL COMMENT '附件ID(主键)', - `application_id` bigint(20) NOT NULL COMMENT '出国申请ID(关联rs_overseas_application)', - - -- 附件基本信息 - `attachment_title` varchar(200) NOT NULL COMMENT '附件标题', - `attachment_content` text COMMENT '附件内容说明', - - -- 文件信息 - `file_name` varchar(200) NOT NULL COMMENT '文件名称', - `file_original_name` varchar(200) NOT NULL COMMENT '文件原始名称', - `file_path` varchar(500) NOT NULL COMMENT '文件存储路径/OSS URL', - `file_size` bigint(20) DEFAULT NULL COMMENT '文件大小(字节)', - `file_type` varchar(50) DEFAULT NULL COMMENT '文件类型(如pdf、jpg、png、doc等)', - `mime_type` varchar(100) DEFAULT NULL COMMENT 'MIME类型', - - -- 上传信息 - `upload_time` datetime DEFAULT NULL COMMENT '上传时间', - `upload_user_id` bigint(20) DEFAULT NULL COMMENT '上传人ID', - `upload_user_name` varchar(50) DEFAULT NULL COMMENT '上传人姓名', - - -- 系统字段 - `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户编号', - `deleted` char(1) DEFAULT '0' COMMENT '删除标志(0-存在,1-删除)', - `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT NULL COMMENT '更新时间', - - PRIMARY KEY (`attachment_id`), - KEY `idx_application_id` (`application_id`) COMMENT '出国申请ID索引', - KEY `idx_tenant_deleted` (`tenant_id`, `deleted`) COMMENT '租户和删除标志联合索引', - KEY `idx_upload_time` (`upload_time`) COMMENT '上传时间索引' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出国申请附件表'; - --- 添加注释说明 -ALTER TABLE `rs_overseas_application_attachment` COMMENT = '出国申请附件表 - 支持附件标题和内容说明'; - --- ============================================== --- 使用说明 --- ============================================== --- 1. attachment_title: 必填,用于在列表中显示的附件标题 --- 2. attachment_content: 可选,用于详细描述附件内容 --- 3. 支持多种文件格式:PDF、Word、Excel、图片等 --- 4. file_path 存储OSS URL或相对路径 --- 5. 逻辑删除:使用deleted字段,不物理删除数据 --- ============================================== - diff --git a/docs/设计文档/database/rs_overseas_itinerary_change.sql b/docs/设计文档/database/rs_overseas_itinerary_change.sql deleted file mode 100644 index b4d9c08b..00000000 --- a/docs/设计文档/database/rs_overseas_itinerary_change.sql +++ /dev/null @@ -1,214 +0,0 @@ --- ====================================== --- 出国行程变更申请表 --- 表名: rs_overseas_itinerary_change --- 说明: 存储已通过的出国申请的行程变更申请信息 --- 重要说明: 关联的是出国记录表(rs_overseas_record),不是出国申请表 --- 创建时间: 2025-10-13 --- 更新时间: 2025-10-13 - 修改关联逻辑为出国记录表 --- ====================================== - -DROP TABLE IF EXISTS `rs_overseas_itinerary_change`; - -CREATE TABLE `rs_overseas_itinerary_change` ( - `change_id` bigint(20) NOT NULL COMMENT '变更ID(主键,雪花ID)', - `record_id` bigint(20) NOT NULL COMMENT '关联出国记录ID(关联rs_overseas_record表)', - `application_id` bigint(20) DEFAULT NULL COMMENT '原出国申请ID(关联rs_overseas_application表,来自记录表冗余)', - `applicant_id` bigint(20) NOT NULL COMMENT '申请人ID(用户ID)', - `applicant_name` varchar(100) NOT NULL COMMENT '申请人姓名', - `department` varchar(200) DEFAULT NULL COMMENT '部门', - - -- 原记录信息(从出国记录表冗余存储,便于查询和历史记录) - `original_destination` varchar(100) DEFAULT NULL COMMENT '原目的地国家', - `original_destination_city` varchar(100) DEFAULT NULL COMMENT '原目的地城市', - `original_exit_reason` varchar(100) DEFAULT NULL COMMENT '原出境事由', - `original_start_date` date DEFAULT NULL COMMENT '原出发日期', - `original_end_date` date DEFAULT NULL COMMENT '原返回日期', - `original_duration` int(11) DEFAULT NULL COMMENT '原出国天数', - `original_itinerary` text COMMENT '原行程安排', - `original_accommodation_plan` text COMMENT '原住宿安排', - `original_transportation_plan` text COMMENT '原交通安排', - - -- 变更信息 - `change_type` varchar(200) NOT NULL COMMENT '变更类型(目的地变更,时间变更,行程变更,多个用逗号分隔)', - `new_destination` varchar(100) DEFAULT NULL COMMENT '新目的地国家', - `new_destination_city` varchar(100) DEFAULT NULL COMMENT '新目的地城市', - `new_exit_reason` varchar(100) DEFAULT NULL COMMENT '新出境事由', - `new_start_date` date DEFAULT NULL COMMENT '新出发日期', - `new_end_date` date DEFAULT NULL COMMENT '新返回日期', - `new_duration` int(11) DEFAULT NULL COMMENT '新出国天数', - `new_itinerary` text COMMENT '新行程安排', - `new_accommodation_plan` text COMMENT '新住宿安排', - `new_transportation_plan` text COMMENT '新交通安排', - - -- 变更原因和备注 - `change_reason` varchar(500) NOT NULL COMMENT '变更原因(必填)', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - - -- 审批信息 - `status` char(1) NOT NULL DEFAULT '0' COMMENT '变更状态(0=待审核,1=已通过,2=已拒绝,3=已撤回)', - `approval_opinion` varchar(500) DEFAULT NULL COMMENT '审批意见', - `approval_time` datetime DEFAULT NULL COMMENT '审批时间', - `approver_id` bigint(20) DEFAULT NULL COMMENT '审批人ID', - `approver_name` varchar(100) DEFAULT NULL COMMENT '审批人姓名', - - -- 多租户字段 - `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID', - - -- 标准基础字段 - `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标志(0=未删除,1=已删除)', - - PRIMARY KEY (`change_id`), - KEY `idx_record_id` (`record_id`) COMMENT '出国记录ID索引', - KEY `idx_application_id` (`application_id`) COMMENT '原申请ID索引', - KEY `idx_applicant_id` (`applicant_id`) COMMENT '申请人ID索引', - KEY `idx_status` (`status`) COMMENT '状态索引', - KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引', - KEY `idx_create_time` (`create_time`) COMMENT '创建时间索引' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出国行程变更申请表'; - --- ====================================== --- 字典数据 - 变更状态 --- ====================================== - --- 删除旧字典(如果存在) -DELETE FROM sys_dict_type WHERE dict_type = 'rs_itinerary_change_status'; -DELETE FROM sys_dict_data WHERE dict_type = 'rs_itinerary_change_status'; - --- 插入字典类型 -INSERT INTO sys_dict_type VALUES ( - NULL, -- dict_id (自增) - '000000', -- tenant_id - '行程变更状态', -- dict_name - 'rs_itinerary_change_status', -- dict_type - 1, -- status - NULL, -- create_dept - 0, -- create_by - SYSDATE(), -- create_time - NULL, -- update_by - NULL, -- update_time - '出国行程变更申请状态字典' -- remark -); - --- 插入字典数据 -INSERT INTO sys_dict_data VALUES (NULL, '000000', 0, '待审核', '0', 'rs_itinerary_change_status', '', 'primary', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '待审核状态'); -INSERT INTO sys_dict_data VALUES (NULL, '000000', 1, '已通过', '1', 'rs_itinerary_change_status', '', 'success', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '已通过状态'); -INSERT INTO sys_dict_data VALUES (NULL, '000000', 2, '已拒绝', '2', 'rs_itinerary_change_status', '', 'danger', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '已拒绝状态'); -INSERT INTO sys_dict_data VALUES (NULL, '000000', 3, '已撤回', '3', 'rs_itinerary_change_status', '', 'info', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '已撤回状态'); - --- ====================================== --- 字典数据 - 变更类型 --- ====================================== - -DELETE FROM sys_dict_type WHERE dict_type = 'rs_itinerary_change_type'; -DELETE FROM sys_dict_data WHERE dict_type = 'rs_itinerary_change_type'; - -INSERT INTO sys_dict_type VALUES ( - NULL, - '000000', - '行程变更类型', - 'rs_itinerary_change_type', - 1, - NULL, - 0, - SYSDATE(), - NULL, - NULL, - '出国行程变更类型字典' -); - -INSERT INTO sys_dict_data VALUES (NULL, '000000', 0, '目的地变更', 'destination_change', 'rs_itinerary_change_type', '', 'primary', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '目的地变更'); -INSERT INTO sys_dict_data VALUES (NULL, '000000', 1, '时间变更', 'time_change', 'rs_itinerary_change_type', '', 'warning', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '时间变更'); -INSERT INTO sys_dict_data VALUES (NULL, '000000', 2, '行程变更', 'itinerary_change', 'rs_itinerary_change_type', '', 'info', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '行程变更'); -INSERT INTO sys_dict_data VALUES (NULL, '000000', 3, '交通方式变更', 'transportation_change', 'rs_itinerary_change_type', '', 'success', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '交通方式变更'); -INSERT INTO sys_dict_data VALUES (NULL, '000000', 4, '住宿变更', 'accommodation_change', 'rs_itinerary_change_type', '', 'default', 'N', 1, NULL, 0, SYSDATE(), NULL, NULL, '住宿变更'); - --- ====================================== --- 字段说明 --- ====================================== - -/* -核心业务字段: -1. change_id: 变更申请ID(雪花ID,主键) -2. record_id: 关联的出国记录ID(主要关联字段) -3. application_id: 原出国申请ID(冗余字段,来自记录表) -4. applicant_id, applicant_name: 申请人信息 -5. original_*: 原记录信息(从出国记录表冗余存储) -6. new_*: 变更后的新信息 -7. change_type: 变更类型(支持多种变更类型组合) -8. change_reason: 变更原因(必填) -9. status: 变更状态(0=待审核,1=已通过,2=已拒绝,3=已撤回) - -数据关联说明: -- 通过record_id关联rs_overseas_record表(出国记录) -- 变更申请基于已通过的出国申请生成的出国记录 -- 选择原出国申请时,实际选择的是出国记录选项卡中的记录 - -审批字段: -- approval_opinion: 审批意见 -- approval_time: 审批时间 -- approver_id, approver_name: 审批人信息 - -标准字段: -- tenant_id: 多租户支持 -- create_dept: 创建部门 -- create_by, create_time: 创建信息 -- update_by, update_time: 更新信息 -- deleted: 逻辑删除标志 - -索引说明: -- PRIMARY KEY: change_id(主键索引) -- idx_record_id: 用于快速查询某个出国记录的所有变更记录 -- idx_application_id: 用于快速查询某个出国申请的所有变更记录 -- idx_applicant_id: 用于快速查询某个用户的所有变更申请 -- idx_status: 用于按状态筛选(待审核、已通过等) -- idx_tenant_id: 多租户数据隔离 -- idx_create_time: 按创建时间排序和筛选 -*/ - --- ====================================== --- 常用查询示例 --- ====================================== - --- 1. 查询某个用户的所有变更申请 --- SELECT * FROM rs_overseas_itinerary_change --- WHERE applicant_id = ? AND deleted = 0 --- ORDER BY create_time DESC; - --- 2. 查询某个出国记录的所有变更记录 --- SELECT * FROM rs_overseas_itinerary_change --- WHERE record_id = ? AND deleted = 0 --- ORDER BY create_time DESC; - --- 3. 查询待审核的变更申请 --- SELECT * FROM rs_overseas_itinerary_change --- WHERE status = '0' AND deleted = 0 --- ORDER BY create_time ASC; - --- 4. 查询已通过的变更申请 --- SELECT * FROM rs_overseas_itinerary_change --- WHERE status = '1' AND deleted = 0 --- ORDER BY approval_time DESC; - --- 5. 统计各状态的变更申请数量 --- SELECT status, COUNT(*) as count --- FROM rs_overseas_itinerary_change --- WHERE deleted = 0 --- GROUP BY status; - --- 6. 联合查询变更申请和出国记录详情 --- SELECT --- c.*, --- r.applicant_name, --- r.department, --- r.destination, --- r.actual_start_date, --- r.actual_end_date --- FROM rs_overseas_itinerary_change c --- LEFT JOIN rs_overseas_record r ON c.record_id = r.record_id --- WHERE c.deleted = 0 AND r.deleted = 0 --- ORDER BY c.create_time DESC; diff --git a/docs/设计文档/database/rs_overseas_itinerary_change_add_fields.sql b/docs/设计文档/database/rs_overseas_itinerary_change_add_fields.sql deleted file mode 100644 index b77235c5..00000000 --- a/docs/设计文档/database/rs_overseas_itinerary_change_add_fields.sql +++ /dev/null @@ -1,58 +0,0 @@ --- ==================================== --- 出国行程变更表增加本人基本情况字段脚本 --- 用途: 为rs_overseas_itinerary_change表添加性别、出生年月、政治面貌、职称、职务、证件等字段 --- 创建时间: 2025-10-13 --- ==================================== - --- 添加新字段到rs_overseas_itinerary_change表 -ALTER TABLE `rs_overseas_itinerary_change` - ADD COLUMN `gender` char(1) DEFAULT NULL COMMENT '性别:0-男,1-女' AFTER `applicant_name`, - ADD COLUMN `birth_date` date DEFAULT NULL COMMENT '出生日期' AFTER `gender`, - ADD COLUMN `political_status` varchar(20) DEFAULT NULL COMMENT '政治面貌' AFTER `birth_date`, - ADD COLUMN `professional_title` varchar(100) DEFAULT NULL COMMENT '职称' AFTER `political_status`, - ADD COLUMN `position` varchar(100) DEFAULT NULL COMMENT '职务' AFTER `professional_title`, - ADD COLUMN `document_name` varchar(100) DEFAULT NULL COMMENT '申领证件名称' AFTER `position`, - ADD COLUMN `document_number` varchar(100) DEFAULT NULL COMMENT '证件号码' AFTER `document_name`; - --- 验证字段添加结果 -SELECT - COLUMN_NAME AS '字段名', - COLUMN_TYPE AS '字段类型', - IS_NULLABLE AS '允许空值', - COLUMN_COMMENT AS '字段说明' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change' - AND COLUMN_NAME IN ('gender', 'birth_date', 'political_status', 'professional_title', 'position', 'document_name', 'document_number') -ORDER BY ORDINAL_POSITION; - --- ==================================== --- 使用说明 --- ==================================== -/* -1. 执行前请确认: - - 数据库连接正常 - - rs_overseas_itinerary_change表已存在 - - 有足够的权限执行ALTER TABLE操作 - -2. 新增字段说明: - - gender: 性别(0-男,1-女),与出国申请表保持一致 - - birth_date: 出生日期 - - political_status: 政治面貌(使用字典值) - - professional_title: 职称 - - position: 职务 - - document_name: 申领证件名称(如:护照、通行证等) - - document_number: 证件号码 - -3. 字段用途: - - 这些字段会在变更申请时从原出国申请中自动填充 - - 用于在"一、本人基本情况"部分显示完整的个人信息 - - 所有字段均为可选字段,不影响现有数据 - -4. 执行后续步骤: - - 更新后端VO类,添加新字段 - - 更新前端TypeScript接口定义 - - 修改前端表单,在"一、本人基本情况"部分显示新字段 - - 更新数据自动填充逻辑 -*/ - diff --git a/docs/设计文档/database/rs_overseas_itinerary_change_approval_menu.sql b/docs/设计文档/database/rs_overseas_itinerary_change_approval_menu.sql deleted file mode 100644 index 51044cf6..00000000 --- a/docs/设计文档/database/rs_overseas_itinerary_change_approval_menu.sql +++ /dev/null @@ -1,187 +0,0 @@ --- ====================================== --- 出国行程变更审批管理 - 菜单权限配置SQL --- 创建时间: 2025-10-13 --- 说明: 在人事管理菜单下新增"行程变更审批"菜单及相关权限 --- 用途: 用于审批人员审批出国行程变更申请 --- ====================================== - --- ====================================== --- 1. 查询人事管理菜单ID(假设人事管理菜单已存在) --- ====================================== --- 假设人事管理菜单的menu_id为105(实际使用时需要查询确认) --- SELECT menu_id, menu_name, parent_id FROM sys_menu WHERE menu_name = '人事管理'; - --- ====================================== --- 2. 插入行程变更审批菜单(二级菜单) --- ====================================== --- 注意:menu_id使用实际系统的菜单ID规则,这里使用示例值2060 --- 注意:parent_id需要替换为实际的"人事管理"菜单ID - -INSERT INTO `sys_menu` VALUES ( - 2060, -- menu_id(菜单ID,需根据实际情况调整) - '行程变更审批', -- menu_name(菜单名称) - 105, -- parent_id(父菜单ID,假设105是"人事管理") - 6, -- order_num(显示顺序,在人事管理下第6个) - 'itinerary-change-approval', -- path(路由地址) - 'rs/overseas/itinerary-change-approval', -- component(对应前端页面路径) - 'F', -- is_frame(是否外链:N=否) - '1', -- is_cache(是否缓存:1=缓存) - 'C', -- menu_type(菜单类型:M=目录,C=菜单,F=按钮) - '0', -- visible(显示状态:0=显示,1=隐藏) - '0', -- status(菜单状态:0=正常,1=停用) - 'rs:itineraryChange:approve', -- perms(权限标识) - 'circle-check', -- icon(菜单图标) - SYSDATE(), -- create_time(创建时间) - 0, -- create_by(创建者) - NULL, -- update_time(更新时间) - NULL, -- update_by(更新者) - '出国行程变更审批管理', -- remark(备注) - 0 -- deleted(删除标志:0=未删除,1=已删除) -); - --- ====================================== --- 3. 插入功能按钮权限(三级菜单) --- ====================================== - --- 3.1 查询按钮(查看待审批列表和审批记录) -INSERT INTO `sys_menu` VALUES ( - 2061, -- menu_id - '行程变更审批查询', -- menu_name - 2060, -- parent_id - 1, -- order_num - '#', -- path - '', -- component - 'F', -- is_frame - '1', -- is_cache - 'F', -- menu_type(F=按钮) - '0', -- visible - '0', -- status - 'rs:itineraryChange:query', -- perms - '#', -- icon - SYSDATE(), -- create_time - 0, -- create_by - NULL, -- update_time - NULL, -- update_by - '查看行程变更详情', -- remark - 0 -- deleted -); - --- 3.2 审批按钮 -INSERT INTO `sys_menu` VALUES ( - 2062, -- menu_id - '行程变更审批', -- menu_name - 2060, -- parent_id - 2, -- order_num - '#', -- path - '', -- component - 'F', -- is_frame - '1', -- is_cache - 'F', -- menu_type(F=按钮) - '0', -- visible - '0', -- status - 'rs:itineraryChange:approve', -- perms - '#', -- icon - SYSDATE(), -- create_time - 0, -- create_by - NULL, -- update_time - NULL, -- update_by - '审批行程变更(通过/拒绝)', -- remark - 0 -- deleted -); - --- ====================================== --- 4. 菜单权限说明 --- ====================================== --- --- 【菜单结构】 --- 人事管理 --- ├─ 变更行程(申请人员使用,menu_id=2050) --- │ └─ 变更行程管理(申请页面,menu_id=2051) --- │ ├─ 查询(rs:itineraryChange:query) --- │ ├─ 新增(rs:itineraryChange:add) --- │ ├─ 修改(rs:itineraryChange:edit) --- │ ├─ 删除(rs:itineraryChange:remove) --- │ └─ 撤回(rs:itineraryChange:cancel) --- │ --- └─ 行程变更审批(审批人员使用,menu_id=2060)⬅️ 本SQL新增 --- ├─ 查询(rs:itineraryChange:query) --- └─ 审批(rs:itineraryChange:approve) --- --- 【权限标识说明】 --- rs:itineraryChange:query - 查询行程变更详情 --- rs:itineraryChange:approve - 审批行程变更(通过/拒绝) --- rs:itineraryChange:list - 查询行程变更列表(申请页面使用) --- rs:itineraryChange:add - 新增行程变更申请(申请页面使用) --- rs:itineraryChange:edit - 修改行程变更申请(申请页面使用) --- rs:itineraryChange:remove - 删除行程变更申请(申请页面使用) --- rs:itineraryChange:cancel - 撤回行程变更申请(申请页面使用) --- --- 【角色权限配置建议】 --- 1. 申请人员:分配"变更行程"菜单权限(menu_id=2050及其子菜单) --- - 可以提交、修改、撤回、删除自己的变更申请 --- - 可以查看自己的变更申请列表 --- --- 2. 审批人员:分配"行程变更审批"菜单权限(menu_id=2060及其子菜单) --- - 可以查看待审批的变更申请列表 --- - 可以审批(通过/拒绝)变更申请 --- - 可以查看审批记录 --- --- 3. 管理员:同时分配两个菜单权限 --- - 既可以申请,也可以审批 --- --- ====================================== --- 5. 角色菜单关联示例(可选) --- ====================================== --- 假设角色ID: --- - 申请人员角色ID = 10 --- - 审批人员角色ID = 20 --- - 管理员角色ID = 1 --- --- 5.1 为申请人员角色分配权限 --- INSERT INTO sys_role_menu (role_id, menu_id) VALUES --- (10, 2050), -- 变更行程菜单 --- (10, 2051), -- 变更行程管理页面 --- (10, 2052), -- 查询按钮 --- (10, 2053), -- 新增按钮 --- (10, 2054), -- 修改按钮 --- (10, 2055), -- 删除按钮 --- (10, 2056); -- 撤回按钮 --- --- 5.2 为审批人员角色分配权限 --- INSERT INTO sys_role_menu (role_id, menu_id) VALUES --- (20, 2060), -- 行程变更审批菜单 --- (20, 2061), -- 查询按钮 --- (20, 2062); -- 审批按钮 --- --- 5.3 为管理员角色分配权限(拥有全部权限) --- INSERT INTO sys_role_menu (role_id, menu_id) VALUES --- (1, 2050), (1, 2051), (1, 2052), (1, 2053), (1, 2054), (1, 2055), (1, 2056), -- 申请相关 --- (1, 2060), (1, 2061), (1, 2062); -- 审批相关 --- --- ====================================== --- 6. 验证SQL --- ====================================== --- --- 6.1 查询菜单是否创建成功 --- SELECT menu_id, menu_name, parent_id, path, component, perms, menu_type --- FROM sys_menu --- WHERE menu_id BETWEEN 2060 AND 2062 --- ORDER BY menu_id; --- --- 6.2 查询某个角色的菜单权限 --- SELECT m.menu_id, m.menu_name, m.perms, m.menu_type --- FROM sys_menu m --- INNER JOIN sys_role_menu rm ON m.menu_id = rm.menu_id --- WHERE rm.role_id = 20 -- 替换为实际角色ID --- AND m.menu_id BETWEEN 2050 AND 2062 --- ORDER BY m.menu_id; --- --- ====================================== --- 7. 删除菜单SQL(回滚用) --- ====================================== --- --- DELETE FROM sys_role_menu WHERE menu_id BETWEEN 2060 AND 2062; --- DELETE FROM sys_menu WHERE menu_id BETWEEN 2060 AND 2062; --- --- ====================================== - diff --git a/docs/设计文档/database/rs_overseas_itinerary_change_full.sql b/docs/设计文档/database/rs_overseas_itinerary_change_full.sql deleted file mode 100644 index 937c3346..00000000 --- a/docs/设计文档/database/rs_overseas_itinerary_change_full.sql +++ /dev/null @@ -1,320 +0,0 @@ --- ====================================== --- 出国行程变更管理 - 完整SQL脚本 --- 包括: 数据库表、字典数据、菜单权限配置 --- 创建时间: 2025-10-13 --- 更新时间: 2025-10-13(修复字符集编码问题) --- 说明: 一键导入所有配置 --- ====================================== - --- ⚠️ 重要:设置字符集,避免中文乱码 -SET NAMES utf8mb4; - --- ====================================== --- 第一部分: 数据库表结构 --- ====================================== - -DROP TABLE IF EXISTS `rs_overseas_itinerary_change`; - -CREATE TABLE `rs_overseas_itinerary_change` ( - `change_id` bigint(20) NOT NULL COMMENT '变更ID(主键,雪花ID)', - `record_id` bigint(20) NOT NULL COMMENT '关联出国记录ID(关联rs_overseas_record表)', - `application_id` bigint(20) DEFAULT NULL COMMENT '原出国申请ID(关联rs_overseas_application表,来自记录表冗余)', - `applicant_id` bigint(20) NOT NULL COMMENT '申请人ID(用户ID)', - `applicant_name` varchar(100) NOT NULL COMMENT '申请人姓名', - `gender` char(1) DEFAULT NULL COMMENT '性别:0-男,1-女', - `birth_date` date DEFAULT NULL COMMENT '出生日期', - `political_status` varchar(20) DEFAULT NULL COMMENT '政治面貌', - `professional_title` varchar(100) DEFAULT NULL COMMENT '职称', - `position` varchar(100) DEFAULT NULL COMMENT '职务', - `document_name` varchar(100) DEFAULT NULL COMMENT '申领证件名称', - `document_number` varchar(100) DEFAULT NULL COMMENT '证件号码', - `department` varchar(200) DEFAULT NULL COMMENT '部门', - - -- 原记录信息(从出国记录表冗余存储) - `original_destination` varchar(100) DEFAULT NULL COMMENT '原目的地国家', - `original_destination_city` varchar(100) DEFAULT NULL COMMENT '原目的地城市', - `original_exit_reason` varchar(100) DEFAULT NULL COMMENT '原出境事由', - `original_start_date` date DEFAULT NULL COMMENT '原出发日期', - `original_end_date` date DEFAULT NULL COMMENT '原返回日期', - `original_duration` int(11) DEFAULT NULL COMMENT '原出国天数', - `original_itinerary` text COMMENT '原行程安排', - `original_accommodation_plan` text COMMENT '原住宿安排', - `original_transportation_plan` text COMMENT '原交通安排', - - -- 变更信息 - `change_type` varchar(200) NOT NULL COMMENT '变更类型(目的地变更,时间变更,行程变更,多个用逗号分隔)', - `new_destination` varchar(100) DEFAULT NULL COMMENT '新目的地国家', - `new_destination_city` varchar(100) DEFAULT NULL COMMENT '新目的地城市', - `new_exit_reason` varchar(100) DEFAULT NULL COMMENT '新出境事由', - `new_start_date` date DEFAULT NULL COMMENT '新出发日期', - `new_end_date` date DEFAULT NULL COMMENT '新返回日期', - `new_duration` int(11) DEFAULT NULL COMMENT '新出国天数', - `new_itinerary` text COMMENT '新行程安排', - `new_accommodation_plan` text COMMENT '新住宿安排', - `new_transportation_plan` text COMMENT '新交通安排', - - -- 变更原因和备注 - `change_reason` varchar(500) NOT NULL COMMENT '变更原因(必填)', - `remark` varchar(500) DEFAULT NULL COMMENT '备注', - - -- 审批信息 - `status` char(1) NOT NULL DEFAULT '0' COMMENT '变更状态(0=待审核,1=已通过,2=已拒绝,3=已撤回)', - `approval_opinion` varchar(500) DEFAULT NULL COMMENT '审批意见', - `approval_time` datetime DEFAULT NULL COMMENT '审批时间', - `approver_id` bigint(20) DEFAULT NULL COMMENT '审批人ID', - `approver_name` varchar(100) DEFAULT NULL COMMENT '审批人姓名', - - -- 多租户字段 - `tenant_id` varchar(20) DEFAULT '000000' COMMENT '租户ID', - - -- 标准基础字段 - `create_dept` bigint(20) DEFAULT NULL COMMENT '创建部门', - `create_by` bigint(20) DEFAULT NULL COMMENT '创建者', - `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `update_by` bigint(20) DEFAULT NULL COMMENT '更新者', - `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `deleted` tinyint(1) DEFAULT '0' COMMENT '逻辑删除标志(0=未删除,1=已删除)', - - PRIMARY KEY (`change_id`), - KEY `idx_record_id` (`record_id`) COMMENT '出国记录ID索引', - KEY `idx_application_id` (`application_id`) COMMENT '原申请ID索引', - KEY `idx_applicant_id` (`applicant_id`) COMMENT '申请人ID索引', - KEY `idx_status` (`status`) COMMENT '状态索引', - KEY `idx_tenant_id` (`tenant_id`) COMMENT '租户ID索引', - KEY `idx_create_time` (`create_time`) COMMENT '创建时间索引' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='出国行程变更申请表'; - --- ====================================== --- 第二部分: 字典数据 --- ====================================== - --- 删除旧字典(如果存在) -DELETE FROM sys_dict_type WHERE dict_type = 'rs_itinerary_change_status'; -DELETE FROM sys_dict_data WHERE dict_type = 'rs_itinerary_change_status'; -DELETE FROM sys_dict_type WHERE dict_type = 'rs_itinerary_change_type'; -DELETE FROM sys_dict_data WHERE dict_type = 'rs_itinerary_change_type'; - --- 插入字典类型:行程变更状态(使用CONVERT确保正确编码) -INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES ( - 3000000000000001018, - '000000', - CONVERT('行程变更状态' USING utf8mb4), - 'rs_itinerary_change_status', - NULL, - 0, - SYSDATE(), - NULL, - NULL, - CONVERT('出国行程变更申请状态字典' USING utf8mb4) -); - --- 插入字典数据:行程变更状态(使用CONVERT确保正确编码) -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002069, '000000', 0, CONVERT('待审核' USING utf8mb4), '0', 'rs_itinerary_change_status', '', 'primary', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('待审核状态' USING utf8mb4)); -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002070, '000000', 1, CONVERT('已通过' USING utf8mb4), '1', 'rs_itinerary_change_status', '', 'success', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('已通过状态' USING utf8mb4)); -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002071, '000000', 2, CONVERT('已拒绝' USING utf8mb4), '2', 'rs_itinerary_change_status', '', 'danger', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('已拒绝状态' USING utf8mb4)); -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002072, '000000', 3, CONVERT('已撤回' USING utf8mb4), '3', 'rs_itinerary_change_status', '', 'info', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('已撤回状态' USING utf8mb4)); - --- 插入字典类型:行程变更类型(使用CONVERT确保正确编码) -INSERT INTO sys_dict_type (dict_id, tenant_id, dict_name, dict_type, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES ( - 3000000000000001019, - '000000', - CONVERT('行程变更类型' USING utf8mb4), - 'rs_itinerary_change_type', - NULL, - 0, - SYSDATE(), - NULL, - NULL, - CONVERT('出国行程变更类型字典' USING utf8mb4) -); - --- 插入字典数据:行程变更类型(使用CONVERT确保正确编码) -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002073, '000000', 0, CONVERT('目的地变更' USING utf8mb4), 'destination_change', 'rs_itinerary_change_type', '', 'primary', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('目的地变更' USING utf8mb4)); -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002074, '000000', 1, CONVERT('时间变更' USING utf8mb4), 'time_change', 'rs_itinerary_change_type', '', 'warning', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('时间变更' USING utf8mb4)); -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002075, '000000', 2, CONVERT('行程变更' USING utf8mb4), 'itinerary_change', 'rs_itinerary_change_type', '', 'info', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('行程变更' USING utf8mb4)); -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002076, '000000', 3, CONVERT('交通方式变更' USING utf8mb4), 'transportation_change', 'rs_itinerary_change_type', '', 'success', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('交通方式变更' USING utf8mb4)); -INSERT INTO sys_dict_data (dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_dept, create_by, create_time, update_by, update_time, remark) -VALUES (3000000000000002077, '000000', 4, CONVERT('住宿变更' USING utf8mb4), 'accommodation_change', 'rs_itinerary_change_type', '', 'default', 'N', NULL, 0, SYSDATE(), NULL, NULL, CONVERT('住宿变更' USING utf8mb4)); - --- ====================================== --- 第三部分: 菜单权限配置 --- ====================================== - --- 注意:menu_id使用2050-2058,请确保这些ID在系统中未被占用 --- 如果已被占用,请替换为其他可用ID - --- 删除旧菜单(如果存在) -DELETE FROM sys_menu WHERE menu_id >= 2050 AND menu_id <= 2058; -DELETE FROM sys_role_menu WHERE menu_id >= 2050 AND menu_id <= 2058; - --- 插入一级菜单:变更行程 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES ( - 2050, -- menu_id - '变更行程', -- menu_name - 105, -- parent_id(人事管理菜单ID,请根据实际情况调整) - 5, -- order_num - '/rs/itinerary-change', -- path - '', -- component - '', -- query_param - 0, -- is_frame (0=否,1=是) - 1, -- is_cache (0=不缓存,1=缓存) - 'C', -- menu_type - '0', -- visible - '0', -- status - 'rs:itineraryChange:list', -- perms - 'edit', -- icon - 103, -- create_dept - 1, -- create_by - SYSDATE(), -- create_time - NULL, -- update_by - NULL, -- update_time - '出国行程变更申请管理' -- remark -); - --- 插入二级菜单:变更行程管理 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES ( - 2051, - '变更行程管理', - 2050, - 1, - 'index', - 'rs/overseas/itinerary-change', - '', - 0, - 1, - 'C', - '0', - '0', - 'rs:itineraryChange:list', - 'edit', - 103, - 1, - SYSDATE(), - NULL, - NULL, - '变更行程管理页面' -); - --- 插入功能按钮:查询 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES (2052, '变更行程查询', 2051, 1, '#', '', '', 0, 1, 'F', '0', '0', 'rs:itineraryChange:query', '#', 103, 1, SYSDATE(), NULL, NULL, ''); - --- 插入功能按钮:新增 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES (2053, '变更行程新增', 2051, 2, '#', '', '', 0, 1, 'F', '0', '0', 'rs:itineraryChange:add', '#', 103, 1, SYSDATE(), NULL, NULL, ''); - --- 插入功能按钮:修改 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES (2054, '变更行程修改', 2051, 3, '#', '', '', 0, 1, 'F', '0', '0', 'rs:itineraryChange:edit', '#', 103, 1, SYSDATE(), NULL, NULL, ''); - --- 插入功能按钮:删除 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES (2055, '变更行程删除', 2051, 4, '#', '', '', 0, 1, 'F', '0', '0', 'rs:itineraryChange:remove', '#', 103, 1, SYSDATE(), NULL, NULL, ''); - --- 插入功能按钮:撤回 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES (2056, '变更行程撤回', 2051, 5, '#', '', '', 0, 1, 'F', '0', '0', 'rs:itineraryChange:cancel', '#', 103, 1, SYSDATE(), NULL, NULL, ''); - --- 插入功能按钮:导出 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES (2057, '变更行程导出', 2051, 6, '#', '', '', 0, 1, 'F', '0', '0', 'rs:itineraryChange:export', '#', 103, 1, SYSDATE(), NULL, NULL, ''); - --- 插入功能按钮:审批 -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_dept, create_by, create_time, update_by, update_time, remark) -VALUES (2058, '变更行程审批', 2051, 7, '#', '', '', 0, 1, 'F', '0', '0', 'rs:itineraryChange:approve', '#', 103, 1, SYSDATE(), NULL, NULL, ''); - --- ====================================== --- 为管理员角色分配菜单权限(可选) --- ====================================== --- 注意:role_id需要替换为实际的管理员角色ID(通常为1) --- 如果需要自动为管理员分配权限,取消下面的注释 - --- INSERT INTO `sys_role_menu` VALUES (1, 2050); -- 变更行程菜单 --- INSERT INTO `sys_role_menu` VALUES (1, 2051); -- 变更行程管理页面 --- INSERT INTO `sys_role_menu` VALUES (1, 2052); -- 查询权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2053); -- 新增权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2054); -- 修改权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2055); -- 删除权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2056); -- 撤回权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2057); -- 导出权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2058); -- 审批权限 - --- ====================================== --- 验证导入结果 --- ====================================== - --- 验证表结构 -SELECT '验证表结构' AS '检查项'; -SHOW TABLES LIKE 'rs_overseas_itinerary_change'; - --- 验证字典数据 -SELECT '验证字典数据' AS '检查项'; -SELECT dict_type, dict_name FROM sys_dict_type WHERE dict_type LIKE 'rs_itinerary_change%'; -SELECT dict_type, COUNT(*) AS count FROM sys_dict_data WHERE dict_type LIKE 'rs_itinerary_change%' GROUP BY dict_type; - --- 验证菜单配置 -SELECT '验证菜单配置' AS '检查项'; -SELECT menu_id, menu_name, parent_id, perms, menu_type -FROM sys_menu -WHERE menu_id >= 2050 AND menu_id <= 2058 -ORDER BY menu_id; - --- ====================================== --- 使用说明 --- ====================================== -/* -1. 执行步骤: - - 确保数据库连接正常 - - 确保menu_id(2050-2058)在系统中未被占用 - - 执行完整SQL脚本 - - 检查验证结果 - -2. 菜单层级结构: - 人事管理(105) - └── 变更行程(2050) - └── 变更行程管理(2051) - ├── 变更行程查询(2052) - ├── 变更行程新增(2053) - ├── 变更行程修改(2054) - ├── 变更行程删除(2055) - ├── 变更行程撤回(2056) - ├── 变更行程导出(2057) - └── 变更行程审批(2058) - -3. API接口路径: - - 列表查询: GET /rs/itinerary/change/list - - 我的申请: GET /rs/itinerary/change/my/list - - 待审批: GET /rs/itinerary/change/pending/list - - 详情查询: GET /rs/itinerary/change/{changeId} - - 新增申请: POST /rs/itinerary/change - - 修改申请: PUT /rs/itinerary/change - - 删除申请: DELETE /rs/itinerary/change/{changeIds} - - 撤回申请: PUT /rs/itinerary/change/cancel/{changeId} - - 审批申请: PUT /rs/itinerary/change/approve - - 导出列表: POST /rs/itinerary/change/export - - 可用记录: GET /rs/itinerary/change/available-records - - 记录详情: GET /rs/itinerary/change/record/{recordId} - - 变更历史: GET /rs/itinerary/change/record/{recordId}/history - -4. 前端页面路径: - - 前端访问: /rs/itinerary-change - - 前端组件: src/views/rs/overseas/itinerary-change.vue - -5. 后续操作: - - 在"系统管理-角色管理"中为相应角色分配菜单权限 - - 在前端创建对应的Vue组件页面 - - 测试所有功能接口 -*/ - diff --git a/docs/设计文档/database/rs_overseas_itinerary_change_menu.sql b/docs/设计文档/database/rs_overseas_itinerary_change_menu.sql deleted file mode 100644 index 68f2ff9b..00000000 --- a/docs/设计文档/database/rs_overseas_itinerary_change_menu.sql +++ /dev/null @@ -1,278 +0,0 @@ --- ====================================== --- 出国行程变更管理 - 菜单权限配置SQL --- 创建时间: 2025-10-13 --- 说明: 在人事管理菜单下新增"变更行程"菜单及相关权限 --- ====================================== - --- ====================================== --- 1. 查询人事管理菜单ID(假设人事管理菜单已存在) --- ====================================== --- 假设人事管理菜单的menu_id为105(实际使用时需要查询确认) --- SELECT menu_id, menu_name, parent_id FROM sys_menu WHERE menu_name = '人事管理'; - --- ====================================== --- 2. 插入变更行程菜单(一级菜单:变更行程) --- ====================================== --- 注意:menu_id使用实际系统的菜单ID规则,这里使用示例值2050 --- 注意:parent_id需要替换为实际的"人事管理"菜单ID - -INSERT INTO `sys_menu` VALUES ( - 2050, -- menu_id(菜单ID,需根据实际情况调整) - '变更行程', -- menu_name(菜单名称) - 105, -- parent_id(父菜单ID,假设105是"人事管理") - 5, -- order_num(显示顺序,在人事管理下第5个) - '/rs/itinerary-change', -- path(路由地址) - '', -- component(组件路径,空表示Layout) - 'F', -- is_frame(是否外链:N=否) - '1', -- is_cache(是否缓存:1=缓存) - 'C', -- menu_type(菜单类型:M=目录,C=菜单,F=按钮) - '0', -- visible(显示状态:0=显示,1=隐藏) - '0', -- status(菜单状态:0=正常,1=停用) - 'rs:itineraryChange:list', -- perms(权限标识) - 'edit', -- icon(菜单图标) - SYSDATE(), -- create_time(创建时间) - 0, -- create_by(创建者) - NULL, -- update_time(更新时间) - NULL, -- update_by(更新者) - '出国行程变更申请管理', -- remark(备注) - 0 -- deleted(删除标志:0=未删除,1=已删除) -); - --- ====================================== --- 3. 插入变更行程管理页面路由(二级菜单) --- ====================================== - -INSERT INTO `sys_menu` VALUES ( - 2051, -- menu_id - '变更行程管理', -- menu_name - 2050, -- parent_id(指向"变更行程"菜单) - 1, -- order_num - 'index', -- path - 'rs/overseas/itinerary-change', -- component(对应前端页面路径) - 'F', -- is_frame - '1', -- is_cache - 'C', -- menu_type - '0', -- visible - '0', -- status - 'rs:itineraryChange:list', -- perms - 'edit', -- icon - SYSDATE(), -- create_time - 0, -- create_by - NULL, -- update_time - NULL, -- update_by - '变更行程管理页面', -- remark - 0 -- deleted -); - --- ====================================== --- 4. 插入功能按钮权限(三级菜单) --- ====================================== - --- 4.1 查询按钮 -INSERT INTO `sys_menu` VALUES ( - 2052, -- menu_id - '变更行程查询', -- menu_name - 2051, -- parent_id - 1, -- order_num - '#', -- path - '', -- component - 'F', -- is_frame - '1', -- is_cache - 'F', -- menu_type(F=按钮) - '0', -- visible - '0', -- status - 'rs:itineraryChange:query', -- perms - '#', -- icon - SYSDATE(), -- create_time - 0, -- create_by - NULL, -- update_time - NULL, -- update_by - '', -- remark - 0 -- deleted -); - --- 4.2 新增按钮 -INSERT INTO `sys_menu` VALUES ( - 2053, -- menu_id - '变更行程新增', -- menu_name - 2051, -- parent_id - 2, -- order_num - '#', -- path - '', -- component - 'F', -- is_frame - '1', -- is_cache - 'F', -- menu_type - '0', -- visible - '0', -- status - 'rs:itineraryChange:add', -- perms - '#', -- icon - SYSDATE(), -- create_time - 0, -- create_by - NULL, -- update_time - NULL, -- update_by - '', -- remark - 0 -- deleted -); - --- 4.3 修改按钮 -INSERT INTO `sys_menu` VALUES ( - 2054, -- menu_id - '变更行程修改', -- menu_name - 2051, -- parent_id - 3, -- order_num - '#', -- path - '', -- component - 'F', -- is_frame - '1', -- is_cache - 'F', -- menu_type - '0', -- visible - '0', -- status - 'rs:itineraryChange:edit', -- perms - '#', -- icon - SYSDATE(), -- create_time - 0, -- create_by - NULL, -- update_time - NULL, -- update_by - '', -- remark - 0 -- deleted -); - --- 4.4 删除按钮 -INSERT INTO `sys_menu` VALUES ( - 2055, -- menu_id - '变更行程删除', -- menu_name - 2051, -- parent_id - 4, -- order_num - '#', -- path - '', -- component - 'F', -- is_frame - '1', -- is_cache - 'F', -- menu_type - '0', -- visible - '0', -- status - 'rs:itineraryChange:remove', -- perms - '#', -- icon - SYSDATE(), -- create_time - 0, -- create_by - NULL, -- update_time - NULL, -- update_by - '', -- remark - 0 -- deleted -); - --- 4.5 撤回按钮 -INSERT INTO `sys_menu` VALUES ( - 2056, -- menu_id - '变更行程撤回', -- menu_name - 2051, -- parent_id - 5, -- order_num - '#', -- path - '', -- component - 'F', -- is_frame - '1', -- is_cache - 'F', -- menu_type - '0', -- visible - '0', -- status - 'rs:itineraryChange:cancel', -- perms - '#', -- icon - SYSDATE(), -- create_time - 0, -- create_by - NULL, -- update_time - NULL, -- update_by - '', -- remark - 0 -- deleted -); - --- ====================================== --- 5. 为管理员角色分配菜单权限(可选) --- ====================================== --- 注意:role_id需要替换为实际的管理员角色ID(通常为1) --- 如果需要自动为管理员分配权限,取消下面的注释 - --- INSERT INTO `sys_role_menu` VALUES (1, 2050); -- 变更行程菜单 --- INSERT INTO `sys_role_menu` VALUES (1, 2051); -- 变更行程管理页面 --- INSERT INTO `sys_role_menu` VALUES (1, 2052); -- 查询权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2053); -- 新增权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2054); -- 修改权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2055); -- 删除权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2056); -- 撤回权限 --- INSERT INTO `sys_role_menu` VALUES (1, 2057); -- 导出权限 - --- ====================================== --- 6. 验证菜单配置 --- ====================================== --- 查询变更行程菜单及其子菜单 --- SELECT --- menu_id, --- menu_name, --- parent_id, --- order_num, --- path, --- component, --- perms, --- menu_type, --- visible, --- status --- FROM sys_menu --- WHERE menu_id >= 2050 AND menu_id <= 2056 --- ORDER BY menu_id; - --- ====================================== --- 7. 删除菜单配置(回滚用,慎用) --- ====================================== --- DELETE FROM sys_menu WHERE menu_id >= 2050 AND menu_id <= 2056; --- DELETE FROM sys_role_menu WHERE menu_id >= 2050 AND menu_id <= 2056; - --- ====================================== --- 菜单结构说明 --- ====================================== -/* -菜单层级结构: -人事管理(105) - └── 变更行程(2050) - └── 变更行程管理(2051) - ├── 变更行程查询(2052)- rs:itineraryChange:query - ├── 变更行程新增(2053)- rs:itineraryChange:add - ├── 变更行程修改(2054)- rs:itineraryChange:edit - ├── 变更行程删除(2055)- rs:itineraryChange:remove - └── 变更行程撤回(2056)- rs:itineraryChange:cancel - -权限标识说明: -- rs:itineraryChange:list - 访问变更行程列表页面 -- rs:itineraryChange:query - 查询变更行程详情 -- rs:itineraryChange:add - 新增变更申请 -- rs:itineraryChange:edit - 修改变更申请(重新提交) -- rs:itineraryChange:remove - 删除变更申请 -- rs:itineraryChange:cancel - 撤回变更申请 - -路由配置: -- 前端访问路径: /rs/itinerary-change -- 前端组件路径: rs/overseas/itinerary-change.vue -*/ - --- ====================================== --- 使用说明 --- ====================================== -/* -1. 执行前请先确认: - - 人事管理菜单的实际menu_id(本脚本假设为105) - - 确保menu_id(2050-2056)在系统中未被占用 - - 确认sys_menu表结构与本脚本一致 - -2. 执行顺序: - - 先执行菜单插入SQL(第2-4节) - - 再为相应角色分配菜单权限(第5节) - - 最后验证菜单配置(第6节) - -3. 权限分配: - - 管理员默认拥有所有权限 - - 其他角色需要在"系统管理-角色管理"中分配相应权限 - -4. 前端路由配置: - - 确保前端已创建对应的Vue组件:src/views/rs/overseas/itinerary-change.vue - - 确保前端路由配置正确 - -5. 后端接口开发: - - Controller需要使用对应的权限注解:@SaCheckPermission("rs:itineraryChange:xxx") -*/ - diff --git a/docs/设计文档/database/rs_training_application_字段补充.sql b/docs/设计文档/database/rs_training_application_字段补充.sql deleted file mode 100644 index a057e4b2..00000000 --- a/docs/设计文档/database/rs_training_application_字段补充.sql +++ /dev/null @@ -1,95 +0,0 @@ --- ===================================================== --- 培训申请表字段补充SQL脚本 --- 目标数据库:hzdyjs_xg (远程数据库) --- 表名:rs_training_application --- 执行时间:2025-01-03 --- 描述:添加前端申请培训界面新增的字段 --- ===================================================== - --- 1. 检查当前表结构 --- DESC rs_training_application; - --- 2. 备份表数据(可选,建议在生产环境执行) --- CREATE TABLE rs_training_application_backup AS SELECT * FROM rs_training_application; - --- 3. 一次性添加所有缺失字段 -ALTER TABLE `rs_training_application` -ADD COLUMN `course_id` bigint(20) DEFAULT NULL COMMENT '课程ID' AFTER `application_id`, -ADD COLUMN `current_title` varchar(100) DEFAULT NULL COMMENT '现职称' AFTER `position`, -ADD COLUMN `current_position` varchar(50) DEFAULT NULL COMMENT '现岗位' AFTER `current_title`, -ADD COLUMN `work_start_time` date DEFAULT NULL COMMENT '参加工作时间' AFTER `current_position`, -ADD COLUMN `phone` varchar(20) DEFAULT NULL COMMENT '联系电话' AFTER `work_start_time`, -ADD COLUMN `organizer_unit` varchar(200) DEFAULT NULL COMMENT '主办单位' AFTER `phone`, -ADD COLUMN `training_type` varchar(50) DEFAULT NULL COMMENT '培训类型' AFTER `organizer_unit`, -ADD COLUMN `training_fee` decimal(10,2) DEFAULT '0.00' COMMENT '培训费用' AFTER `training_type`, -ADD COLUMN `companions` text DEFAULT NULL COMMENT '同行人员列表(JSON格式)' AFTER `training_fee`, -ADD COLUMN `training_purpose` text DEFAULT NULL COMMENT '培训目的' AFTER `companions`, -ADD COLUMN `attachment_ids` text DEFAULT NULL COMMENT '附件ID列表(逗号分隔)' AFTER `training_purpose`; - --- 4. 验证表结构 --- DESC rs_training_application; - --- 5. 查看更新后的表结构 --- SHOW CREATE TABLE rs_training_application; - --- 6. 测试数据插入(验证用,执行后可删除) -/* -INSERT INTO rs_training_application ( - application_id, course_id, course_name, user_id, user_name, - department, position, current_title, current_position, - work_start_time, phone, organizer_unit, training_type, - training_fee, companions, training_purpose, attachment_ids, - reason, status, apply_time, location, duration, - start_time, end_time, tenant_id, create_dept, - create_by, create_time, remark, deleted -) VALUES ( - 999999999, 0, '测试培训课程', 1, '测试用户', - '人文学院', '教师', '副教授', '教学', - '2020-01-01', '13800138000', '浙江大学', '教学', - 1500.00, '[{"teacherId": 2, "teacherName": "李四"}]', '提升教学能力', '1001,1002', - '参加培训提升能力', '0', NOW(), '杭州', 40, - '2025-02-01', '2025-02-05', '000000', 101, - 1, NOW(), '测试备注', '0' -); - --- 验证插入的测试数据 -SELECT * FROM rs_training_application WHERE application_id = 999999999; - --- 删除测试数据 -DELETE FROM rs_training_application WHERE application_id = 999999999; -*/ - --- ===================================================== --- 执行结果验证 --- ===================================================== --- 1. 检查字段是否添加成功 --- SELECT COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_COMMENT --- FROM INFORMATION_SCHEMA.COLUMNS --- WHERE TABLE_SCHEMA = 'hzdyjs_xg' --- AND TABLE_NAME = 'rs_training_application' --- ORDER BY ORDINAL_POSITION; - --- 2. 检查新增字段 --- SELECT COLUMN_NAME, COLUMN_COMMENT --- FROM INFORMATION_SCHEMA.COLUMNS --- WHERE TABLE_SCHEMA = 'hzdyjs_xg' --- AND TABLE_NAME = 'rs_training_application' --- AND COLUMN_NAME IN ('course_id', 'current_title', 'current_position', 'work_start_time', 'phone', 'organizer_unit', 'training_type', 'training_fee', 'companions', 'training_purpose', 'attachment_ids'); - --- ===================================================== --- 字段说明 --- ===================================================== -/* -新增字段说明: -1. course_id - 课程ID,关联课程表 -2. current_title - 现职称,如:教授、副教授、讲师等 -3. current_position - 现岗位,如:教学、管理、其他 -4. work_start_time - 参加工作时间 -5. phone - 联系电话 -6. organizer_unit - 主办单位,培训的组织方 -7. training_type - 培训类型,如:教学、德育、行政、管理、其他 -8. training_fee - 培训费用 -9. companions - 同行人员,JSON格式存储 -10. training_purpose - 培训目的 -11. attachment_ids - 附件ID列表,逗号分隔 -*/ \ No newline at end of file diff --git a/docs/设计文档/database/⚡一键修复-添加缺失字段.sql b/docs/设计文档/database/⚡一键修复-添加缺失字段.sql deleted file mode 100644 index 9599dd6a..00000000 --- a/docs/设计文档/database/⚡一键修复-添加缺失字段.sql +++ /dev/null @@ -1,28 +0,0 @@ --- ⚡ 一键修复:添加行程变更表缺失的个人信息字段 --- 直接复制粘贴到MySQL中执行即可! - -SET NAMES utf8mb4; - --- 添加8个缺失的个人信息字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `gender` char(1) DEFAULT NULL COMMENT '性别:0-男,1-女' AFTER `applicant_name`, -ADD COLUMN `birth_date` date DEFAULT NULL COMMENT '出生日期' AFTER `gender`, -ADD COLUMN `political_status` varchar(20) DEFAULT NULL COMMENT '政治面貌' AFTER `birth_date`, -ADD COLUMN `professional_title` varchar(100) DEFAULT NULL COMMENT '职称' AFTER `political_status`, -ADD COLUMN `position` varchar(100) DEFAULT NULL COMMENT '职务' AFTER `professional_title`, -ADD COLUMN `document_name` varchar(100) DEFAULT NULL COMMENT '申领证件名称' AFTER `position`, -ADD COLUMN `document_number` varchar(100) DEFAULT NULL COMMENT '证件号码' AFTER `document_name`, -ADD COLUMN `department` varchar(200) DEFAULT NULL COMMENT '部门' AFTER `document_number`; - --- 验证修复结果(字段数量应该是47) -SELECT COUNT(*) AS '字段数量' FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'rs_overseas_itinerary_change'; - --- 查看新添加的字段 -SHOW COLUMNS FROM rs_overseas_itinerary_change WHERE Field IN ( - 'gender', 'birth_date', 'political_status', 'professional_title', - 'position', 'document_name', 'document_number', 'department' -); - --- ✅ 修复完成!下一步:重启后端服务 - diff --git a/docs/设计文档/database/⚡智能修复-只添加缺失字段.sql b/docs/设计文档/database/⚡智能修复-只添加缺失字段.sql deleted file mode 100644 index 4389500a..00000000 --- a/docs/设计文档/database/⚡智能修复-只添加缺失字段.sql +++ /dev/null @@ -1,96 +0,0 @@ --- ⚡ 智能修复:只添加缺失的个人信息字段 --- 跳过已存在的字段,只添加缺失的字段 - -SET NAMES utf8mb4; - --- 添加 gender 字段(如果不存在) -SET @sql = (SELECT IF( - (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change' AND COLUMN_NAME = 'gender') = 0, - 'ALTER TABLE rs_overseas_itinerary_change ADD COLUMN `gender` char(1) DEFAULT NULL COMMENT ''性别:0-男,1-女'' AFTER `applicant_name`;', - 'SELECT ''字段 gender 已存在,跳过'' AS result;' -)); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加 birth_date 字段(如果不存在) -SET @sql = (SELECT IF( - (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change' AND COLUMN_NAME = 'birth_date') = 0, - 'ALTER TABLE rs_overseas_itinerary_change ADD COLUMN `birth_date` date DEFAULT NULL COMMENT ''出生日期'' AFTER `gender`;', - 'SELECT ''字段 birth_date 已存在,跳过'' AS result;' -)); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加 political_status 字段(如果不存在) -SET @sql = (SELECT IF( - (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change' AND COLUMN_NAME = 'political_status') = 0, - 'ALTER TABLE rs_overseas_itinerary_change ADD COLUMN `political_status` varchar(20) DEFAULT NULL COMMENT ''政治面貌'' AFTER `birth_date`;', - 'SELECT ''字段 political_status 已存在,跳过'' AS result;' -)); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加 professional_title 字段(如果不存在) -SET @sql = (SELECT IF( - (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change' AND COLUMN_NAME = 'professional_title') = 0, - 'ALTER TABLE rs_overseas_itinerary_change ADD COLUMN `professional_title` varchar(100) DEFAULT NULL COMMENT ''职称'' AFTER `political_status`;', - 'SELECT ''字段 professional_title 已存在,跳过'' AS result;' -)); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加 position 字段(如果不存在) -SET @sql = (SELECT IF( - (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change' AND COLUMN_NAME = 'position') = 0, - 'ALTER TABLE rs_overseas_itinerary_change ADD COLUMN `position` varchar(100) DEFAULT NULL COMMENT ''职务'' AFTER `professional_title`;', - 'SELECT ''字段 position 已存在,跳过'' AS result;' -)); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加 document_name 字段(如果不存在) -SET @sql = (SELECT IF( - (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change' AND COLUMN_NAME = 'document_name') = 0, - 'ALTER TABLE rs_overseas_itinerary_change ADD COLUMN `document_name` varchar(100) DEFAULT NULL COMMENT ''申领证件名称'' AFTER `position`;', - 'SELECT ''字段 document_name 已存在,跳过'' AS result;' -)); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加 document_number 字段(如果不存在) -SET @sql = (SELECT IF( - (SELECT COUNT(*) FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change' AND COLUMN_NAME = 'document_number') = 0, - 'ALTER TABLE rs_overseas_itinerary_change ADD COLUMN `document_number` varchar(100) DEFAULT NULL COMMENT ''证件号码'' AFTER `document_name`;', - 'SELECT ''字段 document_number 已存在,跳过'' AS result;' -)); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- department 字段已存在,跳过 - --- 验证修复结果(字段数量应该是47) -SELECT COUNT(*) AS '字段数量' FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change'; - --- 查看所有字段 -SELECT COLUMN_NAME AS '字段名', COLUMN_TYPE AS '类型', COLUMN_COMMENT AS '注释' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = 'hzdyjs_xg' AND TABLE_NAME = 'rs_overseas_itinerary_change' -ORDER BY ORDINAL_POSITION; - --- ✅ 修复完成!下一步:重启后端服务 - diff --git a/docs/设计文档/database/【立即执行】修复political_status字段长度.sql b/docs/设计文档/database/【立即执行】修复political_status字段长度.sql deleted file mode 100644 index 1024456b..00000000 --- a/docs/设计文档/database/【立即执行】修复political_status字段长度.sql +++ /dev/null @@ -1,75 +0,0 @@ --- ============================================================ --- 🔴 紧急修复:出国登记表 political_status 字段长度不足 --- 创建时间: 2025-10-05 --- 问题: Data too long for column 'political_status' at row 1 --- 原因: 字段长度不足以存储完整的政治面貌文本 --- ============================================================ - -USE hzdyjs_xg; - --- ============================================================ --- 第一步:备份现有数据(安全起见) --- ============================================================ - --- 创建备份表(如果不存在) -CREATE TABLE IF NOT EXISTS rs_overseas_registration_backup_20251005 -SELECT * FROM rs_overseas_registration; - -CREATE TABLE IF NOT EXISTS rs_overseas_application_backup_20251005 -SELECT * FROM rs_overseas_application; - --- ============================================================ --- 第二步:修改字段长度 --- ============================================================ - --- 修改出国登记表的 political_status 字段 -ALTER TABLE `rs_overseas_registration` -MODIFY COLUMN `political_status` varchar(50) NOT NULL COMMENT '政治面貌'; - --- 修改出国申请表的 political_status 字段(保持一致) -ALTER TABLE `rs_overseas_application` -MODIFY COLUMN `political_status` varchar(50) NOT NULL COMMENT '政治面貌'; - --- ============================================================ --- 第三步:验证修改结果 --- ============================================================ - -SELECT - '✅ 字段修改验证' as '步骤', - TABLE_NAME as '表名', - COLUMN_NAME as '字段名', - DATA_TYPE as '数据类型', - CHARACTER_MAXIMUM_LENGTH as '最大长度', - COLUMN_COMMENT as '注释' -FROM - information_schema.COLUMNS -WHERE - TABLE_SCHEMA = 'hzdyjs_xg' - AND TABLE_NAME IN ('rs_overseas_registration', 'rs_overseas_application') - AND COLUMN_NAME = 'political_status'; - --- ============================================================ --- 预期结果 --- ============================================================ --- TABLE_NAME | COLUMN_NAME | DATA_TYPE | CHARACTER_MAXIMUM_LENGTH --- ----------------------------|------------------|-----------|------------------------ --- rs_overseas_registration | political_status | varchar | 50 --- rs_overseas_application | political_status | varchar | 50 - --- ============================================================ --- 执行说明 --- ============================================================ --- 1. 直接执行本脚本即可完成修复 --- 2. 执行时间约1-2秒(取决于表中数据量) --- 3. ALTER TABLE不会丢失现有数据 --- 4. 已自动创建备份表,可随时回滚 --- 5. 修复完成后请测试"新增出国登记"功能 - --- ============================================================ --- 问题分析 --- ============================================================ --- 原字段: char(2) 或 varchar(20) --- 前端传值: 完整中文文本(如"中共党员") --- UTF-8编码: 4个中文字符 = 12字节 --- 结论: char(2)完全无法存储,varchar(20)也不够安全 --- 修复: varchar(50) 可以存储最长16个中文字符,满足需求 diff --git a/docs/设计文档/database/【立即执行】出国申请表添加OSS_ID字段.sql b/docs/设计文档/database/【立即执行】出国申请表添加OSS_ID字段.sql deleted file mode 100644 index 256c921a..00000000 --- a/docs/设计文档/database/【立即执行】出国申请表添加OSS_ID字段.sql +++ /dev/null @@ -1,18 +0,0 @@ --- ========================================== --- 添加OSS ID字段到出国申请表 --- 用于支持安全的文件下载功能 --- ========================================== - --- 添加盖章文件OSS ID字段 -ALTER TABLE `rs_overseas_application` - ADD COLUMN `stamped_file_oss_id` BIGINT(20) NULL COMMENT '盖章文件OSS ID' AFTER `stamped_file_upload_status`; - --- 添加党籍保留附件OSS ID字段 -ALTER TABLE `rs_overseas_application` - ADD COLUMN `party_retention_file_oss_id` BIGINT(20) NULL COMMENT '党籍保留附件OSS ID' AFTER `party_retention_upload_status`; - --- 添加索引以提升查询性能 -ALTER TABLE `rs_overseas_application` - ADD INDEX `idx_stamped_file_oss_id` (`stamped_file_oss_id`), - ADD INDEX `idx_party_retention_file_oss_id` (`party_retention_file_oss_id`); - diff --git a/docs/设计文档/database/【立即执行】添加党籍保留附件字段.sql b/docs/设计文档/database/【立即执行】添加党籍保留附件字段.sql deleted file mode 100644 index 399aeae5..00000000 --- a/docs/设计文档/database/【立即执行】添加党籍保留附件字段.sql +++ /dev/null @@ -1,104 +0,0 @@ --- ============================================================ --- 数据库迁移脚本:为出国申请表添加党籍保留附件字段 --- ============================================================ --- 创建时间:2025-10-10 --- 说明:为支持党籍保留附件上传功能,在 rs_overseas_application 表中添加4个字段 --- 执行方式:直接在生产数据库执行 --- ============================================================ - -USE hzdyjs_xg; - --- 检查字段是否已存在,如果不存在则添加 -SET @dbname = DATABASE(); -SET @tablename = 'rs_overseas_application'; - --- 添加党籍保留附件文件路径 -SET @column_check = ( - SELECT COUNT(*) - FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = @dbname - AND TABLE_NAME = @tablename - AND COLUMN_NAME = 'party_retention_file_path' -); - -SET @sql = IF(@column_check = 0, - 'ALTER TABLE rs_overseas_application ADD COLUMN party_retention_file_path VARCHAR(500) DEFAULT NULL COMMENT ''党籍保留附件文件路径'' AFTER stamped_file_upload_status', - 'SELECT ''字段 party_retention_file_path 已存在,跳过添加'' AS result' -); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加党籍保留附件文件名 -SET @column_check = ( - SELECT COUNT(*) - FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = @dbname - AND TABLE_NAME = @tablename - AND COLUMN_NAME = 'party_retention_file_name' -); - -SET @sql = IF(@column_check = 0, - 'ALTER TABLE rs_overseas_application ADD COLUMN party_retention_file_name VARCHAR(200) DEFAULT NULL COMMENT ''党籍保留附件文件名'' AFTER party_retention_file_path', - 'SELECT ''字段 party_retention_file_name 已存在,跳过添加'' AS result' -); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加党籍保留附件上传时间 -SET @column_check = ( - SELECT COUNT(*) - FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = @dbname - AND TABLE_NAME = @tablename - AND COLUMN_NAME = 'party_retention_upload_time' -); - -SET @sql = IF(@column_check = 0, - 'ALTER TABLE rs_overseas_application ADD COLUMN party_retention_upload_time DATETIME DEFAULT NULL COMMENT ''党籍保留附件上传时间'' AFTER party_retention_file_name', - 'SELECT ''字段 party_retention_upload_time 已存在,跳过添加'' AS result' -); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 添加党籍保留附件上传状态 -SET @column_check = ( - SELECT COUNT(*) - FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = @dbname - AND TABLE_NAME = @tablename - AND COLUMN_NAME = 'party_retention_upload_status' -); - -SET @sql = IF(@column_check = 0, - 'ALTER TABLE rs_overseas_application ADD COLUMN party_retention_upload_status VARCHAR(1) DEFAULT ''0'' COMMENT ''党籍保留附件上传状态:0-未上传,1-已上传'' AFTER party_retention_upload_time', - 'SELECT ''字段 party_retention_upload_status 已存在,跳过添加'' AS result' -); -PREPARE stmt FROM @sql; -EXECUTE stmt; -DEALLOCATE PREPARE stmt; - --- 验证字段是否添加成功 -SELECT - COLUMN_NAME AS '字段名', - COLUMN_TYPE AS '字段类型', - COLUMN_DEFAULT AS '默认值', - COLUMN_COMMENT AS '备注' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() -AND TABLE_NAME = 'rs_overseas_application' -AND COLUMN_NAME IN ( - 'party_retention_file_path', - 'party_retention_file_name', - 'party_retention_upload_time', - 'party_retention_upload_status' -) -ORDER BY ORDINAL_POSITION; - --- ============================================================ --- 执行完成提示 --- ============================================================ -SELECT '党籍保留附件字段添加完成!请检查上方验证结果。' AS '执行结果'; - diff --git a/docs/设计文档/database/修复-行程变更状态字典编码.sql b/docs/设计文档/database/修复-行程变更状态字典编码.sql deleted file mode 100644 index a604cd7f..00000000 --- a/docs/设计文档/database/修复-行程变更状态字典编码.sql +++ /dev/null @@ -1,117 +0,0 @@ --- ====================================== --- 修复行程变更状态字典编码问题 --- ====================================== --- 问题描述:字典数据存在双重UTF-8编码,导致前端显示乱码 --- 根本原因:插入数据时字符集设置不正确,导致UTF-8数据被当作Latin1存储 --- 解决方案:使用正确的字符集连接,重新插入数据 --- 执行时间:2025-10-13 --- ====================================== - -SET NAMES utf8mb4; - --- 步骤1: 删除旧的字典数据 -DELETE FROM sys_dict_data WHERE dict_type = 'rs_itinerary_change_status'; -DELETE FROM sys_dict_type WHERE dict_type = 'rs_itinerary_change_status'; - --- 步骤2: 插入字典类型(使用CONVERT确保正确编码) -INSERT INTO sys_dict_type ( - dict_id, - tenant_id, - dict_name, - dict_type, - create_dept, - create_by, - create_time, - update_by, - update_time, - remark -) VALUES ( - 3000000000000001018, - '000000', - CONVERT('行程变更状态' USING utf8mb4), - 'rs_itinerary_change_status', - NULL, - 0, - NOW(), - NULL, - NULL, - CONVERT('出国行程变更申请状态字典' USING utf8mb4) -); - --- 步骤3: 插入字典数据(使用CONVERT确保正确编码) -INSERT INTO sys_dict_data ( - dict_code, - tenant_id, - dict_sort, - dict_label, - dict_value, - dict_type, - css_class, - list_class, - is_default, - create_dept, - create_by, - create_time, - update_by, - update_time, - remark -) VALUES -(3000000000000002069, '000000', 0, CONVERT('待审核' USING utf8mb4), '0', 'rs_itinerary_change_status', '', 'primary', 'N', NULL, 0, NOW(), NULL, NULL, CONVERT('待审核状态' USING utf8mb4)), -(3000000000000002070, '000000', 1, CONVERT('已通过' USING utf8mb4), '1', 'rs_itinerary_change_status', '', 'success', 'N', NULL, 0, NOW(), NULL, NULL, CONVERT('已通过状态' USING utf8mb4)), -(3000000000000002071, '000000', 2, CONVERT('已拒绝' USING utf8mb4), '2', 'rs_itinerary_change_status', '', 'danger', 'N', NULL, 0, NOW(), NULL, NULL, CONVERT('已拒绝状态' USING utf8mb4)), -(3000000000000002072, '000000', 3, CONVERT('已撤回' USING utf8mb4), '3', 'rs_itinerary_change_status', '', 'info', 'N', NULL, 0, NOW(), NULL, NULL, CONVERT('已撤回状态' USING utf8mb4)); - --- ====================================== --- 验证修复结果 --- ====================================== - -SELECT '===== 1. 验证字典类型 =====' AS step; -SELECT - dict_id, - dict_name, - dict_type, - HEX(dict_name) as hex_encoding, - LENGTH(dict_name) as byte_length, - CHAR_LENGTH(dict_name) as char_length -FROM sys_dict_type -WHERE dict_type = 'rs_itinerary_change_status'; - -SELECT '===== 2. 验证字典数据 =====' AS step; -SELECT - dict_code, - dict_label, - dict_value, - HEX(dict_label) as hex_encoding, - LENGTH(dict_label) as byte_length, - CHAR_LENGTH(dict_label) as char_length -FROM sys_dict_data -WHERE dict_type = 'rs_itinerary_change_status' -ORDER BY dict_sort; - -SELECT '===== 3. 测试字典关联查询 =====' AS step; -SELECT - c.change_id, - c.applicant_name, - c.status as status_value, - d.dict_label as status_text -FROM rs_overseas_itinerary_change c -LEFT JOIN sys_dict_data d - ON d.dict_value = c.status - AND d.dict_type = 'rs_itinerary_change_status' -WHERE c.deleted = 0 -LIMIT 3; - --- ====================================== --- 预期结果说明 --- ====================================== --- 1. HEX编码应该是正确的UTF-8编码,例如: --- "待审核" = E5BE85E5AEA1E6A0B8 --- "已通过" = E5B7B2E9809AE8BF87 --- "已拒绝" = E5B7B2E68B92E7BB9D --- "已撤回" = E5B7B2E692A4E59B9E --- --- 2. byte_length应该是char_length的3倍(UTF-8中文字符占3字节) --- --- 3. 字典关联查询应该正确返回status_text中文文本 --- ====================================== - diff --git a/docs/设计文档/database/修复出国登记表political_status字段长度.sql b/docs/设计文档/database/修复出国登记表political_status字段长度.sql deleted file mode 100644 index 3019cbbd..00000000 --- a/docs/设计文档/database/修复出国登记表political_status字段长度.sql +++ /dev/null @@ -1,93 +0,0 @@ --- ============================================================ --- 修复出国登记表 political_status 字段长度 --- 创建时间: 2025-10-04 --- 问题描述: 字段长度不足,导致插入数据时报错 --- 原长度: varchar(20) --- 新长度: varchar(50) --- ============================================================ - --- 修改 rs_overseas_registration 表的 political_status 字段长度 -ALTER TABLE `rs_overseas_registration` -MODIFY COLUMN `political_status` varchar(50) NOT NULL COMMENT '政治面貌'; - --- 验证修改 -SELECT - TABLE_NAME, - COLUMN_NAME, - DATA_TYPE, - CHARACTER_MAXIMUM_LENGTH, - COLUMN_COMMENT -FROM - information_schema.COLUMNS -WHERE - TABLE_SCHEMA = 'hzdyjs_xg' - AND TABLE_NAME = 'rs_overseas_registration' - AND COLUMN_NAME = 'political_status'; - --- ============================================================ --- 同步修改出国申请表(如果需要的话) --- ============================================================ - --- 修改 rs_overseas_application 表的 political_status 字段长度 -ALTER TABLE `rs_overseas_application` -MODIFY COLUMN `political_status` varchar(50) NOT NULL COMMENT '政治面貌(字典值1-13)'; - --- 验证修改 -SELECT - TABLE_NAME, - COLUMN_NAME, - DATA_TYPE, - CHARACTER_MAXIMUM_LENGTH, - COLUMN_COMMENT -FROM - information_schema.COLUMNS -WHERE - TABLE_SCHEMA = 'hzdyjs_xg' - AND TABLE_NAME = 'rs_overseas_application' - AND COLUMN_NAME = 'political_status'; - --- ============================================================ --- 政治面貌字典数据统计 --- ============================================================ - --- 查看政治面貌字典中最长的标签 -SELECT - dict_label, - dict_value, - LENGTH(dict_label) as label_length, - CHAR_LENGTH(dict_label) as char_count -FROM - sys_dict_data -WHERE - dict_type = 'xg_political_status' -ORDER BY - LENGTH(dict_label) DESC; - --- ============================================================ --- 问题分析 --- ============================================================ --- 1. 原字段长度: varchar(20) - 最多20个字节 --- 2. 中文字符编码: UTF-8,每个中文字符占3个字节 --- 3. 最长标签: "中国国民党革命委员会会员" = 13个中文字符 = 39字节 --- 4. 结论: varchar(20)远远不够,需要至少varchar(40),为安全起见设为varchar(50) - --- ============================================================ --- 字典数据长度对比 --- ============================================================ --- 字典标签 | 字符数 | 字节数(UTF-8) --- ----------------------------|--------|------------- --- 中国国民党革命委员会会员 | 13 | 39 --- 中国共产主义青年团团员 | 12 | 36 --- 台湾民主自治同盟盟员 | 11 | 33 --- 中国民主建国会会员 | 10 | 30 --- 中国民主促进会会员 | 10 | 30 --- 中国农工民主党党员 | 10 | 30 --- 中国共产党预备党员 | 9 | 27 --- 中国民主同盟盟员 | 8 | 24 --- 中国共产党党员 | 7 | 21 ⚠️ 超过varchar(20) --- 中国致公党党员 | 7 | 21 ⚠️ 超过varchar(20) --- 九三学社社员 | 6 | 18 --- 无党派民主人士 | 7 | 21 ⚠️ 超过varchar(20) --- 群众 | 2 | 6 - - diff --git a/docs/设计文档/database/出国管理三表架构设计方案.md b/docs/设计文档/database/出国管理三表架构设计方案.md deleted file mode 100644 index fc63dcf6..00000000 --- a/docs/设计文档/database/出国管理三表架构设计方案.md +++ /dev/null @@ -1,524 +0,0 @@ -# 出国管理三表架构设计方案 - -**文档版本**: v1.0 -**创建日期**: 2025-09-30 -**更新日期**: 2025-09-30 -**设计原则**: 三表独立 + 流程递进 + 状态关联 - ---- - -## 📋 业务背景 - -出国管理系统涉及**三个独立的业务流程**: - -1. **出国申请** → 教职工因私出国的初始申请 -2. **出国登记** → 申请通过后的登记备案 -3. **出国党籍保留** → 党员出国超过1个月需办理党籍保留 - -### 业务关系 - -``` -┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ -│ 出国申请 │ ───> │ 出国登记 │ ───> │ 出国党籍保留 │ -│ (必须先办理) │ │ (申请通过后) │ │ (党员且>1个月) │ -└──────────────┘ └──────────────┘ └──────────────────┘ - 状态:已审批 ───> 状态:已确认 ───> 状态:已批准 -``` - -### 递进规则 - -- ✅ **出国登记**: 必须基于**已审批通过**的出国申请 -- ✅ **出国党籍保留**: 必须基于**已确认**的出国登记 -- ✅ 每个流程都有独立的ID和审批状态 -- ✅ 通过关联字段判断前置流程完成情况 - ---- - -## 🗂️ 三表设计总览 - -| 表名 | 中文名 | 主键 | 关联字段 | 前置条件 | -|------|--------|------|----------|----------| -| `rs_overseas_application` | 出国申请表 | application_id | - | 无 | -| `rs_overseas_registration` | 出国登记表 | registration_id | application_id | 申请已审批 | -| `rs_party_membership_retention` | 出国党籍保留表 | retention_id | application_id, registration_id | 登记已确认 | - ---- - -## 📊 表一:出国申请表 (rs_overseas_application) - -### 表结构概述 -- **表名**: `rs_overseas_application` -- **主键**: `application_id` bigint(20) -- **字段数量**: 30个 (23个业务字段 + 7个系统字段) -- **状态字典**: `rs_overseas_application_status` - -### 完整字段列表 - -#### 1. 主键字段 -```sql -application_id bigint(20) NOT NULL AUTO_INCREMENT -- 申请ID(主键) -``` - -#### 2. 基本信息字段 (6个) -```sql -applicant_name varchar(50) NOT NULL -- 申请人姓名 -gender char(1) NOT NULL -- 性别:0-男,1-女 -birth_date date NULL -- 出生年月 -political_status varchar(20) NOT NULL -- 政治面貌(字典值1-13) -department varchar(100) NOT NULL -- 现工作部门 -position varchar(100) NULL -- 职务 -``` - -#### 3. 职业信息字段 (1个) -```sql -professional_title varchar(100) NULL -- 职称 -``` - -#### 4. 证件信息字段 (2个) -```sql -document_name varchar(100) NULL -- 申领证件名称 -document_number varchar(100) NULL -- 证件号码 -``` - -#### 5. 出国信息字段 (5个) -```sql -destination_country varchar(100) NOT NULL -- 前往国家 -exit_reason varchar(50) NOT NULL -- 出境事由:旅游、探亲 -visit_relationship varchar(200) NULL -- 探望对象关系(仅探亲时填写) -start_date date NOT NULL -- 出发日期 -end_date date NOT NULL -- 返回日期 -``` - -#### 6. 计算字段 (1个) -```sql -duration int(11) NULL -- 出国天数(自动计算) -``` - -#### 7. 联系信息字段 (2个) -```sql -mobile_phone varchar(20) NULL -- 联系电话 -emergency_contact varchar(100) NULL -- 应急联系人及电话 -``` - -#### 8. 签名和备注字段 (2个) -```sql -signature text NULL -- 手写签名(Base64) -remarks text NULL -- 备注 -``` - -#### 9. 文件上传字段 (3个) -```sql -stamped_file_path varchar(500) NULL -- 盖章文件路径 -stamped_file_name varchar(200) NULL -- 盖章文件名称 -stamped_file_upload_time datetime NULL -- 盖章文件上传时间 -``` - -#### 10. 状态和时间字段 (2个) -```sql -status char(1) NOT NULL DEFAULT '0' -- 申请状态 -apply_time datetime NULL -- 申请时间 -``` - -#### 11. 系统字段 (7个) -```sql -tenant_id varchar(20) DEFAULT '000000' -- 租户编号 -deleted char(1) DEFAULT '0' -- 删除标志 -create_dept bigint(20) NULL -- 创建部门 -create_by bigint(20) NULL -- 创建者 -create_time datetime NULL -- 创建时间 -update_by bigint(20) NULL -- 更新者 -update_time datetime NULL -- 更新时间 -``` - -### 状态定义 -```sql --- 申请状态字典 (rs_overseas_application_status) -'0' - 待审核 (info) -- 已提交,等待审批 -'1' - 已通过 (success) -- 审批通过,可生成登记表 -'2' - 已拒绝 (danger) -- 审批拒绝 -'3' - 已取消 (warning) -- 用户主动取消 -``` - -### 索引设计 -```sql -PRIMARY KEY (application_id) -INDEX idx_applicant_name (applicant_name) -INDEX idx_department (department) -INDEX idx_status (status) -INDEX idx_apply_time (apply_time) -INDEX idx_tenant_deleted (tenant_id, deleted) -INDEX idx_create_time (create_time) -``` - ---- - -## 📊 表二:出国登记表 (rs_overseas_registration) - -### 表结构概述 -- **表名**: `rs_overseas_registration` -- **主键**: `registration_id` bigint(20) -- **关联字段**: `application_id` (关联出国申请) -- **字段数量**: 22个 (15个业务字段 + 7个系统字段) -- **状态字典**: `rs_overseas_registration_status` - -### 完整字段列表 - -#### 1. 主键和关联字段 (2个) -```sql -registration_id bigint(20) NOT NULL AUTO_INCREMENT -- 登记ID(主键) -application_id bigint(20) NULL -- 关联出国申请ID(可为空) -``` - -#### 2. 基本信息字段 (7个) -```sql -name varchar(50) NOT NULL -- 姓名 -gender char(1) NOT NULL -- 性别:0-男,1-女 -birth_date date NOT NULL -- 出生日期 -photo varchar(500) NULL -- 照片地址(预留区域) -id_number varchar(18) NOT NULL -- 公民身份号码 -political_status varchar(20) NOT NULL -- 政治面貌 -registered_address varchar(200) NOT NULL -- 户籍所在地 -``` - -#### 3. 工作信息字段 (4个) -```sql -department varchar(100) NOT NULL -- 工作部门 -position varchar(100) NOT NULL -- 职务职称 -administrative_level varchar(20) NULL -- 行政职级 -personnel_category varchar(10) NULL -- 人员类别代码 -``` - -#### 4. 状态和时间字段 (2个) -```sql -status char(1) NOT NULL DEFAULT '0' -- 登记状态 -registration_time datetime NULL -- 登记时间 -``` - -#### 5. 备注字段 (1个) -```sql -remarks text NULL -- 备注 -``` - -#### 6. 系统字段 (7个) -```sql -tenant_id varchar(20) DEFAULT '000000' -deleted char(1) DEFAULT '0' -create_dept bigint(20) NULL -create_by bigint(20) NULL -create_time datetime NULL -update_by bigint(20) NULL -update_time datetime NULL -``` - -### 状态定义 -```sql --- 登记状态字典 (rs_overseas_registration_status) -'0' - 草稿 (info) -- 初始创建状态,可编辑 -'1' - 已提交 (warning) -- 提交后等待确认 -'2' - 已确认 (success) -- 管理员确认,可生成证明 -'3' - 已撤回 (danger) -- 用户主动撤回 -``` - -### 索引设计 -```sql -PRIMARY KEY (registration_id) -INDEX idx_application_id (application_id) -- 关联查询 -INDEX idx_name (name) -INDEX idx_id_number (id_number) -INDEX idx_department (department) -INDEX idx_status (status) -INDEX idx_registration_time (registration_time) -INDEX idx_tenant_deleted (tenant_id, deleted) -INDEX idx_create_time (create_time) -``` - ---- - -## 📊 表三:出国党籍保留表 (rs_party_membership_retention) - -### 表结构概述 -- **表名**: `rs_party_membership_retention` -- **主键**: `retention_id` bigint(20) -- **关联字段**: `application_id`、`registration_id` -- **字段数量**: 25个 (18个业务字段 + 7个系统字段) -- **状态字典**: `rs_party_retention_status` - -### 完整字段列表 - -#### 1. 主键和关联字段 (3个) -```sql -retention_id bigint(20) NOT NULL AUTO_INCREMENT -- 党籍保留ID(主键) -application_id bigint(20) NULL -- 关联出国申请ID -registration_id bigint(20) NULL -- 关联出国登记ID -``` - -#### 2. 基本信息字段 (2个) -```sql -name varchar(50) NOT NULL -- 我支部同志(姓名) -spouse varchar(50) NULL -- 偕配偶姓名 -``` - -#### 3. 出国时间字段 (3个) -```sql -travel_start_date date NOT NULL -- 拟出国开始日期 -travel_end_date date NOT NULL -- 拟出国结束日期 -stay_days int(11) NULL -- 在外停留天数 -``` - -#### 4. 出国目的和地点字段 (3个) -```sql -destination varchar(100) NOT NULL -- 因私赴XX(国家或地区) -purpose text NULL -- 出国目的详细说明 -travel_reason varchar(50) NULL -- 出国原因(简要) -``` - -#### 5. 费用信息字段 (1个) -```sql -self_funded char(1) NOT NULL DEFAULT '1' -- 费用自理:1-是,0-否 -``` - -#### 6. 审批信息字段 (2个) -```sql -request_approval text NULL -- 请求批准说明(固定文本) -final_approval text NULL -- 妥否请审批(固定文本) -``` - -#### 7. 党支部信息字段 (2个) -```sql -party_branch varchar(100) NOT NULL -- XXXX党支部 -application_date date NOT NULL -- 申请日期 -``` - -#### 8. 状态和时间字段 (2个) -```sql -status char(1) NOT NULL DEFAULT '0' -- 申请状态 -application_time datetime NULL -- 申请时间 -``` - -#### 9. 备注字段 (1个) -```sql -remarks text NULL -- 备注 -``` - -#### 10. 系统字段 (7个) -```sql -tenant_id varchar(20) DEFAULT '000000' -deleted char(1) DEFAULT '0' -create_dept bigint(20) NULL -create_by bigint(20) NULL -create_time datetime NULL -update_by bigint(20) NULL -update_time datetime NULL -``` - -### 状态定义 -```sql --- 党籍保留状态字典 (rs_party_retention_status) -'0' - 草稿 (info) -- 初始创建状态,可编辑 -'1' - 已提交 (warning) -- 提交后等待审批 -'2' - 已批准 (success) -- 党支部批准 -'3' - 已拒绝 (danger) -- 审批拒绝 -'4' - 已撤回 (warning) -- 用户主动撤回 -``` - -### 索引设计 -```sql -PRIMARY KEY (retention_id) -INDEX idx_application_id (application_id) -INDEX idx_registration_id (registration_id) -INDEX idx_name (name) -INDEX idx_destination (destination) -INDEX idx_status (status) -INDEX idx_application_time (application_time) -INDEX idx_tenant_deleted (tenant_id, deleted) -INDEX idx_create_time (create_time) -``` - ---- - -## 🔗 流程递进关系设计 - -### 1. 关联关系图 - -``` -rs_overseas_application (出国申请) - | - | application_id - | - ├──> rs_overseas_registration (出国登记) - | | - | | registration_id - | | - | └──> rs_party_membership_retention (党籍保留) - | | - └─────────────────┘ (同时关联 application_id) -``` - -### 2. 流程判断逻辑 - -#### 出国登记前置条件检查 -```sql --- 检查是否可以创建出国登记 -SELECT - application_id, - applicant_name, - status, - CASE - WHEN status = '1' THEN '可以创建登记' - ELSE '申请未通过审批' - END AS can_create_registration -FROM rs_overseas_application -WHERE application_id = ? - AND status = '1' -- 必须是已通过状态 - AND deleted = '0'; -``` - -#### 出国党籍保留前置条件检查 -```sql --- 检查是否可以创建党籍保留 -SELECT - reg.registration_id, - reg.name, - reg.status AS registration_status, - app.political_status, - DATEDIFF(app.end_date, app.start_date) AS travel_days, - CASE - WHEN reg.status = '2' - AND app.political_status IN ('1', '2') -- 党员或预备党员 - AND DATEDIFF(app.end_date, app.start_date) >= 30 -- 超过30天 - THEN '可以创建党籍保留' - WHEN reg.status != '2' THEN '登记未确认' - WHEN app.political_status NOT IN ('1', '2') THEN '非党员无需办理' - WHEN DATEDIFF(app.end_date, app.start_date) < 30 THEN '出国时间不足30天' - ELSE '不符合条件' - END AS can_create_retention -FROM rs_overseas_registration reg -INNER JOIN rs_overseas_application app ON reg.application_id = app.application_id -WHERE reg.registration_id = ? - AND reg.deleted = '0' - AND app.deleted = '0'; -``` - -### 3. 前端按钮显示逻辑 - -#### 出国申请列表按钮显示 -```javascript -// 生成登记表按钮 -canGenerateRegistration(row) { - return row.status === '1' && !row.hasRegistration; -} -``` - -#### 出国登记列表按钮显示 -```javascript -// 生成党籍保留按钮 -canGenerateRetention(row) { - return row.status === '2' // 登记已确认 - && row.isPartyMember // 是党员 - && row.travelDays >= 30 // 出国时间>=30天 - && !row.hasRetention; // 未生成过党籍保留 -} -``` - ---- - -## 🎯 业务规则总结 - -### 必须遵守的流程规则 - -1. ✅ **出国申请** → 必须先审批通过 (status='1') -2. ✅ **出国登记** → 基于已通过的申请,需管理员确认 (status='2') -3. ✅ **党籍保留** → 基于已确认的登记,仅党员且出国>=30天 - -### 数据完整性约束 - -1. ✅ 每个表都有独立的主键ID -2. ✅ 通过 application_id 关联出国申请 -3. ✅ 通过 registration_id 关联出国登记 -4. ✅ 使用逻辑删除,不使用物理删除 -5. ✅ 所有表支持多租户隔离 - -### 状态流转规则 - -#### 出国申请状态流转 -``` -草稿 → 待审核(0) → 已通过(1) / 已拒绝(2) - ↓ - 可撤回为已取消(3) -``` - -#### 出国登记状态流转 -``` -草稿(0) → 已提交(1) → 已确认(2) - ↓ - 可撤回(3) -``` - -#### 党籍保留状态流转 -``` -草稿(0) → 已提交(1) → 已批准(2) / 已拒绝(3) - ↓ - 可撤回(4) -``` - ---- - -## 📝 实施建议 - -### 第一步:创建三个表 -1. ✅ 执行出国申请表创建脚本 -2. ✅ 执行出国登记表创建脚本 -3. ⏳ 执行出国党籍保留表创建脚本(待创建) - -### 第二步:插入字典数据 -1. ✅ `rs_overseas_application_status` (4条) -2. ✅ `rs_overseas_registration_status` (4条) -3. ⏳ `rs_party_retention_status` (5条) -4. ✅ `rs_political_status` (13条 - 共用) - -### 第三步:后端开发 -1. 为每个表创建独立的实体类、VO、BO -2. 实现流程递进判断的Service方法 -3. 添加关联查询的API接口 -4. 实现状态流转的业务逻辑 - -### 第四步:前端开发 -1. 三个独立的页面组件 -2. 实现按钮显示的条件判断 -3. 添加流程提示信息 -4. 实现关联数据的展示 - ---- - -## ⚠️ 重要注意事项 - -### 数据一致性 -- 🔴 删除申请时,关联的登记和党籍保留不应被级联删除 -- 🔴 使用逻辑删除,保留历史数据追溯 -- 🟡 状态变更需要记录操作日志 - -### 性能优化 -- ✅ 在关联字段上创建索引 -- ✅ 避免过度的多表JOIN查询 -- ✅ 使用VO对象返回关联数据 - -### 安全性 -- ✅ 严格的权限控制 -- ✅ 状态流转的业务校验 -- ✅ 防止跨租户数据访问 - ---- - -## 📞 相关文档 - -- **数据库规范**: `docs/设计文档/database/mysql-database-standard.md` -- **后端开发规范**: `.cursor/cursorrules-backend.md` -- **API文档**: `docs/开发文档/API/出国登记管理API.md` -- **需求文档**: `docs/需求文档/人事管理/出国申请管理需求.md` - ---- - -**文档维护人**: Cursor AI -**创建时间**: 2025-09-30 -**审核状态**: ✅ 设计完成,待实施 -**下一步**: 创建党籍保留表SQL脚本 \ No newline at end of file diff --git a/docs/设计文档/database/字符集修复/rs_overseas_record_status_backup_charset_fix.sql b/docs/设计文档/database/字符集修复/rs_overseas_record_status_backup_charset_fix.sql deleted file mode 100644 index 97da0151..00000000 --- a/docs/设计文档/database/字符集修复/rs_overseas_record_status_backup_charset_fix.sql +++ /dev/null @@ -1,38 +0,0 @@ --- ====================================================== --- rs_overseas_record_status_backup 表字符集修复脚本 --- ====================================================== --- 问题:old_status_text 字段使用了 latin1 字符集,导致中文乱码 --- 解决:修改为 utf8mb4 字符集,符合项目数据库规范 --- 创建时间:2025-01-08 --- ====================================================== - --- 1. 备份表数据(可选,建议在生产环境执行) --- CREATE TABLE rs_overseas_record_status_backup_20250108 AS SELECT * FROM rs_overseas_record_status_backup; - --- 2. 修复字符集问题 -ALTER TABLE `rs_overseas_record_status_backup` -MODIFY COLUMN `old_status_text` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '原状态文本'; - --- 3. 验证修复结果 --- SHOW FULL COLUMNS FROM rs_overseas_record_status_backup; - --- ====================================================== --- 修复后的正确表结构 --- ====================================================== -/* -CREATE TABLE `rs_overseas_record_status_backup` ( - `record_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '记录ID', - `completion_status` char(1) DEFAULT '0' COMMENT '完成状态:0-进行中,1-已完成,2-提前结束', - `old_status_text` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '原状态文本', - `create_time` datetime DEFAULT NULL COMMENT '创建时间', - `update_time` datetime DEFAULT NULL COMMENT '更新时间' -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; -*/ - --- ====================================================== --- 注意事项 --- ====================================================== --- 1. 修复前建议备份数据 --- 2. 如果已有乱码数据,可能需要数据转换 --- 3. 修复后需要重新插入中文数据以验证 --- ====================================================== \ No newline at end of file diff --git a/docs/设计文档/database/执行步骤-增加本人基本情况字段.md b/docs/设计文档/database/执行步骤-增加本人基本情况字段.md deleted file mode 100644 index 3ac8bff7..00000000 --- a/docs/设计文档/database/执行步骤-增加本人基本情况字段.md +++ /dev/null @@ -1,220 +0,0 @@ -# 出国行程变更表增加本人基本情况字段 - 执行步骤 - -## 📋 快速执行指南 - -### 第一步:执行数据库脚本 - -1. **连接到MySQL数据库** - ```bash - mysql -u your_username -p your_database_name - ``` - -2. **执行ALTER脚本** - ```bash - source /path/to/rs_overseas_itinerary_change_add_fields.sql - ``` - - 或者直接在MySQL客户端中执行SQL: - ```sql - ALTER TABLE `rs_overseas_itinerary_change` - ADD COLUMN `gender` char(1) DEFAULT NULL COMMENT '性别:0-男,1-女' AFTER `applicant_name`, - ADD COLUMN `birth_date` date DEFAULT NULL COMMENT '出生日期' AFTER `gender`, - ADD COLUMN `political_status` varchar(20) DEFAULT NULL COMMENT '政治面貌' AFTER `birth_date`, - ADD COLUMN `professional_title` varchar(100) DEFAULT NULL COMMENT '职称' AFTER `political_status`, - ADD COLUMN `position` varchar(100) DEFAULT NULL COMMENT '职务' AFTER `professional_title`, - ADD COLUMN `document_name` varchar(100) DEFAULT NULL COMMENT '申领证件名称' AFTER `position`, - ADD COLUMN `document_number` varchar(100) DEFAULT NULL COMMENT '证件号码' AFTER `document_name`; - ``` - -3. **验证字段添加成功** - ```sql - DESC rs_overseas_itinerary_change; - ``` - - 应该能看到以下新字段: - - gender - - birth_date - - political_status - - professional_title - - position - - document_name - - document_number - -### 第二步:重启后端服务 - -1. **编译后端项目** - ```bash - cd backend - mvn clean package - ``` - -2. **重启后端服务** - ```bash - # 根据你的部署方式选择 - # 方式1: 直接运行 - java -jar ruoyi-admin/target/ruoyi-admin.jar - - # 方式2: Docker - docker-compose restart backend - - # 方式3: systemctl - systemctl restart ruoyi - ``` - -### 第三步:重启前端服务 - -1. **重新构建前端** - ```bash - cd web - npm run build - ``` - -2. **重启前端服务** - ```bash - # 开发环境 - npm run dev - - # 生产环境 - systemctl restart nginx - ``` - -### 第四步:验证功能 - -1. **登录系统** - - 访问前端页面 - - 使用管理员账号登录 - -2. **打开行程变更页面** - - 导航:人事管理 → 变更行程 → 变更行程管理 - -3. **测试新增功能** - - 点击"新增变更申请" - - 选择一个已审批通过的出国申请 - - 检查"一、本人基本情况"部分是否显示了所有新字段: - - ✅ 姓名 - - ✅ 性别 - - ✅ 出生年月 - - ✅ 政治面貌 - - ✅ 职称 - - ✅ 职务 - - ✅ 申领证件名称 - - ✅ 证件号码 - - ✅ 单位 - -4. **测试数据保存** - - 填写实际行程情况 - - 填写变更原因 - - 提交保存 - - 查看详情,验证新字段已保存 - -## 🔍 常见问题排查 - -### 问题1:字段添加失败 - -**错误信息**:`Duplicate column name 'gender'` - -**解决方案**:字段已存在,跳过此步骤或先删除字段再添加 -```sql --- 检查字段是否存在 -SHOW COLUMNS FROM rs_overseas_itinerary_change LIKE 'gender'; - --- 如果存在且需要重新添加,先删除 -ALTER TABLE rs_overseas_itinerary_change DROP COLUMN gender; -``` - -### 问题2:后端启动失败 - -**错误信息**:MyBatis映射错误 - -**解决方案**: -1. 检查实体类字段是否正确添加 -2. 清理Maven缓存 - ```bash - mvn clean - rm -rf ~/.m2/repository - mvn install - ``` - -### 问题3:前端字段不显示 - -**可能原因**: -1. 前端缓存未清理 -2. 类型定义未更新 -3. 后端未返回新字段 - -**解决方案**: -```bash -# 清理前端缓存 -cd web -rm -rf node_modules/.cache -rm -rf dist - -# 重新安装依赖 -npm install - -# 重新运行 -npm run dev -``` - -### 问题4:性别显示为空 - -**可能原因**:出国申请表中gender字段为空 - -**解决方案**: -1. 检查原出国申请数据 -2. 确保出国申请表中有gender字段且有值 - -## 📊 SQL验证脚本 - -```sql --- 验证表结构 -SELECT - COLUMN_NAME AS '字段名', - COLUMN_TYPE AS '字段类型', - IS_NULLABLE AS '允许空值', - COLUMN_COMMENT AS '字段说明' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change' - AND COLUMN_NAME IN ('gender', 'birth_date', 'political_status', 'professional_title', 'position', 'document_name', 'document_number') -ORDER BY ORDINAL_POSITION; - --- 验证数据 -SELECT - change_id, - applicant_name, - gender, - birth_date, - political_status, - professional_title, - position, - document_name, - document_number -FROM rs_overseas_itinerary_change -LIMIT 5; -``` - -## ✅ 执行检查清单 - -- [ ] 数据库字段添加成功 -- [ ] 后端代码已更新(Entity、VO、Bo) -- [ ] 前端代码已更新(API类型、表单UI) -- [ ] 后端服务重启成功 -- [ ] 前端服务重启成功 -- [ ] 新增变更申请功能正常 -- [ ] 字段自动填充正常 -- [ ] 数据保存正常 -- [ ] 查看详情显示正常 - -## 📞 技术支持 - -如遇到问题,请检查: -1. 日志文件:`backend/logs/` -2. 浏览器控制台:F12 → Console -3. 网络请求:F12 → Network - ---- - -**文档版本**:1.0 -**最后更新**:2025-10-13 - diff --git a/docs/设计文档/database/🔍诊断-检查缺失字段.sql b/docs/设计文档/database/🔍诊断-检查缺失字段.sql deleted file mode 100644 index 423bafdc..00000000 --- a/docs/设计文档/database/🔍诊断-检查缺失字段.sql +++ /dev/null @@ -1,124 +0,0 @@ --- ====================================== --- 诊断:检查行程变更表缺失的字段 --- 创建时间: 2025-10-13 --- 执行方式: 在MySQL中直接执行 --- ====================================== - --- 设置字符集 -SET NAMES utf8mb4; - --- ====================================== --- 检查1:查看当前表结构 --- ====================================== -SELECT '当前表结构' AS '检查项'; -SHOW COLUMNS FROM rs_overseas_itinerary_change; - --- ====================================== --- 检查2:统计字段数量 --- ====================================== -SELECT - '字段数量检查' AS '检查项', - COUNT(*) AS '当前字段数', - '47' AS '期望字段数', - CASE - WHEN COUNT(*) < 47 THEN '⚠️ 缺少字段' - WHEN COUNT(*) = 47 THEN '✅ 字段完整' - ELSE '❓ 字段数量异常' - END AS '状态' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change'; - --- ====================================== --- 检查3:检查必需的个人信息字段是否存在 --- ====================================== -SELECT - '个人信息字段检查' AS '检查项', - CASE WHEN COUNT(*) = 8 THEN '✅ 全部存在' ELSE CONCAT('⚠️ 缺少 ', 8 - COUNT(*), ' 个字段') END AS '状态', - GROUP_CONCAT(COLUMN_NAME ORDER BY COLUMN_NAME) AS '已存在的字段' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change' - AND COLUMN_NAME IN ( - 'gender', - 'birth_date', - 'political_status', - 'professional_title', - 'position', - 'document_name', - 'document_number', - 'department' - ); - --- ====================================== --- 检查4:列出缺失的字段 --- ====================================== -SELECT '缺失字段列表' AS '检查项'; - -SELECT - field_name AS '缺失字段', - field_type AS '字段类型', - field_comment AS '字段说明' -FROM ( - SELECT 'gender' AS field_name, 'char(1)' AS field_type, '性别:0-男,1-女' AS field_comment - UNION ALL - SELECT 'birth_date', 'date', '出生日期' - UNION ALL - SELECT 'political_status', 'varchar(20)', '政治面貌' - UNION ALL - SELECT 'professional_title', 'varchar(100)', '职称' - UNION ALL - SELECT 'position', 'varchar(100)', '职务' - UNION ALL - SELECT 'document_name', 'varchar(100)', '申领证件名称' - UNION ALL - SELECT 'document_number', 'varchar(100)', '证件号码' - UNION ALL - SELECT 'department', 'varchar(200)', '部门' -) AS required_fields -WHERE field_name NOT IN ( - SELECT COLUMN_NAME - FROM information_schema.COLUMNS - WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change' -); - --- ====================================== --- 检查5:查看表的基本信息 --- ====================================== -SELECT - '表基本信息' AS '检查项', - TABLE_NAME AS '表名', - TABLE_ROWS AS '数据行数', - CREATE_TIME AS '创建时间', - UPDATE_TIME AS '更新时间', - TABLE_COMMENT AS '表注释' -FROM information_schema.TABLES -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change'; - --- ====================================== --- 诊断结果说明 --- ====================================== -/* -诊断说明: - -1. 如果"字段数量检查"显示 ⚠️ 缺少字段: - - 说明表结构不完整 - - 需要执行修复SQL(🔧修复-添加缺失字段.sql) - -2. 如果"个人信息字段检查"显示 ⚠️ 缺少X个字段: - - 说明缺少个人信息相关字段 - - 这正是导致"Unknown column 'gender'"错误的原因 - -3. "缺失字段列表"会显示具体缺少哪些字段: - - 如果有结果,说明需要添加这些字段 - - 如果无结果,说明字段完整 - -下一步操作: -1. 如果发现缺失字段 → 执行 🔧修复-添加缺失字段.sql -2. 如果字段完整 → 检查后端Mapper映射是否正确 -3. 修复后重启后端服务 -4. 重新测试审批功能 -*/ - diff --git a/docs/设计文档/database/🔧修复-添加缺失字段.sql b/docs/设计文档/database/🔧修复-添加缺失字段.sql deleted file mode 100644 index abc21dc4..00000000 --- a/docs/设计文档/database/🔧修复-添加缺失字段.sql +++ /dev/null @@ -1,163 +0,0 @@ --- ====================================== --- 修复:添加行程变更表缺失的字段 --- 创建时间: 2025-10-13 --- 问题描述: 表中缺少gender等个人信息字段 --- 执行方式: 直接在MySQL中执行 --- ====================================== - --- 设置字符集 -SET NAMES utf8mb4; - --- ====================================== --- 第一步:检查当前表结构(诊断用) --- ====================================== -SHOW COLUMNS FROM rs_overseas_itinerary_change; - --- ====================================== --- 第二步:添加缺失的个人信息字段 --- ====================================== - --- 添加性别字段(在applicant_name之后) -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `gender` char(1) DEFAULT NULL COMMENT '性别:0-男,1-女' -AFTER `applicant_name`; - --- 添加出生日期字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `birth_date` date DEFAULT NULL COMMENT '出生日期' -AFTER `gender`; - --- 添加政治面貌字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `political_status` varchar(20) DEFAULT NULL COMMENT '政治面貌' -AFTER `birth_date`; - --- 添加职称字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `professional_title` varchar(100) DEFAULT NULL COMMENT '职称' -AFTER `political_status`; - --- 添加职务字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `position` varchar(100) DEFAULT NULL COMMENT '职务' -AFTER `professional_title`; - --- 添加证件名称字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `document_name` varchar(100) DEFAULT NULL COMMENT '申领证件名称' -AFTER `position`; - --- 添加证件号码字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `document_number` varchar(100) DEFAULT NULL COMMENT '证件号码' -AFTER `document_name`; - --- 添加部门字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `department` varchar(200) DEFAULT NULL COMMENT '部门' -AFTER `document_number`; - --- ====================================== --- 第三步:验证修复结果 --- ====================================== - --- 查看表结构(检查字段是否添加成功) -SELECT '验证表结构' AS '检查项'; -SHOW COLUMNS FROM rs_overseas_itinerary_change; - --- 统计字段数量(应该是47个字段) -SELECT - '字段数量检查' AS '检查项', - COUNT(*) AS '当前字段数', - '47' AS '期望字段数' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change'; - --- 查看新添加的字段 -SELECT - COLUMN_NAME AS '字段名', - COLUMN_TYPE AS '字段类型', - IS_NULLABLE AS '允许NULL', - COLUMN_DEFAULT AS '默认值', - COLUMN_COMMENT AS '字段说明' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change' - AND COLUMN_NAME IN ( - 'gender', - 'birth_date', - 'political_status', - 'professional_title', - 'position', - 'document_name', - 'document_number', - 'department' - ) -ORDER BY ORDINAL_POSITION; - --- ====================================== --- 使用说明 --- ====================================== -/* -问题原因: -数据库中的表结构是旧版本,缺少个人信息相关字段。 -后端查询时包含了这些字段,导致SQL语法错误。 - -修复步骤: -1. 确保数据库连接正常 -2. 选择正确的数据库(USE your_database) -3. 执行本SQL脚本 -4. 检查验证结果,确认字段已添加 -5. 重启后端服务(清除MyBatis缓存) -6. 重新测试审批功能 - -添加的字段列表: -- gender: 性别(char(1)) -- birth_date: 出生日期(date) -- political_status: 政治面貌(varchar(20)) -- professional_title: 职称(varchar(100)) -- position: 职务(varchar(100)) -- document_name: 申领证件名称(varchar(100)) -- document_number: 证件号码(varchar(100)) -- department: 部门(varchar(200)) - -注意事项: -1. 这些字段都允许为NULL(因为已有数据可能没有这些信息) -2. 新添加的字段会自动插入到applicant_name字段之后 -3. 现有数据的这些字段值为NULL -4. 如需填充数据,可以从其他表(如用户表)同步 -*/ - --- ====================================== --- 可选:从其他表同步数据(如果需要) --- ====================================== -/* --- 示例:从sys_user表同步性别和部门信息 -UPDATE rs_overseas_itinerary_change c -LEFT JOIN sys_user u ON c.applicant_id = u.user_id -SET - c.gender = u.sex, - c.department = ( - SELECT dept_name - FROM sys_dept - WHERE dept_id = u.dept_id - ) -WHERE c.gender IS NULL; - --- 示例:从rs_overseas_application表同步个人信息 -UPDATE rs_overseas_itinerary_change c -LEFT JOIN rs_overseas_application a ON c.application_id = a.application_id -SET - c.gender = a.gender, - c.birth_date = a.birth_date, - c.political_status = a.political_status, - c.professional_title = a.professional_title, - c.position = a.position, - c.document_name = a.document_name, - c.document_number = a.document_number, - c.department = a.department -WHERE c.application_id IS NOT NULL - AND c.gender IS NULL; -*/ - diff --git a/docs/问题解决方案/✅审批记录显示问题修复报告.md b/docs/问题解决方案/✅审批记录显示问题修复报告.md deleted file mode 100644 index 36925252..00000000 --- a/docs/问题解决方案/✅审批记录显示问题修复报告.md +++ /dev/null @@ -1,405 +0,0 @@ -# ✅ 审批记录显示问题修复报告 - -## 📋 问题描述 - -**功能位置**: 人事管理 → 出国行程变更审批 -**问题现象**: 在"待审批变更"选项卡审批通过后,切换到"审批记录"选项卡,看不到刚才审批的数据 -**发现时间**: 2025-10-13 -**修复状态**: ✅ **已修复** - ---- - -## 🔍 问题分析 - -### 根本原因 - -发现了**两个独立的问题**: - -#### 问题1:后端不支持多状态查询 🐛 - -**问题描述**: -- 前端查询审批记录时,传递参数 `status = '1,2'`(已通过和已拒绝) -- 后端 MyBatis Mapper 使用 `status = #{status}` 精确匹配 -- 导致查询条件变成 `status = '1,2'`,而不是 `status IN ('1', '2')` -- 所以查询结果为空 - -**问题代码**: -```xml - - - AND c.status = #{status} - -``` - ---- - -#### 问题2:审批后只刷新当前选项卡 🐛 - -**问题描述**: -- 用户在"待审批"选项卡点击审批通过 -- 审批成功后,代码只刷新了"待审批"列表 -- "审批记录"列表没有刷新,仍显示旧数据 -- 用户切换到"审批记录"选项卡时,看不到新审批的数据 - -**问题代码**: -```typescript -// 审批成功后 -if (state.activeTab === 'pending') { - getPendingList(); // ✅ 刷新待审批列表 -} else if (state.activeTab === 'record') { - getRecordList(); // ❌ 但用户在pending选项卡,不会执行 -} -``` - ---- - -## 🔧 修复方案 - -### 修复1:支持多状态查询 - -**修改文件**: `backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasItineraryChangeMapper.xml` - -**修改内容**: -```xml - - - - - AND c.status IN - - #{item} - - - - - AND c.status = #{status} - - - -``` - -**修复效果**: -- ✅ 支持单状态查询:`status = '1'` → `WHERE status = '1'` -- ✅ 支持多状态查询:`status = '1,2'` → `WHERE status IN ('1', '2')` -- ✅ 兼容现有所有查询 - ---- - -### 修复2:审批后同时刷新两个列表 - -**修改文件**: `web/src/views/rs/overseas/itinerary-change-approval.vue` - -**修改内容**: -```typescript -// 修复前:只刷新当前选项卡 -if (state.activeTab === 'pending') { - getPendingList(); -} else if (state.activeTab === 'record') { - getRecordList(); -} - -// 修复后:同时刷新两个列表 -await getPendingList(); -await getRecordList(); -``` - -**修复效果**: -- ✅ 审批成功后,待审批列表自动移除已审批的数据 -- ✅ 审批记录列表自动显示新审批的数据 -- ✅ 用户切换选项卡时,立即看到最新数据 - ---- - -## 📊 修复文件清单 - -| 文件类型 | 文件路径 | 修改内容 | -|---------|----------|---------| -| 后端Mapper | `backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasItineraryChangeMapper.xml` | 支持多状态IN查询 | -| 前端页面 | `web/src/views/rs/overseas/itinerary-change-approval.vue` | 审批后同时刷新两个列表 | - ---- - -## 🧪 测试验证 - -### 测试步骤 - -#### 1️⃣ 准备测试数据 -```bash -# 确保有待审批的变更申请 -- 状态:待审核 (status = '0') -- 至少2条数据 -``` - -#### 2️⃣ 测试审批通过功能 - -**操作**: -1. 登录系统 -2. 打开:**人事管理 → 出国行程变更审批** -3. 默认在"待审批变更"选项卡 -4. 找到一条待审批的数据 -5. 点击【通过】按钮 -6. 填写审批意见(可选) -7. 点击【确定】 - -**预期结果**: -``` -✅ 提示"审批成功" -✅ "待审批变更"列表自动刷新,该条数据消失 -✅ 切换到"审批记录"选项卡 -✅ 审批记录列表显示刚才审批通过的数据 -✅ 状态显示为"已通过" -✅ 显示审批意见、审批时间、审批人 -``` - -#### 3️⃣ 测试审批拒绝功能 - -**操作**: -1. 在"待审批变更"选项卡 -2. 找到另一条待审批的数据 -3. 点击【拒绝】按钮 -4. 填写拒绝理由(必填) -5. 点击【确定】 - -**预期结果**: -``` -✅ 提示"审批成功" -✅ "待审批变更"列表自动刷新,该条数据消失 -✅ 切换到"审批记录"选项卡 -✅ 审批记录列表显示刚才拒绝的数据 -✅ 状态显示为"已拒绝" -✅ 显示拒绝理由、审批时间、审批人 -``` - -#### 4️⃣ 测试审批记录筛选 - -**操作**: -1. 在"审批记录"选项卡 -2. 在搜索条件中选择"审批状态" -3. 分别选择: - - 只显示"已通过" - - 只显示"已拒绝" - - 不选择(显示全部) - -**预期结果**: -``` -✅ 选择"已通过":只显示status='1'的数据 -✅ 选择"已拒绝":只显示status='2'的数据 -✅ 不选择状态:显示status='1'和'2'的所有数据 -✅ 分页功能正常 -✅ 搜索功能正常 -``` - ---- - -## 🎯 技术要点 - -### MyBatis 动态SQL技巧 - -**支持多状态查询的实现**: - -```xml - - - - AND c.status IN - - #{item} - - - - - AND c.status = #{status} - - -``` - -**优点**: -- ✅ 兼容单值和多值查询 -- ✅ 前端无需改动,传递逗号分隔字符串即可 -- ✅ SQL注入安全(使用 `#{item}` 预编译) -- ✅ 性能优秀(使用IN查询) - ---- - -### Vue 数据刷新策略 - -**审批后刷新多个列表**: - -```typescript -// ❌ 错误做法:只刷新当前选项卡 -if (activeTab === 'pending') { - getPendingList(); -} - -// ✅ 正确做法:刷新所有相关列表 -await getPendingList(); -await getRecordList(); -``` - -**原因**: -- 审批操作影响两个选项卡的数据 -- 用户可能在审批后立即切换选项卡 -- 预先刷新避免看到旧数据 -- 使用 `await` 确保顺序执行 - ---- - -## 📝 相关文档 - -### 问题追踪 -- [📊行程变更审批功能-问题汇总.md](./📊行程变更审批功能-问题汇总.md) - 所有问题汇总 - -### API文档 -- [出国行程变更管理API.md](../开发文档/API/出国行程变更管理API.md) - 完整API文档 - -### 测试文档 -- [行程变更审批功能-测试指南.md](./行程变更审批功能-测试指南.md) - 详细测试指南 - ---- - -## 💡 经验总结 - -### 问题教训 - -1. **MyBatis查询要考虑多值场景** - - 不要只用 `= #{param}` - - 考虑使用 IN、LIKE等更灵活的查询 - - 使用 `` 支持动态查询条件 - -2. **数据刷新要考虑关联影响** - - 一个操作可能影响多个列表 - - 刷新所有相关数据,避免数据不一致 - - 不要只刷新当前可见的数据 - -3. **前后端参数传递要约定清楚** - - 多状态查询:逗号分隔还是数组? - - 后端要兼容前端的传参方式 - - 文档要明确说明参数格式 - ---- - -## 🚀 后续优化建议 - -### 1. 统一多状态查询方案 - -**当前问题**: -- 每个Mapper都要单独实现多状态查询逻辑 -- 代码重复 - -**优化建议**: -```java -// 在Query基类中添加通用方法 -public class BaseQuery { - // 将逗号分隔的字符串转为List - public List splitStatus(String status) { - if (StringUtils.isEmpty(status)) { - return Collections.emptyList(); - } - return Arrays.asList(status.split(",")); - } -} -``` - -**使用方式**: -```xml - - AND c.status IN - - #{item} - - -``` - ---- - -### 2. 前端使用数组传递多状态 - -**当前方式**: -```typescript -params.status = '1,2'; // 逗号分隔字符串 -``` - -**优化方式**: -```typescript -params.statusList = ['1', '2']; // 数组 -``` - -**后端接收**: -```java -public class RsOverseasItineraryChangeQuery { - private List statusList; // 状态列表 -} -``` - -**Mapper使用**: -```xml - - AND c.status IN - - #{item} - - -``` - -**优点**: -- ✅ 类型安全 -- ✅ 更符合RESTful规范 -- ✅ 避免字符串分割 - ---- - -### 3. 添加列表自动刷新机制 - -**优化建议**: -- 使用 WebSocket 实时推送审批结果 -- 其他用户审批后,当前用户列表自动刷新 -- 提升用户体验 - ---- - -## 📊 修复前后对比 - -### 修复前 ❌ - -| 操作 | 结果 | -|------|------| -| 在"待审批"选项卡审批通过 | ✅ 待审批列表刷新 | -| 切换到"审批记录"选项卡 | ❌ 看不到刚才审批的数据 | -| 手动点击搜索按钮 | ✅ 才能看到新数据 | - -### 修复后 ✅ - -| 操作 | 结果 | -|------|------| -| 在"待审批"选项卡审批通过 | ✅ 待审批列表刷新 | -| 切换到"审批记录"选项卡 | ✅ 立即看到刚才审批的数据 | -| 无需任何额外操作 | ✅ 数据实时同步 | - ---- - -## 🎉 总结 - -### 修复完成 ✅ - -**问题**: 审批记录选项卡不显示已审批的数据 -**原因**: 后端不支持多状态查询 + 前端只刷新当前选项卡 -**修复**: 支持IN查询 + 同时刷新两个列表 -**状态**: ✅ **已完全修复** - -### 待验证 ⚠️ - -**需要您做的**: -1. ✅ 重启后端服务(应用Mapper.xml修改) -2. ✅ 清除浏览器缓存 -3. ✅ 按照测试步骤验证功能 -4. ✅ 确认问题已解决 - ---- - -**修复完成时间**: 2025-10-13 -**修复文件数量**: 2个 -**修复代码行数**: 约30行 -**测试状态**: 待验证 - -**准备好测试了吗?请按照上面的测试步骤验证!** 🚀💪 - diff --git a/docs/问题解决方案/行程变更审批功能-测试指南.md b/docs/问题解决方案/行程变更审批功能-测试指南.md deleted file mode 100644 index a9bc0229..00000000 --- a/docs/问题解决方案/行程变更审批功能-测试指南.md +++ /dev/null @@ -1,363 +0,0 @@ -# 行程变更审批功能 - 测试指南 - -## 🎯 测试目标 - -验证出国行程变更审批功能是否正常工作(修复参数错误后) - ---- - -## 📋 测试前准备 - -### 1. 清除浏览器缓存 - -**方式一:硬刷新(推荐)** -- Windows/Linux: `Ctrl + Shift + R` 或 `Ctrl + F5` -- Mac: `Cmd + Shift + R` - -**方式二:清除浏览器缓存** -1. 打开浏览器开发者工具(F12) -2. 右键点击刷新按钮 -3. 选择"清空缓存并硬性重新加载" - -### 2. 准备测试数据 - -确保数据库中有: -- ✅ 至少1个待审核的行程变更申请 -- ✅ 当前用户有审批权限(`rs:itineraryChange:approve`) - -### 3. 检查权限配置 - -**菜单路径**: 系统管理 → 角色管理 → [你的角色] → 权限配置 - -**必需权限**: -- ✅ `rs:itineraryChange:list` - 查询变更列表 -- ✅ `rs:itineraryChange:query` - 查看变更详情 -- ✅ `rs:itineraryChange:approve` - 审批变更申请 - ---- - -## 🧪 测试场景 - -### 场景1:审批通过 - -#### 步骤 - -1. **导航到审批页面** - - 菜单:人事管理 → 出国行程变更审批 - -2. **查看待审批列表** - - 默认打开"待审批变更"Tab - - 确认列表中有待审核的变更申请 - -3. **点击审批通过按钮** - - 找到一条待审批记录 - - 点击【通过】按钮(绿色对勾图标) - -4. **填写审批信息** - - 审批结果:选择"通过" - - 审批意见:输入"同意变更"(可选) - - 点击【确定】 - -#### 预期结果 - -**成功提示**: -``` -✅ 弹出"审批成功"提示 -✅ 对话框自动关闭 -✅ 列表自动刷新 -``` - -**数据验证**: -``` -✅ 该记录从"待审批变更"Tab中消失 -✅ 在"审批记录"Tab中可以看到该记录 -✅ 状态显示为"已通过" -✅ 显示审批人姓名 -✅ 显示审批时间 -✅ 显示审批意见 -``` - -#### 验证API请求 - -**打开开发者工具** (F12) → Network → 找到请求: - -**请求URL**: -``` -PUT /rs/itinerary/change/approve -``` - -**请求体**: -```json -{ - "changeId": 1977740586114916354, - "approved": true, - "opinion": "同意变更" -} -``` - -**响应**: -```json -{ - "code": 200, - "msg": "操作成功" -} -``` - ---- - -### 场景2:审批拒绝 - -#### 步骤 - -1. **导航到审批页面** - - 菜单:人事管理 → 出国行程变更审批 - -2. **查看待审批列表** - - 在"待审批变更"Tab中 - -3. **点击审批拒绝按钮** - - 找到一条待审批记录 - - 点击【拒绝】按钮(红色X图标) - -4. **填写审批信息** - - 审批结果:选择"拒绝" - - 审批意见:输入"不符合变更条件"(必填) - - 点击【确定】 - -#### 预期结果 - -**成功提示**: -``` -✅ 弹出"审批成功"提示 -✅ 对话框自动关闭 -✅ 列表自动刷新 -``` - -**数据验证**: -``` -✅ 该记录从"待审批变更"Tab中消失 -✅ 在"审批记录"Tab中可以看到该记录 -✅ 状态显示为"已拒绝"(红色) -✅ 显示审批人姓名 -✅ 显示审批时间 -✅ 显示拒绝原因 -``` - -#### 验证API请求 - -**请求URL**: -``` -PUT /rs/itinerary/change/approve -``` - -**请求体**: -```json -{ - "changeId": 1977740586114916355, - "approved": false, - "opinion": "不符合变更条件" -} -``` - -**响应**: -```json -{ - "code": 200, - "msg": "操作成功" -} -``` - ---- - -### 场景3:查看审批记录 - -#### 步骤 - -1. **切换到"审批记录"Tab** - - 点击"审批记录"选项卡 - -2. **查看审批历史** - - 查看所有已审批的记录 - - 包括已通过和已拒绝的 - -#### 预期结果 - -**列表显示**: -``` -✅ 显示所有已审批的变更申请 -✅ 显示状态:已通过(绿色)或已拒绝(红色) -✅ 显示申请人、目的地、时间等信息 -✅ 显示审批人、审批时间、审批意见 -``` - -**搜索功能**: -``` -✅ 可以按申请人姓名搜索 -✅ 可以按审批状态筛选 -✅ 可以按时间范围筛选 -``` - ---- - -### 场景4:查看变更详情 - -#### 步骤 - -1. **在待审批列表中** - - 找到一条记录 - - 点击【查看详情】按钮(眼睛图标) - -2. **查看详细信息** - - 弹出详情对话框 - - 查看变更申请的完整信息 - -#### 预期结果 - -**详情对话框**: -``` -✅ 显示基本信息:申请人、部门、性别等 -✅ 显示原始信息:原目的地、原时间等 -✅ 显示变更信息:新目的地、新时间等 -✅ 显示变更原因 -✅ 显示附件材料(如有) -``` - ---- - -## 🔍 常见问题排查 - -### 问题1:点击审批通过/拒绝无反应 - -**可能原因**: -- 浏览器缓存未清除 -- 权限不足 - -**解决方案**: -1. 清除浏览器缓存(硬刷新) -2. 检查用户权限是否包含 `rs:itineraryChange:approve` -3. 查看浏览器控制台是否有错误信息 - ---- - -### 问题2:提示"Cannot invoke Object.toString()" - -**可能原因**: -- 前端代码未更新 -- 浏览器使用了旧的JS文件 - -**解决方案**: -1. 确认前端代码已更新 -2. 清除浏览器缓存 -3. 检查Network中的请求体是否包含正确的字段: - - ✅ `approved` (boolean) - - ❌ `approvalStatus` (string) - ---- - -### 问题3:审批后状态未更新 - -**可能原因**: -- 后端状态更新失败 -- 列表未刷新 - -**解决方案**: -1. 手动刷新页面 -2. 检查后端日志 -3. 检查数据库中的状态字段 - -**验证SQL**: -```sql -SELECT - change_id, - applicant_name, - status, - approver_name, - approve_time, - approval_opinion -FROM rs_overseas_itinerary_change -WHERE change_id = <你的变更ID>; -``` - ---- - -### 问题4:审批记录Tab为空 - -**可能原因**: -- 没有已审批的记录 -- 查询条件过滤了所有记录 - -**解决方案**: -1. 先完成一次审批 -2. 检查是否设置了搜索条件 -3. 点击【重置】按钮清除搜索条件 - ---- - -## 📊 测试数据对比表 - -### 审批前后状态变化 - -| 操作 | 审批前状态 | 审批后状态 | 显示Tab | -|------|-----------|-----------|---------| -| 审批通过 | 待审核(0) | 已通过(1) | 审批记录 | -| 审批拒绝 | 待审核(0) | 已拒绝(2) | 审批记录 | - -### 审批后填充的字段 - -| 字段名 | 说明 | 示例值 | -|--------|------|--------| -| status | 审批状态 | '1'(通过) 或 '2'(拒绝) | -| approver_id | 审批人ID | 1 | -| approver_name | 审批人姓名 | admin | -| approve_time | 审批时间 | 2025-10-13 14:30:00 | -| approval_opinion | 审批意见 | 同意变更 | - ---- - -## ✅ 测试通过标准 - -### 核心验证点 - -1. ✅ **审批通过**: 提示成功,状态变为"已通过" -2. ✅ **审批拒绝**: 提示成功,状态变为"已拒绝" -3. ✅ **请求参数**: 包含 `approved`(boolean)、`opinion`(string) -4. ✅ **数据更新**: 审批人、审批时间、审批意见正确填充 -5. ✅ **列表刷新**: 审批后列表自动刷新,记录移动到正确的Tab -6. ✅ **权限控制**: 无权限用户看不到审批按钮 - ---- - -## 🔄 回归测试 - -### 其他功能验证 - -完成审批功能测试后,还需验证: - -1. **变更申请提交** (变更行程管理) - - [ ] 提交新的变更申请 - - [ ] 修改待审核的变更申请 - - [ ] 撤回待审核的变更申请 - -2. **变更记录查询** (变更行程管理) - - [ ] "变更申请"Tab显示所有状态的申请 - - [ ] "变更记录"Tab只显示已通过的申请 - -3. **权限控制** - - [ ] 无审批权限的用户看不到审批菜单 - - [ ] 无审批权限的用户看不到审批按钮 - ---- - -## 📞 相关文档 - -- [行程变更审批接口参数错误修复总结](./行程变更审批接口参数错误修复总结.md) -- [出国行程变更管理API文档](../开发文档/API/出国行程变更管理API.md) -- [变更记录Tab显示逻辑修复总结](./变更记录Tab显示逻辑修复总结.md) - ---- - -**测试指南创建时间**: 2025-10-13 -**适用版本**: 出国行程变更管理功能 v1.0 -**测试类型**: 功能测试 + 接口测试 -**测试重点**: 审批接口参数修复验证 - diff --git a/docs/问题解决方案/行程变更审批接口参数错误修复总结.md b/docs/问题解决方案/行程变更审批接口参数错误修复总结.md deleted file mode 100644 index c0e58178..00000000 --- a/docs/问题解决方案/行程变更审批接口参数错误修复总结.md +++ /dev/null @@ -1,368 +0,0 @@ -# 行程变更审批接口参数错误修复总结 - -## 📋 问题描述 - -**错误现象**: -- 在"出国行程变更审批"菜单下,点击"审批通过"按钮时报错 -- 错误信息:`Cannot invoke "Object.toString()" because the return value of "java.util.Map.get(Object)" is null` - -**发生时间**: 2025-10-13 -**影响功能**: 出国行程变更审批功能 - ---- - -## 🔍 问题分析 - -### 错误堆栈定位 - -**后端Controller代码** (`RsOverseasItineraryChangeController.java:155-160`): - -```java -@PutMapping("/approve") -public R approve(@RequestBody Map params) { - Long changeId = Long.valueOf(params.get("changeId").toString()); - Boolean approved = Boolean.valueOf(params.get("approved").toString()); // ❌ 这里报错 - String opinion = params.get("opinion") != null ? params.get("opinion").toString() : ""; - - return toAjax(changeService.approveChange(changeId, approved, opinion)); -} -``` - -**错误原因**: -- `params.get("approved")` 返回 `null` -- 调用 `null.toString()` 导致空指针异常 - ---- - -## 🐛 根本原因 - -### 前后端参数不匹配 - -| 端 | 字段名 | 类型 | 说明 | -|----|--------|------|------| -| **后端期望** | `approved` | Boolean | true=通过,false=拒绝 | -| **后端期望** | `opinion` | String | 审批意见 | -| **前端发送** | `approvalStatus` | String | '1'=通过,'2'=拒绝 ❌ | -| **前端发送** | `approvalOpinion` | String | 审批意见 ❌ | - -**问题总结**: -1. ✅ 字段名不匹配:`approvalStatus` vs `approved` -2. ✅ 字段名不匹配:`approvalOpinion` vs `opinion` -3. ✅ 数据类型不匹配:字符串'1'/'2' vs 布尔值true/false - ---- - -## 🔧 修复方案 - -### 1. 修复前端TypeScript接口定义 - -**文件**: `web/src/api/rs/overseas.ts` - -**修改前**: -```typescript -export interface ItineraryChangeApprovalRequest { - changeId: number | string; - approvalStatus: string; // ❌ 错误 - approvalOpinion?: string; // ❌ 错误 -} -``` - -**修改后**: -```typescript -export interface ItineraryChangeApprovalRequest { - changeId: number | string; - approved: boolean; // ✅ true=通过,false=拒绝 - opinion?: string; // ✅ 审批意见 -} -``` - ---- - -### 2. 修复前端审批表单 - -**文件**: `web/src/views/rs/overseas/itinerary-change-approval.vue` - -#### 2.1 表单模板 - -**修改前**: -```vue - - - 通过 - 拒绝 - - - - - -``` - -**修改后**: -```vue - - - 通过 - 拒绝 - - - - - -``` - -#### 2.2 表单数据 - -**修改前**: -```typescript -approvalForm: { - changeId: 0, - approvalStatus: '1', // ❌ 字符串 - approvalOpinion: '' // ❌ 字段名错误 -} -``` - -**修改后**: -```typescript -approvalForm: { - changeId: 0, - approved: true, // ✅ 布尔值 - opinion: '' // ✅ 字段名正确 -} -``` - -#### 2.3 表单验证规则 - -**修改前**: -```typescript -approvalRules: { - approvalStatus: [{ required: true, message: '请选择审批结果', trigger: 'change' }] -} -``` - -**修改后**: -```typescript -approvalRules: { - approved: [{ required: true, message: '请选择审批结果', trigger: 'change' }] -} -``` - -#### 2.4 审批处理函数 - -**修改前**: -```typescript -const handleApprovalPass = (row: ItineraryChangeVO) => { - state.approvalForm.changeId = row.changeId as number; - state.approvalForm.approvalStatus = '1'; // ❌ - state.approvalForm.approvalOpinion = ''; // ❌ -}; - -const handleApprovalReject = (row: ItineraryChangeVO) => { - state.approvalForm.changeId = row.changeId as number; - state.approvalForm.approvalStatus = '2'; // ❌ - state.approvalForm.approvalOpinion = ''; // ❌ -}; -``` - -**修改后**: -```typescript -const handleApprovalPass = (row: ItineraryChangeVO) => { - state.approvalForm.changeId = row.changeId as number; - state.approvalForm.approved = true; // ✅ - state.approvalForm.opinion = ''; // ✅ -}; - -const handleApprovalReject = (row: ItineraryChangeVO) => { - state.approvalForm.changeId = row.changeId as number; - state.approvalForm.approved = false; // ✅ - state.approvalForm.opinion = ''; // ✅ -}; -``` - ---- - -### 3. 更新API文档 - -**文件**: `docs/开发文档/API/出国行程变更管理API.md` - -添加了重要提示: - -```markdown -**重要提示**: -- ⚠️ **后端接口使用Map接收参数,前端必须发送正确的字段名** -- ✅ 字段名:`changeId`、`approved`、`opinion` -- ❌ 不要使用:`approvalStatus`、`approvalOpinion` -``` - ---- - -## ✅ 修复验证 - -### 验证步骤 - -1. **清除浏览器缓存** - - Windows/Linux: `Ctrl + Shift + R` - - Mac: `Cmd + Shift + R` - -2. **测试审批通过** - - 导航到:人事管理 → 出国行程变更审批 - - 在"待审批变更"Tab中找到一条记录 - - 点击【通过】按钮 - - 填写审批意见 - - 提交 - -3. **测试审批拒绝** - - 选择一条待审批记录 - - 点击【拒绝】按钮 - - 填写拒绝原因 - - 提交 - -### 预期结果 - -**审批通过**: -``` -✅ 提示"审批成功" -✅ 状态变为"已通过" -✅ 记录显示在"审批记录"Tab中 -✅ 显示审批人、审批时间、审批意见 -``` - -**审批拒绝**: -``` -✅ 提示"审批成功" -✅ 状态变为"已拒绝" -✅ 记录显示在"审批记录"Tab中 -✅ 显示拒绝原因 -``` - -### 发送的请求数据 - -**审批通过**: -```json -{ - "changeId": 1977740586114916354, - "approved": true, - "opinion": "同意变更" -} -``` - -**审批拒绝**: -```json -{ - "changeId": 1977740586114916354, - "approved": false, - "opinion": "不同意变更" -} -``` - ---- - -## 📁 修改文件清单 - -| 序号 | 文件路径 | 修改类型 | 说明 | -|-----|---------|---------|------| -| 1 | `web/src/api/rs/overseas.ts` | 🔧 修改 | 更新TypeScript接口定义 | -| 2 | `web/src/views/rs/overseas/itinerary-change-approval.vue` | 🔧 修改 | 更新表单模板、数据、验证规则、处理函数 | -| 3 | `docs/开发文档/API/出国行程变更管理API.md` | 📝 更新 | 添加参数重要提示 | -| 4 | `docs/问题解决方案/行程变更审批接口参数错误修复总结.md` | ✨ 新增 | 本文档 | - ---- - -## 💡 经验总结 - -### 问题根因 - -1. **前后端参数不一致**: 后端使用Map接收参数,字段名必须完全匹配 -2. **类型转换问题**: 后端期望Boolean,前端发送String会导致逻辑错误 -3. **缺少参数验证**: 后端直接调用`.toString()`,没有做空值检查 - -### 最佳实践 - -#### 1. 统一参数命名 -``` -✅ 前后端使用相同的字段名 -✅ 避免使用"approval"前缀,直接使用"approved"、"opinion" -✅ TypeScript接口定义与后端Controller参数保持一致 -``` - -#### 2. 类型安全 -``` -✅ 使用布尔值表示是/否,不要用字符串'1'/'2' -✅ TypeScript接口定义中明确类型(boolean vs string) -✅ 后端使用强类型BO接收参数,而不是Map -``` - -#### 3. 后端参数接收建议 - -**当前方式**(使用Map,容易出错): -```java -public R approve(@RequestBody Map params) { - Long changeId = Long.valueOf(params.get("changeId").toString()); // ❌ 空值异常风险 - Boolean approved = Boolean.valueOf(params.get("approved").toString()); -} -``` - -**推荐方式**(使用BO,类型安全): -```java -public R approve(@RequestBody ItineraryChangeApprovalBo bo) { - return toAjax(changeService.approveChange(bo.getChangeId(), bo.getApproved(), bo.getOpinion())); -} - -// ItineraryChangeApprovalBo.java -@Data -public class ItineraryChangeApprovalBo { - @NotNull(message = "变更ID不能为空") - private Long changeId; - - @NotNull(message = "审批结果不能为空") - private Boolean approved; - - private String opinion; -} -``` - -#### 4. API文档规范 -``` -✅ 明确标注字段名、类型、是否必填 -✅ 提供完整的请求体示例 -✅ 说明常见错误和注意事项 -``` - ---- - -## 🔄 类似问题预防 - -### 检查清单 - -在开发新的审批接口时,请检查: - -- [ ] 前端TypeScript接口定义与后端Controller参数一致 -- [ ] 使用布尔值表示是/否,不使用字符串 -- [ ] 字段命名简洁明确(approved而不是approvalStatus) -- [ ] 后端优先使用BO接收参数,避免使用Map -- [ ] 后端参数做空值检查 -- [ ] API文档完整准确 - -### 相关接口 - -以下接口可能有类似问题,需要检查: - -- [ ] 出国申请审批 -- [ ] 出国登记审批 -- [ ] 其他需要审批的功能模块 - ---- - -## 📞 相关文档 - -- [出国行程变更管理API文档](../开发文档/API/出国行程变更管理API.md) -- [变更记录Tab显示逻辑修复总结](./变更记录Tab显示逻辑修复总结.md) -- [后端开发规范](.cursor/cursorrules-backend.md) - ---- - -**修复完成时间**: 2025-10-13 -**影响范围**: 出国行程变更审批功能 -**修复类型**: 前端参数修正 -**风险评估**: 低(仅影响前端参数传递,不影响数据库) -**回归测试**: 需要验证审批通过和拒绝功能 - diff --git a/docs/问题解决方案/行程变更状态乱码修复总结.md b/docs/问题解决方案/行程变更状态乱码修复总结.md deleted file mode 100644 index f0a32d4c..00000000 --- a/docs/问题解决方案/行程变更状态乱码修复总结.md +++ /dev/null @@ -1,239 +0,0 @@ -# ✅ 行程变更状态乱码问题修复总结 - -## 📋 问题描述 - -**现象**:出国行程变更申请菜单下,变更申请选项卡列表中的"变更状态"字段显示乱码 - -**影响范围**: -- 变更申请列表页面 -- 变更记录列表页面 -- 详情查看弹窗 - -**错误示例**:显示为 `a独...a®re...` 而不是 "待审核"、"已通过"等正常中文 - ---- - -## 🔍 问题诊断过程 - -### 1. 技术实现链路分析 - -#### 数据库层(Mapper XML) -```xml - - - SELECT - c.status, - dict_status.dict_label AS status_text -- 字典关联翻译 - FROM rs_overseas_itinerary_change c - LEFT JOIN sys_dict_data dict_status - ON dict_status.dict_value = c.status - AND dict_status.dict_type = 'rs_itinerary_change_status' - -``` - -#### 后端VO类 -```java -// RsOverseasItineraryChangeVo.java 第206-211行 -private String status; // 状态值:0,1,2,3 -private String statusText; // 状态文本:待审核,已通过,已拒绝,已撤回 -``` - -#### 前端显示 -```vue - - - - -``` - -### 2. 编码问题诊断 - -执行HEX编码检查: -```sql -SELECT dict_label, HEX(dict_label) FROM sys_dict_data -WHERE dict_type = 'rs_itinerary_change_status'; -``` - -**诊断结果**: -``` -错误的HEX编码(双重UTF-8编码): -- "待审核": C3A5C2BEE280A6C3A5C2AEC2A1C3A6C2A0C2B8 ❌ - -正确的HEX编码(UTF-8): -- "待审核": E5BE85E5AEA1E6A0B8 ✅ -``` - -### 3. 根本原因 - -**双重UTF-8编码问题**: -- 插入数据时未正确设置字符集(缺少 `SET NAMES utf8mb4`) -- UTF-8编码的中文被当作Latin1字符集存储 -- 导致字节被二次编码,形成乱码 - ---- - -## 🔧 解决方案 - -### 修复步骤 - -1. **设置正确的字符集连接** - ```sql - SET NAMES utf8mb4; - ``` - -2. **删除错误的字典数据** - ```sql - DELETE FROM sys_dict_data WHERE dict_type = 'rs_itinerary_change_status'; - DELETE FROM sys_dict_type WHERE dict_type = 'rs_itinerary_change_status'; - ``` - -3. **使用CONVERT函数确保正确编码** - ```sql - INSERT INTO sys_dict_data (dict_label, ...) - VALUES (CONVERT('待审核' USING utf8mb4), ...); - ``` - -### 完整修复脚本 - -**位置**:`docs/设计文档/database/修复-行程变更状态字典编码.sql` - -**执行方式**: -```bash -mysql -h -P -u -p < 修复-行程变更状态字典编码.sql -``` - ---- - -## ✅ 验证结果 - -### 1. 编码验证 -```sql -SELECT dict_label, HEX(dict_label), LENGTH(dict_label), CHAR_LENGTH(dict_label) -FROM sys_dict_data -WHERE dict_type = 'rs_itinerary_change_status'; -``` - -**结果**: -| dict_label | HEX编码 | 字节长度 | 字符长度 | -|-----------|---------|---------|---------| -| 待审核 | E5BE85E5AEA1E6A0B8 | 9 | 3 | -| 已通过 | E5B7B2E9809AE8BF87 | 9 | 3 | -| 已拒绝 | E5B7B2E68B92E7BB9D | 9 | 3 | -| 已撤回 | E5B7B2E692A4E59B9E | 9 | 3 | - -✅ **编码正确**:字节长度 = 字符长度 × 3(UTF-8中文占3字节) - -### 2. 字典关联查询验证 -```sql -SELECT c.applicant_name, c.status, d.dict_label as status_text -FROM rs_overseas_itinerary_change c -LEFT JOIN sys_dict_data d - ON d.dict_value = c.status - AND d.dict_type = 'rs_itinerary_change_status' -WHERE c.deleted = 0; -``` - -**结果**: -| 申请人 | 状态值 | 状态文本 | -|-------|-------|---------| -| admin | 0 | 待审核 | - -✅ **关联查询正确**:status_text正确显示中文 - -### 3. 前端显示验证 - -刷新前端页面后,变更状态列正确显示: -- 🔵 待审核(蓝色标签) -- 🟢 已通过(绿色标签) -- 🔴 已拒绝(红色标签) -- ⚪ 已撤回(灰色标签) - ---- - -## 📚 字典数据说明 - -### 字典类型 -- **dict_type**: `rs_itinerary_change_status` -- **dict_name**: 行程变更状态 - -### 字典数据明细 - -| dict_value | dict_label | list_class | 说明 | -|-----------|-----------|-----------|------| -| 0 | 待审核 | primary | 蓝色标签 | -| 1 | 已通过 | success | 绿色标签 | -| 2 | 已拒绝 | danger | 红色标签 | -| 3 | 已撤回 | info | 灰色标签 | - ---- - -## 💡 经验总结 - -### 问题关键点 - -1. ✅ **字符集设置至关重要** - - 必须在连接时设置:`SET NAMES utf8mb4` - - 插入中文数据前必须确保字符集正确 - -2. ✅ **使用CONVERT函数保险** - - `CONVERT('中文' USING utf8mb4)` 确保编码正确 - - 避免字符集自动转换导致的编码问题 - -3. ✅ **HEX函数是诊断利器** - - `HEX(column)` 可以查看实际存储的字节编码 - - 正确的UTF-8中文字符:每字符3字节 - -### 预防措施 - -1. **SQL脚本规范** - ```sql - -- 所有包含中文的SQL脚本都应该: - SET NAMES utf8mb4; -- 第一行设置字符集 - ``` - -2. **数据库客户端配置** - ```bash - # 使用mysql命令行时: - mysql --default-character-set=utf8mb4 ... - ``` - -3. **建表规范** - ```sql - CREATE TABLE ... DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - ``` - ---- - -## 📁 相关文件 - -### 修复脚本 -- `docs/设计文档/database/修复-行程变更状态字典编码.sql` - -### 代码文件 -- **后端Mapper**: `backend/ruoyi-modules/ruoyi-rs/src/main/resources/mapper/rs/RsOverseasItineraryChangeMapper.xml` -- **后端VO**: `backend/ruoyi-modules/ruoyi-rs/src/main/java/org/dromara/rs/domain/vo/RsOverseasItineraryChangeVo.java` -- **前端页面**: `web/src/views/rs/overseas/itinerary-change.vue` - -### 原始SQL脚本 -- `docs/设计文档/database/rs_overseas_itinerary_change_full.sql` - ---- - -## 🎯 状态说明 - -- ✅ **问题已修复**:2025-10-13 -- ✅ **数据库已更新**:字典数据编码正确 -- ✅ **前端显示正常**:中文状态正确显示 -- ✅ **修复脚本已归档**:可用于后续类似问题 - ---- - -**修复完成时间**:2025-10-13 -**问题类型**:数据库字符集编码问题 -**修复方法**:重新插入正确编码的字典数据 -**影响范围**:行程变更管理功能的状态显示 - diff --git a/docs/问题解决方案/📊行程变更审批功能-问题汇总.md b/docs/问题解决方案/📊行程变更审批功能-问题汇总.md deleted file mode 100644 index 2a569f96..00000000 --- a/docs/问题解决方案/📊行程变更审批功能-问题汇总.md +++ /dev/null @@ -1,443 +0,0 @@ -# 📊 行程变更审批功能 - 问题汇总 - -## 📋 概述 - -本文档汇总了"出国行程变更审批"功能开发和测试过程中遇到的所有问题及解决方案。 - -**功能模块**: 出国行程变更审批 -**发现时间**: 2025-10-13 -**修复状态**: ✅ 已全部修复 -**测试状态**: 🔄 待验证 - ---- - -## 🐛 问题清单 - -### 问题1:审批记录选项卡不显示已审批数据 ✅ - -**发现时间**: 2025-10-13 最新 -**错误现象**: 在"待审批变更"选项卡审批通过后,切换到"审批记录"选项卡,看不到刚才审批的数据 -**预期行为**: 审批记录选项卡应该立即显示刚审批通过/拒绝的数据 - -**根本原因**: -1. **后端Mapper不支持多状态查询**: 前端传递 `status='1,2'`,但Mapper使用 `status = #{status}` 精确匹配,导致查询失败 -2. **前端只刷新当前选项卡**: 审批成功后只刷新待审批列表,不刷新审批记录列表 - -**修复方案**: - -**1️⃣ 后端支持多状态IN查询**: -```xml - - - - - AND c.status IN - - #{item} - - - - AND c.status = #{status} - - - -``` - -**2️⃣ 前端审批后同时刷新两个列表**: -```typescript -// 修复前:只刷新当前选项卡 -if (state.activeTab === 'pending') { - getPendingList(); -} - -// 修复后:同时刷新两个列表 -await getPendingList(); -await getRecordList(); -``` - -**修复文件**: -- `backend/.../mapper/rs/RsOverseasItineraryChangeMapper.xml` - 支持多状态查询 -- `web/src/views/rs/overseas/itinerary-change-approval.vue` - 同时刷新列表 - -**详细文档**: -- [✅审批记录显示问题修复报告.md](./✅审批记录显示问题修复报告.md) - ---- - -### 问题2:变更记录Tab显示逻辑错误 ✅ - -**发现时间**: 2025-10-13 早期 -**错误现象**: "变更记录"Tab显示了所有状态的变更申请 -**预期行为**: 只显示已通过(status='1')的变更记录 - -**根本原因**: -- 前端查询"变更记录"时未设置status过滤条件 -- 导致查询返回所有状态的数据 - -**修复方案**: -```typescript -// 修复前:无status过滤 -const fetchRecordList = () => { - getItineraryChangeList(queryParams.value).then(...) -} - -// 修复后:添加status='1'过滤 -const fetchRecordList = () => { - queryParams.value.status = '1'; // ✅ 只查询已通过的记录 - getItineraryChangeList(queryParams.value).then(...) -} -``` - -**修复文件**: -- `web/src/views/rs/overseas/itinerary-change.vue` - -**详细文档**: -- [变更记录Tab显示逻辑修复总结.md](./变更记录Tab显示逻辑修复总结.md) - ---- - -### 问题3:审批接口参数错误 ✅ - -**发现时间**: 2025-10-13 中期 -**错误现象**: 点击审批通过时报错 -**错误信息**: -``` -Cannot invoke "Object.toString()" because the return value of "java.util.Map.get(Object)" is null -``` - -**根本原因**: -- 前后端接口参数名不匹配 -- 前端发送:`approvalStatus` (String) + `approvalOpinion` (String) -- 后端期望:`approved` (Boolean) + `opinion` (String) - -**修复方案**: - -**1️⃣ 修复TypeScript接口定义**: -```typescript -// 修复前 -export interface ItineraryChangeApprovalRequest { - changeId: number | string; - approvalStatus: string; // ❌ - approvalOpinion?: string; // ❌ -} - -// 修复后 -export interface ItineraryChangeApprovalRequest { - changeId: number | string; - approved: boolean; // ✅ - opinion?: string; // ✅ -} -``` - -**2️⃣ 修复前端表单**: -```typescript -// 修复前 -approvalForm: { - changeId: 0, - approvalStatus: '1', // ❌ 字符串 - approvalOpinion: '' // ❌ 字段名错误 -} - -// 修复后 -approvalForm: { - changeId: 0, - approved: true, // ✅ 布尔值 - opinion: '' // ✅ 字段名正确 -} -``` - -**修复文件**: -- `web/src/api/rs/overseas.ts` -- `web/src/views/rs/overseas/itinerary-change-approval.vue` - -**详细文档**: -- [行程变更审批接口参数错误修复总结.md](./行程变更审批接口参数错误修复总结.md) -- [行程变更审批功能-测试指南.md](./行程变更审批功能-测试指南.md) - ---- - -### 问题4:数据库字段缺失 ✅ - -**发现时间**: 2025-10-13 晚期 -**错误现象**: 修复问题3后,点击审批通过仍然报错 -**错误信息**: -``` -java.sql.SQLSyntaxErrorException: Unknown column 'gender' in 'field list' -``` - -**根本原因**: -- 数据库表结构是旧版本,缺少个人信息字段 -- 后端Mapper查询包含了这些字段,但表中不存在 - -**缺失字段**(8个): -```sql -gender -- 性别 char(1) -birth_date -- 出生日期 date -political_status -- 政治面貌 varchar(20) -professional_title -- 职称 varchar(100) -position -- 职务 varchar(100) -document_name -- 申领证件名称 varchar(100) -document_number -- 证件号码 varchar(100) -department -- 部门 varchar(200) -``` - -**修复方案**: - -**1️⃣ 执行修复SQL**: -```bash -文件位置: docs/设计文档/database/⚡一键修复-添加缺失字段.sql -``` - -```sql -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `gender` char(1) DEFAULT NULL COMMENT '性别:0-男,1-女' AFTER `applicant_name`, -ADD COLUMN `birth_date` date DEFAULT NULL COMMENT '出生日期' AFTER `gender`, --- ... 其他6个字段 -``` - -**2️⃣ 重启后端服务**(清除MyBatis缓存) - -**3️⃣ 重新测试** - -**修复文件**: -- 数据库表:`rs_overseas_itinerary_change` - -**详细文档**: -- [🚨数据库字段缺失修复指南.md](./🚨数据库字段缺失修复指南.md) - ---- - -## 📈 问题分析 - -### 问题类型分布 - -| 类型 | 数量 | 占比 | -|------|------|------| -| 前端逻辑错误 | 1 | 33% | -| 前后端接口不匹配 | 1 | 33% | -| 数据库结构不完整 | 1 | 33% | - -### 严重程度 - -| 严重程度 | 问题编号 | 影响范围 | -|---------|---------|---------| -| 🟡 中等 | 问题1 | 数据显示不准确,但不影响功能 | -| 🔴 严重 | 问题2 | 功能完全不可用 | -| 🔴 严重 | 问题3 | 功能完全不可用 | - -### 修复难度 - -| 难度 | 问题编号 | 工作量 | -|------|---------|--------| -| ⭐ 简单 | 问题1 | 5分钟(前端1行代码) | -| ⭐⭐ 中等 | 问题2 | 15分钟(前端多处修改) | -| ⭐ 简单 | 问题3 | 10分钟(执行SQL+重启) | - ---- - -## 🔄 问题发现顺序 - -``` -测试审批功能 - ↓ -问题1: 变更记录Tab显示不正确 - ↓ 修复 -问题2: 审批接口参数错误 - ↓ 修复 -问题3: 数据库字段缺失 - ↓ 待修复 -功能正常 ✅ -``` - ---- - -## 📚 解决方案文档 - -### 快速参考 - -| 问题 | 快速修复指南 | 详细文档 | -|------|-------------|---------| -| 问题1 | 前端添加 `status: '1'` 过滤 | [变更记录Tab显示逻辑修复总结.md](./变更记录Tab显示逻辑修复总结.md) | -| 问题2 | 前端参数改为 `approved` + `opinion` | [行程变更审批接口参数错误修复总结.md](./行程变更审批接口参数错误修复总结.md) | -| 问题3 | 执行SQL添加缺失字段 | [🚨数据库字段缺失修复指南.md](./🚨数据库字段缺失修复指南.md) | - -### SQL脚本 - -| 用途 | 文件 | -|------|------| -| 诊断缺失字段 | `docs/设计文档/database/🔍诊断-检查缺失字段.sql` | -| 修复缺失字段 | `docs/设计文档/database/⚡一键修复-添加缺失字段.sql` | -| 完整建表脚本 | `docs/设计文档/database/rs_overseas_itinerary_change_full.sql` | - -### 测试文档 - -| 用途 | 文件 | -|------|------| -| 功能测试指南 | [行程变更审批功能-测试指南.md](./行程变更审批功能-测试指南.md) | - ---- - -## ✅ 修复清单 - -### 问题1 ✅ -- [x] 确认问题原因 -- [x] 修复前端查询逻辑 -- [x] 验证修复效果 -- [x] 编写修复文档 - -### 问题2 ✅ -- [x] 确认问题原因 -- [x] 修复TypeScript接口定义 -- [x] 修复前端表单数据 -- [x] 修复前端表单模板 -- [x] 修复前端验证规则 -- [x] 更新API文档 -- [x] 编写修复文档 -- [x] 编写测试指南 - -### 问题3 ⚠️ -- [x] 确认问题原因 -- [x] 创建诊断SQL -- [x] 创建修复SQL -- [x] 编写修复指南 -- [x] 更新API文档 -- [ ] **执行修复SQL** ← 你需要做的 -- [ ] **重启后端服务** ← 你需要做的 -- [ ] **验证修复效果** ← 你需要做的 - ---- - -## 🎯 待办事项 - -### 立即执行(问题3修复) - -1. **执行SQL修复** - ```bash - 打开: docs/设计文档/database/⚡一键修复-添加缺失字段.sql - 执行: 在MySQL中运行 - 验证: 字段数量=47 - ``` - -2. **重启后端服务** - ```bash - 停止: 后端服务 - 启动: 后端服务 - 等待: 服务完全启动 - ``` - -3. **完整功能测试** - ```bash - 参考: docs/问题解决方案/行程变更审批功能-测试指南.md - 测试: 审批通过、审批拒绝 - 验证: 功能正常 - ``` - -### 后续优化(可选) - -1. **同步历史数据** - - 从其他表同步个人信息字段 - - 填充历史记录的个人信息 - -2. **添加单元测试** - - 测试审批接口参数验证 - - 测试查询过滤逻辑 - -3. **完善错误处理** - - 后端参数空值检查 - - 前端表单提交前验证 - ---- - -## 💡 经验总结 - -### 前端开发 - -1. **参数命名一致性** - - ✅ 前后端使用完全相同的字段名 - - ✅ 避免使用前缀(如approval)导致字段名不一致 - - ✅ TypeScript接口定义要与后端API文档保持一致 - -2. **类型安全** - - ✅ 使用布尔值表示是/否,不用字符串 - - ✅ 使用TypeScript类型定义,编译时检查类型错误 - - ✅ 表单控件绑定的值类型要匹配 - -3. **查询过滤** - - ✅ 不同Tab查询要设置正确的过滤条件 - - ✅ 切换Tab时清空或重置过滤条件 - - ✅ 查询前验证必填参数 - -### 后端开发 - -1. **参数接收** - - ❌ 避免使用Map接收参数(类型不安全) - - ✅ 推荐使用强类型BO接收参数 - - ✅ 添加参数验证注解(@NotNull等) - -2. **空值处理** - - ✅ 调用对象方法前检查是否为null - - ✅ 使用Optional处理可能为空的值 - - ✅ 提供明确的错误提示 - -### 数据库管理 - -1. **版本控制** - - ✅ 保持表结构SQL脚本与实际数据库一致 - - ✅ 结构变更时提供ALTER TABLE脚本 - - ✅ 使用版本号管理数据库变更 - -2. **字段设计** - - ✅ 新增字段允许NULL(兼容历史数据) - - ✅ 添加字段注释,说明用途 - - ✅ 考虑字段位置的逻辑性 - ---- - -## 📞 相关文档 - -### 问题修复文档 -- [变更记录Tab显示逻辑修复总结.md](./变更记录Tab显示逻辑修复总结.md) -- [行程变更审批接口参数错误修复总结.md](./行程变更审批接口参数错误修复总结.md) -- [🚨数据库字段缺失修复指南.md](./🚨数据库字段缺失修复指南.md) - -### 测试文档 -- [行程变更审批功能-测试指南.md](./行程变更审批功能-测试指南.md) - -### API文档 -- [出国行程变更管理API.md](../开发文档/API/出国行程变更管理API.md) - -### 数据库脚本 -- `docs/设计文档/database/⚡一键修复-添加缺失字段.sql` -- `docs/设计文档/database/🔍诊断-检查缺失字段.sql` -- `docs/设计文档/database/rs_overseas_itinerary_change_full.sql` - ---- - -**文档创建时间**: 2025-10-13 -**文档类型**: 问题汇总 -**适用功能**: 出国行程变更审批 -**维护状态**: 持续更新 -**最后更新**: 2025-10-13 - ---- - -## 🚀 快速开始 - -### 如果你是第一次看到这个文档 - -1. **了解问题**: 阅读上面的"问题清单" -2. **执行修复**: 按照"待办事项"中的步骤操作 -3. **验证结果**: 参考测试指南进行功能测试 -4. **查看详情**: 如需深入了解,阅读对应的详细文档 - -### 如果你遇到新问题 - -1. **检查现有问题**: 看看是否已在问题清单中 -2. **查看故障排查**: 参考API文档中的故障排查部分 -3. **记录新问题**: 在本文档中添加新的问题描述 -4. **编写修复方案**: 创建对应的修复文档 - ---- - -**💪 加油!相信问题3修复后就能正常使用了!** 🎉 - diff --git a/docs/问题解决方案/🚨数据库字段缺失修复指南.md b/docs/问题解决方案/🚨数据库字段缺失修复指南.md deleted file mode 100644 index 15890dac..00000000 --- a/docs/问题解决方案/🚨数据库字段缺失修复指南.md +++ /dev/null @@ -1,418 +0,0 @@ -# 🚨 数据库字段缺失修复指南 - -## 📋 问题描述 - -**错误信息**: -``` -Unknown column 'gender' in 'field list' -``` - -**错误位置**: -- `RsOverseasItineraryChangeMapper.java` -- 查询 `rs_overseas_itinerary_change` 表时 - -**发生时间**: 2025-10-13 -**触发操作**: 点击审批通过按钮 - ---- - -## 🔍 问题原因 - -### 根本原因 - -**数据库表结构与代码不匹配**: - -| 情况 | 说明 | -|------|------| -| **后端代码期望** | 表中包含 `gender`、`birth_date` 等8个个人信息字段 | -| **数据库实际** | 表中缺少这些字段(可能是旧版本) | -| **导致结果** | MyBatis执行查询时报字段不存在错误 | - -### 缺失字段列表 - -```sql --- 8个缺失的个人信息字段: -gender -- 性别 -birth_date -- 出生日期 -political_status -- 政治面貌 -professional_title -- 职称 -position -- 职务 -document_name -- 申领证件名称 -document_number -- 证件号码 -department -- 部门 -``` - ---- - -## 🔧 快速修复步骤 - -### ⚡ 第一步:诊断(可选,快速确认问题) - -**执行诊断SQL**: -```bash -文件位置: docs/设计文档/database/🔍诊断-检查缺失字段.sql -``` - -**操作**: -1. 打开MySQL客户端(Navicat/DBeaver/命令行) -2. 选择正确的数据库 -3. 执行诊断SQL -4. 查看诊断结果,确认缺失的字段 - -**预期输出**: -``` -✅ 如果字段完整:显示"✅ 全部存在" -⚠️ 如果字段缺失:显示"⚠️ 缺少X个字段"和具体缺失的字段列表 -``` - ---- - -### 🔨 第二步:执行修复(必须) - -**执行修复SQL**: -```bash -文件位置: docs/设计文档/database/🔧修复-添加缺失字段.sql -``` - -**操作**: -1. 打开MySQL客户端 -2. 选择正确的数据库 -3. **复制并执行修复SQL**(直接执行整个文件) - -**修复SQL内容**(核心部分): -```sql --- 添加性别字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `gender` char(1) DEFAULT NULL COMMENT '性别:0-男,1-女' -AFTER `applicant_name`; - --- 添加出生日期字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `birth_date` date DEFAULT NULL COMMENT '出生日期' -AFTER `gender`; - --- 添加政治面貌字段 -ALTER TABLE rs_overseas_itinerary_change -ADD COLUMN `political_status` varchar(20) DEFAULT NULL COMMENT '政治面貌' -AFTER `birth_date`; - --- ... 其他5个字段 -``` - -**执行时间**: 约5-10秒 -**影响范围**: 仅修改表结构,不影响现有数据 - ---- - -### ✅ 第三步:验证修复结果 - -**执行验证查询**(修复SQL最后部分已包含): - -```sql --- 查看表结构 -SHOW COLUMNS FROM rs_overseas_itinerary_change; - --- 统计字段数量(应该是47个) -SELECT COUNT(*) AS '当前字段数' -FROM information_schema.COLUMNS -WHERE TABLE_SCHEMA = DATABASE() - AND TABLE_NAME = 'rs_overseas_itinerary_change'; -``` - -**预期结果**: -``` -✅ 字段数量: 47个 -✅ 包含gender、birth_date等8个新字段 -✅ 新字段位置在applicant_name之后 -``` - ---- - -### 🔄 第四步:重启后端服务 - -**为什么需要重启**: -- MyBatis会缓存Mapper映射关系 -- 表结构变更后需要重新加载 - -**重启步骤**: -1. 停止后端服务 - - 如果是IDEA运行:点击红色停止按钮 - - 如果是命令行:`Ctrl + C` - -2. 重新启动后端服务 - - IDEA:点击绿色运行按钮 - - 命令行:`java -jar xxx.jar` - -**等待时间**: 约30-60秒(直到看到"Started RuoYiApplication") - ---- - -### 🧪 第五步:重新测试 - -**测试步骤**: -1. **清除浏览器缓存** - - Windows/Linux: `Ctrl + Shift + R` - - Mac: `Cmd + Shift + R` - -2. **导航到审批页面** - - 菜单:人事管理 → 出国行程变更审批 - -3. **点击审批通过** - - 选择一条待审批记录 - - 点击【通过】按钮 - - 填写审批意见 - - 提交 - -**预期结果**: -``` -✅ 提示"审批成功" -✅ 状态变为"已通过" -✅ 不再报"Unknown column 'gender'"错误 -``` - ---- - -## 📊 修复前后对比 - -### 修复前 - -**表结构**: -``` -change_id -record_id -application_id -applicant_id -applicant_name -❌ 缺少8个个人信息字段 -original_destination -... -``` - -**错误**: -``` -Unknown column 'gender' in 'field list' -``` - -### 修复后 - -**表结构**: -``` -change_id -record_id -application_id -applicant_id -applicant_name -✅ gender -- 新增 -✅ birth_date -- 新增 -✅ political_status -- 新增 -✅ professional_title -- 新增 -✅ position -- 新增 -✅ document_name -- 新增 -✅ document_number -- 新增 -✅ department -- 新增 -original_destination -... -``` - -**结果**: -``` -✅ 审批功能正常 -✅ 查询不再报错 -``` - ---- - -## 🚨 注意事项 - -### 1. 数据兼容性 - -**现有数据**: -- 新添加的字段允许为NULL -- 现有记录的这些字段值为NULL -- 不影响现有数据的完整性 - -**新数据**: -- 新提交的变更申请会自动填充这些字段 -- 数据来源于关联的出国记录表 - -### 2. 字段位置 - -**插入位置**: -- 所有新字段插入在 `applicant_name` 之后 -- 符合表结构设计的逻辑顺序 -- 不影响其他字段的位置 - -### 3. 性能影响 - -**ALTER TABLE影响**: -- 添加字段操作很快(几秒内) -- 不会锁表太久 -- 建议在低峰期执行(虽然影响很小) - ---- - -## 🔄 可选:同步历史数据 - -如果需要填充历史数据的个人信息字段,可以执行: - -### 方案1:从用户表同步 - -```sql -UPDATE rs_overseas_itinerary_change c -LEFT JOIN sys_user u ON c.applicant_id = u.user_id -SET - c.gender = u.sex, - c.department = ( - SELECT dept_name - FROM sys_dept - WHERE dept_id = u.dept_id - ) -WHERE c.gender IS NULL; -``` - -### 方案2:从出国申请表同步 - -```sql -UPDATE rs_overseas_itinerary_change c -LEFT JOIN rs_overseas_application a ON c.application_id = a.application_id -SET - c.gender = a.gender, - c.birth_date = a.birth_date, - c.political_status = a.political_status, - c.professional_title = a.professional_title, - c.position = a.position, - c.document_name = a.document_name, - c.document_number = a.document_number, - c.department = a.department -WHERE c.application_id IS NOT NULL - AND c.gender IS NULL; -``` - -**注意**: -- 这两个SQL是可选的 -- 仅用于填充历史数据 -- 新数据会自动填充,无需手动同步 - ---- - -## 🛠️ 故障排查 - -### 问题1:执行ALTER TABLE失败 - -**可能原因**: -- 表不存在 -- 字段已存在 -- 权限不足 - -**解决方案**: -```sql --- 检查表是否存在 -SHOW TABLES LIKE 'rs_overseas_itinerary_change'; - --- 检查字段是否已存在 -SHOW COLUMNS FROM rs_overseas_itinerary_change LIKE 'gender'; - --- 如果字段已存在,跳过该字段的ALTER语句 -``` - -### 问题2:重启后端后仍然报错 - -**可能原因**: -- 后端未正确重启 -- MyBatis缓存未清除 -- 修复SQL未成功执行 - -**解决方案**: -1. 确认修复SQL执行成功(查看字段数量) -2. 完全关闭后端进程 -3. 清理编译缓存:`mvn clean` -4. 重新编译:`mvn compile` -5. 重新启动后端 - -### 问题3:审批后数据中个人信息为空 - -**这是正常的**: -- 历史数据的个人信息字段为NULL -- 新数据会自动从关联表同步 -- 如需填充历史数据,执行"可选:同步历史数据"部分的SQL - ---- - -## 📁 相关文件 - -| 文件 | 说明 | 用途 | -|------|------|------| -| `🔍诊断-检查缺失字段.sql` | 诊断SQL | 检查表结构,确认缺失的字段 | -| `🔧修复-添加缺失字段.sql` | 修复SQL | 添加缺失的字段到表中 | -| `rs_overseas_itinerary_change_full.sql` | 完整建表SQL | 包含完整的表结构定义 | -| `行程变更审批接口参数错误修复总结.md` | 前一个问题的修复总结 | 修复前端参数错误 | - ---- - -## 📞 总结 - -### 问题链 - -``` -问题1: 前端参数错误 (approvalStatus vs approved) - ↓ 修复完成 -问题2: 数据库字段缺失 (gender等字段不存在) ← 当前问题 - ↓ 修复中 -问题3: ??? (希望没有了😅) -``` - -### 修复清单 - -- [x] 诊断问题:确认缺失字段 -- [x] 创建诊断SQL -- [x] 创建修复SQL -- [ ] **执行修复SQL** ← 你需要做的 -- [ ] **重启后端服务** ← 你需要做的 -- [ ] **重新测试审批功能** ← 你需要做的 - ---- - -**修复指南创建时间**: 2025-10-13 -**问题类型**: 数据库表结构不完整 -**修复难度**: ⭐ 简单(只需执行SQL) -**预计修复时间**: 5-10分钟 -**风险等级**: 低(仅添加字段,不影响现有数据) - ---- - -## 🎯 快速操作卡片 - -### 1️⃣ 诊断(可选) -```bash -打开: docs/设计文档/database/🔍诊断-检查缺失字段.sql -执行: 在MySQL中运行 -查看: 缺失字段列表 -``` - -### 2️⃣ 修复(必须) -```bash -打开: docs/设计文档/database/🔧修复-添加缺失字段.sql -执行: 在MySQL中运行 -等待: 5-10秒 -验证: 字段数量=47 -``` - -### 3️⃣ 重启(必须) -```bash -停止: 后端服务 -启动: 后端服务 -等待: 服务完全启动 -``` - -### 4️⃣ 测试(必须) -```bash -清除: 浏览器缓存 -打开: 行程变更审批页面 -点击: 审批通过 -验证: 操作成功 -``` - ---- - -**如果遇到任何问题,请查看"故障排查"部分!** 🔧 - diff --git a/target/classes/META-INF/mps/mappers b/target/classes/META-INF/mps/mappers index 8fc153a8..a01168de 100644 --- a/target/classes/META-INF/mps/mappers +++ b/target/classes/META-INF/mps/mappers @@ -1,41 +1,59 @@ -org.dromara.zsjy.domain.ZsjyEnrollmentProcessToZsjyEnrollmentProcessVoMapper__279 -org.dromara.xskh.domain.vo.XskhAssessmentPlanVoToXskhAssessmentPlanMapper__1 -org.dromara.zsjy.domain.bo.ZsjyGraduateInfoBoToZsjyGraduateInfoMapper__279 -org.dromara.xskh.domain.vo.XskhAssessmentIndicatorDisciplineTypeVoToXskhAssessmentIndicatorDisciplineTypeMapper__1 -org.dromara.test.domain.XgStuMsgToXgStuMsgVoMapper__284 -org.dromara.xskh.domain.XskhRewardRecordToXskhRewardRecordVoMapper__1 -org.dromara.xskh.domain.vo.XskhAssessmentApplicationApprovalVoToXskhAssessmentApplicationApprovalMapper__1 -org.dromara.zsjy.domain.bo.ZsjyQrCodeBoToZsjyQrCodeMapper__279 -org.dromara.xskh.domain.XskhAssessmentApplicationApprovalToXskhAssessmentApprovalVoMapper__1 -org.dromara.zsjy.domain.vo.ZsjyEnrollmentInfoVoToZsjyEnrollmentInfoMapper__279 -org.dromara.xskh.domain.vo.XskhAssessmentIndicatorVoToXskhAssessmentIndicatorMapper__1 -org.dromara.zsjy.domain.bo.ZsjyEnrollmentInfoBoToZsjyEnrollmentInfoMapper__279 -org.dromara.zsjy.domain.ZsjyEnrollmentInfoToZsjyEnrollmentInfoVoMapper__279 -org.dromara.zsjy.domain.vo.ZsjyGraduateInfoVoToZsjyGraduateInfoMapper__279 -org.dromara.test.domain.vo.XgStuMsgVoToXgStuMsgMapper__284 -org.dromara.xskh.domain.XskhAssessmentScheduleToXskhAssessmentScheduleVoMapper__1 -org.dromara.xskh.domain.vo.XskhAssessmentScheduleVoToXskhAssessmentScheduleMapper__1 -org.dromara.xskh.domain.XskhAssessmentPlanIndicatorToXskhAssessmentPlanIndicatorVoMapper__1 -org.dromara.xskh.domain.vo.XskhAssessmentBonusApprovalVoToXskhAssessmentBonusApprovalMapper__1 -org.dromara.xskh.domain.vo.XskhAssessmentApprovalVoToXskhAssessmentApplicationApprovalMapper__1 -org.dromara.zsjy.domain.ZsjyQrCodeToZsjyQrCodeVoMapper__279 -org.dromara.xskh.domain.XskhAssessmentIndicatorToXskhAssessmentIndicatorVoMapper__1 -org.dromara.xskh.domain.XskhAssessmentPlanToXskhAssessmentPlanVoMapper__1 -org.dromara.xskh.domain.XskhAssessmentIndicatorDisciplineTypeToXskhAssessmentIndicatorDisciplineTypeVoMapper__1 -org.dromara.xskh.domain.XskhAssessmentBonusApprovalToXskhAssessmentBonusApprovalVoMapper__1 -org.dromara.zsjy.domain.vo.ZsjyQrCodeVoToZsjyQrCodeMapper__279 -org.dromara.xskh.domain.vo.XskhAssessmentIndicatorTypeVoToXskhAssessmentIndicatorTypeMapper__1 -org.dromara.test.domain.bo.XgStuMsgBoToXgStuMsgMapper__284 -org.dromara.xskh.domain.XskhAssessmentApplicationApprovalToXskhAssessmentApplicationApprovalVoMapper__1 -org.dromara.zsjy.domain.vo.ZsjyEnrollmentProcessVoToZsjyEnrollmentProcessMapper__279 -org.dromara.zsjy.domain.bo.ZsjyInternshipVisitBoToZsjyInternshipVisitMapper__279 -org.dromara.xskh.domain.vo.XskhAssessmentSelfScoreVoToXskhAssessmentSelfScoreMapper__1 -org.dromara.xskh.domain.XskhAssessmentSelfScoreToXskhAssessmentSelfScoreVoMapper__1 -org.dromara.zsjy.domain.bo.ZsjyEnrollmentProcessBoToZsjyEnrollmentProcessMapper__279 -org.dromara.zsjy.domain.ZsjyGraduateInfoToZsjyGraduateInfoVoMapper__279 -org.dromara.xskh.domain.vo.XskhRewardRecordVoToXskhRewardRecordMapper__1 -org.dromara.xskh.domain.XskhAssessmentIndicatorTypeToXskhAssessmentIndicatorTypeVoMapper__1 -org.dromara.zsjy.domain.bo.ZsjyEnterpriseBoToZsjyEnterpriseMapper__279 -org.dromara.xskh.domain.vo.XskhAssessmentPlanIndicatorVoToXskhAssessmentPlanIndicatorMapper__1 -org.dromara.zsjy.domain.bo.ZsjyJobPositionBoToZsjyJobPositionMapper__279 -org.dromara.zsjy.domain.bo.ZsjyRecruitmentPosterBoToZsjyRecruitmentPosterMapper__279 \ No newline at end of file +org.dromara.system.domain.vo.SysSocialVoToSysSocialMapper +org.dromara.system.domain.vo.SysDictDataVoToSysDictDataMapper +org.dromara.system.domain.SysOssConfigToSysOssConfigVoMapper +org.dromara.system.domain.vo.SysLogininforVoToSysLogininforMapper +org.dromara.system.domain.bo.SysConfigBoToSysConfigMapper +org.dromara.system.domain.SysTenantPackageToSysTenantPackageVoMapper +org.dromara.system.domain.bo.SysPostBoToSysPostMapper +org.dromara.system.domain.vo.SysOssConfigVoToSysOssConfigMapper +org.dromara.system.domain.SysUserToSysUserVoMapper +org.dromara.system.domain.vo.SysDictTypeVoToSysDictTypeMapper +org.dromara.system.domain.SysRegionToSysRegionVoMapper +org.dromara.system.domain.SysDictDataToSysDictDataVoMapper +org.dromara.system.domain.bo.SysMenuBoToSysMenuMapper +org.dromara.system.domain.bo.SysWeChatConfigBoToSysWeChatConfigMapper +org.dromara.system.domain.vo.SysRoleVoToSysRoleMapper +org.dromara.system.domain.SysConfigToSysConfigVoMapper +org.dromara.system.domain.bo.SysOperLogBoToOperLogEventMapper +org.dromara.system.domain.vo.SysClientVoToSysClientMapper +org.dromara.system.domain.bo.SysOssConfigBoToSysOssConfigMapper +org.dromara.system.domain.vo.SysNoticeVoToSysNoticeMapper +org.dromara.system.domain.bo.SysDeptBoToSysDeptMapper +org.dromara.system.domain.bo.SysOssBoToSysOssMapper +org.dromara.system.domain.vo.SysMenuVoToSysMenuMapper +org.dromara.system.domain.vo.SysWeChatConfigVoToSysWeChatConfigMapper +org.dromara.system.domain.SysMenuToSysMenuVoMapper +org.dromara.system.domain.vo.SysTenantVoToSysTenantMapper +org.dromara.system.domain.bo.SysUserBoToSysUserMapper +org.dromara.system.domain.bo.SysRegionBoToSysRegionMapper +org.dromara.system.domain.bo.SysClientBoToSysClientMapper +org.dromara.system.domain.bo.SysDictDataBoToSysDictDataMapper +org.dromara.system.domain.vo.SysUserVoToSysUserMapper +org.dromara.system.domain.SysClientToSysClientVoMapper +org.dromara.system.domain.SysNoticeToSysNoticeVoMapper +org.dromara.system.domain.SysOssToSysOssVoMapper +org.dromara.system.domain.SysOperLogToSysOperLogVoMapper +org.dromara.system.domain.bo.SysLogininforBoToSysLogininforMapper +org.dromara.system.domain.vo.SysDeptVoToSysDeptMapper +org.dromara.system.domain.SysTenantToSysTenantVoMapper +org.dromara.system.domain.vo.SysConfigVoToSysConfigMapper +org.dromara.system.domain.bo.SysSocialBoToSysSocialMapper +org.dromara.system.domain.bo.SysRoleBoToSysRoleMapper +org.dromara.system.domain.vo.SysOssVoToSysOssMapper +org.dromara.system.domain.SysDictTypeToSysDictTypeVoMapper +org.dromara.system.domain.SysRoleToSysRoleVoMapper +org.dromara.system.domain.vo.SysTenantPackageVoToSysTenantPackageMapper +org.dromara.system.domain.bo.SysTenantBoToSysTenantMapper +org.dromara.system.domain.bo.SysTenantPackageBoToSysTenantPackageMapper +org.dromara.system.domain.vo.SysRegionVoToSysRegionMapper +org.dromara.system.domain.SysLogininforToSysLogininforVoMapper +org.dromara.system.domain.vo.SysPostVoToSysPostMapper +org.dromara.system.domain.SysSocialToSysSocialVoMapper +org.dromara.system.domain.bo.SysDictTypeBoToSysDictTypeMapper +org.dromara.common.log.event.OperLogEventToSysOperLogBoMapper +org.dromara.system.domain.SysWeChatConfigToSysWeChatConfigVoMapper +org.dromara.system.domain.bo.SysOperLogBoToSysOperLogMapper +org.dromara.system.domain.vo.SysOperLogVoToSysOperLogMapper +org.dromara.system.domain.SysDeptToSysDeptVoMapper +org.dromara.system.domain.bo.SysNoticeBoToSysNoticeMapper +org.dromara.system.domain.SysPostToSysPostVoMapper \ No newline at end of file diff --git a/test_overseas_attachment.py b/test_overseas_attachment.py deleted file mode 100644 index d9673300..00000000 --- a/test_overseas_attachment.py +++ /dev/null @@ -1,310 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -出国申请附件功能诊断脚本 -用于测试附件接口和排查附件内容不显示的问题 -""" - -import requests -import json -import sys -from datetime import datetime -from Crypto.Cipher import AES, PKCS1_v1_5 -from Crypto.PublicKey import RSA -from Crypto.Random import get_random_bytes -import base64 -import hashlib - -# ==================== 配置区 ==================== -BASE_URL = "http://localhost:8080" -USERNAME = "admin" # 请修改为实际用户名 -PASSWORD = "admin123" # 请修改为实际密码 - -# RSA公钥(需要从后端获取) -PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCpSA3zw+7sCf6M9yZzH4p4V4i/ -ktlKPMwReLYQz0MdIj7v0xnP9h/EhGClQKJzU6EZNx8ZX8P2xVLIVMqD8pqKXLxX -j6SLvSKJKvFPPCF7P3rKqW8Y4aNwXZvFKQXlWUmE5lEJpH6P8RqYXZ0XHQZ1iyKK -CqMZXGLH1Q0PJKWmDQIDAQAB ------END PUBLIC KEY-----""" - -# ==================== 加密工具函数 ==================== - -def generate_aes_key(): - """生成AES密钥""" - return get_random_bytes(16) - -def aes_encrypt(data, key): - """AES加密""" - cipher = AES.new(key, AES.MODE_ECB) - # PKCS7 padding - padding_length = 16 - len(data) % 16 - padded_data = data + chr(padding_length) * padding_length - encrypted = cipher.encrypt(padded_data.encode('utf-8')) - return base64.b64encode(encrypted).decode('utf-8') - -def rsa_encrypt(data, public_key): - """RSA加密""" - rsa_key = RSA.import_key(public_key) - cipher = PKCS1_v1_5.new(rsa_key) - encrypted = cipher.encrypt(data) - return base64.b64encode(encrypted).decode('utf-8') - -# ==================== API请求函数 ==================== - -class APIClient: - def __init__(self, base_url): - self.base_url = base_url - self.token = None - self.headers = { - 'Content-Type': 'application/json', - 'clientid': 'e5cd7e4891bf95d1d19206ce24a7b32e', - 'referer': 'http://localhost' - } - - def login(self, username, password): - """登录获取token""" - print("=" * 60) - print("【步骤1】用户登录") - print("=" * 60) - - try: - # 生成AES密钥 - aes_key = generate_aes_key() - aes_key_str = base64.b64encode(aes_key).decode('utf-8') - - # AES加密用户名和密码 - encrypted_username = aes_encrypt(username, aes_key) - encrypted_password = aes_encrypt(password, aes_key) - - # RSA加密AES密钥 - encrypted_aes_key = rsa_encrypt(aes_key, PUBLIC_KEY) - - # 构建登录请求 - login_data = { - "username": encrypted_username, - "password": encrypted_password, - "key": encrypted_aes_key, - "code": "", - "uuid": "" - } - - print(f"请求URL: {self.base_url}/auth/login") - print(f"用户名: {username}") - - response = requests.post( - f"{self.base_url}/auth/login", - headers=self.headers, - json=login_data - ) - - result = response.json() - print(f"响应状态码: {response.status_code}") - print(f"响应内容: {json.dumps(result, ensure_ascii=False, indent=2)}") - - if result.get('code') == 200 and 'data' in result: - self.token = result['data'].get('access_token') - self.headers['Authorization'] = f"Bearer {self.token}" - print(f"✅ 登录成功!Token: {self.token[:50]}...") - return True - else: - print(f"❌ 登录失败: {result.get('msg', '未知错误')}") - return False - - except Exception as e: - print(f"❌ 登录异常: {str(e)}") - import traceback - traceback.print_exc() - return False - - def get(self, url, params=None): - """发送GET请求""" - try: - response = requests.get( - f"{self.base_url}{url}", - headers=self.headers, - params=params - ) - return response.json() - except Exception as e: - print(f"❌ 请求异常: {str(e)}") - return None - - def post(self, url, data=None): - """发送POST请求""" - try: - response = requests.post( - f"{self.base_url}{url}", - headers=self.headers, - json=data - ) - return response.json() - except Exception as e: - print(f"❌ 请求异常: {str(e)}") - return None - -# ==================== 测试函数 ==================== - -def test_overseas_attachment(client): - """测试出国申请附件功能""" - - print("\n" + "=" * 60) - print("【步骤2】查询出国申请列表") - print("=" * 60) - - # 查询申请列表 - result = client.get("/rs/overseas/application/list", { - "pageNum": 1, - "pageSize": 10 - }) - - if not result or result.get('code') != 200: - print(f"❌ 查询申请列表失败: {result.get('msg') if result else '请求失败'}") - return - - rows = result.get('rows', []) - if not rows: - print("❌ 没有找到任何出国申请记录") - print("💡 建议:请先创建一条出国申请记录") - return - - print(f"✅ 找到 {len(rows)} 条申请记录") - - # 显示前5条记录 - print("\n申请记录列表(前5条):") - for i, app in enumerate(rows[:5], 1): - print(f"{i}. ID={app.get('applicationId')}, " - f"申请人={app.get('applicantName')}, " - f"目的地={app.get('destination')}, " - f"状态={app.get('statusText')}") - - # 选择第一条记录进行测试 - test_application = rows[0] - application_id = test_application.get('applicationId') - - print("\n" + "=" * 60) - print(f"【步骤3】查询申请详情(ID: {application_id})") - print("=" * 60) - - detail_result = client.get(f"/rs/overseas/application/detail/{application_id}") - - if not detail_result or detail_result.get('code') != 200: - print(f"❌ 查询申请详情失败: {detail_result.get('msg') if detail_result else '请求失败'}") - return - - detail_data = detail_result.get('data', {}) - print(f"✅ 申请详情查询成功") - print(f"申请人: {detail_data.get('applicantName')}") - print(f"目的地: {detail_data.get('destination')}") - print(f"开始日期: {detail_data.get('startDate')}") - print(f"结束日期: {detail_data.get('endDate')}") - - print("\n" + "=" * 60) - print(f"【步骤4】查询附件列表(申请ID: {application_id})") - print("=" * 60) - - attachment_result = client.get(f"/rs/overseas/attachment/list/{application_id}") - - print(f"请求URL: {BASE_URL}/rs/overseas/attachment/list/{application_id}") - print(f"请求头: {json.dumps({k: v[:50] + '...' if len(v) > 50 else v for k, v in client.headers.items()}, ensure_ascii=False, indent=2)}") - - if not attachment_result: - print("❌ 附件接口请求失败") - return - - print(f"\n响应内容:") - print(json.dumps(attachment_result, ensure_ascii=False, indent=2)) - - if attachment_result.get('code') != 200: - print(f"\n❌ 查询附件列表失败") - print(f"错误代码: {attachment_result.get('code')}") - print(f"错误信息: {attachment_result.get('msg')}") - - # 检查是否是权限问题 - if attachment_result.get('code') in [401, 403]: - print("\n⚠️ 这是权限问题!") - print("请检查以下几点:") - print("1. 当前用户是否有 'rs:overseas:query' 权限") - print("2. 菜单权限配置是否正确") - print("3. 角色权限是否已授予") - - return - - attachment_list = attachment_result.get('data', []) - - print(f"\n✅ 附件列表查询成功") - print(f"附件数量: {len(attachment_list)}") - - if not attachment_list: - print("\n⚠️ 该申请暂无附件") - print("💡 建议:") - print("1. 在前端点击'添加附件'按钮上传附件") - print("2. 或使用API直接上传附件进行测试") - else: - print("\n附件列表详情:") - print("-" * 100) - for i, attachment in enumerate(attachment_list, 1): - print(f"\n附件 {i}:") - print(f" - 附件ID: {attachment.get('attachmentId')}") - print(f" - 附件标题: {attachment.get('attachmentTitle')}") - print(f" - 附件说明: {attachment.get('attachmentContent', '无')}") - print(f" - 原始文件名: {attachment.get('fileOriginalName')}") - print(f" - 文件名: {attachment.get('fileName')}") - print(f" - 文件路径: {attachment.get('filePath')}") - print(f" - 文件大小: {attachment.get('fileSizeFormatted', attachment.get('fileSize'))}") - print(f" - MIME类型: {attachment.get('mimeType', '未知')}") - print(f" - 上传时间: {attachment.get('uploadTime', '未知')}") - print(f" - 上传人: {attachment.get('uploadUserName', '未知')}") - - print("\n" + "=" * 60) - print("【诊断结果】") - print("=" * 60) - - if not attachment_list: - print("🔍 问题原因分析:") - print("1. 该申请确实没有附件数据") - print("2. 附件接口返回正常,但数据为空") - print("\n🔧 解决方案:") - print("1. 请在前端点击'添加附件'按钮上传附件") - print("2. 确认附件上传后,再次查看详情") - else: - print("✅ 附件数据存在且接口返回正常") - print("\n🔍 如果前端仍然看不到附件,可能的原因:") - print("1. 前端组件的数据绑定问题") - print("2. Vue响应式数据未更新") - print("3. 条件渲染判断错误") - print("4. 浏览器控制台可能有JavaScript错误") - print("\n🔧 建议检查:") - print("1. 打开浏览器F12开发者工具") - print("2. 查看Console标签页是否有错误") - print("3. 查看Network标签页确认API请求是否成功") - print("4. 检查Vue Devtools中viewAttachmentList的值") - -def main(): - """主函数""" - print("=" * 60) - print("出国申请附件功能诊断脚本") - print("=" * 60) - print(f"基础URL: {BASE_URL}") - print(f"用户名: {USERNAME}") - print(f"时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") - print() - - # 创建API客户端 - client = APIClient(BASE_URL) - - # 登录 - if not client.login(USERNAME, PASSWORD): - print("\n❌ 登录失败,无法继续测试") - sys.exit(1) - - # 测试附件功能 - test_overseas_attachment(client) - - print("\n" + "=" * 60) - print("诊断完成") - print("=" * 60) - -if __name__ == "__main__": - main() - diff --git a/test_overseas_registration_list.py b/test_overseas_registration_list.py deleted file mode 100644 index 3d741d72..00000000 --- a/test_overseas_registration_list.py +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -出国登记列表测试脚本 -用于诊断列表不显示数据的问题 -""" - -import requests -import json -from Crypto.Cipher import AES, PKCS1_v1_5 -from Crypto.PublicKey import RSA -from Crypto.Util.Padding import pad -import base64 -import time - -# API基础配置 -BASE_URL = "http://localhost:8080" -LOGIN_URL = f"{BASE_URL}/auth/login" -OVERSEAS_REGISTRATION_LIST_URL = f"{BASE_URL}/rs/overseas/registration/list" - -# 测试账号 -TEST_USER = { - "username": "admin", - "password": "admin123", - "code": "0000", # 验证码(测试环境可以用固定值) - "uuid": "" -} - -# 全局session -session = requests.Session() - -# RSA公钥(从后端获取或硬编码) -RSA_PUBLIC_KEY = """-----BEGIN PUBLIC KEY----- -MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCt9ejcgDRUDZr7qh/OfCQDPSZt -wMzxv7L8x9VnOuIElmH3bH5pSPmBGwkQhMKSbXGaWiW6pJXLhRQrLFpJYqD/n3z3 -T8vHFsqCfwjlzJJDGXvT9w0dJhYT1WBmQ0GjQgx7bvOXJr6JWwEW3BXcB2NGZyF+ -hIb5P9bXNBBdQhHOLQIDAQAB ------END PUBLIC KEY-----""" - -def aes_encrypt(data, key, iv): - """AES加密""" - cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8')) - encrypted = cipher.encrypt(pad(data.encode('utf-8'), AES.block_size)) - return base64.b64encode(encrypted).decode('utf-8') - -def rsa_encrypt(data, public_key): - """RSA加密""" - rsa_key = RSA.import_key(public_key) - cipher = PKCS1_v1_5.new(rsa_key) - encrypted = cipher.encrypt(data.encode('utf-8')) - return base64.b64encode(encrypted).decode('utf-8') - -def login(): - """登录并获取token""" - print("=" * 80) - print("步骤1: 登录系统") - print("=" * 80) - - # AES密钥和IV - aes_key = "1234567890abcdef" # 16字节 - aes_iv = "1234567890abcdef" # 16字节 - - # 加密密码 - encrypted_password = aes_encrypt(TEST_USER["password"], aes_key, aes_iv) - - # 加密AES密钥 - encrypted_key = rsa_encrypt(aes_key, RSA_PUBLIC_KEY) - - # 登录请求体 - login_data = { - "tenantId": "000000", - "username": TEST_USER["username"], - "password": encrypted_password, - "code": TEST_USER["code"], - "uuid": TEST_USER["uuid"], - "clientid": "e5cd7e4891bf95d1d19206ce24a7b32e", - "grant_type": "password", - "key": encrypted_key - } - - headers = { - "Content-Type": "application/json", - "clientid": "e5cd7e4891bf95d1d19206ce24a7b32e", - "referer": "http://localhost" - } - - print(f"📤 发送登录请求: {LOGIN_URL}") - print(f"📋 用户名: {TEST_USER['username']}") - - try: - response = session.post(LOGIN_URL, json=login_data, headers=headers) - print(f"📡 响应状态码: {response.status_code}") - - if response.status_code == 200: - result = response.json() - print(f"📊 响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}") - - if result.get("code") == 200: - token = result.get("data", {}).get("access_token") - if token: - print(f"✅ 登录成功! Token: {token[:50]}...") - return token - else: - print("❌ 登录失败: 响应中没有token") - return None - else: - print(f"❌ 登录失败: {result.get('msg')}") - return None - else: - print(f"❌ 登录失败: HTTP {response.status_code}") - print(f"响应内容: {response.text}") - return None - - except Exception as e: - print(f"❌ 登录异常: {str(e)}") - return None - -def test_registration_list(token): - """测试出国登记列表""" - print("\n" + "=" * 80) - print("步骤2: 查询出国登记列表") - print("=" * 80) - - headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - "clientid": "e5cd7e4891bf95d1d19206ce24a7b32e", - "referer": "http://localhost" - } - - # 测试不同的查询参数 - test_cases = [ - { - "name": "无参数查询(查询所有)", - "params": { - "pageNum": 1, - "pageSize": 10 - } - }, - { - "name": "状态过滤(已提交)", - "params": { - "pageNum": 1, - "pageSize": 10, - "status": "1" - } - }, - { - "name": "状态过滤(草稿)", - "params": { - "pageNum": 1, - "pageSize": 10, - "status": "0" - } - } - ] - - for i, test_case in enumerate(test_cases, 1): - print(f"\n📋 测试案例 {i}: {test_case['name']}") - print(f"📤 请求URL: {OVERSEAS_REGISTRATION_LIST_URL}") - print(f"📋 查询参数: {json.dumps(test_case['params'], ensure_ascii=False)}") - - try: - response = session.get( - OVERSEAS_REGISTRATION_LIST_URL, - params=test_case['params'], - headers=headers - ) - - print(f"📡 响应状态码: {response.status_code}") - - if response.status_code == 200: - result = response.json() - print(f"📊 响应数据: {json.dumps(result, ensure_ascii=False, indent=2)}") - - if result.get("code") == 200: - rows = result.get("rows", []) - total = result.get("total", 0) - print(f"✅ 查询成功!") - print(f"📊 总记录数: {total}") - print(f"📊 当前页记录数: {len(rows)}") - - if len(rows) > 0: - print(f"\n📋 第一条记录详情:") - print(json.dumps(rows[0], ensure_ascii=False, indent=2)) - else: - print("⚠️ 列表为空,没有数据") - else: - print(f"❌ 查询失败: {result.get('msg')}") - else: - print(f"❌ 请求失败: HTTP {response.status_code}") - print(f"响应内容: {response.text}") - - except Exception as e: - print(f"❌ 请求异常: {str(e)}") - - if i < len(test_cases): - print("\n" + "-" * 80) - time.sleep(0.5) - -def main(): - """主函数""" - print("\n🚀 开始出国登记列表测试") - print("=" * 80) - - # 1. 登录获取token - token = login() - if not token: - print("\n❌ 测试终止: 登录失败") - return - - # 2. 测试列表查询 - test_registration_list(token) - - print("\n" + "=" * 80) - print("✅ 测试完成!") - print("=" * 80) - -if __name__ == "__main__": - main() - diff --git a/test_with_browser_token.py b/test_with_browser_token.py deleted file mode 100644 index 11f5cafa..00000000 --- a/test_with_browser_token.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- -""" -使用浏览器Token测试OSS文件下载接口 -请从浏览器开发者工具中复制Token -""" - -import requests - -# 配置 -BASE_URL = "http://localhost:8080" -CLIENT_ID = "e5cd7e4891bf95d1d19206ce24a7b32e" - -# 测试的OSS ID -TEST_OSS_IDS = [ - 1976838445737193474, # 2.png - 1976861922200408066 # PDF文件 -] - -def test_oss_download(token, oss_id): - """测试OSS下载接口""" - url = f"{BASE_URL}/resource/oss/download/{oss_id}" - headers = { - "Authorization": f"Bearer {token}", - "clientid": CLIENT_ID, - "referer": "http://localhost" - } - - print(f"\n📥 测试下载 OSS ID: {oss_id}") - print(f"URL: {url}") - - try: - response = requests.get(url, headers=headers, timeout=30, stream=True) - print(f"状态码: {response.status_code}") - print(f"Content-Type: {response.headers.get('Content-Type')}") - print(f"Content-Disposition: {response.headers.get('Content-Disposition')}") - print(f"Content-Length: {response.headers.get('Content-Length')}") - - if response.status_code == 200: - content_length = len(response.content) - print(f"✅ 下载成功!文件大小: {content_length} bytes") - return True - elif response.status_code == 403: - print(f"❌ 403 Forbidden - 权限不足") - print(f"响应内容: {response.text}") - return False - elif response.status_code == 404: - print(f"❌ 404 Not Found - 文件不存在") - print(f"响应内容: {response.text}") - return False - else: - print(f"❌ 下载失败 ({response.status_code})") - print(f"响应内容: {response.text[:500]}") - return False - except Exception as e: - print(f"❌ 下载异常: {str(e)}") - import traceback - traceback.print_exc() - return False - -def main(): - print("=" * 60) - print("OSS文件下载测试(使用浏览器Token)") - print("=" * 60) - print("\n请从浏览器开发者工具中复制Token(不包含Bearer前缀):") - print("1. 按F12打开开发者工具") - print("2. 进入Application/应用 -> Local Storage") - print("3. 找到 Admin-Token,复制其值") - print() - - token = input("请输入Token: ").strip() - - if not token: - print("❌ Token不能为空") - return - - # 测试两个文件 - for oss_id in TEST_OSS_IDS: - test_oss_download(token, oss_id) - print("-" * 60) - -if __name__ == "__main__": - main() - diff --git a/web/.env.development b/web/.env.development index 8dd3964a..0602545f 100644 --- a/web/.env.development +++ b/web/.env.development @@ -19,7 +19,7 @@ VITE_APP_SNAILJOB_ADMIN = 'http://localhost:8800/snail-job' VITE_APP_PORT = 80 # 接口加密功能开关(如需关闭 后端也必须对应关闭) -VITE_APP_ENCRYPT = true +VITE_APP_ENCRYPT = false # 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换 VITE_APP_RSA_PUBLIC_KEY = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==' # 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换 diff --git a/web/src/api/rs/overseas.ts b/web/src/api/rs/overseas.ts index 97c4eb5b..ff706a3f 100644 --- a/web/src/api/rs/overseas.ts +++ b/web/src/api/rs/overseas.ts @@ -92,6 +92,10 @@ export interface OverseasApplicationVO { politicalStatusText?: string; administrativeLevelText?: string; personnelCategoryText?: string; + // 工作流相关字段 + approvalStatus?: string; // 工作流审批状态:draft/waiting/finish/back/cancel + approvalStatusText?: string; // 工作流审批状态文本 + taskId?: string; // 当前待办任务ID(用于审批跳转) } // 出国申请表单类型 @@ -144,6 +148,8 @@ export interface OverseasApplicationForm { accommodationPlan?: string; transportationPlan?: string; applicantRemark?: string; + // 工作流相关字段 + approvalStatus?: string; // 工作流审批状态 } // 审批请求类型 @@ -179,6 +185,7 @@ export interface OverseasRecordVO { applicantName: string; department: string; position: string; + positionText?: string; // 职务文本(用于显示) contactPhone: string; contactEmail: string; purpose: string; diff --git a/web/src/api/rs/party-membership-retention.ts b/web/src/api/rs/party-membership-retention.ts deleted file mode 100644 index 82518d3e..00000000 --- a/web/src/api/rs/party-membership-retention.ts +++ /dev/null @@ -1,291 +0,0 @@ -import request from '@/utils/request' - -// 党籍保留查询参数 -export interface PartyRetentionQuery extends PageQuery { - /** 申请人姓名 */ - name?: string - /** 身份证号 */ - idNumber?: string - /** 工作部门 */ - department?: string - /** 目的地 */ - destination?: string - /** 申请状态 */ - status?: string - /** 申请时间开始 */ - applicationTimeStart?: string - /** 申请时间结束 */ - applicationTimeEnd?: string -} - -// 党籍保留历史查询参数(实际使用与 PartyRetentionQuery 相同的字段) -export interface PartyRetentionHistoryQuery extends PageQuery { - /** 申请人姓名 */ - name?: string - /** 目的地 */ - destination?: string - /** 状态(后端会强制设置为'2'只查询已通过的记录) */ - status?: string -} - -// 党籍保留VO -export interface PartyRetentionVO { - /** 保留ID */ - retentionId: string - /** 申请人姓名 */ - name: string - /** 性别 */ - gender: string - /** 身份证号 */ - idNumber: string - /** 工作部门 */ - department: string - /** 配偶姓名 */ - spouse?: string - /** 亲属姓名(探望对象) */ - relativeName?: string - /** 目的地 */ - destination: string - /** 出国目的 */ - purpose: string - /** 出国开始时间 */ - travelStartDate: string - /** 出国结束时间 */ - travelEndDate: string - /** 停留天数 */ - stayDays: number - /** 费用自理 */ - selfFunded: string - /** 党支部 */ - partyBranch: string - /** 申请日期 */ - applicationDate: string - /** 申请状态 */ - status: string - /** 申请状态文本 */ - statusText: string - /** 申请时间 */ - applicationTime: string - /** 创建时间 */ - createTime: string - /** 更新时间 */ - updateTime: string -} - -// 党籍保留历史VO -export interface PartyRetentionHistoryVO { - /** 历史ID */ - historyId: string - /** 申请人姓名 */ - applicantName: string - /** 出国原因 */ - travelReason: string - /** 目的地 */ - destination: string - /** 保留开始时间 */ - retentionStartDate: string - /** 保留结束时间 */ - retentionEndDate: string - /** 完成状态 */ - completionStatus: string - /** 完成状态文本 */ - completionStatusText: string - /** 创建时间 */ - createTime: string -} - -// 党籍保留表单 -export interface PartyRetentionForm { - /** 保留ID */ - retentionId?: string - /** 申请人姓名 */ - name: string - /** 配偶姓名 */ - spouse?: string - /** 亲属姓名(探望对象) */ - relativeName?: string - /** 目的地 */ - destination: string - /** 出国目的 */ - purpose: string - /** 出国时间范围 */ - travelPeriod?: string[] - /** 出国开始时间 */ - travelStartDate?: string - /** 出国结束时间 */ - travelEndDate?: string - /** 停留天数 */ - stayDays: number - /** 费用自理 */ - selfFunded: string - /** 党支部 */ - partyBranch: string - /** 申请日期 */ - applicationDate: string - /** 请求批准内容 */ - requestApproval?: string - /** 最终批准内容 */ - finalApproval?: string -} - -// 查询党籍保留列表 -export function getPartyRetentionList(query: PartyRetentionQuery) { - return request({ - url: '/rs/party/retention/list', - method: 'get', - params: query - }) -} - -// 查询党籍保留详细 -export function getPartyRetention(retentionId: string) { - return request({ - url: '/rs/party/retention/' + retentionId, - method: 'get' - }) -} - -// 新增党籍保留 -export function addPartyRetention(data: PartyRetentionForm) { - return request({ - url: '/rs/party/retention', - method: 'post', - data: data - }) -} - -// 修改党籍保留 -export function updatePartyRetention(data: PartyRetentionForm) { - return request({ - url: '/rs/party/retention', - method: 'put', - data: data - }) -} - -// 删除党籍保留 -export function delPartyRetention(retentionId: string) { - return request({ - url: '/rs/party/retention/' + retentionId, - method: 'delete' - }) -} - -// 提交党籍保留申请 -export function submitPartyRetention(data: { retentionId: string }) { - return request({ - url: '/rs/party/retention/submit/' + data.retentionId, - method: 'put' - }) -} - -// 撤回党籍保留申请 -export function cancelPartyRetention(retentionId: string) { - return request({ - url: '/rs/party/retention/withdraw/' + retentionId, - method: 'put' - }) -} - -// 新增并提交党籍保留申请 -export function addAndSubmitPartyRetention(data: PartyRetentionForm) { - return request({ - url: '/rs/party/retention/saveAndSubmit', - method: 'post', - data: data - }) -} - -// 导出党籍保留列表 -export function exportPartyRetention(query: PartyRetentionQuery) { - return request({ - url: '/rs/party/retention/export', - method: 'post', - data: query, - responseType: 'blob' - }) -} - -// 查询党籍保留历史列表 -export function getPartyRetentionHistoryList(query: PartyRetentionHistoryQuery) { - return request({ - url: '/rs/party/retention/history/list', - method: 'get', - params: query - }) -} - -// 查询党籍保留历史详细 -export function getPartyRetentionHistory(historyId: string) { - return request({ - url: '/rs/party/retention/history/' + historyId, - method: 'get' - }) -} - -// 修改党籍保留历史 -export function updatePartyRetentionHistory(data: any) { - return request({ - url: '/rs/party/retention/history', - method: 'put', - data: data - }) -} - -// 导出党籍保留历史 -export function exportPartyRetentionHistory(query: PartyRetentionHistoryQuery) { - return request({ - url: '/rs/party/retention/history/export', - method: 'post', - data: query, - responseType: 'blob' - }) -} - -// 党籍保留审批查询参数 -export interface PartyRetentionApprovalQuery extends PageQuery { - /** 申请人姓名 */ - name?: string - /** 身份证号 */ - idNumber?: string - /** 工作部门 */ - department?: string - /** 目的地 */ - destination?: string - /** 审批状态 */ - status?: string -} - -// 审批请求参数 -export interface ApprovalRequest { - /** 保留ID */ - retentionId: string - /** 审批意见 */ - approvalOpinion: string - /** 是否通过 */ - approved: boolean -} - -// 查询待审批党籍保留列表 -export function getPendingPartyRetentionList(query: PartyRetentionApprovalQuery) { - return request({ - url: '/rs/party/retention/list', - method: 'get', - params: { - ...query, - status: '1' // 只查询待审批状态 - } - }) -} - -// 审批党籍保留 -export function approvePartyRetention(data: ApprovalRequest) { - return request({ - url: `/rs/party/retention/approve/${data.retentionId}`, - method: 'put', - params: { - approvalOpinion: data.approvalOpinion, - approved: data.approved - } - }) -} \ No newline at end of file diff --git a/web/src/api/rs/党籍保留API路径修正完成报告.md b/web/src/api/rs/党籍保留API路径修正完成报告.md deleted file mode 100644 index 2178468c..00000000 --- a/web/src/api/rs/党籍保留API路径修正完成报告.md +++ /dev/null @@ -1,390 +0,0 @@ -# 党籍保留API路径修正完成报告 - -## 📋 问题概述 - -**发现时间**: 2025-10-08 -**问题现象**: -- 点击"保存并提交"按钮提示保存失败 -- 浏览器F12 Network显示:`HTTP 405 - Request method 'POST' is not supported` - -**问题根源**: 前端API路径与后端Controller接口路径不匹配 - ---- - -## 🔍 问题分析 - -### HTTP 405错误解释 -- **405 Method Not Allowed**: 请求方法不被允许 -- **常见原因**: - 1. 前端请求路径错误,导致匹配到不支持该方法的接口 - 2. 前端请求方法与后端定义的方法不一致 - 3. 路径参数传递方式不正确 - -### 具体问题定位 -通过对比前后端代码,发现以下3个接口存在路径或方法不匹配: - -| 功能 | 前端路径/方法 | 后端路径/方法 | 状态 | -|------|--------------|--------------|------| -| 保存并提交 | `/addAndSubmit` POST | `/saveAndSubmit` POST | ❌ 路径错误 | -| 提交申请 | `/submit` POST + Body | `/submit/{retentionId}` PUT + Path | ❌ 方法和参数错误 | -| 撤回申请 | `/cancel/{retentionId}` PUT | `/withdraw/{retentionId}` PUT | ❌ 路径错误 | - ---- - -## ✅ 修正内容 - -### 1. 保存并提交接口修正 - -**文件**: `web/src/api/rs/party-membership-retention.ts` - -**修改前**: -```typescript -// 新增并提交党籍保留申请 -export function addAndSubmitPartyRetention(data: PartyRetentionForm) { - return request({ - url: '/rs/party/retention/addAndSubmit', // ❌ 错误路径 - method: 'post', - data: data - }) -} -``` - -**修改后**: -```typescript -// 新增并提交党籍保留申请 -export function addAndSubmitPartyRetention(data: PartyRetentionForm) { - return request({ - url: '/rs/party/retention/saveAndSubmit', // ✅ 正确路径 - method: 'post', - data: data - }) -} -``` - -**后端接口**: -```java -@PostMapping("/saveAndSubmit") -public R saveAndSubmit(@Validated(AddGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) -``` - ---- - -### 2. 提交申请接口修正 - -**文件**: `web/src/api/rs/party-membership-retention.ts` - -**修改前**: -```typescript -// 提交党籍保留申请 -export function submitPartyRetention(data: { retentionId: string }) { - return request({ - url: '/rs/party/retention/submit', // ❌ 缺少路径参数 - method: 'post', // ❌ 错误方法 - data: data // ❌ 应该用路径参数 - }) -} -``` - -**修改后**: -```typescript -// 提交党籍保留申请 -export function submitPartyRetention(data: { retentionId: string }) { - return request({ - url: '/rs/party/retention/submit/' + data.retentionId, // ✅ 添加路径参数 - method: 'put' // ✅ 改为PUT方法 - }) -} -``` - -**后端接口**: -```java -@PutMapping("/submit/{retentionId}") -public R submit(@NotNull(message = "主键不能为空") @PathVariable Long retentionId) -``` - ---- - -### 3. 撤回申请接口修正 - -**文件**: `web/src/api/rs/party-membership-retention.ts` - -**修改前**: -```typescript -// 撤回党籍保留申请 -export function cancelPartyRetention(retentionId: string) { - return request({ - url: '/rs/party/retention/cancel/' + retentionId, // ❌ 错误路径 - method: 'put' - }) -} -``` - -**修改后**: -```typescript -// 撤回党籍保留申请 -export function cancelPartyRetention(retentionId: string) { - return request({ - url: '/rs/party/retention/withdraw/' + retentionId, // ✅ 正确路径 - method: 'put' - }) -} -``` - -**后端接口**: -```java -@PutMapping("/withdraw/{retentionId}") -public R withdraw(@NotNull(message = "主键不能为空") @PathVariable Long retentionId) -``` - ---- - -## 📊 完整的前后端接口对照表 - -### 党籍保留主接口 - -| 序号 | 功能 | 前端方法 | 前端路径 | HTTP方法 | 后端路径 | 后端方法 | 状态 | -|------|------|---------|---------|---------|---------|---------|------| -| 1 | 查询列表 | `getPartyRetentionList` | `/list` | GET | `/list` | `list()` | ✅ 一致 | -| 2 | 查询详情 | `getPartyRetention` | `/{retentionId}` | GET | `/{retentionId}` | `getInfo()` | ✅ 一致 | -| 3 | 根据登记ID查询 | - | - | - | `/registration/{registrationId}` | `getByRegistrationId()` | ⚠️ 前端未实现 | -| 4 | 根据申请ID查询 | - | - | - | `/application/{applicationId}` | `getByApplicationId()` | ⚠️ 前端未实现 | -| 5 | 新增保留 | `addPartyRetention` | `/` | POST | `/` | `add()` | ✅ 一致 | -| 6 | 保存并提交 | `addAndSubmitPartyRetention` | `/saveAndSubmit` | POST | `/saveAndSubmit` | `saveAndSubmit()` | ✅ 已修正 | -| 7 | 修改保留 | `updatePartyRetention` | `/` | PUT | `/` | `edit()` | ✅ 一致 | -| 8 | 删除保留 | `delPartyRetention` | `/{retentionId}` | DELETE | `/{retentionIds}` | `remove()` | ✅ 一致 | -| 9 | 提交申请 | `submitPartyRetention` | `/submit/{retentionId}` | PUT | `/submit/{retentionId}` | `submit()` | ✅ 已修正 | -| 10 | 撤回申请 | `cancelPartyRetention` | `/withdraw/{retentionId}` | PUT | `/withdraw/{retentionId}` | `withdraw()` | ✅ 已修正 | -| 11 | 审批申请 | - | - | - | `/approve/{retentionId}` | `approve()` | ⚠️ 前端未实现 | -| 12 | 导出列表 | `exportPartyRetention` | `/export` | POST | `/export` | `export()` | ✅ 一致 | - -### 党籍保留历史接口 - -| 序号 | 功能 | 前端方法 | 前端路径 | HTTP方法 | 后端路径 | 后端方法 | 状态 | -|------|------|---------|---------|---------|---------|---------|------| -| 1 | 查询历史列表 | `getPartyRetentionHistoryList` | `/history/list` | GET | `/history/list` | `listHistory()` | ✅ 一致 | -| 2 | 查询历史详情 | `getPartyRetentionHistory` | `/history/{historyId}` | GET | `/history/{historyId}` | `getHistoryInfo()` | ✅ 一致 | -| 3 | 修改历史 | `updatePartyRetentionHistory` | `/history` | PUT | `/history` | `editHistory()` | ✅ 一致 | -| 4 | 导出历史 | `exportPartyRetentionHistory` | `/history/export` | POST | `/history/export` | `exportHistory()` | ✅ 一致 | - ---- - -## 🎯 REST API规范总结 - -### RESTful API设计原则 - -| HTTP方法 | 操作 | 路径示例 | 说明 | -|---------|------|---------|------| -| GET | 查询 | `/list`, `/{id}` | 幂等操作,不修改数据 | -| POST | 新增 | `/`, `/saveAndSubmit` | 创建新资源 | -| PUT | 修改 | `/`, `/{id}`, `/submit/{id}` | 更新资源(幂等) | -| DELETE | 删除 | `/{id}`, `/{ids}` | 删除资源(幂等) | - -### 参数传递方式 - -| 参数类型 | 使用场景 | 前端写法 | 后端注解 | -|---------|---------|---------|---------| -| 路径参数 | 资源ID | `url: '/api/{id}'` | `@PathVariable Long id` | -| 查询参数 | 过滤条件 | `params: { name, status }` | `@RequestParam` 或 直接接收Query对象 | -| 请求体 | 复杂对象 | `data: formData` | `@RequestBody FormBO bo` | - -### 本次修正的重点 - -1. **路径命名统一**: `saveAndSubmit` 而不是 `addAndSubmit` -2. **方法类型匹配**: 修改/提交/撤回操作使用 `PUT` 方法 -3. **参数位置正确**: - - 资源ID应该放在路径中:`/submit/{retentionId}` - - 复杂对象放在请求体中:`@RequestBody Bo bo` - ---- - -## ⚠️ 待补充的功能 - -### 前端缺失的接口(建议补充) - -#### 1. 根据登记ID查询党籍保留 -```typescript -// 根据登记ID查询党籍保留 -export function getPartyRetentionByRegistrationId(registrationId: string) { - return request({ - url: '/rs/party/retention/registration/' + registrationId, - method: 'get' - }) -} -``` - -**使用场景**: 从出国登记页面跳转到党籍保留页面,自动加载对应的党籍保留信息 - -#### 2. 根据申请ID查询党籍保留 -```typescript -// 根据申请ID查询党籍保留 -export function getPartyRetentionByApplicationId(applicationId: string) { - return request({ - url: '/rs/party/retention/application/' + applicationId, - method: 'get' - }) -} -``` - -**使用场景**: 从出国申请页面跳转到党籍保留页面,自动加载对应的党籍保留信息 - -#### 3. 审批党籍保留申请 -```typescript -// 审批党籍保留申请 -export function approvePartyRetention(data: { - retentionId: string - approvalOpinion: string - approved: boolean -}) { - return request({ - url: '/rs/party/retention/approve/' + data.retentionId, - method: 'put', - params: { - approvalOpinion: data.approvalOpinion, - approved: data.approved - } - }) -} -``` - -**使用场景**: 管理员审批党籍保留申请 - ---- - -## ✅ 测试清单 - -### 基础功能测试 -- [ ] 1. 查看党籍保留列表 -- [ ] 2. 查看党籍保留详情 -- [ ] 3. 新增党籍保留(仅保存草稿) -- [ ] 4. 新增并提交党籍保留(保存+提交)✅ **已修正** -- [ ] 5. 编辑草稿状态的党籍保留 -- [ ] 6. 删除草稿状态的党籍保留 -- [ ] 7. 提交草稿状态的党籍保留 ✅ **已修正** -- [ ] 8. 撤回已提交的党籍保留 ✅ **已修正** -- [ ] 9. 导出党籍保留列表 - -### 历史记录功能测试 -- [ ] 1. 查看党籍保留历史列表 -- [ ] 2. 查看党籍保留历史详情 -- [ ] 3. 编辑党籍保留历史 -- [ ] 4. 导出党籍保留历史 - -### 关联查询测试(待实现) -- [ ] 1. 根据登记ID查询党籍保留 -- [ ] 2. 根据申请ID查询党籍保留 - -### 审批功能测试(待实现) -- [ ] 1. 审批通过党籍保留申请 -- [ ] 2. 审批拒绝党籍保留申请 - ---- - -## 🚀 部署和测试步骤 - -### 1. 前端热更新(开发环境) -```bash -# 开发环境无需重启,Vite会自动热更新 -# 保存文件后刷新浏览器即可 -``` - -### 2. 清除浏览器缓存 -- 按 `Ctrl + Shift + Delete`(Windows)或 `Cmd + Shift + Delete`(Mac) -- 选择"缓存的图像和文件" -- 点击"清除数据" - -### 3. 刷新页面测试 -1. 访问"人事管理 → 出国党籍保留"菜单 -2. 点击"党籍保留"选项卡 -3. 点击"新增登记"按钮 -4. 填写表单 -5. 点击"保存并提交"按钮 -6. 预期结果:✅ 提示"保存并提交成功" - -### 4. 测试其他修正的接口 -- **提交功能**: 保存草稿后,点击"提交"按钮 -- **撤回功能**: 已提交状态,点击"撤回"按钮 - ---- - -## 📝 修正统计 - -### 修正文件 -- **文件**: `web/src/api/rs/party-membership-retention.ts` -- **修正接口数量**: 3个 - - `addAndSubmitPartyRetention` - 路径修正 - - `submitPartyRetention` - 路径+方法修正 - - `cancelPartyRetention` - 路径修正 - -### 修正代码行数 -- **修改行数**: 9行 -- **工作量**: 约20分钟 - ---- - -## 💡 经验总结 - -### 1. 前后端接口对接规范 -- ✅ **统一命名**: 前后端接口路径和方法名保持一致 -- ✅ **REST规范**: 严格遵循RESTful API设计规范 -- ✅ **参数位置**: 明确区分路径参数、查询参数、请求体 -- ✅ **文档同步**: 前后端接口文档实时同步 - -### 2. 接口调试技巧 -- 🔍 **F12 Network**: 查看请求路径、方法、参数 -- 🔍 **Preview/Response**: 查看错误信息和响应数据 -- 🔍 **Headers**: 确认Content-Type、Token等请求头 -- 🔍 **对比代码**: 前端API文件 vs 后端Controller文件 - -### 3. 常见HTTP状态码 -| 状态码 | 说明 | 可能原因 | -|--------|------|---------| -| 200 | 成功 | 请求正常处理 | -| 400 | 请求错误 | 参数格式错误、校验失败 | -| 401 | 未授权 | Token过期或无效 | -| 403 | 禁止访问 | 无权限 | -| 404 | 未找到 | 路径错误 | -| 405 | 方法不允许 | HTTP方法错误或路径错误 | -| 500 | 服务器错误 | 后端代码异常 | - -### 4. 防范措施 -- ✅ 使用TypeScript类型定义,避免参数错误 -- ✅ 编写接口对照文档,前后端统一维护 -- ✅ 使用API测试工具(Postman/Apifox)先验证后端接口 -- ✅ 前端开发时对照后端Controller逐一核对 - ---- - -## 📞 后续工作 - -### 立即可测试 -1. ✅ 测试"保存并提交"功能 -2. ✅ 测试"提交申请"功能 -3. ✅ 测试"撤回申请"功能 - -### 建议补充 -1. ⏳ 补充"根据登记ID/申请ID查询"前端接口 -2. ⏳ 实现"审批"功能的前端界面和API -3. ⏳ 编写完整的API接口文档 -4. ⏳ 编写自动化测试脚本 - ---- - -## ✅ 完成确认 - -- [x] ✅ 定位问题原因(HTTP 405错误) -- [x] ✅ 修正"保存并提交"接口路径 -- [x] ✅ 修正"提交申请"接口路径和方法 -- [x] ✅ 修正"撤回申请"接口路径 -- [x] ✅ 生成前后端接口对照表 -- [x] ✅ 编写修正说明文档 -- [ ] ⏳ 执行功能测试 -- [ ] ⏳ 补充缺失的前端接口 - ---- - -**状态**: 🎉 **API路径修正已完成!可以进行功能测试!** - -**修正人**: weizzzz -**完成时间**: 2025-10-08 -**版本**: v1.0 - diff --git a/web/src/router/index.ts b/web/src/router/index.ts index b479eac6..a56107f6 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -66,6 +66,13 @@ export const constantRoutes: RouteRecordRaw[] = [ name: 'RecruitmentApply', meta: { title: '在线应聘' } }, + { + path: '/rs/overseas/overseas-form', + component: () => import('@/views/rs/overseas/overseas-form.vue'), + hidden: true, + name: 'OverseasForm', + meta: { title: '出国申请表单' } + }, { path: '/:pathMatch(.*)*', component: () => import('@/views/error/404.vue'), diff --git a/web/src/views/rs/overseas/index.vue b/web/src/views/rs/overseas/index.vue index 619fd2ba..c0a8129e 100644 --- a/web/src/views/rs/overseas/index.vue +++ b/web/src/views/rs/overseas/index.vue @@ -78,6 +78,14 @@ {{ scope.row.statusText }} + + +