“rs模块新增登记、党籍v0.1版”

This commit is contained in:
weizhong committed 2025-10-10 17:05:45 +08:00
1 parent 06731fd6e0
commit ae3caa26b8
29 files changed
+4247 -263

No files matched your search

+7
View File
@@ -116,6 +116,13 @@
<artifactId>itextpdf</artifactId> <artifactId>itextpdf</artifactId>
<version>5.5.13.3</version> <version>5.5.13.3</version>
</dependency> </dependency>
<!-- iText亚洲字体支持包 - 支持中文 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
</dependencies> </dependencies>
</project> </project>
@@ -27,14 +27,23 @@ import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import cn.dev33.satoken.annotation.SaCheckPermission; import cn.dev33.satoken.annotation.SaCheckPermission;
import org.dromara.rs.domain.bo.RsOverseasApplicationBo; import org.dromara.rs.domain.bo.RsOverseasApplicationBo;
import org.dromara.rs.domain.bo.GenerateRegistrationFormBo;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo; import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.vo.RsOverseasApprovalVo; import org.dromara.rs.domain.vo.RsOverseasApprovalVo;
import org.dromara.rs.domain.vo.RegistrationFormVO;
import org.dromara.rs.domain.query.RsOverseasApplicationQuery; import org.dromara.rs.domain.query.RsOverseasApplicationQuery;
import org.dromara.rs.service.IRsOverseasApplicationService; import org.dromara.rs.service.IRsOverseasApplicationService;
import org.dromara.rs.service.IRsOverseasApprovalService; import org.dromara.rs.service.IRsOverseasApprovalService;
import org.dromara.rs.service.IRegistrationFormService;
import java.util.Date; import java.util.Date;
import java.util.Map; import java.util.Map;
import java.util.HashMap; import java.util.HashMap;
import org.springframework.web.multipart.MultipartFile;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.common.oss.entity.UploadResult;
import java.io.IOException;
import java.text.SimpleDateFormat;
/** /**
* 出国申请Controller * 出国申请Controller
@@ -53,6 +62,9 @@ public class RsOverseasApplicationController extends BaseController {
@Autowired @Autowired
private IRsOverseasApprovalService overseasApprovalService; private IRsOverseasApprovalService overseasApprovalService;
@Autowired
private IRegistrationFormService registrationFormService;
/** /**
* 查询出国申请列表 * 查询出国申请列表
*/ */
@@ -139,11 +151,27 @@ public class RsOverseasApplicationController extends BaseController {
ExcelUtil.exportExcel(list, "出国申请数据", RsOverseasApplicationVo.class, response); ExcelUtil.exportExcel(list, "出国申请数据", RsOverseasApplicationVo.class, response);
} }
/**
* 下载登记备案表模板
*/
@GetMapping("/downloadRegistrationTemplate")
public void downloadRegistrationTemplate(HttpServletResponse response) {
registrationFormService.downloadRegistrationTemplate(response);
}
/**
* 下载保留党籍申请模板
*/
@GetMapping("/downloadPartyMembershipTemplate")
public void downloadPartyMembershipTemplate(HttpServletResponse response) {
registrationFormService.downloadPartyMembershipTemplate(response);
}
/** /**
* 获取出国申请详细信息 * 获取出国申请详细信息
*/ */
@SaCheckPermission("rs:overseas:query") @SaCheckPermission("rs:overseas:query")
@GetMapping("/{applicationId}") @GetMapping("/detail/{applicationId}")
public R<RsOverseasApplicationVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long applicationId) { public R<RsOverseasApplicationVo> getInfo(@NotNull(message = "主键不能为空") @PathVariable Long applicationId) {
return R.ok(applicationService.queryById(applicationId)); return R.ok(applicationService.queryById(applicationId));
} }
@@ -155,6 +183,14 @@ public class RsOverseasApplicationController extends BaseController {
@Log(title = "出国申请", businessType = BusinessType.INSERT) @Log(title = "出国申请", businessType = BusinessType.INSERT)
@PostMapping @PostMapping
public R<Void> add(@Validated(AddGroup.class) @RequestBody RsOverseasApplicationBo bo) { public R<Void> add(@Validated(AddGroup.class) @RequestBody RsOverseasApplicationBo bo) {
// 🔍 调试日志:检查接收到的关键字段
System.out.println("========== Controller 接收到的数据 ==========");
System.out.println("emergencyContact: " + bo.getEmergencyContact());
System.out.println("destination: " + bo.getDestination());
System.out.println("idNumber: " + bo.getIdNumber());
System.out.println("unitContact: " + bo.getUnitContact());
System.out.println("===============================================");
// 自动设置申请人信息 // 自动设置申请人信息
bo.setApplicantId(LoginHelper.getUserId()); bo.setApplicantId(LoginHelper.getUserId());
bo.setApplicantName(LoginHelper.getUsername()); bo.setApplicantName(LoginHelper.getUsername());
@@ -192,6 +228,48 @@ public class RsOverseasApplicationController extends BaseController {
@Log(title = "提交出国申请", businessType = BusinessType.UPDATE) @Log(title = "提交出国申请", businessType = BusinessType.UPDATE)
@PutMapping("/submit/{applicationId}") @PutMapping("/submit/{applicationId}")
public R<Void> submit(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId) { public R<Void> submit(@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId) {
// 获取申请详情进行校验
RsOverseasApplicationVo application = applicationService.queryById(applicationId);
if (application == null) {
return R.fail("申请不存在");
}
// 1. 校验出国登记前置条件(近3个月是否有已通过的出国登记)
java.util.Map<String, Object> 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<String, Object> 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: 添加保留党籍附件上传校验(需要在数据库和实体类中添加字段)
// 当前版本暂不强制校验保留党籍附件
}
// 所有校验通过,执行提交操作
return toAjax(applicationService.submitApplication(applicationId)); return toAjax(applicationService.submitApplication(applicationId));
} }
@@ -206,6 +284,35 @@ public class RsOverseasApplicationController extends BaseController {
return toAjax(applicationService.resubmitApplication(applicationId, bo)); return toAjax(applicationService.resubmitApplication(applicationId, bo));
} }
/**
* 校验出国申请前置条件
* 用于前端在填写表单时实时提示用户
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/validatePrerequisites")
public R<java.util.Map<String, Object>> validatePrerequisites(
@RequestParam(required = false) String politicalStatus,
@RequestParam(required = false) Integer duration) {
Long applicantId = LoginHelper.getUserId();
java.util.Map<String, Object> result = new java.util.HashMap<>();
// 1. 校验出国登记前置条件
java.util.Map<String, Object> registrationValidation =
applicationService.validateRegistrationPrerequisite(applicantId);
result.put("registrationValidation", registrationValidation);
// 2. 校验党籍保留前置条件(如果提供了政治面貌和出国天数)
if (politicalStatus != null && duration != null) {
java.util.Map<String, Object> retentionValidation =
applicationService.validatePartyMembershipRetentionPrerequisite(
applicantId, politicalStatus, duration);
result.put("retentionValidation", retentionValidation);
}
return R.ok(result);
}
/** /**
* 取消出国申请 * 取消出国申请
*/ */
@@ -298,4 +405,165 @@ public class RsOverseasApplicationController extends BaseController {
this.approvalOpinion = approvalOpinion; this.approvalOpinion = approvalOpinion;
} }
} }
// ==================== PDF生成相关接口 ====================
/**
* 生成出国申请登记表PDF
*/
@SaCheckPermission("rs:overseas:generate")
@PostMapping("/generateRegistrationForm")
@Log(title = "生成出国申请登记表", businessType = BusinessType.OTHER)
public R<RegistrationFormVO> generateRegistrationForm(@RequestBody @Validated GenerateRegistrationFormBo bo) {
RegistrationFormVO result = registrationFormService.generateRegistrationFormPdf(bo);
return R.ok(result);
}
/**
* 下载生成的PDF文档
*/
@SaCheckPermission("rs:overseas:download")
@GetMapping("/downloadPdf/{documentId}")
public void downloadPdf(@PathVariable String documentId, HttpServletResponse response) {
registrationFormService.downloadPdf(documentId, response);
}
/**
* 预览生成的PDF文档
*/
@SaCheckPermission("rs:overseas:preview")
@GetMapping("/previewPdf/{documentId}")
public void previewPdf(@PathVariable String documentId, HttpServletResponse response) {
registrationFormService.downloadPdf(documentId, response);
}
// ==================== 盖章文件上传接口 ====================
/**
* 上传登记备案表盖章文件
*/
@SaCheckPermission("rs:overseas:add")
@PostMapping("/uploadStampedFile")
@Log(title = "上传盖章文件", businessType = BusinessType.OTHER)
public R<Map<String, Object>> uploadStampedFile(@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限制");
}
// 生成文件名
String originalFilename = file.getOriginalFilename();
String fileName = "stamped_" + applicationId + "_" + System.currentTimeMillis()
+ getFileExtension(originalFilename);
// 上传到OSS
OssClient ossClient = OssFactory.instance();
String filePath = "overseas/stamped/" + fileName;
UploadResult uploadResult = ossClient.upload(file.getInputStream(), filePath,
file.getSize(), file.getContentType());
// 更新申请记录 - 只更新盖章文件相关字段
applicationService.updateStampedFileInfo(applicationId, uploadResult.getUrl(),
originalFilename, new Date(), "1");
// 返回结果
Map<String, Object> result = new HashMap<>();
result.put("filePath", uploadResult.getUrl());
result.put("fileName", originalFilename);
result.put("uploadTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return R.ok("盖章文件上传成功", result);
} catch (IOException e) {
e.printStackTrace();
return R.fail("文件上传失败:" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
return R.fail("系统异常:" + e.getMessage());
}
}
/**
* 上传党籍保留附件
*/
@PostMapping("/uploadPartyRetentionFile")
@Log(title = "上传党籍保留附件", businessType = BusinessType.OTHER)
public R<Map<String, Object>> 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限制");
}
// 生成文件名
String originalFilename = file.getOriginalFilename();
String fileName = "party_retention_" + applicationId + "_" + System.currentTimeMillis()
+ getFileExtension(originalFilename);
// 上传到OSS
OssClient ossClient = OssFactory.instance();
String filePath = "overseas/party-retention/" + fileName;
UploadResult uploadResult = ossClient.upload(file.getInputStream(), filePath,
file.getSize(), file.getContentType());
// 更新申请记录 - 只更新党籍保留附件相关字段
applicationService.updatePartyRetentionFileInfo(applicationId, uploadResult.getUrl(),
originalFilename, new Date(), "1");
// 返回结果
Map<String, Object> result = new HashMap<>();
result.put("filePath", uploadResult.getUrl());
result.put("fileName", originalFilename);
result.put("uploadTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return R.ok("党籍保留附件上传成功", result);
} catch (IOException e) {
e.printStackTrace();
return R.fail("文件上传失败:" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
return R.fail("系统异常:" + e.getMessage());
}
}
/**
* 验证文件类型
*/
private boolean isValidFileType(String contentType) {
if (contentType == null) return false;
return contentType.equals("application/pdf") ||
contentType.equals("image/jpeg") ||
contentType.equals("image/jpg") ||
contentType.equals("image/png");
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String filename) {
if (filename == null || !filename.contains(".")) {
return ".pdf";
}
return filename.substring(filename.lastIndexOf("."));
}
} }
@@ -30,6 +30,11 @@ import org.dromara.rs.domain.bo.RsOverseasRecordBo;
import org.dromara.rs.domain.vo.RsOverseasRecordVo; import org.dromara.rs.domain.vo.RsOverseasRecordVo;
import org.dromara.rs.domain.query.RsOverseasRecordQuery; import org.dromara.rs.domain.query.RsOverseasRecordQuery;
import org.dromara.rs.service.IRsOverseasRecordService; import org.dromara.rs.service.IRsOverseasRecordService;
import org.dromara.rs.util.OverseasReceiptPdfUtil;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
/** /**
* 出国记录Controller * 出国记录Controller
@@ -61,8 +66,16 @@ public class RsOverseasRecordController extends BaseController {
@GetMapping("/my/list") @GetMapping("/my/list")
public TableDataInfo<RsOverseasRecordVo> myList(RsOverseasRecordQuery query) { public TableDataInfo<RsOverseasRecordVo> myList(RsOverseasRecordQuery query) {
Long userId = LoginHelper.getUserId(); Long userId = LoginHelper.getUserId();
System.out.println("========== 出国记录Controller: 接收到查询请求 ==========");
System.out.println("当前用户ID: " + userId);
System.out.println("原始查询参数: " + query);
query.setApplicantId(userId); query.setApplicantId(userId);
return recordService.queryMyRecordPageList(query); System.out.println("设置申请人ID后: " + query);
TableDataInfo<RsOverseasRecordVo> result = recordService.queryMyRecordPageList(query);
System.out.println("Controller返回结果: total=" + result.getTotal() + ", rows=" + (result.getRows() != null ? result.getRows().size() : 0));
System.out.println("========== 出国记录Controller: 查询完成 ==========");
return result;
} }
/** /**
@@ -147,6 +160,53 @@ public class RsOverseasRecordController extends BaseController {
return toAjax(recordService.deleteWithValidByIds(recordIds, true)); return toAjax(recordService.deleteWithValidByIds(recordIds, true));
} }
/**
* 下载因私出国(境)情况回执PDF
*/
@SaCheckPermission("rs:overseas:query")
@Log(title = "下载出国情况回执", businessType = BusinessType.EXPORT)
@GetMapping("/downloadReceipt/{recordId}")
public void downloadReceipt(@NotNull(message = "记录ID不能为空") @PathVariable Long recordId,
HttpServletResponse response) {
try {
// 查询出国记录详情
RsOverseasRecordVo record = recordService.queryById(recordId);
if (record == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.setContentType("application/json;charset=UTF-8");
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 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);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":500,\"msg\":\"PDF生成失败: " + e.getMessage() + "\"}");
} catch (IOException ioException) {
// 记录日志
System.err.println("PDF生成失败: " + e.getMessage());
}
}
}
/** /**
* 初始化测试数据(临时接口,用于解决出国记录列表显示问题) * 初始化测试数据(临时接口,用于解决出国记录列表显示问题)
*/ */
@@ -44,12 +44,6 @@ public class RsJob extends BaseEntity {
@ExcelProperty(value = "部门") @ExcelProperty(value = "部门")
private String department; private String department;
/**
* 薪资范围
*/
@ExcelProperty(value = "薪资范围")
private String salaryRange;
/** /**
* 工作地点 * 工作地点
*/ */
@@ -35,6 +35,12 @@ public class RsOverseasApplication extends BaseEntity {
@ExcelProperty(value = "申请ID") @ExcelProperty(value = "申请ID")
private Long applicationId; private Long applicationId;
/**
* 编号(自动生成的登记编号)
*/
@ExcelProperty(value = "编号")
private String serialNumber;
/** /**
* 申请人ID * 申请人ID
*/ */
@@ -48,13 +54,140 @@ public class RsOverseasApplication extends BaseEntity {
private String applicantName; private String applicantName;
/** /**
* 所属部门 * 性别:0-男,1-女
*/
@ExcelProperty(value = "性别")
private String gender;
/**
* 出生日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@ExcelProperty(value = "出生日期")
private Date birthDate;
/**
* 照片地址(2寸免冠白色或淡蓝色底彩照)
*/
@ExcelProperty(value = "照片")
private String photo;
/**
* 公民身份号码(18位身份证号)
*/
@ExcelProperty(value = "公民身份号码")
private String idNumber;
/**
* 政治面貌
*/
@ExcelProperty(value = "政治面貌")
private String politicalStatus;
/**
* 户籍所在地(填写至县市区)
*/
@ExcelProperty(value = "户籍所在地")
private String registeredAddress;
/**
* 所属部门(工作部门/教学系部)
*/ */
@ExcelProperty(value = "所属部门") @ExcelProperty(value = "所属部门")
private String department; private String department;
/** /**
* 联系电话 * 职务职称
*/
@ExcelProperty(value = "职务职称")
private String position;
/**
* 职称
*/
@ExcelProperty(value = "职称")
private String professionalTitle;
/**
* 行政职级
*/
@ExcelProperty(value = "行政职级")
private String administrativeLevel;
/**
* 人员类别代码(根据报备人员范围中的情形项填写)
*/
@ExcelProperty(value = "人员类别代码")
private String personnelCategory;
/**
* 登记备案责任单位
*/
@ExcelProperty(value = "登记备案责任单位")
private String registrationUnit;
/**
* 登记备案责任单位负责人签名
*/
@ExcelProperty(value = "登记备案责任单位负责人签名")
private String unitLeaderSignature;
/**
* 登记备案责任单位联系人
*/
@ExcelProperty(value = "登记备案责任单位联系人")
private String unitContact;
/**
* 办公电话
*/
@ExcelProperty(value = "办公电话")
private String officePhone;
/**
* 申领证件名称
*/
@ExcelProperty(value = "申领证件名称")
private String documentName;
/**
* 证件号码
*/
@ExcelProperty(value = "证件号码")
private String documentNumber;
/**
* 前往国家
*/
@ExcelProperty(value = "前往国家")
private String destinationCountry;
/**
* 出境事由
*/
@ExcelProperty(value = "出境事由")
private String exitReason;
/**
* 探望对象姓名及与申请者关系
*/
@ExcelProperty(value = "探望对象关系")
private String visitRelationship;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remarks;
/**
* 移动电话
*/
@ExcelProperty(value = "移动电话")
private String mobilePhone;
/**
* 联系电话(保留原字段兼容性)
*/ */
@ExcelProperty(value = "联系电话") @ExcelProperty(value = "联系电话")
private String contactPhone; private String contactPhone;
@@ -177,6 +310,68 @@ public class RsOverseasApplication extends BaseEntity {
@ExcelProperty(value = "申请人备注") @ExcelProperty(value = "申请人备注")
private String applicantRemark; private String applicantRemark;
/**
* 应急联系人及电话
*/
@ExcelProperty(value = "应急联系人及电话")
private String emergencyContact;
/**
* 手写签字(base64格式)
*/
@ExcelProperty(value = "手写签字")
private String signature;
/**
* 登记备案表盖章文件路径
*/
@ExcelProperty(value = "登记备案表盖章文件路径")
private String stampedRegistrationFilePath;
/**
* 登记备案表盖章文件名
*/
@ExcelProperty(value = "登记备案表盖章文件名")
private String stampedRegistrationFileName;
/**
* 盖章文件上传时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "盖章文件上传时间")
private Date stampedFileUploadTime;
/**
* 盖章文件上传状态:0-未上传,1-已上传
*/
@ExcelProperty(value = "盖章文件上传状态")
private String stampedFileUploadStatus;
/**
* 党籍保留附件文件路径
*/
@ExcelProperty(value = "党籍保留附件文件路径")
private String partyRetentionFilePath;
/**
* 党籍保留附件文件名
*/
@ExcelProperty(value = "党籍保留附件文件名")
private String partyRetentionFileName;
/**
* 党籍保留附件上传时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "党籍保留附件上传时间")
private Date partyRetentionUploadTime;
/**
* 党籍保留附件上传状态:0-未上传,1-已上传
*/
@ExcelProperty(value = "党籍保留附件上传状态")
private String partyRetentionUploadStatus;
/** /**
* 基础字段:创建者 * 基础字段:创建者
*/ */
@@ -59,12 +59,24 @@ public class RsOverseasRecord extends BaseEntity {
@ExcelProperty(value = "所属部门") @ExcelProperty(value = "所属部门")
private String department; private String department;
/**
* 职务
*/
@ExcelProperty(value = "职务")
private String position;
/** /**
* 联系电话 * 联系电话
*/ */
@ExcelProperty(value = "联系电话") @ExcelProperty(value = "联系电话")
private String contactPhone; private String contactPhone;
/**
* 联系邮箱
*/
@ExcelProperty(value = "联系邮箱")
private String contactEmail;
/** /**
* 出国目的 * 出国目的
*/ */
@@ -115,6 +127,12 @@ public class RsOverseasRecord extends BaseEntity {
@ExcelProperty(value = "实际花费金额") @ExcelProperty(value = "实际花费金额")
private BigDecimal actualBudget; private BigDecimal actualBudget;
/**
* 经费来源
*/
@ExcelProperty(value = "经费来源")
private String budgetSource;
/** /**
* 邀请方信息 * 邀请方信息
*/ */
@@ -155,11 +155,6 @@ public class RsTrainingApplication extends BaseEntity {
*/ */
private String tenantId; private String tenantId;
/**
* 创建部门
*/
private Long createDept;
/** /**
* 拒绝时间 * 拒绝时间
*/ */
@@ -116,7 +116,6 @@ public class RsTrainingRecord extends BaseEntity {
private String feedback; private String feedback;
private String tenantId; private String tenantId;
private Long createDept;
/** /**
* 部门ID * 部门ID
@@ -9,6 +9,7 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.DecimalMin; import jakarta.validation.constraints.DecimalMin;
import jakarta.validation.constraints.Min; import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
@@ -35,6 +36,11 @@ public class RsOverseasApplicationBo extends BaseEntity {
@NotNull(message = "申请ID不能为空", groups = { EditGroup.class }) @NotNull(message = "申请ID不能为空", groups = { EditGroup.class })
private Long applicationId; private Long applicationId;
/**
* 编号(自动生成的登记编号)
*/
private String serialNumber;
/** /**
* 申请人ID * 申请人ID
*/ */
@@ -44,28 +50,125 @@ public class RsOverseasApplicationBo extends BaseEntity {
/** /**
* 申请人姓名 * 申请人姓名
*/ */
@NotBlank(message = "申请人姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String applicantName; private String applicantName;
/**
* 性别:0-男,1-女
*/
@NotBlank(message = "性别不能为空", groups = { AddGroup.class, EditGroup.class })
private String gender;
/**
* 出生日期
*/
@NotNull(message = "出生日期不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
/**
* 照片地址
*/
private String photo;
/**
* 公民身份号码(18位身份证号)
* 注意:此字段为可选字段,不进行格式校验(因为可能使用护照等其他证件)
*/
private String idNumber;
/**
* 政治面貌
*/
@NotBlank(message = "政治面貌不能为空", groups = { AddGroup.class, EditGroup.class })
private String politicalStatus;
/**
* 户籍所在地(填写至县市区)
*/
@NotBlank(message = "户籍所在地不能为空", groups = { AddGroup.class, EditGroup.class })
private String registeredAddress;
/** /**
* 所属部门 * 所属部门
*/ */
@NotBlank(message = "所属部门不能为空", groups = { AddGroup.class, EditGroup.class })
private String department; private String department;
/** /**
* 联系电话 * 职务职称
*/
@NotBlank(message = "职务职称不能为空", groups = { AddGroup.class, EditGroup.class })
private String position;
/**
* 职称
*/
private String professionalTitle;
/**
* 行政职级
*/
private String administrativeLevel;
/**
* 人员类别代码(根据报备人员范围中的情形项填写)
*/
@NotBlank(message = "人员类别代码不能为空", groups = { AddGroup.class, EditGroup.class })
private String personnelCategory;
/**
* 登记备案责任单位
*/
private String registrationUnit;
/**
* 登记备案责任单位负责人签名
*/
private String unitLeaderSignature;
/**
* 登记备案责任单位联系人
*/
@NotBlank(message = "登记备案责任单位联系人不能为空", groups = { AddGroup.class, EditGroup.class })
private String unitContact;
/**
* 办公电话
*/
@Pattern(regexp = "^(\\d{3,4}-)?\\d{7,8}$", message = "办公电话格式不正确", groups = { AddGroup.class, EditGroup.class })
private String officePhone;
/**
* 申领证件名称
*/
private String documentName;
/**
* 证件号码
*/
private String documentNumber;
/**
* 移动电话
*/
@NotBlank(message = "移动电话不能为空", groups = { AddGroup.class, EditGroup.class })
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "移动电话格式不正确", groups = { AddGroup.class, EditGroup.class })
private String mobilePhone;
/**
* 联系电话(保留兼容性)
*/ */
private String contactPhone; private String contactPhone;
/** /**
* 出国目的 * 出国目的
*/ */
@NotBlank(message = "出国目的不能为空", groups = { AddGroup.class, EditGroup.class })
private String purpose; private String purpose;
/** /**
* 目的详细说明 * 目的详细说明
*/ */
@NotBlank(message = "目的详细说明不能为空", groups = { AddGroup.class, EditGroup.class })
private String purposeDetail; private String purposeDetail;
/** /**
@@ -74,6 +177,21 @@ public class RsOverseasApplicationBo extends BaseEntity {
@NotBlank(message = "目的地国家不能为空", groups = { AddGroup.class, EditGroup.class }) @NotBlank(message = "目的地国家不能为空", groups = { AddGroup.class, EditGroup.class })
private String destination; private String destination;
/**
* 前往国家
*/
private String destinationCountry;
/**
* 出境事由
*/
private String exitReason;
/**
* 探望对象姓名及与申请者关系
*/
private String visitRelationship;
/** /**
* 开始日期 * 开始日期
*/ */
@@ -97,8 +215,6 @@ public class RsOverseasApplicationBo extends BaseEntity {
/** /**
* 预算金额 * 预算金额
*/ */
@NotNull(message = "预算金额不能为空", groups = { AddGroup.class, EditGroup.class })
@DecimalMin(value = "0.01", message = "预算金额必须大于0", groups = { AddGroup.class, EditGroup.class })
private BigDecimal budget; private BigDecimal budget;
/** /**
@@ -119,8 +235,6 @@ public class RsOverseasApplicationBo extends BaseEntity {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTime; private Date approvalTime;
/** /**
* 目的地 * 目的地
*/ */
@@ -146,6 +260,22 @@ public class RsOverseasApplicationBo extends BaseEntity {
*/ */
private String applicantRemark; private String applicantRemark;
/**
* 应急联系人及电话
*/
@NotBlank(message = "应急联系人及电话不能为空")
private String emergencyContact;
/**
* 手写签字(base64格式)
*/
private String signature;
/**
* 备注
*/
private String remarks;
/** /**
* 管理员备注 * 管理员备注
*/ */
@@ -165,4 +295,25 @@ public class RsOverseasApplicationBo extends BaseEntity {
* 申请状态 * 申请状态
*/ */
private String status; private String status;
/**
* 登记备案表盖章文件路径
*/
private String stampedRegistrationFilePath;
/**
* 登记备案表盖章文件名
*/
private String stampedRegistrationFileName;
/**
* 盖章文件上传时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date stampedFileUploadTime;
/**
* 盖章文件上传状态:0-未上传,1-已上传
*/
private String stampedFileUploadStatus;
} }
@@ -28,6 +28,11 @@ public class RsOverseasApplicationVo implements Serializable {
*/ */
private Long applicationId; private Long applicationId;
/**
* 编号(自动生成的登记编号)
*/
private String serialNumber;
/** /**
* 申请人ID * 申请人ID
*/ */
@@ -38,11 +43,117 @@ public class RsOverseasApplicationVo implements Serializable {
*/ */
private String applicantName; private String applicantName;
/**
* 性别
*/
private String gender;
/**
* 性别文本
*/
private String genderText;
/**
* 出生日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
/**
* 照片地址
*/
private String photo;
/**
* 公民身份号码
*/
private String idNumber;
/**
* 政治面貌
*/
private String politicalStatus;
/**
* 政治面貌文本
*/
private String politicalStatusText;
/**
* 户籍所在地
*/
private String registeredAddress;
/** /**
* 所属部门 * 所属部门
*/ */
private String department; private String department;
/**
* 职务职称
*/
private String position;
/**
* 职称
*/
private String professionalTitle;
/**
* 行政职级
*/
private String administrativeLevel;
/**
* 行政职级文本
*/
private String administrativeLevelText;
/**
* 人员类别代码
*/
private String personnelCategory;
/**
* 人员类别代码文本
*/
private String personnelCategoryText;
/**
* 登记备案责任单位
*/
private String registrationUnit;
/**
* 登记备案责任单位负责人签名
*/
private String unitLeaderSignature;
/**
* 登记备案责任单位联系人
*/
private String unitContact;
/**
* 办公电话
*/
private String officePhone;
/**
* 申领证件名称
*/
private String documentName;
/**
* 证件号码
*/
private String documentNumber;
/**
* 移动电话
*/
private String mobilePhone;
/** /**
* 联系电话 * 联系电话
*/ */
@@ -68,6 +179,21 @@ public class RsOverseasApplicationVo implements Serializable {
*/ */
private String destination; private String destination;
/**
* 前往国家
*/
private String destinationCountry;
/**
* 出境事由
*/
private String exitReason;
/**
* 探望对象姓名及与申请者关系
*/
private String visitRelationship;
/** /**
* 目的地 * 目的地
*/ */
@@ -157,11 +283,47 @@ public class RsOverseasApplicationVo implements Serializable {
*/ */
private String applicantRemark; private String applicantRemark;
/**
* 应急联系人及电话
*/
private String emergencyContact;
/**
* 手写签字(base64格式)
*/
private String signature;
/**
* 备注
*/
private String remarks;
/** /**
* 管理员备注 * 管理员备注
*/ */
private String adminRemark; private String adminRemark;
/**
* 登记备案表盖章文件路径
*/
private String stampedRegistrationFilePath;
/**
* 登记备案表盖章文件名
*/
private String stampedRegistrationFileName;
/**
* 盖章文件上传时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date stampedFileUploadTime;
/**
* 盖章文件上传状态:0-未上传,1-已上传
*/
private String stampedFileUploadStatus;
/** /**
* 租户编号 * 租户编号
*/ */
@@ -47,11 +47,89 @@ public class RsOverseasRecordVo implements Serializable {
* 所属部门 * 所属部门
*/ */
private String department; private String department;
/**
* 职位
*/
private String position;
/** /**
* 联系电话 * 联系电话
*/ */
private String contactPhone; 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;
/**
* 备注(来自申请表)
*/
private String remarks;
/** /**
* 出国目的 * 出国目的
@@ -120,6 +198,16 @@ public class RsOverseasRecordVo implements Serializable {
*/ */
private String transportationPlan; private String transportationPlan;
/**
* 申请状态(来自申请表)
*/
private String status;
/**
* 申请状态文本(来自申请表)
*/
private String statusText;
/** /**
* 完成状态 * 完成状态
*/ */
@@ -158,4 +158,52 @@ public interface IRsOverseasApplicationService {
* @return 是否成功 * @return 是否成功
*/ */
Boolean resubmitApplication(Long applicationId, RsOverseasApplicationBo applicationData); Boolean resubmitApplication(Long applicationId, RsOverseasApplicationBo applicationData);
/**
* 更新盖章文件信息
*
* @param applicationId 申请ID
* @param filePath 文件路径
* @param fileName 文件名
* @param uploadTime 上传时间
* @param uploadStatus 上传状态
* @return 是否成功
*/
Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName,
java.util.Date uploadTime, String uploadStatus);
/**
* 更新党籍保留附件信息
*
* @param applicationId 申请ID
* @param filePath 文件路径
* @param fileName 文件名
* @param uploadTime 上传时间
* @param uploadStatus 上传状态
* @return 是否成功
*/
Boolean updatePartyRetentionFileInfo(Long applicationId, String filePath, String fileName,
java.util.Date uploadTime, String uploadStatus);
/**
* 校验出国登记前置条件
* 检查近3个月内是否有已通过的出国登记记录
*
* @param applicantId 申请人ID
* @return 校验结果Map,包含 isValid(是否通过校验)和 message(提示信息)
*/
java.util.Map<String, Object> validateRegistrationPrerequisite(Long applicantId);
/**
* 校验党籍保留前置条件
* 检查是否为中共党员且出国天数>30天,如果是则需要检查党籍保留申请
*
* @param applicantId 申请人ID
* @param politicalStatus 政治面貌
* @param duration 出国天数
* @return 校验结果Map,包含 isValid(是否通过校验)、needRetention(是否需要党籍保留)和 message(提示信息)
*/
java.util.Map<String, Object> validatePartyMembershipRetentionPrerequisite(Long applicantId,
String politicalStatus,
Integer duration);
} }
@@ -2,7 +2,10 @@ package org.dromara.rs.service.impl;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Calendar;
import org.dromara.common.core.utils.DateUtils; import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
@@ -12,12 +15,17 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.rs.mapper.RsOverseasApplicationMapper; import org.dromara.rs.mapper.RsOverseasApplicationMapper;
import org.dromara.rs.mapper.RsOverseasApprovalMapper; 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.RsOverseasApplication;
import org.dromara.rs.domain.RsOverseasApproval; 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.bo.RsOverseasApplicationBo;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo; import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.query.RsOverseasApplicationQuery; import org.dromara.rs.domain.query.RsOverseasApplicationQuery;
@@ -47,6 +55,12 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS
@Autowired @Autowired
private IRsOverseasRecordService overseasRecordService; private IRsOverseasRecordService overseasRecordService;
@Autowired
private RsOverseasRegistrationMapper registrationMapper;
@Autowired
private RsPartyMembershipRetentionMapper retentionMapper;
/** /**
* 查询出国申请分页列表 * 查询出国申请分页列表
*/ */
@@ -198,7 +212,71 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS
update.setDuration(days); update.setDuration(days);
} }
validEntityBeforeSave(update); validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
// 使用LambdaUpdateWrapper安全更新,只更新非null字段
LambdaUpdateWrapper<RsOverseasApplication> 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.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.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.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.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());
return baseMapper.update(null, updateWrapper) > 0;
} }
/** /**
@@ -465,9 +543,7 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS
} }
} }
if (entity.getBudget() != null && entity.getBudget().doubleValue() <= 0) { // 预算金额校验已删除(业务不需要预算金额字段)
throw new RuntimeException("预算金额必须大于0");
}
} }
/** /**
@@ -498,23 +574,66 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS
private RsOverseasApplication buildEntityFromBo(RsOverseasApplicationBo bo) { private RsOverseasApplication buildEntityFromBo(RsOverseasApplicationBo bo) {
RsOverseasApplication entity = new RsOverseasApplication(); RsOverseasApplication entity = new RsOverseasApplication();
entity.setApplicationId(bo.getApplicationId()); entity.setApplicationId(bo.getApplicationId());
// ==================== 基本信息 ====================
entity.setApplicantId(bo.getApplicantId()); entity.setApplicantId(bo.getApplicantId());
entity.setApplicantName(bo.getApplicantName()); 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.setDepartment(bo.getDepartment()); entity.setDepartment(bo.getDepartment());
entity.setContactPhone(bo.getContactPhone()); 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.setUnitContact(bo.getUnitContact()); // 单位联系人
entity.setOfficePhone(bo.getOfficePhone()); // 办公电话
entity.setMobilePhone(bo.getMobilePhone()); // 移动电话
entity.setContactPhone(bo.getContactPhone()); // 联系电话(兼容字段)
entity.setEmergencyContact(bo.getEmergencyContact()); // 应急联系人及电话 ⬅️ 关键修复
// ==================== 出国目的和行程 ====================
entity.setPurpose(bo.getPurpose()); entity.setPurpose(bo.getPurpose());
entity.setPurposeDetail(bo.getPurposeDetail()); entity.setPurposeDetail(bo.getPurposeDetail());
entity.setDestination(bo.getDestination()); entity.setDestination(bo.getDestination());
entity.setDestinationCountry(bo.getDestinationCountry()); // 前往国家
entity.setDestinationCity(bo.getDestinationCity()); entity.setDestinationCity(bo.getDestinationCity());
entity.setStartDate(bo.getStartDate()); entity.setStartDate(bo.getStartDate());
entity.setEndDate(bo.getEndDate()); entity.setEndDate(bo.getEndDate());
entity.setDuration(bo.getDuration()); entity.setDuration(bo.getDuration());
entity.setBudget(bo.getBudget()); entity.setBudget(bo.getBudget());
// ==================== 证件和文档 ====================
entity.setInvitation(bo.getInvitation()); entity.setInvitation(bo.getInvitation());
entity.setDocumentName(bo.getDocumentName()); // 申领证件名称
entity.setDocumentNumber(bo.getDocumentNumber()); // 证件号码
entity.setItinerary(bo.getItinerary()); entity.setItinerary(bo.getItinerary());
entity.setAccommodationPlan(bo.getAccommodationPlan()); entity.setAccommodationPlan(bo.getAccommodationPlan());
entity.setTransportationPlan(bo.getTransportationPlan()); entity.setTransportationPlan(bo.getTransportationPlan());
entity.setApplicantRemark(bo.getApplicantRemark());
// ==================== 出境事由和备注 ====================
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());
return entity; return entity;
} }
@@ -543,4 +662,178 @@ public class RsOverseasApplicationServiceImpl implements IRsOverseasApplicationS
validatedByIds(ids, isValid); validatedByIds(ids, isValid);
return baseMapper.deleteBatchIds(ids) > 0; return baseMapper.deleteBatchIds(ids) > 0;
} }
/**
* 更新盖章文件信息
*/
@Override
@Transactional
public Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName,
java.util.Date uploadTime, String uploadStatus) {
return baseMapper.update(null,
new LambdaUpdateWrapper<RsOverseasApplication>()
.eq(RsOverseasApplication::getApplicationId, applicationId)
.set(RsOverseasApplication::getStampedRegistrationFilePath, filePath)
.set(RsOverseasApplication::getStampedRegistrationFileName, fileName)
.set(RsOverseasApplication::getStampedFileUploadTime, uploadTime)
.set(RsOverseasApplication::getStampedFileUploadStatus, uploadStatus)
) > 0;
}
/**
* 更新党籍保留附件信息
*/
@Override
@Transactional
public Boolean updatePartyRetentionFileInfo(Long applicationId, String filePath, String fileName,
java.util.Date uploadTime, String uploadStatus) {
return baseMapper.update(null,
new LambdaUpdateWrapper<RsOverseasApplication>()
.eq(RsOverseasApplication::getApplicationId, applicationId)
.set(RsOverseasApplication::getPartyRetentionFilePath, filePath)
.set(RsOverseasApplication::getPartyRetentionFileName, fileName)
.set(RsOverseasApplication::getPartyRetentionUploadTime, uploadTime)
.set(RsOverseasApplication::getPartyRetentionUploadStatus, uploadStatus)
) > 0;
}
/**
* 校验出国登记前置条件
* 检查当前自然年内是否有已通过的出国登记记录
*/
@Override
public Map<String, Object> validateRegistrationPrerequisite(Long applicantId) {
Map<String, Object> result = new HashMap<>();
// 计算当前自然年的开始日期(1月1日 00:00:00)
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);
// 查询当前自然年内的出国登记记录(使用createBy字段)
LambdaQueryWrapper<RsOverseasRegistration> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(RsOverseasRegistration::getCreateBy, applicantId)
.ge(RsOverseasRegistration::getCreateTime, currentYearStart)
.orderByDesc(RsOverseasRegistration::getCreateTime)
.last("LIMIT 1");
RsOverseasRegistration latestRegistration = registrationMapper.selectOne(queryWrapper);
if (latestRegistration == null) {
// 当前自然年内没有登记记录
result.put("isValid", false);
result.put("message", "请先提交出国登记申请,审核通过后再提交出国申请");
log.warn("用户{}在{}年内没有出国登记记录", applicantId, currentYear);
return result;
}
String status = latestRegistration.getStatus();
if ("2".equals(status)) {
// 状态为已通过(审批通过)- 出国登记状态2表示已通过
result.put("isValid", true);
result.put("message", "出国登记已通过审核");
result.put("registrationId", latestRegistration.getRegistrationId());
log.info("用户{}的出国登记已通过审核,registrationId: {}", applicantId, latestRegistration.getRegistrationId());
} else if ("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);
}
return result;
}
/**
* 校验党籍保留前置条件
* 检查是否为中共党员且出国天数>30天,如果是则需要检查当前自然年内的党籍保留申请
*/
@Override
public Map<String, Object> validatePartyMembershipRetentionPrerequisite(Long applicantId,
String politicalStatus,
Integer duration) {
Map<String, Object> 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;
}
// 需要党籍保留申请,检查当前自然年内是否有党籍保留申请(使用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<RsPartyMembershipRetention> 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 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);
}
return result;
}
} }
@@ -87,20 +87,36 @@ public class RsOverseasRecordServiceImpl implements IRsOverseasRecordService {
*/ */
@Override @Override
public TableDataInfo<RsOverseasRecordVo> queryMyRecordPageList(RsOverseasRecordQuery query) { public TableDataInfo<RsOverseasRecordVo> queryMyRecordPageList(RsOverseasRecordQuery query) {
log.info("============= 出国记录列表查询开始 =============");
log.info("查询参数: {}", query);
log.info("申请人ID: {}", query.getApplicantId());
log.info("分页参数: pageNum={}, pageSize={}", query.getPageNum(), query.getPageSize());
// 使用联合查询方法,包含已通过审批的申请数据 // 使用联合查询方法,包含已通过审批的申请数据
List<RsOverseasRecordVo> list = baseMapper.selectMyOverseasRecordList(query); List<RsOverseasRecordVo> list = baseMapper.selectMyOverseasRecordList(query);
log.info("查询结果数量: {}", list != null ? list.size() : 0);
if (list != null && !list.isEmpty()) {
log.info("第一条记录: {}", list.get(0));
} else {
log.warn("⚠️ 查询结果为空!请检查数据库中是否有数据");
}
// 手动分页处理 // 手动分页处理
int pageNum = query.getPageNum(); int pageNum = query.getPageNum();
int pageSize = query.getPageSize(); int pageSize = query.getPageSize();
int start = (pageNum - 1) * pageSize; int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, list.size()); int end = Math.min(start + pageSize, list.size());
log.info("分页计算: start={}, end={}, totalSize={}", start, end, list.size());
List<RsOverseasRecordVo> pageList; List<RsOverseasRecordVo> pageList;
if (start > list.size()) { if (start > list.size()) {
pageList = Arrays.asList(); pageList = Arrays.asList();
log.warn("⚠️ 起始位置超出数据范围,返回空列表");
} else { } else {
pageList = list.subList(start, end); pageList = list.subList(start, end);
log.info("分页后数据量: {}", pageList.size());
} }
// 构建分页对象 // 构建分页对象
@@ -108,7 +124,11 @@ public class RsOverseasRecordServiceImpl implements IRsOverseasRecordService {
page.setRecords(pageList); page.setRecords(pageList);
page.setTotal(list.size()); page.setTotal(list.size());
return TableDataInfo.build(page); TableDataInfo<RsOverseasRecordVo> result = TableDataInfo.build(page);
log.info("返回结果: total={}, rows={}", result.getTotal(), result.getRows() != null ? result.getRows().size() : 0);
log.info("============= 出国记录列表查询结束 =============");
return result;
} }
/** /**
@@ -8,7 +8,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<id property="jobId" column="job_id"/> <id property="jobId" column="job_id"/>
<result property="title" column="title"/> <result property="title" column="title"/>
<result property="department" column="department"/> <result property="department" column="department"/>
<result property="salaryRange" column="salary_range"/>
<result property="location" column="location"/> <result property="location" column="location"/>
<result property="description" column="description"/> <result property="description" column="description"/>
<result property="requirements" column="requirements"/> <result property="requirements" column="requirements"/>
@@ -27,7 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectRsJobVo"> <sql id="selectRsJobVo">
select j.job_id, j.title, j.department, j.salary_range, j.location, j.description, j.requirements, select j.job_id, j.title, j.department, j.location, j.description, j.requirements,
j.is_active, j.view_count, j.tenant_id, j.create_by, j.create_time, j.update_by, j.update_time, j.remark, j.is_active, j.view_count, j.tenant_id, j.create_by, j.create_time, j.update_by, j.update_time, j.remark,
(select count(1) from rs_application a where a.job_id = j.job_id and a.deleted = '0' and a.tenant_id = j.tenant_id) as applicant_count (select count(1) from rs_application a where a.job_id = j.job_id and a.deleted = '0' and a.tenant_id = j.tenant_id) as applicant_count
from rs_job j from rs_job j
@@ -6,12 +6,33 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="RsOverseasApplication" id="RsOverseasApplicationResult"> <resultMap type="RsOverseasApplication" id="RsOverseasApplicationResult">
<id property="applicationId" column="application_id"/> <id property="applicationId" column="application_id"/>
<result property="serialNumber" column="serial_number"/>
<result property="applicantId" column="applicant_id"/> <result property="applicantId" column="applicant_id"/>
<result property="applicantName" column="applicant_name"/> <result property="applicantName" column="applicant_name"/>
<result property="gender" column="gender"/>
<result property="birthDate" column="birth_date"/>
<result property="photo" column="photo"/>
<result property="idNumber" column="id_number"/>
<result property="politicalStatus" column="political_status"/>
<result property="registeredAddress" column="registered_address"/>
<result property="department" column="department"/> <result property="department" column="department"/>
<result property="position" column="position"/> <result property="position" column="position"/>
<result property="professionalTitle" column="professional_title"/>
<result property="administrativeLevel" column="administrative_level"/>
<result property="personnelCategory" column="personnel_category"/>
<result property="registrationUnit" column="registration_unit"/>
<result property="unitLeaderSignature" column="unit_leader_signature"/>
<result property="unitContact" column="unit_contact"/>
<result property="officePhone" column="office_phone"/>
<result property="mobilePhone" column="mobile_phone"/>
<result property="contactPhone" column="contact_phone"/> <result property="contactPhone" column="contact_phone"/>
<result property="contactEmail" column="contact_email"/> <result property="contactEmail" column="contact_email"/>
<result property="documentName" column="document_name"/>
<result property="documentNumber" column="document_number"/>
<result property="destinationCountry" column="destination_country"/>
<result property="exitReason" column="exit_reason"/>
<result property="visitRelationship" column="visit_relationship"/>
<result property="remarks" column="remarks"/>
<result property="purpose" column="purpose"/> <result property="purpose" column="purpose"/>
<result property="purposeDetail" column="purpose_detail"/> <result property="purposeDetail" column="purpose_detail"/>
<result property="destination" column="destination"/> <result property="destination" column="destination"/>
@@ -31,6 +52,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="approvalTime" column="approval_time"/> <result property="approvalTime" column="approval_time"/>
<result property="adminRemark" column="admin_remark"/> <result property="adminRemark" column="admin_remark"/>
<result property="applicantRemark" column="applicant_remark"/> <result property="applicantRemark" column="applicant_remark"/>
<result property="emergencyContact" column="emergency_contact"/>
<result property="signature" column="signature"/>
<result property="stampedRegistrationFilePath" column="stamped_registration_file_path"/>
<result property="stampedRegistrationFileName" column="stamped_registration_file_name"/>
<result property="stampedFileUploadTime" column="stamped_file_upload_time"/>
<result property="stampedFileUploadStatus" column="stamped_file_upload_status"/>
<result property="tenantId" column="tenant_id"/> <result property="tenantId" column="tenant_id"/>
<result property="createBy" column="create_by"/> <result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/> <result property="createTime" column="create_time"/>
@@ -43,15 +70,27 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="statusText" column="status_text"/> <result property="statusText" column="status_text"/>
<result property="purposeText" column="purpose_text"/> <result property="purposeText" column="purpose_text"/>
<result property="approvalLevelText" column="approval_level_text"/> <result property="approvalLevelText" column="approval_level_text"/>
<result property="genderText" column="gender_text"/>
<result property="politicalStatusText" column="political_status_text"/>
<result property="administrativeLevelText" column="administrative_level_text"/>
<result property="personnelCategoryText" column="personnel_category_text"/>
</resultMap> </resultMap>
<sql id="selectOverseasApplicationVo"> <sql id="selectOverseasApplicationVo">
select oa.application_id, oa.applicant_id, oa.applicant_name, oa.department, oa.position, select oa.application_id, oa.serial_number, oa.applicant_id, oa.applicant_name,
oa.contact_phone, oa.contact_email, oa.purpose, oa.purpose_detail, oa.destination, oa.destination_city, oa.gender, oa.birth_date, oa.photo, oa.id_number, oa.political_status, oa.registered_address,
oa.department, oa.position, oa.professional_title, oa.administrative_level, oa.personnel_category,
oa.registration_unit, oa.unit_leader_signature, oa.unit_contact, oa.office_phone, oa.mobile_phone,
oa.contact_phone, oa.contact_email, oa.document_name, oa.document_number,
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.budget, oa.invitation, oa.itinerary, oa.start_date, oa.end_date, oa.duration, oa.budget, oa.invitation, oa.itinerary,
oa.accommodation_plan, oa.transportation_plan, oa.status, oa.current_approver, oa.approval_level, oa.accommodation_plan, oa.transportation_plan, oa.status, oa.current_approver, oa.approval_level,
oa.apply_time, oa.approval_time, oa.admin_remark, oa.applicant_remark, oa.tenant_id, oa.apply_time, oa.approval_time, oa.admin_remark, oa.applicant_remark,
oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted, 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.tenant_id, oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted,
CASE CASE
WHEN oa.status IS NULL OR oa.status = '' THEN '草稿' WHEN oa.status IS NULL OR oa.status = '' THEN '草稿'
WHEN oa.status = '0' THEN '待审核' WHEN oa.status = '0' THEN '待审核'
@@ -75,7 +114,52 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHEN '2' THEN '分管领导' WHEN '2' THEN '分管领导'
WHEN '3' THEN '校领导' WHEN '3' THEN '校领导'
ELSE '未知级别' ELSE '未知级别'
END as approval_level_text END as approval_level_text,
CASE oa.gender
WHEN '0' THEN '男'
WHEN '1' THEN '女'
ELSE '未知'
END as gender_text,
CASE oa.political_status
WHEN '1' THEN '中共党员'
WHEN '2' THEN '中共预备党员'
WHEN '3' THEN '共青团员'
WHEN '4' THEN '民革党员'
WHEN '5' THEN '民盟盟员'
WHEN '6' THEN '民建会员'
WHEN '7' THEN '民进会员'
WHEN '8' THEN '农工党党员'
WHEN '9' THEN '致公党党员'
WHEN '10' THEN '九三学社社员'
WHEN '11' THEN '台盟盟员'
WHEN '12' THEN '无党派人士'
WHEN '13' THEN '群众'
ELSE oa.political_status
END as political_status_text,
CASE oa.administrative_level
WHEN '0' THEN '无'
WHEN '1' THEN '一级巡视员'
WHEN '2' THEN '二级巡视员'
WHEN '3' THEN '一级调研员'
WHEN '4' THEN '二级调研员'
WHEN '5' THEN '三级调研员'
WHEN '6' THEN '四级调研员'
WHEN '7' THEN '一级主任科员'
WHEN '8' THEN '二级主任科员'
WHEN '9' THEN '三级主任科员'
WHEN '10' THEN '四级主任科员'
WHEN '11' THEN '一级科员'
WHEN '12' THEN '二级科员'
ELSE oa.administrative_level
END as administrative_level_text,
CASE oa.personnel_category
WHEN '1' THEN '1-厅局级'
WHEN '2' THEN '2-县处级'
WHEN '3' THEN '3-乡科级'
WHEN '4' THEN '4-一般干部'
WHEN '5' THEN '5-其他重要岗位人员'
ELSE oa.personnel_category
END as personnel_category_text
from rs_overseas_application oa from rs_overseas_application oa
</sql> </sql>
@@ -216,4 +300,204 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
order by oa.apply_time asc order by oa.apply_time asc
</select> </select>
<!-- 插入出国申请 - 包含所有字段 -->
<insert id="insertOverseasApplication" parameterType="RsOverseasApplication" useGeneratedKeys="true" keyProperty="applicationId">
insert into rs_overseas_application
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="serialNumber != null and serialNumber != ''">serial_number,</if>
<if test="applicantId != null">applicant_id,</if>
<if test="applicantName != null and applicantName != ''">applicant_name,</if>
<if test="gender != null and gender != ''">gender,</if>
<if test="birthDate != null">birth_date,</if>
<if test="photo != null and photo != ''">photo,</if>
<if test="idNumber != null and idNumber != ''">id_number,</if>
<if test="politicalStatus != null and politicalStatus != ''">political_status,</if>
<if test="registeredAddress != null and registeredAddress != ''">registered_address,</if>
<if test="department != null and department != ''">department,</if>
<if test="position != null and position != ''">position,</if>
<if test="professionalTitle != null and professionalTitle != ''">professional_title,</if>
<if test="administrativeLevel != null and administrativeLevel != ''">administrative_level,</if>
<if test="personnelCategory != null and personnelCategory != ''">personnel_category,</if>
<if test="registrationUnit != null and registrationUnit != ''">registration_unit,</if>
<if test="unitLeaderSignature != null and unitLeaderSignature != ''">unit_leader_signature,</if>
<if test="unitContact != null and unitContact != ''">unit_contact,</if>
<if test="officePhone != null and officePhone != ''">office_phone,</if>
<if test="mobilePhone != null and mobilePhone != ''">mobile_phone,</if>
<if test="contactPhone != null and contactPhone != ''">contact_phone,</if>
<if test="contactEmail != null and contactEmail != ''">contact_email,</if>
<if test="documentName != null and documentName != ''">document_name,</if>
<if test="documentNumber != null and documentNumber != ''">document_number,</if>
<if test="destinationCountry != null and destinationCountry != ''">destination_country,</if>
<if test="exitReason != null and exitReason != ''">exit_reason,</if>
<if test="visitRelationship != null and visitRelationship != ''">visit_relationship,</if>
<if test="remarks != null and remarks != ''">remarks,</if>
<if test="purpose != null and purpose != ''">purpose,</if>
<if test="purposeDetail != null and purposeDetail != ''">purpose_detail,</if>
<if test="destination != null and destination != ''">destination,</if>
<if test="destinationCity != null and destinationCity != ''">destination_city,</if>
<if test="startDate != null">start_date,</if>
<if test="endDate != null">end_date,</if>
<if test="duration != null">duration,</if>
<if test="budget != null">budget,</if>
<if test="invitation != null and invitation != ''">invitation,</if>
<if test="itinerary != null and itinerary != ''">itinerary,</if>
<if test="accommodationPlan != null and accommodationPlan != ''">accommodation_plan,</if>
<if test="transportationPlan != null and transportationPlan != ''">transportation_plan,</if>
<if test="status != null and status != ''">status,</if>
<if test="currentApprover != null and currentApprover != ''">current_approver,</if>
<if test="approvalLevel != null and approvalLevel != ''">approval_level,</if>
<if test="applyTime != null">apply_time,</if>
<if test="approvalTime != null">approval_time,</if>
<if test="adminRemark != null and adminRemark != ''">admin_remark,</if>
<if test="applicantRemark != null and applicantRemark != ''">applicant_remark,</if>
<if test="emergencyContact != null">emergency_contact,</if>
<if test="signature != null and signature != ''">signature,</if>
<if test="stampedRegistrationFilePath != null and stampedRegistrationFilePath != ''">stamped_registration_file_path,</if>
<if test="stampedRegistrationFileName != null and stampedRegistrationFileName != ''">stamped_registration_file_name,</if>
<if test="stampedFileUploadTime != null">stamped_file_upload_time,</if>
<if test="stampedFileUploadStatus != null and stampedFileUploadStatus != ''">stamped_file_upload_status,</if>
<if test="tenantId != null and tenantId != ''">tenant_id,</if>
<if test="deleted != null and deleted != ''">deleted,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="serialNumber != null and serialNumber != ''">#{serialNumber},</if>
<if test="applicantId != null">#{applicantId},</if>
<if test="applicantName != null and applicantName != ''">#{applicantName},</if>
<if test="gender != null and gender != ''">#{gender},</if>
<if test="birthDate != null">#{birthDate},</if>
<if test="photo != null and photo != ''">#{photo},</if>
<if test="idNumber != null and idNumber != ''">#{idNumber},</if>
<if test="politicalStatus != null and politicalStatus != ''">#{politicalStatus},</if>
<if test="registeredAddress != null and registeredAddress != ''">#{registeredAddress},</if>
<if test="department != null and department != ''">#{department},</if>
<if test="position != null and position != ''">#{position},</if>
<if test="professionalTitle != null and professionalTitle != ''">#{professionalTitle},</if>
<if test="administrativeLevel != null and administrativeLevel != ''">#{administrativeLevel},</if>
<if test="personnelCategory != null and personnelCategory != ''">#{personnelCategory},</if>
<if test="registrationUnit != null and registrationUnit != ''">#{registrationUnit},</if>
<if test="unitLeaderSignature != null and unitLeaderSignature != ''">#{unitLeaderSignature},</if>
<if test="unitContact != null and unitContact != ''">#{unitContact},</if>
<if test="officePhone != null and officePhone != ''">#{officePhone},</if>
<if test="mobilePhone != null and mobilePhone != ''">#{mobilePhone},</if>
<if test="contactPhone != null and contactPhone != ''">#{contactPhone},</if>
<if test="contactEmail != null and contactEmail != ''">#{contactEmail},</if>
<if test="documentName != null and documentName != ''">#{documentName},</if>
<if test="documentNumber != null and documentNumber != ''">#{documentNumber},</if>
<if test="destinationCountry != null and destinationCountry != ''">#{destinationCountry},</if>
<if test="exitReason != null and exitReason != ''">#{exitReason},</if>
<if test="visitRelationship != null and visitRelationship != ''">#{visitRelationship},</if>
<if test="remarks != null and remarks != ''">#{remarks},</if>
<if test="purpose != null and purpose != ''">#{purpose},</if>
<if test="purposeDetail != null and purposeDetail != ''">#{purposeDetail},</if>
<if test="destination != null and destination != ''">#{destination},</if>
<if test="destinationCity != null and destinationCity != ''">#{destinationCity},</if>
<if test="startDate != null">#{startDate},</if>
<if test="endDate != null">#{endDate},</if>
<if test="duration != null">#{duration},</if>
<if test="budget != null">#{budget},</if>
<if test="invitation != null and invitation != ''">#{invitation},</if>
<if test="itinerary != null and itinerary != ''">#{itinerary},</if>
<if test="accommodationPlan != null and accommodationPlan != ''">#{accommodationPlan},</if>
<if test="transportationPlan != null and transportationPlan != ''">#{transportationPlan},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="currentApprover != null and currentApprover != ''">#{currentApprover},</if>
<if test="approvalLevel != null and approvalLevel != ''">#{approvalLevel},</if>
<if test="applyTime != null">#{applyTime},</if>
<if test="approvalTime != null">#{approvalTime},</if>
<if test="adminRemark != null and adminRemark != ''">#{adminRemark},</if>
<if test="applicantRemark != null and applicantRemark != ''">#{applicantRemark},</if>
<if test="emergencyContact != null">#{emergencyContact},</if>
<if test="signature != null and signature != ''">#{signature},</if>
<if test="stampedRegistrationFilePath != null and stampedRegistrationFilePath != ''">#{stampedRegistrationFilePath},</if>
<if test="stampedRegistrationFileName != null and stampedRegistrationFileName != ''">#{stampedRegistrationFileName},</if>
<if test="stampedFileUploadTime != null">#{stampedFileUploadTime},</if>
<if test="stampedFileUploadStatus != null and stampedFileUploadStatus != ''">#{stampedFileUploadStatus},</if>
<if test="tenantId != null and tenantId != ''">#{tenantId},</if>
<if test="deleted != null and deleted != ''">#{deleted},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
</trim>
</insert>
<!-- 更新出国申请 - 包含所有字段 -->
<update id="updateOverseasApplication" parameterType="RsOverseasApplication">
update rs_overseas_application
<trim prefix="SET" suffixOverrides=",">
<if test="serialNumber != null and serialNumber != ''">serial_number = #{serialNumber},</if>
<if test="applicantId != null">applicant_id = #{applicantId},</if>
<if test="applicantName != null and applicantName != ''">applicant_name = #{applicantName},</if>
<if test="gender != null and gender != ''">gender = #{gender},</if>
<if test="birthDate != null">birth_date = #{birthDate},</if>
<if test="photo != null">photo = #{photo},</if>
<if test="idNumber != null">id_number = #{idNumber},</if>
<if test="politicalStatus != null and politicalStatus != ''">political_status = #{politicalStatus},</if>
<if test="registeredAddress != null">registered_address = #{registeredAddress},</if>
<if test="department != null and department != ''">department = #{department},</if>
<if test="position != null">position = #{position},</if>
<if test="professionalTitle != null">professional_title = #{professionalTitle},</if>
<if test="administrativeLevel != null">administrative_level = #{administrativeLevel},</if>
<if test="personnelCategory != null">personnel_category = #{personnelCategory},</if>
<if test="registrationUnit != null">registration_unit = #{registrationUnit},</if>
<if test="unitLeaderSignature != null">unit_leader_signature = #{unitLeaderSignature},</if>
<if test="unitContact != null">unit_contact = #{unitContact},</if>
<if test="officePhone != null">office_phone = #{officePhone},</if>
<if test="mobilePhone != null">mobile_phone = #{mobilePhone},</if>
<if test="contactPhone != null">contact_phone = #{contactPhone},</if>
<if test="contactEmail != null">contact_email = #{contactEmail},</if>
<if test="documentName != null">document_name = #{documentName},</if>
<if test="documentNumber != null">document_number = #{documentNumber},</if>
<if test="destinationCountry != null">destination_country = #{destinationCountry},</if>
<if test="exitReason != null">exit_reason = #{exitReason},</if>
<if test="visitRelationship != null">visit_relationship = #{visitRelationship},</if>
<if test="remarks != null">remarks = #{remarks},</if>
<if test="purpose != null">purpose = #{purpose},</if>
<if test="purposeDetail != null">purpose_detail = #{purposeDetail},</if>
<if test="destination != null">destination = #{destination},</if>
<if test="destinationCity != null">destination_city = #{destinationCity},</if>
<if test="startDate != null">start_date = #{startDate},</if>
<if test="endDate != null">end_date = #{endDate},</if>
<if test="duration != null">duration = #{duration},</if>
<if test="budget != null">budget = #{budget},</if>
<if test="invitation != null">invitation = #{invitation},</if>
<if test="itinerary != null">itinerary = #{itinerary},</if>
<if test="accommodationPlan != null">accommodation_plan = #{accommodationPlan},</if>
<if test="transportationPlan != null">transportation_plan = #{transportationPlan},</if>
<if test="status != null and status != ''">status = #{status},</if>
<if test="currentApprover != null">current_approver = #{currentApprover},</if>
<if test="approvalLevel != null">approval_level = #{approvalLevel},</if>
<if test="applyTime != null">apply_time = #{applyTime},</if>
<if test="approvalTime != null">approval_time = #{approvalTime},</if>
<if test="adminRemark != null">admin_remark = #{adminRemark},</if>
<if test="applicantRemark != null">applicant_remark = #{applicantRemark},</if>
<if test="emergencyContact != null">emergency_contact = #{emergencyContact},</if>
<if test="signature != null">signature = #{signature},</if>
<if test="stampedRegistrationFilePath != null">stamped_registration_file_path = #{stampedRegistrationFilePath},</if>
<if test="stampedRegistrationFileName != null">stamped_registration_file_name = #{stampedRegistrationFileName},</if>
<if test="stampedFileUploadTime != null">stamped_file_upload_time = #{stampedFileUploadTime},</if>
<if test="stampedFileUploadStatus != null">stamped_file_upload_status = #{stampedFileUploadStatus},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
</trim>
where application_id = #{applicationId}
</update>
<!-- 删除出国申请(逻辑删除) -->
<update id="deleteOverseasApplicationById" parameterType="Long">
update rs_overseas_application set deleted = '1' where application_id = #{applicationId}
</update>
<!-- 批量删除出国申请(逻辑删除) -->
<update id="deleteOverseasApplicationByIds" parameterType="Long">
update rs_overseas_application set deleted = '1' where application_id in
<foreach item="applicationId" collection="array" open="(" separator="," close=")">
#{applicationId}
</foreach>
</update>
</mapper> </mapper>
@@ -40,6 +40,22 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap id="RsOverseasRecordVoResult" type="org.dromara.rs.domain.vo.RsOverseasRecordVo" extends="RsOverseasRecordResult"> <resultMap id="RsOverseasRecordVoResult" type="org.dromara.rs.domain.vo.RsOverseasRecordVo" extends="RsOverseasRecordResult">
<result property="dataSource" column="data_source"/> <result property="dataSource" column="data_source"/>
<!-- 来自申请表的扩展字段 -->
<result property="gender" column="gender"/>
<result property="genderText" column="gender_text"/>
<result property="birthDate" column="birth_date"/>
<result property="politicalStatus" column="political_status"/>
<result property="politicalStatusText" column="political_status_text"/>
<result property="professionalTitle" column="professional_title"/>
<result property="documentName" column="document_name"/>
<result property="documentNumber" column="document_number"/>
<result property="mobilePhone" column="mobile_phone"/>
<result property="destinationCountry" column="destination_country"/>
<result property="exitReason" column="exit_reason"/>
<result property="emergencyContact" column="emergency_contact"/>
<result property="remarks" column="remarks"/>
<result property="purposeText" column="purpose_text"/>
<result property="completionStatusText" column="completion_status_text"/>
</resultMap> </resultMap>
<sql id="selectOverseasRecordVo"> <sql id="selectOverseasRecordVo">
@@ -52,10 +68,74 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
'record' as data_source 'record' as data_source
from rs_overseas_record ors from rs_overseas_record ors
</sql> </sql>
<!-- 查询出国记录详情(关联申请表数据) -->
<sql id="selectOverseasRecordDetailVo">
select
ors.record_id, ors.application_id, ors.applicant_id, ors.applicant_name, ors.department,
ors.position, ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city,
ors.actual_start_date, ors.actual_end_date, ors.actual_duration, ors.actual_budget,
ors.invitation, ors.itinerary, ors.accommodation_plan, ors.transportation_plan,
ors.completion_status, ors.achievement, ors.report_content, ors.certificate_path,
ors.tenant_id, ors.create_dept, ors.create_by, ors.create_time, ors.update_by, ors.update_time, ors.deleted,
-- 来自申请表的扩展字段
oa.gender,
CASE oa.gender
WHEN '0' THEN '男'
WHEN '1' THEN '女'
ELSE oa.gender
END as gender_text,
oa.birth_date,
oa.political_status,
CASE oa.political_status
WHEN '1' THEN '中共党员'
WHEN '2' THEN '中共预备党员'
WHEN '3' THEN '共青团员'
WHEN '4' THEN '民革党员'
WHEN '5' THEN '民盟盟员'
WHEN '6' THEN '民建会员'
WHEN '7' THEN '民进会员'
WHEN '8' THEN '农工党党员'
WHEN '9' THEN '致公党党员'
WHEN '10' THEN '九三学社社员'
WHEN '11' THEN '台盟盟员'
WHEN '12' THEN '无党派人士'
WHEN '13' THEN '群众'
ELSE oa.political_status
END as political_status_text,
oa.professional_title,
oa.document_name,
oa.document_number,
oa.mobile_phone,
oa.destination_country,
oa.exit_reason,
oa.emergency_contact,
oa.remarks,
-- 出国目的文本转换
CASE ors.purpose
WHEN 'academic_exchange' THEN '学术交流'
WHEN 'conference' THEN '学术会议'
WHEN 'training' THEN '培训学习'
WHEN 'cooperation' THEN '合作项目'
WHEN 'research' THEN '科研访问'
WHEN 'other' THEN '其他'
ELSE ors.purpose
END as purpose_text,
-- 完成状态文本转换
CASE ors.completion_status
WHEN '0' THEN '未完成'
WHEN '1' THEN '进行中'
WHEN '2' THEN '已完成'
ELSE '未知状态'
END as completion_status_text,
'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'
</sql>
<!-- 联合查询:出国记录 + 已完成审批的申请 --> <!-- 联合查询:出国记录 + 已完成审批的申请 -->
<sql id="selectOverseasRecordWithApprovedApplicationsVo"> <sql id="selectOverseasRecordWithApprovedApplicationsVo">
-- 查询已创建的出国记录 -- 查询已创建的出国记录(关联申请表获取扩展字段)
select select
ors.record_id, ors.application_id, ors.applicant_id, ors.applicant_name, ors.department, 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, ors.contact_phone, ors.contact_email, ors.purpose, ors.purpose_detail, ors.destination, ors.destination_city,
@@ -64,8 +144,68 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
ors.invitation, ors.itinerary, ors.accommodation_plan, ors.transportation_plan, ors.invitation, ors.itinerary, ors.accommodation_plan, ors.transportation_plan,
ors.completion_status, ors.achievement, ors.report_content, ors.certificate_path, ors.completion_status, ors.achievement, ors.report_content, ors.certificate_path,
ors.tenant_id, ors.create_dept, ors.create_by, ors.create_time, ors.update_by, ors.update_time, ors.deleted, ors.tenant_id, ors.create_dept, ors.create_by, ors.create_time, ors.update_by, ors.update_time, ors.deleted,
-- 来自申请表的扩展字段
oa.gender,
CASE oa.gender
WHEN '0' THEN '男'
WHEN '1' THEN '女'
ELSE oa.gender
END as gender_text,
oa.birth_date,
oa.political_status,
CASE oa.political_status
WHEN '1' THEN '中共党员'
WHEN '2' THEN '中共预备党员'
WHEN '3' THEN '共青团员'
WHEN '4' THEN '民革党员'
WHEN '5' THEN '民盟盟员'
WHEN '6' THEN '民建会员'
WHEN '7' THEN '民进会员'
WHEN '8' THEN '农工党党员'
WHEN '9' THEN '致公党党员'
WHEN '10' THEN '九三学社社员'
WHEN '11' THEN '台盟盟员'
WHEN '12' THEN '无党派人士'
WHEN '13' THEN '群众'
ELSE oa.political_status
END as political_status_text,
oa.professional_title,
oa.document_name,
oa.document_number,
oa.mobile_phone,
oa.destination_country,
oa.exit_reason,
oa.emergency_contact,
oa.remarks,
-- 申请状态(来自申请表)
oa.status,
-- 申请状态文本转换
CASE oa.status
WHEN '0' THEN '待审核'
WHEN '1' THEN '已通过'
WHEN '2' THEN '已拒绝'
ELSE oa.status
END as status_text,
-- 出国目的文本转换
CASE ors.purpose
WHEN 'academic_exchange' THEN '学术交流'
WHEN 'conference_participation' THEN '参加会议'
WHEN 'collaborative_research' THEN '合作研究'
WHEN 'short_term_training' THEN '短期培训'
WHEN 'other' THEN '其他'
ELSE ors.purpose
END as purpose_text,
-- 完成状态文本转换
CASE ors.completion_status
WHEN '0' THEN '未完成'
WHEN '1' THEN '进行中'
WHEN '2' THEN '已完成'
WHEN '3' THEN '已取消'
ELSE ors.completion_status
END as completion_status_text,
'record' as data_source 'record' as data_source
from rs_overseas_record ors from rs_overseas_record ors
left join rs_overseas_application oa on ors.application_id = oa.application_id and oa.deleted = '0'
where ors.deleted = '0' where ors.deleted = '0'
UNION ALL UNION ALL
@@ -80,6 +220,59 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
oa.invitation, oa.itinerary, oa.accommodation_plan, oa.transportation_plan, oa.invitation, oa.itinerary, oa.accommodation_plan, oa.transportation_plan,
'1' as completion_status, NULL as achievement, NULL as report_content, NULL as certificate_path, '1' as completion_status, NULL as achievement, NULL as report_content, NULL as certificate_path,
oa.tenant_id, NULL as create_dept, oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted, oa.tenant_id, NULL as create_dept, oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted,
-- 来自申请表的字段(UNION ALL 第二部分直接从申请表获取)
oa.gender,
CASE oa.gender
WHEN '0' THEN '男'
WHEN '1' THEN '女'
ELSE oa.gender
END as gender_text,
oa.birth_date,
oa.political_status,
CASE oa.political_status
WHEN '1' THEN '中共党员'
WHEN '2' THEN '中共预备党员'
WHEN '3' THEN '共青团员'
WHEN '4' THEN '民革党员'
WHEN '5' THEN '民盟盟员'
WHEN '6' THEN '民建会员'
WHEN '7' THEN '民进会员'
WHEN '8' THEN '农工党党员'
WHEN '9' THEN '致公党党员'
WHEN '10' THEN '九三学社社员'
WHEN '11' THEN '台盟盟员'
WHEN '12' THEN '无党派人士'
WHEN '13' THEN '群众'
ELSE oa.political_status
END as political_status_text,
oa.professional_title,
oa.document_name,
oa.document_number,
oa.mobile_phone,
oa.destination_country,
oa.exit_reason,
oa.emergency_contact,
oa.remarks,
-- 申请状态(来自申请表)
oa.status,
-- 申请状态文本转换(已通过审批的申请)
CASE oa.status
WHEN '0' THEN '待审核'
WHEN '1' THEN '已通过'
WHEN '2' THEN '已拒绝'
ELSE oa.status
END as status_text,
-- 出国目的文本转换
CASE oa.purpose
WHEN 'academic_exchange' THEN '学术交流'
WHEN 'conference_participation' THEN '参加会议'
WHEN 'collaborative_research' THEN '合作研究'
WHEN 'short_term_training' THEN '短期培训'
WHEN 'other' THEN '其他'
ELSE oa.purpose
END as purpose_text,
-- 完成状态文本(已通过审批未创建记录的状态为"进行中")
'进行中' as completion_status_text,
'approved_application' as data_source 'approved_application' as data_source
from rs_overseas_application oa from rs_overseas_application oa
where oa.deleted = '0' where oa.deleted = '0'
@@ -140,7 +333,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<select id="selectOverseasRecordById" parameterType="Long" resultMap="RsOverseasRecordVoResult"> <select id="selectOverseasRecordById" parameterType="Long" resultMap="RsOverseasRecordVoResult">
<include refid="selectOverseasRecordVo"/> <include refid="selectOverseasRecordDetailVo"/>
where ors.record_id = #{recordId} and ors.deleted = 0 where ors.record_id = #{recordId} and ors.deleted = 0
</select> </select>
@@ -612,18 +612,6 @@
</select> </select>
</div> </div>
</div> </div>
<div class="el-col el-col-6" style="padding: 0 10px;">
<div class="el-form-item">
<label>薪资范围</label>
<select>
<option value="">全部</option>
<option value="5k-10k">5k-10k</option>
<option value="10k-15k">10k-15k</option>
<option value="15k-20k">15k-20k</option>
<option value="20k+">20k以上</option>
</select>
</div>
</div>
<div class="el-col el-col-6" style="padding: 0 10px;"> <div class="el-col el-col-6" style="padding: 0 10px;">
<div class="el-form-item"> <div class="el-form-item">
<label>工作地点</label> <label>工作地点</label>
@@ -813,7 +801,6 @@
</th> </th>
<th>职位名称</th> <th>职位名称</th>
<th>部门</th> <th>部门</th>
<th>薪资范围</th>
<th>工作地点</th> <th>工作地点</th>
<th>发布日期</th> <th>发布日期</th>
<th>状态</th> <th>状态</th>
@@ -826,7 +813,6 @@
<td><input type="checkbox" style="width: auto;"></td> <td><input type="checkbox" style="width: auto;"></td>
<td>高级Java开发工程师</td> <td>高级Java开发工程师</td>
<td>教学部门</td> <td>教学部门</td>
<td>15k-25k</td>
<td>杭州</td> <td>杭州</td>
<td>2025-07-01</td> <td>2025-07-01</td>
<td><span class="badge badge-active">招聘中</span></td> <td><span class="badge badge-active">招聘中</span></td>
@@ -911,11 +897,6 @@
</select> </select>
</div> </div>
<div class="form-group">
<label for="salary-range">薪资范围</label>
<input type="text" id="salary-range" placeholder="例如:15k-25k">
</div>
<div class="form-group"> <div class="form-group">
<label for="location">工作地点</label> <label for="location">工作地点</label>
<input type="text" id="location" placeholder="请输入工作地点"> <input type="text" id="location" placeholder="请输入工作地点">
+7
View File
@@ -143,6 +143,13 @@
## 🔗 API文档导航 ## 🔗 API文档导航
### 👨‍💼 人事管理模块
| API文档 | 描述 | 状态 | 更新时间 |
|---------|------|------|----------|
| [出国申请管理API](./出国申请管理API.md) | 出国申请审批流程管理(三级审批) | ✅ 完成 | 2025-01-28 |
| **[出国登记管理API](./出国登记管理API.md)** | **出国登记信息管理(已优化字段)** | **✅ 完成** | **2025-09-30** |
### 👥 学工管理模块 ### 👥 学工管理模块
| API文档 | 描述 | 状态 | 更新时间 | | API文档 | 描述 | 状态 | 更新时间 |
@@ -21,7 +21,6 @@
| job_id | bigint(20) | 是 | 职位ID,主键 | | job_id | bigint(20) | 是 | 职位ID,主键 |
| title | varchar(100) | 是 | 职位名称 | | title | varchar(100) | 是 | 职位名称 |
| department | varchar(50) | 是 | 部门 | | department | varchar(50) | 是 | 部门 |
| salary_range | varchar(50) | 是 | 薪资范围 |
| location | varchar(100) | 是 | 工作地点 | | location | varchar(100) | 是 | 工作地点 |
| description | text | 是 | 职位描述 | | description | text | 是 | 职位描述 |
| requirements | text | 是 | 任职要求 | | requirements | text | 是 | 任职要求 |
@@ -130,7 +129,6 @@
"jobId": 1, "jobId": 1,
"title": "软件工程师", "title": "软件工程师",
"department": "tech", "department": "tech",
"salaryRange": "15k-25k",
"location": "北京", "location": "北京",
"isActive": "0", "isActive": "0",
"publishTime": "2025-07-01 10:00:00", "publishTime": "2025-07-01 10:00:00",
@@ -181,7 +179,6 @@
"jobId": 1, "jobId": 1,
"title": "软件工程师", "title": "软件工程师",
"department": "tech", "department": "tech",
"salaryRange": "15k-25k",
"location": "北京", "location": "北京",
"description": "负责系统设计和开发...", "description": "负责系统设计和开发...",
"requirements": "本科及以上学历,3年以上相关经验...", "requirements": "本科及以上学历,3年以上相关经验...",
@@ -208,7 +205,7 @@
|-------|------|------|------| |-------|------|------|------|
| title | string | 是 | 职位名称 | | title | string | 是 | 职位名称 |
| department | string | 是 | 部门 | | department | string | 是 | 部门 |
| salaryRange | string | 是 | 薪资范围 |
| location | string | 是 | 工作地点 | | location | string | 是 | 工作地点 |
| description | string | 是 | 职位描述 | | description | string | 是 | 职位描述 |
| requirements | string | 是 | 任职要求 | | requirements | string | 是 | 任职要求 |
@@ -236,7 +233,6 @@
| jobId | long | 是 | 职位ID | | jobId | long | 是 | 职位ID |
| title | string | 是 | 职位名称 | | title | string | 是 | 职位名称 |
| department | string | 是 | 部门 | | department | string | 是 | 部门 |
| salaryRange | string | 是 | 薪资范围 |
| location | string | 是 | 工作地点 | | location | string | 是 | 工作地点 |
| description | string | 是 | 职位描述 | | description | string | 是 | 职位描述 |
| requirements | string | 是 | 任职要求 | | requirements | string | 是 | 任职要求 |
@@ -382,7 +378,7 @@ rs/
1. 进入职位管理页面 (`/rs/job`) 1. 进入职位管理页面 (`/rs/job`)
2. 点击"新增"按钮添加职位 2. 点击"新增"按钮添加职位
3. 填写职位信息,包括职位名称、部门、薪资范围等 3. 填写职位信息,包括职位名称、部门、工作地点等
4. 点击"确定"保存职位信息 4. 点击"确定"保存职位信息
### 申请管理页面 ### 申请管理页面
+87 -5
View File
@@ -639,11 +639,21 @@ referer: http://localhost
### 完成状态 (completionStatus) ### 完成状态 (completionStatus)
| 编码 | 含义 | 描述 | > **⚠️ 重要说明**:完成状态(completionStatus)与申请状态(status)是两个不同的概念!
| ---- | -------- | ------------------------ | > - **申请状态(status)**:表示审批流程的状态(待审核/已通过/已拒绝)
| 0 | 进行中 | 出国活动正在进行 | > - **完成状态(completionStatus)**:表示出国任务执行的状态(未完成/进行中/已完成/已取消)
| 1 | 已完成 | 出国活动已完成 |
| 2 | 提前结束 | 出国活动提前结束 | | 编码 | 含义 | 描述 | 使用场景 |
| ---- | -------- | ------------------------ | -------- |
| 0 | 未完成 | 出国活动尚未开始 | 已通过审批但尚未出发 |
| 1 | 进行中 | 出国活动正在进行 | 已出发但未返回(默认值) |
| 2 | 已完成 | 出国活动已完成 | 已返回并完成回执 |
| 3 | 已取消 | 出国活动取消 | 计划取消 |
**字段关系示例**:
- 出国记录中的申请若已通过审批(`status='1'`),默认完成状态为"进行中"(`completionStatus='1'`)
- 出国记录列表显示"完成状态"字段,详情弹窗显示"申请状态"字段
- 所有出国记录的申请状态都应该是"已通过"(`status='1'`),因为只有通过审批的申请才会出现在记录中
--- ---
@@ -714,6 +724,78 @@ referer: http://localhost
| v1.2 | 2025-01-09 | 新增修复记录接口,修复审批通过后记录创建问题 | weizzzz | | v1.2 | 2025-01-09 | 新增修复记录接口,修复审批通过后记录创建问题 | weizzzz |
| v1.3 | 2025-01-08 | 调整我的申请列表接口,显示完整申请历史(包括已通过申请) | weizzzz | | v1.3 | 2025-01-08 | 调整我的申请列表接口,显示完整申请历史(包括已通过申请) | weizzzz |
| v1.4 | 2025-01-08 | 修正我的申请列表接口,排除已通过申请(已通过申请显示在出国记录中) | weizzzz | | v1.4 | 2025-01-08 | 修正我的申请列表接口,排除已通过申请(已通过申请显示在出国记录中) | weizzzz |
| v1.5 | 2025-01-28 | 新增盖章文件上传功能,用户需上传盖章文件后才能提交申请 | weizzzz |
### 8. 上传登记备案表盖章文件
**接口地址**:`POST /rs/overseas/application/uploadStampedFile`
**权限要求**:`rs:overseas:add`
#### 功能说明
用于上传填写并加盖公章后的登记备案表文件。支持PDF、JPG、PNG格式,文件大小不超过10MB。上传成功后,用户才能提交出国申请。
#### 请求参数
**Content-Type**: `multipart/form-data`
| 参数名 | 类型 | 必填 | 说明 |
|---------------|--------|------|--------------------------------|
| file | File | 是 | 盖章文件,支持PDF、JPG、PNG格式 |
| applicationId | Long | 是 | 申请ID |
#### 请求示例
```bash
POST /rs/overseas/application/uploadStampedFile
Content-Type: multipart/form-data
Authorization: Bearer {token}
clientid: ruoyi-web
referer: http://localhost
form-data:
file: [选择的盖章文件]
applicationId: 1
```
#### 响应示例
**成功响应**:
```json
{
"code": 200,
"msg": "盖章文件上传成功",
"data": {
"filePath": "overseas/stamped/stamped_1_1704076800000.pdf",
"fileName": "出国境登记表_张三.pdf",
"uploadTime": "2025-01-28 14:30:00"
}
}
```
**失败响应**:
```json
{
"code": 500,
"msg": "不支持的文件格式,请上传PDF、JPG或PNG文件"
}
```
#### 业务规则
- 文件格式:只支持PDF、JPG、PNG格式
- 文件大小:不超过10MB
- 数量限制:每个申请只能上传一个盖章文件
- 上传成功后,系统自动更新申请记录的盖章文件信息
- 必须先上传盖章文件,才能提交出国申请
#### 数据库更新
上传成功后,系统会自动更新以下字段:
- `stamped_registration_file_path`: 文件存储路径
- `stamped_registration_file_name`: 原始文件名
- `stamped_file_upload_time`: 上传时间
- `stamped_file_upload_status`: 上传状态(设为'1')
--- ---
@@ -97,7 +97,7 @@ rs_job # 招聘岗位表
├── job_id # 岗位ID ├── job_id # 岗位ID
├── title # 岗位名称 ├── title # 岗位名称
├── department # 部门 ├── department # 部门
├── salary_range # 薪资范围
├── location # 工作地点 ├── location # 工作地点
├── description # 岗位描述 ├── description # 岗位描述
├── requirements # 任职要求 ├── requirements # 任职要求
@@ -58,7 +58,7 @@
- 出国申请流程:申请提交 → 部门审核 → 领导审批 → 出国执行 - 出国申请流程:申请提交 → 部门审核 → 领导审批 → 出国执行
#### 2. **详细字段需求** #### 2. **详细字段需求**
- **招聘职位**:职位名称、部门、岗位要求、薪资范围、工作地点等 - **招聘职位**:职位名称、部门、岗位要求、工作地点等
- **简历信息**:基本信息、教育背景、工作经历、技能证书、附件等 - **简历信息**:基本信息、教育背景、工作经历、技能证书、附件等
- **培训记录**:培训名称、培训时间、学时、培训类别、证书等 - **培训记录**:培训名称、培训时间、学时、培训类别、证书等
- **出国申请**:出国目的、目的地、时间、预算、行程安排等 - **出国申请**:出国目的、目的地、时间、预算、行程安排等
@@ -9,11 +9,11 @@ cursor
2. 核心功能: 2. 核心功能:
- 应聘者能浏览公开职位 - 应聘者能浏览公开职位
- HR能发布新职位(含薪资范围/要求等字段) - HR能发布新职位(含工作地点/要求等字段)
- 响应式布局(支持移动端) - 响应式布局(支持移动端)
3. 数据字段参考: 3. 数据字段参考:
- 职位:title, department, salary_range, location, description, requirements - 职位:title, department, location, description, requirements
- 简历:personal_info, education[], work_experience[], skills - 简历:personal_info, education[], work_experience[], skills
4. 角色权限: 4. 角色权限:
@@ -73,7 +73,7 @@ cursor
- /apply 简历表单(分教育/工作经历多步骤) - /apply 简历表单(分教育/工作经历多步骤)
2. 主要功能组件: 2. 主要功能组件:
- 职位搜索过滤器(部门/薪资范围) - 职位搜索过滤器(部门/工作地点)
- 简历PDF上传组件 - 简历PDF上传组件
- 申请状态跟踪面板 - 申请状态跟踪面板
+254 -11
View File
@@ -24,29 +24,57 @@ export interface OverseasApplicationQuery {
// 出国申请数据类型 // 出国申请数据类型
export interface OverseasApplicationVO { export interface OverseasApplicationVO {
applicationId: number | string; applicationId: number | string;
serialNumber?: string; // 编号
applicantId: number; applicantId: number;
applicantName: string; applicantName: string;
gender?: string; // 性别
birthDate?: string; // 出生日期
photo?: string; // 照片地址
idNumber?: string; // 身份证号
politicalStatus?: string; // 政治面貌
registeredAddress?: string; // 户籍所在地
department: string; department: string;
position?: string; // 职务
professionalTitle?: string; // 职称
administrativeLevel?: string; // 行政职级
personnelCategory?: string; // 人员类别代码
registrationUnit?: string; // 登记备案责任单位
unitLeaderSignature?: string; // 单位负责人签名
unitContact?: string; // 单位联系人
officePhone?: string; // 办公电话
mobilePhone?: string; // 移动电话
contactPhone: string; contactPhone: string;
documentName?: string; // 申领证件名称
documentNumber?: string; // 证件号码
destinationCountry?: string; // 前往国家
exitReason?: string; // 出境事由
visitRelationship?: string; // 探望对象姓名及与申请者关系
remarks?: string; // 备注
emergencyContact?: string; // 应急联系人及电话
signature?: string; // 手写签字
stampedRegistrationFilePath?: string; // 登记备案表盖章文件路径
stampedRegistrationFileName?: string; // 登记备案表盖章文件名
stampedFileUploadTime?: string; // 盖章文件上传时间
stampedFileUploadStatus?: string; // 盖章文件上传状态
purpose: string; purpose: string;
purposeDetail: string; purposeDetail: string;
destination: string; destination: string;
destinationCity: string; destinationCity?: string;
startDate: string; startDate: string;
endDate: string; endDate: string;
duration: number; duration: number;
budget: number; budget: number;
invitation: string; invitation?: string;
itinerary: string; itinerary: string;
accommodationPlan: string; accommodationPlan?: string;
transportationPlan: string; transportationPlan?: string;
status: string; status: string;
currentApprover: string; currentApprover?: string;
approvalLevel: string; approvalLevel: string;
applyTime: string; applyTime: string;
approvalTime: string; approvalTime?: string;
adminRemark: string; adminRemark?: string;
applicantRemark: string; applicantRemark?: string;
createTime: string; createTime: string;
updateTime: string; updateTime: string;
createBy: string; createBy: string;
@@ -55,15 +83,50 @@ export interface OverseasApplicationVO {
statusText: string; statusText: string;
purposeText: string; purposeText: string;
approvalLevelText: string; approvalLevelText: string;
genderText?: string;
politicalStatusText?: string;
administrativeLevelText?: string;
personnelCategoryText?: string;
} }
// 出国申请表单类型 // 出国申请表单类型
export interface OverseasApplicationForm { export interface OverseasApplicationForm {
applicationId?: number | string; applicationId?: number | string;
serialNumber?: string; // 编号
applicantId?: number; applicantId?: number;
applicantName?: string; applicantName?: string;
department?: string; // 自动匹配,前端不需要输入 gender?: string; // 性别
contactPhone?: string; // 联系电话 birthDate?: string; // 出生日期
photo?: string; // 照片地址
idNumber?: string; // 身份证号
politicalStatus?: string; // 政治面貌
registeredAddress?: string; // 户籍所在地
department?: string; // 所属部门/现工作部门
position?: string; // 职务
professionalTitle?: string; // 职称
administrativeLevel?: string; // 行政职级
personnelCategory?: string; // 人员类别代码
unitLeaderSignature?: string; // 单位负责人签名
unitContact?: string; // 单位联系人
officePhone?: string; // 办公电话
mobilePhone?: string; // 移动电话
contactPhone?: string; // 联系电话(保留兼容性)
documentName?: string; // 申领证件名称
documentNumber?: string; // 证件号码
destinationCountry?: string; // 前往国家
exitReason?: string; // 出境事由
visitRelationship?: string; // 探望对象姓名及与申请者关系
remarks?: string; // 备注
emergencyContact?: string; // 应急联系人及电话
signature?: string; // 手写签字(base64格式)
stampedRegistrationFilePath?: string; // 登记备案表盖章文件路径
stampedRegistrationFileName?: string; // 登记备案表盖章文件名
stampedFileUploadTime?: string; // 盖章文件上传时间
stampedFileUploadStatus?: string; // 盖章文件上传状态:0-未上传,1-已上传
partyRetentionFilePath?: string; // 党籍保留附件文件路径
partyRetentionFileName?: string; // 党籍保留附件文件名
partyRetentionUploadTime?: string; // 党籍保留附件上传时间
partyRetentionUploadStatus?: string; // 党籍保留附件上传状态:0-未上传,1-已上传
purpose: string; purpose: string;
purposeDetail: string; purposeDetail: string;
destination: string; destination: string;
@@ -111,7 +174,9 @@ export interface OverseasRecordVO {
applicantId: number; applicantId: number;
applicantName: string; applicantName: string;
department: string; department: string;
position: string;
contactPhone: string; contactPhone: string;
contactEmail: string;
purpose: string; purpose: string;
purposeDetail: string; purposeDetail: string;
destination: string; destination: string;
@@ -134,7 +199,23 @@ export interface OverseasRecordVO {
updateBy: string; updateBy: string;
// 显示字段 // 显示字段
purposeText: string; purposeText: string;
status?: string; // 申请状态(来自申请表)
statusText?: string; // 申请状态文本(来自申请表)
completionStatusText: string; completionStatusText: string;
// 来自申请表的扩展字段
gender?: string;
genderText?: string;
birthDate?: string;
politicalStatus?: string;
politicalStatusText?: string;
professionalTitle?: string;
documentName?: string;
documentNumber?: string;
mobilePhone?: string;
destinationCountry?: string;
exitReason?: string;
emergencyContact?: string;
remarks?: string;
} }
// 出国记录表单类型 // 出国记录表单类型
@@ -255,7 +336,7 @@ export function listOverseasApplicationByStatus(status: string) {
// 查询出国申请详细 // 查询出国申请详细
export function getOverseasApplication(applicationId: string | number) { export function getOverseasApplication(applicationId: string | number) {
return request({ return request({
url: `/rs/overseas/application/${applicationId}`, url: `/rs/overseas/application/detail/${applicationId}`,
method: 'get' method: 'get'
}); });
} }
@@ -295,6 +376,18 @@ export function resubmitOverseasApplication(applicationId: string | number, data
}); });
} }
// 校验出国申请前置条件
export function validateApplicationPrerequisites(politicalStatus?: string, duration?: number) {
return request({
url: '/rs/overseas/application/validatePrerequisites',
method: 'get',
params: {
politicalStatus,
duration
}
});
}
// 取消出国申请 // 取消出国申请
export function cancelOverseasApplication(applicationId: string | number, reason?: string) { export function cancelOverseasApplication(applicationId: string | number, reason?: string) {
return request({ return request({
@@ -520,3 +613,153 @@ export function fixApprovedOverseasRecords() {
method: 'post' method: 'post'
}); });
} }
// ==================== PDF生成相关接口 ====================
// 生成登记表请求参数类型
export interface GenerateRegistrationFormRequest {
applicationId: number;
documentNumber?: string;
format?: string;
}
// 登记表生成响应类型
export interface RegistrationFormResponse {
documentId: string;
fileName: string;
downloadUrl: string;
previewUrl: string;
fileSize: number;
generateTime: string;
documentNumber: string;
applicationId: number;
}
// 生成出国申请登记表PDF
export function generateRegistrationForm(data: GenerateRegistrationFormRequest) {
return request({
url: '/rs/overseas/application/generateRegistrationForm',
method: 'post',
data: data
});
}
// 下载生成的PDF文档
export function downloadRegistrationFormPdf(documentId: string) {
return request({
url: `/rs/overseas/application/downloadPdf/${documentId}`,
method: 'get',
responseType: 'blob'
});
}
// 预览生成的PDF文档
export function previewRegistrationFormPdf(documentId: string) {
return request({
url: `/rs/overseas/application/previewPdf/${documentId}`,
method: 'get',
responseType: 'blob'
});
}
// 下载登记备案附件模板
export function downloadRegistrationTemplate() {
return request({
url: '/rs/overseas/application/downloadRegistrationTemplate',
method: 'get',
responseType: 'blob'
});
}
// 下载保留党籍申请模板
export function downloadPartyMembershipTemplate() {
return request({
url: '/rs/overseas/application/downloadPartyMembershipTemplate',
method: 'get',
responseType: 'blob'
});
}
// ==================== 出国申请附件相关API ====================
// 附件数据类型
export interface OverseasAttachmentVO {
attachmentId: number | string;
applicationId: number;
attachmentTitle: string;
attachmentContent?: string;
fileName: string;
fileOriginalName: string;
filePath: string;
fileSize: number;
fileSizeFormatted?: string;
fileType?: string;
mimeType?: string;
uploadTime?: string;
uploadUserId?: number;
uploadUserName?: string;
createTime?: string;
updateTime?: string;
}
// 附件BO
export interface OverseasAttachmentBo {
attachmentId?: number | string;
applicationId: number;
attachmentTitle: string;
attachmentContent?: string;
}
// 查询出国申请的附件列表
export function listOverseasAttachments(applicationId: string | number) {
return request({
url: `/rs/overseas/attachment/list/${applicationId}`,
method: 'get'
});
}
// 查询附件详情
export function getOverseasAttachment(attachmentId: string | number) {
return request({
url: `/rs/overseas/attachment/${attachmentId}`,
method: 'get'
});
}
// 上传附件
export function uploadOverseasAttachment(data: FormData) {
return request({
url: '/rs/overseas/attachment/upload',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
},
data: data
});
}
// 更新附件信息(标题和内容)
export function updateOverseasAttachment(data: OverseasAttachmentBo) {
return request({
url: '/rs/overseas/attachment',
method: 'put',
data: data
});
}
// 删除附件
export function deleteOverseasAttachment(attachmentId: string | number) {
return request({
url: `/rs/overseas/attachment/${attachmentId}`,
method: 'delete'
});
}
// 批量删除附件
export function deleteOverseasAttachments(attachmentIds: Array<string | number>) {
return request({
url: '/rs/overseas/attachment/batch',
method: 'delete',
data: attachmentIds
});
}
-6
View File
@@ -75,7 +75,6 @@
<el-tag type="info">{{ getDepartmentLabel(scope.row.department) }}</el-tag> <el-tag type="info">{{ getDepartmentLabel(scope.row.department) }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="薪资范围" align="center" prop="salaryRange" />
<el-table-column label="工作地点" align="center" prop="location" /> <el-table-column label="工作地点" align="center" prop="location" />
<el-table-column label="状态" align="center" prop="isActive" width="120"> <el-table-column label="状态" align="center" prop="isActive" width="120">
<template #default="scope"> <template #default="scope">
@@ -316,9 +315,6 @@
<el-option v-for="dict in departmentOptions" :key="dict.value" :label="dict.label" :value="dict.value" /> <el-option v-for="dict in departmentOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="薪资范围" prop="salaryRange">
<el-input v-model="form.salaryRange" placeholder="请输入薪资范围,如:15k-25k" />
</el-form-item>
<el-form-item label="工作地点" prop="location"> <el-form-item label="工作地点" prop="location">
<el-input v-model="form.location" placeholder="请输入工作地点" /> <el-input v-model="form.location" placeholder="请输入工作地点" />
</el-form-item> </el-form-item>
@@ -376,7 +372,6 @@
<el-descriptions :column="2" border> <el-descriptions :column="2" border>
<el-descriptions-item label="职位名称">{{ viewForm.title }}</el-descriptions-item> <el-descriptions-item label="职位名称">{{ viewForm.title }}</el-descriptions-item>
<el-descriptions-item label="部门">{{ getDepartmentLabel(viewForm.department) }}</el-descriptions-item> <el-descriptions-item label="部门">{{ getDepartmentLabel(viewForm.department) }}</el-descriptions-item>
<el-descriptions-item label="薪资范围">{{ viewForm.salaryRange }}</el-descriptions-item>
<el-descriptions-item label="工作地点">{{ viewForm.location }}</el-descriptions-item> <el-descriptions-item label="工作地点">{{ viewForm.location }}</el-descriptions-item>
<el-descriptions-item label="状态"> <el-descriptions-item label="状态">
<el-tag :type="viewForm.isActive === '0' ? 'success' : 'info'"> <el-tag :type="viewForm.isActive === '0' ? 'success' : 'info'">
@@ -818,7 +813,6 @@ const rules = reactive<Record<string, any>>({
{ min: 2 as number, max: 50 as number, message: '长度在 2 到 50 个字符', trigger: 'blur' } { min: 2 as number, max: 50 as number, message: '长度在 2 到 50 个字符', trigger: 'blur' }
], ],
department: [{ required: true, message: '部门不能为空', trigger: 'change' }], department: [{ required: true, message: '部门不能为空', trigger: 'change' }],
salaryRange: [{ required: true, message: '薪资范围不能为空', trigger: 'blur' }],
location: [{ required: true, message: '工作地点不能为空', trigger: 'blur' }] location: [{ required: true, message: '工作地点不能为空', trigger: 'blur' }]
}); });
+55 -24
View File
@@ -70,20 +70,20 @@
<!-- 待审批申请表格 --> <!-- 待审批申请表格 -->
<el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleViewApplication" v-hasPermi="['rs:overseas:list']"> <el-table v-loading="pendingLoading" border :data="pendingList" @row-dblclick="handleViewApplication" v-hasPermi="['rs:overseas:list']">
<el-table-column label="申请人" align="center" prop="applicantName" width="100" :show-overflow-tooltip="true" /> <el-table-column label="姓名" align="center" prop="applicantName" min-width="100" :show-overflow-tooltip="true" />
<el-table-column label="所属部门" align="center" prop="department" width="140" :show-overflow-tooltip="true" /> <el-table-column label="现工作部门" align="center" prop="department" min-width="140" :show-overflow-tooltip="true" />
<el-table-column label="出国目的" align="center" prop="purposeText" width="100" :show-overflow-tooltip="true" /> <el-table-column label="职称" align="center" prop="professionalTitle" min-width="110" :show-overflow-tooltip="true" />
<el-table-column label="目的地" align="center" prop="destination" width="120" :show-overflow-tooltip="true" /> <el-table-column label="前往国家" align="center" prop="destination" min-width="110" :show-overflow-tooltip="true" />
<el-table-column label="出国时间" align="center" min-width="220" :show-overflow-tooltip="true"> <el-table-column label="出境事由" align="center" prop="exitReason" min-width="100" :show-overflow-tooltip="true" />
<template #default="scope"> {{ formatDate(scope.row.startDate) }} 至 {{ formatDate(scope.row.endDate) }} </template> <el-table-column label="出国天数" align="center" prop="duration" min-width="90">
<template #default="scope">{{ scope.row.duration || '-' }}天</template>
</el-table-column> </el-table-column>
<el-table-column label="天数" align="center" prop="duration" width="60" /> <el-table-column label="申请状态" align="center" prop="statusText" min-width="100">
<el-table-column label="申请时间" align="center" prop="applyTime" width="160" :show-overflow-tooltip="true" />
<el-table-column label="申请状态" align="center" width="100">
<template #default="scope"> <template #default="scope">
<el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag> <el-tag :type="getStatusTagType(scope.row.status)">{{ scope.row.statusText }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="申请时间" align="center" prop="applyTime" min-width="160" :show-overflow-tooltip="true" />
<el-table-column label="操作" align="center" width="180"> <el-table-column label="操作" align="center" width="180">
<template #default="scope"> <template #default="scope">
<el-tooltip content="查看详情" placement="top"> <el-tooltip content="查看详情" placement="top">
@@ -237,25 +237,56 @@
</el-card> </el-card>
<!-- 申请详情对话框 --> <!-- 申请详情对话框 -->
<el-dialog title="申请详情" v-model="viewDialog.visible" width="800px" append-to-body> <el-dialog title="出国申请详情" v-model="viewDialog.visible" width="900px" append-to-body>
<el-descriptions v-if="viewData" :column="2" border> <el-descriptions v-if="viewData" :column="2" border>
<el-descriptions-item label="申请人">{{ viewData.applicantName }}</el-descriptions-item> <!-- 基本信息 -->
<el-descriptions-item label="所属部门">{{ viewData.department }}</el-descriptions-item> <el-descriptions-item label="姓名">{{ viewData.applicantName || '-' }}</el-descriptions-item>
<el-descriptions-item label="联系电话">{{ viewData.contactPhone }}</el-descriptions-item> <el-descriptions-item label="性别">{{ viewData.gender === '0' ? '男' : viewData.gender === '1' ? '女' : '-' }}</el-descriptions-item>
<el-descriptions-item label="出国目的">{{ viewData.purposeText }}</el-descriptions-item> <el-descriptions-item label="出生年月">{{ formatDate(viewData.birthDate) || '-' }}</el-descriptions-item>
<el-descriptions-item label="目的地">{{ viewData.destination }}</el-descriptions-item> <el-descriptions-item label="政治面貌">{{ viewData.politicalStatusText || '-' }}</el-descriptions-item>
<el-descriptions-item label="开始日期">{{ formatDate(viewData.startDate) }}</el-descriptions-item>
<el-descriptions-item label="结束日期">{{ formatDate(viewData.endDate) }}</el-descriptions-item> <!-- 工作信息 -->
<el-descriptions-item label="出国天数">{{ viewData.duration }}天</el-descriptions-item> <el-descriptions-item label="现工作部门">{{ viewData.department || '-' }}</el-descriptions-item>
<el-descriptions-item label="预算金额">¥{{ formatMoney(viewData.budget) }}</el-descriptions-item> <el-descriptions-item label="职称">{{ viewData.professionalTitle || '-' }}</el-descriptions-item>
<el-descriptions-item label="职务">{{ viewData.position || '-' }}</el-descriptions-item>
<el-descriptions-item label="申领证件名称">{{ viewData.documentName || '-' }}</el-descriptions-item>
<!-- 证件信息 -->
<el-descriptions-item label="证件号码">{{ viewData.documentNumber || '-' }}</el-descriptions-item>
<el-descriptions-item label="前往国家">{{ viewData.destinationCountry || viewData.destination || '-' }}</el-descriptions-item>
<!-- 出境信息 -->
<el-descriptions-item label="出境事由">{{ viewData.exitReason || '-' }}</el-descriptions-item>
<!-- 探亲信息(如果是探亲) -->
<el-descriptions-item label="探望对象关系" v-if="viewData.exitReason === '探亲'">
{{ viewData.visitRelationship || '-' }}
</el-descriptions-item>
<!-- 行程安排 -->
<el-descriptions-item label="行程安排" :span="2">{{ viewData.itinerary || '-' }}</el-descriptions-item>
<el-descriptions-item label="出国天数">{{ viewData.duration || 0 }}天</el-descriptions-item>
<!-- 备注 -->
<el-descriptions-item label="备注" v-if="viewData.remarks">{{ viewData.remarks }}</el-descriptions-item>
<!-- 出国时间 -->
<el-descriptions-item label="出发日期">{{ formatDate(viewData.startDate) || '-' }}</el-descriptions-item>
<el-descriptions-item label="返回日期">{{ formatDate(viewData.endDate) || '-' }}</el-descriptions-item>
<!-- 联系信息 -->
<el-descriptions-item label="联系电话">{{ viewData.mobilePhone || viewData.contactPhone || '-' }}</el-descriptions-item>
<el-descriptions-item label="应急联系人及电话">{{ viewData.emergencyContact || '-' }}</el-descriptions-item>
<!-- 申请状态 -->
<el-descriptions-item label="申请状态"> <el-descriptions-item label="申请状态">
<el-tag :type="getStatusTagType(viewData.status)">{{ viewData.statusText }}</el-tag> <el-tag :type="getStatusTagType(viewData.status)">{{ viewData.statusText }}</el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="申请时间" :span="2">{{ viewData.applyTime }}</el-descriptions-item> <el-descriptions-item label="申请时间">{{ viewData.applyTime || '-' }}</el-descriptions-item>
<el-descriptions-item label="目的详细说明" :span="2">{{ viewData.purposeDetail }}</el-descriptions-item>
<el-descriptions-item label="行程安排" :span="2">{{ viewData.itinerary }}</el-descriptions-item> <!-- 管理员备注 -->
<el-descriptions-item label="申请人备注" :span="2" v-if="viewData.applicantRemark">{{ viewData.applicantRemark }}</el-descriptions-item> <el-descriptions-item label="管理员备注" :span="2" v-if="viewData.adminRemark">{{ viewData.adminRemark }}</el-descriptions-item>
</el-descriptions> </el-descriptions>
<!-- 查看审批记录 --> <!-- 查看审批记录 -->
File diff suppressed because it is too large. Load diff
+62 -18
View File
@@ -254,7 +254,7 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" width="200"> <el-table-column label="操作" align="center" width="150">
<template #default="scope"> <template #default="scope">
<el-tooltip content="查看" placement="top"> <el-tooltip content="查看" placement="top">
<el-button <el-button
@@ -265,15 +265,6 @@
v-hasPermi="['rs:trainingrecord:query']" v-hasPermi="['rs:trainingrecord:query']"
></el-button> ></el-button>
</el-tooltip> </el-tooltip>
<el-tooltip content="编辑" placement="top">
<el-button
link
type="primary"
icon="Edit"
@click.stop="handleUpdateRecord(scope.row)"
v-hasPermi="['rs:trainingrecord:edit']"
></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top"> <el-tooltip content="删除" placement="top">
<el-button <el-button
link link
@@ -631,14 +622,11 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="同行人" prop="companions"> <el-form-item label="同行人" prop="companions">
<el-select v-model="applicationForm.companions" multiple placeholder="请选择同行人员" style="width: 100%" clearable filterable> <TeacherMultiSelector
<el-option v-model="selectedCompanions"
v-for="teacher in teacherList" placeholder="请选择同行人员"
:key="teacher.teacherId" @change="handleCompanionsChange"
:label="teacher.teacherName" />
:value="teacher.teacherId"
/>
</el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@@ -1035,6 +1023,8 @@ import { getToken } from '@/utils/auth';
import { getInfo as getUserInfo } from '@/api/login'; import { getInfo as getUserInfo } from '@/api/login';
import { useUserStore } from '@/store/modules/user'; import { useUserStore } from '@/store/modules/user';
import { listClassTeacherCounselorRel } from '@/api/xg/classTeacherCounselorRel'; import { listClassTeacherCounselorRel } from '@/api/xg/classTeacherCounselorRel';
import TeacherMultiSelector from '@/components/TeacherMultiSelector/index.vue';
import { TeacherVO } from '@/api/system/teacher/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore(); const userStore = useUserStore();
@@ -1392,6 +1382,9 @@ const applicationForm = reactive<{
attachmentIds: [] as string[] // 附件ID列表 attachmentIds: [] as string[] // 附件ID列表
}); });
// 同行人选择器相关
const selectedCompanions = ref<TeacherVO[]>([]);
// 培训申请表单校验规则 // 培训申请表单校验规则
const applicationRules = reactive<any>({ const applicationRules = reactive<any>({
// 情况基本信息验证 // 情况基本信息验证
@@ -1863,9 +1856,57 @@ function resetApplicationForm() {
applicationForm.reason = ''; applicationForm.reason = '';
applicationForm.attachmentIds = []; applicationForm.attachmentIds = [];
applicationAttachmentFileList.value = []; applicationAttachmentFileList.value = [];
selectedCompanions.value = [];
applicationFormRef.value?.resetFields(); applicationFormRef.value?.resetFields();
} }
/** 处理同行人变化 */
function handleCompanionsChange(teachers: TeacherVO[]) {
selectedCompanions.value = teachers;
applicationForm.companions = teachers.map(teacher => teacher.userId);
}
/** 加载编辑时的同行人数据 */
async function loadCompanionsForEdit() {
if (applicationForm.companions && applicationForm.companions.length > 0) {
try {
// 这里需要根据userId获取教师信息
// 由于TeacherMultiSelector组件需要完整的TeacherVO对象
// 我们需要调用API获取教师详细信息
const teacherPromises = applicationForm.companions.map(async (userId) => {
try {
// 这里需要调用获取教师详情的API
// 暂时使用teacherList中的数据进行匹配
const teacher = teacherList.value.find(t => t.teacherId === userId);
if (teacher) {
return {
userId: teacher.teacherId,
nickName: teacher.teacherName,
userName: teacher.teacherName,
phonenumber: '',
email: '',
sex: '0',
deptName: ''
} as TeacherVO;
}
return null;
} catch (error) {
console.error('获取教师信息失败:', error);
return null;
}
});
const teachers = await Promise.all(teacherPromises);
selectedCompanions.value = teachers.filter(t => t !== null) as TeacherVO[];
} catch (error) {
console.error('加载同行人数据失败:', error);
selectedCompanions.value = [];
}
} else {
selectedCompanions.value = [];
}
}
/** 取消培训申请对话框 */ /** 取消培训申请对话框 */
function cancelApplicationDialog() { function cancelApplicationDialog() {
applicationDialogVisible.value = false; applicationDialogVisible.value = false;
@@ -2161,6 +2202,9 @@ function handleEditApplication(row: any) {
// 保存当前编辑的申请ID // 保存当前编辑的申请ID
applicationForm.applicationId = row.applicationId; applicationForm.applicationId = row.applicationId;
// 加载同行人数据到选择器
loadCompanionsForEdit();
} }
/** 撤回培训申请 */ /** 撤回培训申请 */