“编译修改”

This commit is contained in:
weizhong committed 2025-10-14 16:48:17 +08:00
1 parent e79bf5a26c
commit 2f2e303781
105 files changed
+26461

No files matched your search

@@ -0,0 +1,116 @@
package org.dromara.rs.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.domain.R;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.rs.domain.bo.RsOverseasApplicationAttachmentBo;
import org.dromara.rs.domain.vo.RsOverseasApplicationAttachmentVo;
import org.dromara.rs.service.IRsOverseasApplicationAttachmentService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.validation.constraints.NotNull;
import java.util.List;
/**
* 出国申请附件Controller
*
* @author weizzzz
* @date 2025-10-10
*/
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/rs/overseas/attachment")
public class RsOverseasApplicationAttachmentController extends BaseController {
private final IRsOverseasApplicationAttachmentService attachmentService;
/**
* 查询出国申请的附件列表
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/list/{applicationId}")
public R<List<RsOverseasApplicationAttachmentVo>> list(
@NotNull(message = "申请ID不能为空") @PathVariable Long applicationId) {
List<RsOverseasApplicationAttachmentVo> list = attachmentService.selectAttachmentList(applicationId);
return R.ok(list);
}
/**
* 查询附件详情
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/{attachmentId}")
public R<RsOverseasApplicationAttachmentVo> getInfo(
@NotNull(message = "附件ID不能为空") @PathVariable Long attachmentId) {
RsOverseasApplicationAttachmentVo vo = attachmentService.selectAttachmentById(attachmentId);
return R.ok(vo);
}
/**
* 上传附件
*
* @param applicationId 申请ID
* @param title 附件标题
* @param content 附件内容说明
* @param file 文件
*/
@SaCheckPermission("rs:overseas:edit")
@Log(title = "上传出国申请附件", businessType = BusinessType.INSERT)
@PostMapping("/upload")
public R<RsOverseasApplicationAttachmentVo> upload(
@NotNull(message = "申请ID不能为空") @RequestParam Long applicationId,
@RequestParam String title,
@RequestParam(required = false) String content,
@RequestParam("file") MultipartFile file) {
try {
RsOverseasApplicationAttachmentVo vo = attachmentService.uploadAttachment(
applicationId, title, content, file);
return R.ok("上传成功", vo);
} catch (Exception e) {
log.error("上传附件失败", e);
return R.fail("上传失败:" + e.getMessage());
}
}
/**
* 更新附件信息(标题和内容)
*/
@SaCheckPermission("rs:overseas:edit")
@Log(title = "更新出国申请附件", businessType = BusinessType.UPDATE)
@PutMapping
public R<Void> update(@Validated @RequestBody RsOverseasApplicationAttachmentBo bo) {
boolean success = attachmentService.updateAttachment(bo);
return toAjax(success);
}
/**
* 删除附件
*/
@SaCheckPermission("rs:overseas:remove")
@Log(title = "删除出国申请附件", businessType = BusinessType.DELETE)
@DeleteMapping("/{attachmentId}")
public R<Void> remove(@NotNull(message = "附件ID不能为空") @PathVariable Long attachmentId) {
boolean success = attachmentService.deleteAttachment(attachmentId);
return toAjax(success);
}
/**
* 批量删除附件
*/
@SaCheckPermission("rs:overseas:remove")
@Log(title = "批量删除出国申请附件", businessType = BusinessType.DELETE)
@DeleteMapping("/batch")
public R<Void> removeBatch(@RequestBody Long[] attachmentIds) {
boolean success = attachmentService.deleteAttachments(attachmentIds);
return toAjax(success);
}
}
@@ -0,0 +1,184 @@
package org.dromara.rs.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import jakarta.validation.constraints.NotEmpty;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.common.web.core.BaseController;
import org.dromara.rs.domain.bo.RsOverseasItineraryChangeBo;
import org.dromara.rs.domain.query.RsOverseasItineraryChangeQuery;
import org.dromara.rs.domain.vo.RsOverseasItineraryChangeVo;
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
import org.dromara.rs.service.IRsOverseasItineraryChangeService;
import org.dromara.rs.service.IRsOverseasRecordService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* 出国行程变更申请Controller
*
* @author weizzzz
* @date 2025-10-13
*/
@Validated
@RequiredArgsConstructor
@RestController
@RequestMapping("/rs/itinerary/change")
public class RsOverseasItineraryChangeController extends BaseController {
private final IRsOverseasItineraryChangeService changeService;
private final IRsOverseasRecordService recordService;
/**
* 查询行程变更申请列表
*/
@SaCheckPermission("rs:itineraryChange:list")
@GetMapping("/list")
public TableDataInfo<RsOverseasItineraryChangeVo> list(RsOverseasItineraryChangeQuery query) {
query.setTenantId(LoginHelper.getTenantId());
return changeService.queryPageList(query);
}
/**
* 查询我的变更记录列表(只显示已通过的变更申请)
* 说明:变更记录Tab只显示已通过审批的变更申请,这些是真正生效的变更记录
*/
@SaCheckPermission("rs:itineraryChange:list")
@GetMapping("/my/list")
public TableDataInfo<RsOverseasItineraryChangeVo> myList(RsOverseasItineraryChangeQuery query) {
query.setTenantId(LoginHelper.getTenantId());
return changeService.queryMyChangeList(query);
}
/**
* 查询待审批的行程变更申请列表
*/
@SaCheckPermission("rs:itineraryChange:approve")
@GetMapping("/pending/list")
public TableDataInfo<RsOverseasItineraryChangeVo> pendingList(RsOverseasItineraryChangeQuery query) {
query.setTenantId(LoginHelper.getTenantId());
return changeService.queryPendingList(query);
}
/**
* 查询行程变更申请详情
*/
@SaCheckPermission("rs:itineraryChange:query")
@GetMapping("/{changeId}")
public R<RsOverseasItineraryChangeVo> getInfo(@PathVariable Long changeId) {
return R.ok(changeService.queryById(changeId));
}
/**
* 获取可用的出国记录列表(用于选择原出国申请)
* 说明:选择的是出国记录选项卡中的记录
*/
@SaCheckPermission("rs:itineraryChange:list")
@GetMapping("/available-records")
public R<List<RsOverseasRecordVo>> getAvailableRecords() {
// 查询当前用户的所有出国记录
org.dromara.rs.domain.query.RsOverseasRecordQuery query = new org.dromara.rs.domain.query.RsOverseasRecordQuery();
query.setApplicantId(LoginHelper.getUserId());
query.setTenantId(LoginHelper.getTenantId());
List<RsOverseasRecordVo> records = recordService.queryList(query);
return R.ok(records);
}
/**
* 根据出国记录ID查询记录详情(用于显示原申请信息)
*/
@SaCheckPermission("rs:itineraryChange:query")
@GetMapping("/record/{recordId}")
public R<RsOverseasRecordVo> getRecordInfo(@PathVariable Long recordId) {
return R.ok(recordService.queryById(recordId));
}
/**
* 新增行程变更申请
*/
@SaCheckPermission("rs:itineraryChange:add")
@Log(title = "行程变更申请", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody RsOverseasItineraryChangeBo bo) {
bo.setTenantId(LoginHelper.getTenantId());
return toAjax(changeService.insertByBo(bo));
}
/**
* 修改行程变更申请(重新提交)
*/
@SaCheckPermission("rs:itineraryChange:edit")
@Log(title = "行程变更申请", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody RsOverseasItineraryChangeBo bo) {
return toAjax(changeService.updateByBo(bo));
}
/**
* 删除行程变更申请
*/
@SaCheckPermission("rs:itineraryChange:remove")
@Log(title = "行程变更申请", businessType = BusinessType.DELETE)
@DeleteMapping("/{changeIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空") @PathVariable Long[] changeIds) {
return toAjax(changeService.deleteByIds(List.of(changeIds)));
}
/**
* 撤回行程变更申请
*/
@SaCheckPermission("rs:itineraryChange:cancel")
@Log(title = "行程变更申请", businessType = BusinessType.UPDATE)
@PutMapping("/cancel/{changeId}")
public R<Void> cancel(@PathVariable Long changeId) {
return toAjax(changeService.cancelChange(changeId));
}
/**
* 审批行程变更申请
*/
@SaCheckPermission("rs:itineraryChange:approve")
@Log(title = "行程变更申请", businessType = BusinessType.UPDATE)
@PutMapping("/approve")
public R<Void> approve(@RequestBody Map<String, Object> params) {
Long changeId = Long.valueOf(params.get("changeId").toString());
Boolean approved = Boolean.valueOf(params.get("approved").toString());
String opinion = params.get("opinion") != null ? params.get("opinion").toString() : "";
return toAjax(changeService.approveChange(changeId, approved, opinion));
}
/**
* 导出行程变更申请列表
*/
@SaCheckPermission("rs:itineraryChange:export")
@Log(title = "行程变更申请", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(RsOverseasItineraryChangeQuery query, HttpServletResponse response) {
query.setTenantId(LoginHelper.getTenantId());
List<RsOverseasItineraryChangeVo> list = changeService.queryList(query);
ExcelUtil.exportExcel(list, "行程变更申请", RsOverseasItineraryChangeVo.class, response);
}
/**
* 查询某个出国记录的所有变更历史
*/
@SaCheckPermission("rs:itineraryChange:query")
@GetMapping("/record/{recordId}/history")
public R<List<RsOverseasItineraryChangeVo>> getChangeHistory(@PathVariable Long recordId) {
return R.ok(changeService.queryByRecordId(recordId));
}
}
@@ -0,0 +1,206 @@
package org.dromara.rs.controller;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.satoken.utils.LoginHelper;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.dromara.rs.domain.bo.RsOverseasRegistrationBo;
import org.dromara.rs.domain.vo.RsOverseasRegistrationVo;
import org.dromara.rs.domain.query.RsOverseasRegistrationQuery;
import org.dromara.rs.service.IRsOverseasRegistrationService;
/**
* 出国登记Controller
*
* @author weizzzz
* @date 2025-09-30
*/
@Validated
@RestController
@RequestMapping("/rs/overseas/registration")
public class RsOverseasRegistrationController extends BaseController {
@Autowired
private IRsOverseasRegistrationService registrationService;
/**
* 查询出国登记列表
*/
@SaCheckPermission("rs:overseas:registration:list")
@GetMapping("/list")
public TableDataInfo<RsOverseasRegistrationVo> list(RsOverseasRegistrationQuery query) {
return registrationService.queryPageList(query);
}
/**
* 查询我的出国登记列表
*/
@SaCheckPermission("rs:overseas:registration:list")
@GetMapping("/my/list")
public TableDataInfo<RsOverseasRegistrationVo> myList(RsOverseasRegistrationQuery query) {
return registrationService.queryMyRegistrationList(query);
}
/**
* 导出出国登记列表
*/
@SaCheckPermission("rs:overseas:registration:export")
@Log(title = "出国登记", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(RsOverseasRegistrationQuery query, HttpServletResponse response) {
List<RsOverseasRegistrationVo> list = registrationService.queryList(query);
ExcelUtil.exportExcel(list, "出国登记", RsOverseasRegistrationVo.class, response);
}
/**
* 获取出国登记详细信息
*/
@SaCheckPermission("rs:overseas:registration:query")
@GetMapping("/{registrationId}")
public R<RsOverseasRegistrationVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long registrationId) {
return R.ok(registrationService.queryById(registrationId));
}
/**
* 根据申请ID获取出国登记信息
*/
@SaCheckPermission("rs:overseas:registration:query")
@GetMapping("/application/{applicationId}")
public R<RsOverseasRegistrationVo> getByApplicationId(@NotNull(message = "申请ID不能为空")
@PathVariable Long applicationId) {
return R.ok(registrationService.queryByApplicationId(applicationId));
}
/**
* 新增出国登记
*/
@SaCheckPermission("rs:overseas:registration:add")
@Log(title = "出国登记", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody RsOverseasRegistrationBo bo) {
return toAjax(registrationService.insertByBo(bo));
}
/**
* 新增并提交出国登记
*/
@SaCheckPermission("rs:overseas:registration:add")
@Log(title = "出国登记", businessType = BusinessType.INSERT)
@PostMapping("/addAndSubmit")
public R<Void> addAndSubmit(@Validated(AddGroup.class) @RequestBody RsOverseasRegistrationBo bo) {
return toAjax(registrationService.addAndSubmit(bo));
}
/**
* 修改出国登记
*/
@SaCheckPermission("rs:overseas:registration:edit")
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody RsOverseasRegistrationBo bo) {
return toAjax(registrationService.updateByBo(bo));
}
/**
* 删除出国登记
*/
@SaCheckPermission("rs:overseas:registration:remove")
@Log(title = "出国登记", businessType = BusinessType.DELETE)
@DeleteMapping("/{registrationIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] registrationIds) {
return toAjax(registrationService.deleteWithValidByIds(List.of(registrationIds), true));
}
/**
* 提交出国登记
*/
@SaCheckPermission("rs:overseas:registration:submit")
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
@PutMapping("/submit/{registrationId}")
public R<Void> submit(@NotNull(message = "主键不能为空")
@PathVariable Long registrationId) {
return toAjax(registrationService.submitRegistration(registrationId));
}
/**
* 确认出国登记
*/
@SaCheckPermission("rs:overseas:registration:confirm")
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
@PutMapping("/confirm/{registrationId}")
public R<Void> confirm(@NotNull(message = "主键不能为空")
@PathVariable Long registrationId) {
return toAjax(registrationService.confirmRegistration(registrationId));
}
/**
* 撤回出国登记
*/
@SaCheckPermission("rs:overseas:registration:withdraw")
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
@PutMapping("/withdraw/{registrationId}")
public R<Void> withdraw(@NotNull(message = "主键不能为空")
@PathVariable Long registrationId) {
return toAjax(registrationService.withdrawRegistration(registrationId));
}
/**
* 基于出国申请创建登记记录
*/
@SaCheckPermission("rs:overseas:registration:create")
@Log(title = "出国登记", businessType = BusinessType.INSERT)
@PostMapping("/create/{applicationId}")
public R<Void> createFromApplication(@NotNull(message = "申请ID不能为空")
@PathVariable Long applicationId) {
return toAjax(registrationService.createFromApplication(applicationId));
}
/**
* 批准出国登记
*/
@SaCheckPermission("rs:registration:approve")
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
@PutMapping("/approve/{registrationId}")
public R<Void> approve(@NotNull(message = "主键不能为空")
@PathVariable Long registrationId,
@RequestBody(required = false) RsOverseasRegistrationBo bo) {
String approveNotes = (bo != null && bo.getRemarks() != null) ? bo.getRemarks() : "同意";
return toAjax(registrationService.approveRegistration(registrationId, approveNotes));
}
/**
* 拒绝出国登记
*/
@SaCheckPermission("rs:registration:reject")
@Log(title = "出国登记", businessType = BusinessType.UPDATE)
@PutMapping("/reject/{registrationId}")
public R<Void> reject(@NotNull(message = "主键不能为空")
@PathVariable Long registrationId,
@RequestBody(required = false) RsOverseasRegistrationBo bo) {
String rejectReason = (bo != null && bo.getRemarks() != null) ? bo.getRemarks() : "不同意";
return toAjax(registrationService.rejectRegistration(registrationId, rejectReason));
}
}
@@ -0,0 +1,234 @@
package org.dromara.rs.controller;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.dromara.rs.domain.bo.RsPartyMembershipRetentionBo;
import org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo;
import org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery;
import org.dromara.rs.service.IRsPartyMembershipRetentionService;
/**
* 出国党籍保留Controller
*
* @author weizzzz
* @date 2025-10-05
*/
@Validated
@RestController
@RequestMapping("/rs/party/retention")
public class RsPartyMembershipRetentionController extends BaseController {
@Autowired
private IRsPartyMembershipRetentionService retentionService;
/**
* 查询出国党籍保留列表
*/
@SaCheckPermission("rs:partyretention:list")
@GetMapping("/list")
public TableDataInfo<RsPartyMembershipRetentionVo> list(RsPartyMembershipRetentionQuery query, PageQuery pageQuery) {
return retentionService.queryPageList(query, pageQuery);
}
/**
* 导出出国党籍保留列表
*/
@SaCheckPermission("rs:partyretention:export")
@Log(title = "出国党籍保留", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(RsPartyMembershipRetentionQuery query, HttpServletResponse response) {
List<RsPartyMembershipRetentionVo> list = retentionService.queryList(query);
ExcelUtil.exportExcel(list, "出国党籍保留", RsPartyMembershipRetentionVo.class, response);
}
/**
* 获取出国党籍保留详细信息
*/
@SaCheckPermission("rs:partyretention:query")
@GetMapping("/{retentionId}")
public R<RsPartyMembershipRetentionVo> getInfo(@NotNull(message = "主键不能为空")
@PathVariable Long retentionId) {
return R.ok(retentionService.queryById(retentionId));
}
/**
* 根据登记ID获取党籍保留信息
*/
@SaCheckPermission("rs:partyretention:query")
@GetMapping("/registration/{registrationId}")
public R<RsPartyMembershipRetentionVo> getByRegistrationId(@NotNull(message = "登记ID不能为空")
@PathVariable Long registrationId) {
return R.ok(retentionService.queryByRegistrationId(registrationId));
}
/**
* 根据申请ID获取党籍保留信息
*/
@SaCheckPermission("rs:partyretention:query")
@GetMapping("/application/{applicationId}")
public R<RsPartyMembershipRetentionVo> getByApplicationId(@NotNull(message = "申请ID不能为空")
@PathVariable Long applicationId) {
return R.ok(retentionService.queryByApplicationId(applicationId));
}
/**
* 新增出国党籍保留
*/
@SaCheckPermission("rs:partyretention:add")
@Log(title = "出国党籍保留", businessType = BusinessType.INSERT)
@PostMapping()
public R<Void> add(@Validated(AddGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) {
return toAjax(retentionService.insertByBo(bo));
}
/**
* 保存并提交出国党籍保留
*/
@SaCheckPermission("rs:partyretention:add")
@Log(title = "出国党籍保留", businessType = BusinessType.INSERT)
@PostMapping("/saveAndSubmit")
public R<Void> saveAndSubmit(@Validated(AddGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) {
return toAjax(retentionService.saveAndSubmit(bo));
}
/**
* 修改出国党籍保留
*/
@SaCheckPermission("rs:partyretention:edit")
@Log(title = "出国党籍保留", businessType = BusinessType.UPDATE)
@PutMapping()
public R<Void> edit(@Validated(EditGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) {
return toAjax(retentionService.updateByBo(bo));
}
/**
* 删除出国党籍保留
*/
@SaCheckPermission("rs:partyretention:remove")
@Log(title = "出国党籍保留", businessType = BusinessType.DELETE)
@DeleteMapping("/{retentionIds}")
public R<Void> remove(@NotEmpty(message = "主键不能为空")
@PathVariable Long[] retentionIds) {
return toAjax(retentionService.deleteWithValidByIds(List.of(retentionIds), true));
}
/**
* 提交出国党籍保留
*/
@SaCheckPermission("rs:partyretention:submit")
@Log(title = "出国党籍保留", businessType = BusinessType.UPDATE)
@PutMapping("/submit/{retentionId}")
public R<Void> submit(@NotNull(message = "主键不能为空")
@PathVariable Long retentionId) {
return toAjax(retentionService.submit(retentionId));
}
/**
* 撤回出国党籍保留
*/
@SaCheckPermission("rs:partyretention:withdraw")
@Log(title = "出国党籍保留", businessType = BusinessType.UPDATE)
@PutMapping("/withdraw/{retentionId}")
public R<Void> withdraw(@NotNull(message = "主键不能为空")
@PathVariable Long retentionId) {
return toAjax(retentionService.withdraw(retentionId));
}
/**
* 审批出国党籍保留
*/
@SaCheckPermission("rs:partyretention:approve")
@Log(title = "出国党籍保留", businessType = BusinessType.UPDATE)
@PutMapping("/approve/{retentionId}")
public R<Void> approve(@NotNull(message = "主键不能为空") @PathVariable Long retentionId,
@RequestParam String approvalOpinion,
@RequestParam Boolean approved) {
return toAjax(retentionService.approve(retentionId, approvalOpinion, approved));
}
// ==================== 保留历史相关接口 ====================
/**
* 查询党籍保留历史列表(只查询已通过的申请)
*/
@SaCheckPermission("rs:partyretention:list")
@GetMapping("/history/list")
public TableDataInfo<RsPartyMembershipRetentionVo> historyList(RsPartyMembershipRetentionQuery query, PageQuery pageQuery) {
// 强制设置status=2,只查询已通过的记录
query.setStatus("2");
return retentionService.queryPageList(query, pageQuery);
}
/**
* 查询党籍保留历史详细信息
*/
@SaCheckPermission("rs:partyretention:query")
@GetMapping("/history/{retentionId}")
public R<RsPartyMembershipRetentionVo> getHistoryInfo(@NotNull(message = "主键不能为空")
@PathVariable Long retentionId) {
RsPartyMembershipRetentionVo vo = retentionService.queryById(retentionId);
if (vo == null) {
return R.fail("记录不存在");
}
// 验证是否是已通过状态
if (!"2".equals(vo.getStatus())) {
return R.fail("该记录不是已通过状态");
}
return R.ok(vo);
}
/**
* 修改党籍保留历史(用于更新完成状态等)
*/
@SaCheckPermission("rs:partyretention:edit")
@Log(title = "党籍保留历史", businessType = BusinessType.UPDATE)
@PutMapping("/history")
public R<Void> updateHistory(@Validated(EditGroup.class) @RequestBody RsPartyMembershipRetentionBo bo) {
// 验证是否是已通过状态
RsPartyMembershipRetentionVo vo = retentionService.queryById(bo.getRetentionId());
if (vo == null) {
return R.fail("记录不存在");
}
if (!"2".equals(vo.getStatus())) {
return R.fail("只能修改已通过状态的记录");
}
return toAjax(retentionService.updateByBo(bo));
}
/**
* 导出党籍保留历史
*/
@SaCheckPermission("rs:partyretention:export")
@Log(title = "党籍保留历史", businessType = BusinessType.EXPORT)
@PostMapping("/history/export")
public void exportHistory(RsPartyMembershipRetentionQuery query, HttpServletResponse response) {
// 强制设置status=2,只导出已通过的记录
query.setStatus("2");
List<RsPartyMembershipRetentionVo> list = retentionService.queryList(query);
ExcelUtil.exportExcel(list, "党籍保留历史", RsPartyMembershipRetentionVo.class, response);
}
}
@@ -0,0 +1,49 @@
package org.dromara.rs.domain;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 文档信息存储
*
* @author weizzzz
* @date 2025-01-03
*/
@Data
public class DocumentInfo {
/**
* 文档ID
*/
private String documentId;
/**
* 文件路径
*/
private String filePath;
/**
* 文件名
*/
private String fileName;
/**
* 申请ID
*/
private Long applicationId;
/**
* 文档编号
*/
private String documentNumber;
/**
* 文件大小
*/
private Long fileSize;
/**
* 创建时间
*/
private LocalDateTime createTime;
}
@@ -0,0 +1,93 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 出国申请附件对象 rs_overseas_application_attachment
*
* @author weizzzz
* @date 2025-10-10
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_overseas_application_attachment")
public class RsOverseasApplicationAttachment extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 附件ID(主键)
*/
@TableId(value = "attachment_id", type = IdType.ASSIGN_ID)
private Long attachmentId;
/**
* 出国申请ID
*/
private Long applicationId;
/**
* 附件标题
*/
private String attachmentTitle;
/**
* 附件内容说明
*/
private String attachmentContent;
/**
* 文件名称
*/
private String fileName;
/**
* 文件原始名称
*/
private String fileOriginalName;
/**
* 文件存储路径/OSS URL
*/
private String filePath;
/**
* 文件大小(字节)
*/
private Long fileSize;
/**
* 文件类型(如pdf、jpg、png、doc等)
*/
private String fileType;
/**
* MIME类型
*/
private String mimeType;
/**
* 上传时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date uploadTime;
/**
* 上传人ID
*/
private Long uploadUserId;
/**
* 上传人姓名
*/
private String uploadUserName;
}
@@ -0,0 +1,230 @@
package org.dromara.rs.domain;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* 出国行程变更对象 rs_overseas_itinerary_change
*
* @author zlwu
* @date 2025-01-13
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_overseas_itinerary_change")
public class RsOverseasItineraryChange extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 变更ID(主键,雪花ID)
*/
@TableId(value = "change_id")
private Long changeId;
/**
* 关联备案记录ID(外键,关联rs_overseas_entry_exit_record.record_id)
*/
private Long recordId;
/**
* 关联出国申请ID(外键,关联rs_overseas_application.application_id,可为空)
*/
private Long applicationId;
/**
* 申请人ID(外键,关联sys_user.user_id)
*/
private Long applicantId;
/**
* 申请人姓名
*/
private String applicantName;
/**
* 性别:0-男,1-女
*/
private String gender;
/**
* 出生日期
*/
private Date birthDate;
/**
* 政治面貌
*/
private String politicalStatus;
/**
* 职称
*/
private String professionalTitle;
/**
* 职务
*/
private String position;
/**
* 申领证件名称
*/
private String documentName;
/**
* 证件号码
*/
private String documentNumber;
/**
* 部门
*/
private String department;
/**
* 原目的地国家
*/
private String originalDestination;
/**
* 原目的地城市
*/
private String originalDestinationCity;
/**
* 原出境事由
*/
private String originalExitReason;
/**
* 原计划开始日期
*/
private Date originalStartDate;
/**
* 原计划结束日期
*/
private Date originalEndDate;
/**
* 原计划天数
*/
private Integer originalDuration;
/**
* 原行程安排(详细)
*/
private String originalItinerary;
/**
* 原住宿计划
*/
private String originalAccommodationPlan;
/**
* 原交通计划
*/
private String originalTransportationPlan;
/**
* 变更类型(字典:rs_itinerary_change_type)
*/
private String changeType;
/**
* 新目的地国家
*/
private String newDestination;
/**
* 新目的地城市
*/
private String newDestinationCity;
/**
* 新出境事由
*/
private String newExitReason;
/**
* 新计划开始日期
*/
private Date newStartDate;
/**
* 新计划结束日期
*/
private Date newEndDate;
/**
* 新计划天数
*/
private Integer newDuration;
/**
* 新行程安排(详细)
*/
private String newItinerary;
/**
* 新住宿计划
*/
private String newAccommodationPlan;
/**
* 新交通计划
*/
private String newTransportationPlan;
/**
* 变更原因(必填)
*/
private String changeReason;
/**
* 备注
*/
private String remark;
/**
* 审批状态(字典:rs_itinerary_change_status,0:待审核 1:已通过 2:已拒绝 3:已撤回)
*/
private String status;
/**
* 审批意见
*/
private String approvalOpinion;
/**
* 审批时间
*/
private Date approvalTime;
/**
* 审批人ID
*/
private Long approverId;
/**
* 审批人姓名
*/
private String approverName;
/**
* 租户编号
*/
private String tenantId;
/**
* 删除标志(0代表存在 1代表删除)
*/
@TableLogic
private Integer deleted;
}
@@ -0,0 +1,212 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import cn.idev.excel.annotation.ExcelProperty;
import cn.idev.excel.annotation.ExcelIgnore;
import java.util.Date;
/**
* 出国登记对象 rs_overseas_registration
*
* @author weizzzz
* @date 2025-09-30
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_overseas_registration")
public class RsOverseasRegistration extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 登记ID
*/
@TableId(value = "registration_id", type = IdType.AUTO)
@ExcelProperty(value = "登记ID")
private Long registrationId;
/**
* 关联申请ID
*/
@ExcelProperty(value = "关联申请ID")
private Long applicationId;
/**
* 姓名
*/
@ExcelProperty(value = "姓名")
private String name;
/**
* 性别:0-男,1-女
*/
@ExcelProperty(value = "性别")
private String gender;
/**
* 出生日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@ExcelProperty(value = "出生日期")
private Date birthDate;
/**
* 政治面貌
*/
@ExcelProperty(value = "政治面貌")
private String politicalStatus;
/**
* 身份证号(数据库字段:id_number)
*/
@TableField("id_number")
@ExcelProperty(value = "身份证号")
private String idNumber;
/**
* 户籍地址(数据库字段:registered_address)
*/
@TableField("registered_address")
@ExcelProperty(value = "户籍地址")
private String registeredAddress;
/**
* 单位部门(数据库字段:department)
*/
@ExcelProperty(value = "单位部门")
private String department;
/**
* 职务(数据库字段:position)
*/
@ExcelProperty(value = "职务")
private String position;
/**
* 行政级别(数据库字段:administrative_level)
*/
@TableField("administrative_level")
@ExcelProperty(value = "行政级别")
private String administrativeLevel;
/**
* 人员类别(数据库字段:personnel_category)
*/
@TableField("personnel_category")
@ExcelProperty(value = "人员类别")
private String personnelCategory;
// ========== 以下字段仅用于前端展示,数据库表中不存在 ==========
/**
* 工作单位及职务(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "工作单位及职务")
private String workUnitPosition;
/**
* 配偶姓名(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "配偶姓名")
private String spouse;
/**
* 联系电话(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "联系电话")
private String contactPhone;
/**
* 出国日期(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@JsonFormat(pattern = "yyyy-MM-dd")
@ExcelProperty(value = "出国日期")
private Date departureDate;
/**
* 预定返回日期(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@JsonFormat(pattern = "yyyy-MM-dd")
@ExcelProperty(value = "预定返回日期")
private Date returnDate;
/**
* 停留天数(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "停留天数")
private Integer stayDays;
/**
* 前往国家(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "前往国家")
private String destinationCountry;
/**
* 前往国家详细信息(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "前往国家详细信息")
private String destinationDetail;
/**
* 事由(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "事由")
private String reason;
/**
* 登记单位(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "登记单位")
private String registrationUnit;
/**
* 出国类型:1-因公出国,2-因私出国(前端字段,数据库无此字段)
*/
@TableField(exist = false)
@ExcelProperty(value = "出国类型")
private String travelType;
/**
* 状态:0-草稿,1-待审核,2-已通过,3-已拒绝
*/
@ExcelProperty(value = "状态")
private String status;
/**
* 提交时间(数据库字段:registration_time)
*/
@TableField("registration_time")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ExcelProperty(value = "提交时间")
private Date submitTime;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remarks;
// 注意:createBy、createTime、updateBy、updateTime 等基础字段已在父类 BaseEntity 中定义
// 父类中的字段带有 @TableField(fill = FieldFill.INSERT) 或 @TableField(fill = FieldFill.INSERT_UPDATE) 注解
// 可以自动填充创建者、创建时间、更新者、更新时间等信息
// 请勿在子类中重复定义这些字段,否则会覆盖父类的自动填充功能!
}
@@ -0,0 +1,189 @@
package org.dromara.rs.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import cn.idev.excel.annotation.ExcelProperty;
import java.util.Date;
/**
* 出国党籍保留对象 rs_party_membership_retention
*
* @author weizzzz
* @date 2025-10-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("rs_party_membership_retention")
public class RsPartyMembershipRetention extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 党籍保留ID(主键)
*/
@TableId(value = "retention_id", type = IdType.AUTO)
@ExcelProperty(value = "党籍保留ID")
private Long retentionId;
/**
* 关联出国申请ID
*/
@TableField("application_id")
@ExcelProperty(value = "关联出国申请ID")
private Long applicationId;
/**
* 关联出国登记ID
*/
@TableField("registration_id")
@ExcelProperty(value = "关联出国登记ID")
private Long registrationId;
/**
* 我支部同志(姓名)
*/
@ExcelProperty(value = "姓名")
private String name;
/**
* 身份证号(18位)
*/
@TableField("id_card")
@ExcelProperty(value = "身份证号")
private String idCard;
/**
* 现任职务
*/
@ExcelProperty(value = "职务")
private String position;
/**
* 偕配偶姓名
*/
@ExcelProperty(value = "配偶姓名")
private String spouse;
/**
* 亲属姓名(探望对象)
*/
@TableField("relative_name")
@ExcelProperty(value = "亲属姓名")
private String relativeName;
/**
* 拟出国开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@TableField("travel_start_date")
@ExcelProperty(value = "出国开始日期")
private Date travelStartDate;
/**
* 拟出国结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@TableField("travel_end_date")
@ExcelProperty(value = "出国结束日期")
private Date travelEndDate;
/**
* 在外停留天数
*/
@TableField("stay_days")
@ExcelProperty(value = "停留天数")
private Integer stayDays;
/**
* 因私赴XX(国家或地区)
*/
@ExcelProperty(value = "目的地")
private String destination;
/**
* 出国目的详细说明
*/
@ExcelProperty(value = "出国目的")
private String purpose;
/**
* 出国原因(简要)
*/
@TableField("travel_reason")
@ExcelProperty(value = "出国原因")
private String travelReason;
/**
* 费用自理:1-是,0-否
*/
@TableField("self_funded")
@ExcelProperty(value = "费用自理")
private String selfFunded;
/**
* 请求审批意见
*/
@TableField("request_approval")
@ExcelProperty(value = "请求审批意见")
private String requestApproval;
/**
* 最终审批意见
*/
@TableField("final_approval")
@ExcelProperty(value = "最终审批意见")
private String finalApproval;
/**
* 党支部名称
*/
@TableField("party_branch")
@ExcelProperty(value = "党支部")
private String partyBranch;
/**
* 申请日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
@TableField("application_date")
@ExcelProperty(value = "申请日期")
private Date applicationDate;
/**
* 状态:0-草稿,1-待审核,2-已通过,3-已拒绝,4-已撤回
*/
@ExcelProperty(value = "状态")
private String status;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@TableField("application_time")
@ExcelProperty(value = "申请时间")
private Date applicationTime;
/**
* 备注
*/
@ExcelProperty(value = "备注")
private String remarks;
/**
* 租户编号
*/
@TableField("tenant_id")
private String tenantId;
/**
* 删除标志:0-未删除,1-已删除
*/
private String deleted;
}
@@ -0,0 +1,30 @@
package org.dromara.rs.domain.bo;
import lombok.Data;
import jakarta.validation.constraints.NotNull;
/**
* 生成登记表请求BO
*
* @author weizzzz
* @date 2025-01-03
*/
@Data
public class GenerateRegistrationFormBo {
/**
* 申请ID
*/
@NotNull(message = "申请ID不能为空")
private Long applicationId;
/**
* 文档编号(可选,不传则自动生成)
*/
private String documentNumber;
/**
* 文档格式(固定为PDF)
*/
private String format = "pdf";
}
@@ -0,0 +1,85 @@
package org.dromara.rs.domain.bo;
import lombok.Data;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.util.Date;
/**
* 出国申请附件BO
*
* @author weizzzz
* @date 2025-10-10
*/
@Data
public class RsOverseasApplicationAttachmentBo extends BaseEntity {
/**
* 附件ID
*/
private Long attachmentId;
/**
* 出国申请ID
*/
@NotNull(message = "申请ID不能为空")
private Long applicationId;
/**
* 附件标题
*/
@NotBlank(message = "附件标题不能为空")
private String attachmentTitle;
/**
* 附件内容说明
*/
private String attachmentContent;
/**
* 文件名称
*/
private String fileName;
/**
* 文件原始名称
*/
private String fileOriginalName;
/**
* 文件存储路径/OSS URL
*/
private String filePath;
/**
* 文件大小(字节)
*/
private Long fileSize;
/**
* 文件类型
*/
private String fileType;
/**
* MIME类型
*/
private String mimeType;
/**
* 上传时间
*/
private Date uploadTime;
/**
* 上传人ID
*/
private Long uploadUserId;
/**
* 上传人姓名
*/
private String uploadUserName;
}
@@ -0,0 +1,238 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsOverseasItineraryChange;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import lombok.EqualsAndHashCode;
import jakarta.validation.constraints.*;
import java.util.Date;
/**
* 出国行程变更业务对象 rs_overseas_itinerary_change
*
* @author zlwu
* @date 2025-01-13
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = RsOverseasItineraryChange.class, reverseConvertGenerate = false)
public class RsOverseasItineraryChangeBo extends BaseEntity {
/**
* 变更ID(主键,雪花ID)
*/
@NotNull(message = "变更ID不能为空", groups = { EditGroup.class })
private Long changeId;
/**
* 关联备案记录ID(外键,关联rs_overseas_entry_exit_record.record_id)
* 注:该字段已废弃,现在使用applicationId
*/
private Long recordId;
/**
* 关联出国申请ID(外键,关联rs_overseas_application.application_id)
*/
@NotNull(message = "关联出国申请ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicationId;
/**
* 申请人ID(外键,关联sys_user.user_id)
*/
@NotNull(message = "申请人ID不能为空", groups = { AddGroup.class, EditGroup.class })
private Long applicantId;
/**
* 申请人姓名
*/
@NotBlank(message = "申请人姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String applicantName;
/**
* 性别:0-男,1-女
*/
private String gender;
/**
* 出生日期
*/
private Date birthDate;
/**
* 政治面貌
*/
private String politicalStatus;
/**
* 职称
*/
private String professionalTitle;
/**
* 职务
*/
private String position;
/**
* 申领证件名称
*/
private String documentName;
/**
* 证件号码
*/
private String documentNumber;
/**
* 部门
*/
private String department;
/**
* 原目的地国家
*/
private String originalDestination;
/**
* 原目的地城市
*/
private String originalDestinationCity;
/**
* 原出境事由
*/
private String originalExitReason;
/**
* 原计划开始日期
*/
private Date originalStartDate;
/**
* 原计划结束日期
*/
private Date originalEndDate;
/**
* 原计划天数
*/
private Integer originalDuration;
/**
* 原行程安排(详细)
*/
private String originalItinerary;
/**
* 原住宿计划
*/
private String originalAccommodationPlan;
/**
* 原交通计划
*/
private String originalTransportationPlan;
/**
* 变更类型(字典:rs_itinerary_change_type)
*/
@NotBlank(message = "变更类型不能为空", groups = { AddGroup.class, EditGroup.class })
private String changeType;
/**
* 新目的地国家
*/
private String newDestination;
/**
* 新目的地城市
*/
private String newDestinationCity;
/**
* 新出境事由
*/
private String newExitReason;
/**
* 新计划开始日期
*/
private Date newStartDate;
/**
* 新计划结束日期
*/
private Date newEndDate;
/**
* 新计划天数
*/
private Integer newDuration;
/**
* 新行程安排(详细)
*/
private String newItinerary;
/**
* 新住宿计划
*/
private String newAccommodationPlan;
/**
* 新交通计划
*/
private String newTransportationPlan;
/**
* 变更原因(必填)
*/
@NotBlank(message = "变更原因不能为空", groups = { AddGroup.class, EditGroup.class })
private String changeReason;
/**
* 备注
*/
private String remark;
/**
* 审批状态(字典:rs_itinerary_change_status,0:待审核 1:已通过 2:已拒绝 3:已撤回)
*/
private String status;
/**
* 审批意见
*/
private String approvalOpinion;
/**
* 审批时间
*/
private Date approvalTime;
/**
* 审批人ID
*/
private Long approverId;
/**
* 审批人姓名
*/
private String approverName;
/**
* 租户编号
*/
private String tenantId;
/**
* 删除标志(0代表存在 1代表删除)
*/
private Integer deleted;
}
@@ -0,0 +1,175 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsOverseasRegistration;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Pattern;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 出国登记业务对象 rs_overseas_registration
*
* @author weizzzz
* @date 2025-09-30
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = RsOverseasRegistration.class, reverseConvertGenerate = false)
public class RsOverseasRegistrationBo extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 登记ID
*/
@NotNull(message = "登记ID不能为空", groups = { EditGroup.class })
private Long registrationId;
/**
* 关联申请ID
*/
private Long applicationId;
/**
* 姓名
*/
@NotBlank(message = "姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String name;
/**
* 性别: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;
/**
* 政治面貌
*/
@NotBlank(message = "政治面貌不能为空", groups = { AddGroup.class, EditGroup.class })
private String politicalStatus;
/**
* 身份证号(数据库字段:id_number)
*/
@NotBlank(message = "身份证号不能为空", groups = { AddGroup.class, EditGroup.class })
private String idNumber;
/**
* 户籍地址(数据库字段:registered_address)
*/
@NotBlank(message = "户籍地址不能为空", groups = { AddGroup.class, EditGroup.class })
private String registeredAddress;
/**
* 单位部门(数据库字段:department)
*/
@NotBlank(message = "单位部门不能为空", groups = { AddGroup.class, EditGroup.class })
private String department;
/**
* 职务(数据库字段:position)
*/
@NotBlank(message = "职务不能为空", groups = { AddGroup.class, EditGroup.class })
private String position;
/**
* 行政级别(数据库字段:administrative_level)
*/
private String administrativeLevel;
/**
* 人员类别(数据库字段:personnel_category)
*/
private String personnelCategory;
// ========== 以下字段数据库表中不存在,仅用于前端展示,不做强制验证 ==========
/**
* 工作单位及职务(前端字段,数据库无此字段)
*/
private String workUnitPosition;
/**
* 配偶姓名(前端字段,数据库无此字段)
*/
private String spouse;
/**
* 联系电话(前端字段,数据库无此字段)
*/
private String contactPhone;
/**
* 出国日期(前端字段,数据库无此字段)
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date departureDate;
/**
* 预定返回日期(前端字段,数据库无此字段)
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date returnDate;
/**
* 停留天数(前端字段,数据库无此字段)
*/
private Integer stayDays;
/**
* 前往国家(前端字段,数据库无此字段)
*/
private String destinationCountry;
/**
* 前往国家详细信息(前端字段,数据库无此字段)
*/
private String destinationDetail;
/**
* 事由(前端字段,数据库无此字段)
*/
private String reason;
/**
* 登记单位(前端字段,数据库无此字段)
*/
private String registrationUnit;
/**
* 出国类型(前端字段,数据库无此字段)
*/
private String travelType;
/**
* 状态:0-草稿,1-待审核,2-已通过,3-已拒绝
*/
private String status;
/**
* 提交时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date submitTime;
/**
* 备注
*/
private String remarks;
}
@@ -0,0 +1,157 @@
package org.dromara.rs.domain.bo;
import org.dromara.rs.domain.RsPartyMembershipRetention;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.mybatis.core.domain.BaseEntity;
import io.github.linpeilie.annotations.AutoMapper;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 出国党籍保留业务对象 rs_party_membership_retention
*
* @author weizzzz
* @date 2025-10-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
@AutoMapper(target = RsPartyMembershipRetention.class, reverseConvertGenerate = false)
public class RsPartyMembershipRetentionBo extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 党籍保留ID(主键)
*/
@NotNull(message = "党籍保留ID不能为空", groups = { EditGroup.class })
private Long retentionId;
/**
* 关联出国申请ID
*/
private Long applicationId;
/**
* 关联出国登记ID
*/
private Long registrationId;
/**
* 我支部同志(姓名)
*/
@NotBlank(message = "姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String name;
/**
* 身份证号(18位)
*/
@NotBlank(message = "身份证号不能为空", groups = { AddGroup.class, EditGroup.class })
private String idCard;
/**
* 现任职务
*/
@NotBlank(message = "职务不能为空", groups = { AddGroup.class, EditGroup.class })
private String position;
/**
* 偕配偶姓名
*/
private String spouse;
/**
* 亲属姓名(探望对象)
*/
@NotBlank(message = "亲属姓名不能为空", groups = { AddGroup.class, EditGroup.class })
private String relativeName;
/**
* 拟出国开始日期
*/
@NotNull(message = "出国开始日期不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd")
private Date travelStartDate;
/**
* 拟出国结束日期
*/
@NotNull(message = "出国结束日期不能为空", groups = { AddGroup.class, EditGroup.class })
@JsonFormat(pattern = "yyyy-MM-dd")
private Date travelEndDate;
/**
* 在外停留天数(自动计算)
*/
private Integer stayDays;
/**
* 因私赴XX(国家或地区)
*/
@NotBlank(message = "目的地不能为空", groups = { AddGroup.class, EditGroup.class })
private String destination;
/**
* 出国目的详细说明(前端必填)
*/
@NotBlank(message = "出国目的不能为空", groups = { AddGroup.class, EditGroup.class })
private String purpose;
/**
* 出国原因(简要)
*/
private String travelReason;
/**
* 费用自理:1-是,0-否(后端自动设置,默认为"1")
*/
private String selfFunded;
/**
* 请求审批意见
*/
private String requestApproval;
/**
* 最终审批意见
*/
private String finalApproval;
/**
* 党支部名称
*/
@NotBlank(message = "党支部名称不能为空", groups = { AddGroup.class, EditGroup.class })
private String partyBranch;
/**
* 申请日期(后端自动设置为当前日期)
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date applicationDate;
/**
* 状态:0-草稿,1-待审核,2-已通过,3-已拒绝,4-已撤回
*/
private String status;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applicationTime;
/**
* 备注
*/
private String remarks;
/**
* 租户编号
*/
private String tenantId;
}
@@ -0,0 +1,83 @@
package org.dromara.rs.domain.query;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.io.Serial;
import java.util.Date;
/**
* 出国行程变更申请查询对象 rs_overseas_itinerary_change
*
* @author weizzzz
* @date 2025-10-13
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsOverseasItineraryChangeQuery extends PageQuery {
@Serial
private static final long serialVersionUID = 1L;
/**
* 关联出国记录ID
*/
private Long recordId;
/**
* 原出国申请ID
*/
private Long applicationId;
/**
* 申请人ID
*/
private Long applicantId;
/**
* 申请人姓名(模糊查询)
*/
private String applicantName;
/**
* 部门(模糊查询)
*/
private String department;
/**
* 变更状态
*/
private String status;
/**
* 变更类型
*/
private String changeType;
/**
* 创建时间开始
*/
private Date createTimeStart;
/**
* 创建时间结束
*/
private Date createTimeEnd;
/**
* 审批时间开始
*/
private Date approvalTimeStart;
/**
* 审批时间结束
*/
private Date approvalTimeEnd;
/**
* 租户ID
*/
private String tenantId;
}
@@ -0,0 +1,132 @@
package org.dromara.rs.domain.query;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 出国登记查询对象 rs_overseas_registration
*
* @author weizzzz
* @date 2025-09-30
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsOverseasRegistrationQuery extends PageQuery {
private static final long serialVersionUID = 1L;
/**
* 登记ID
*/
private Long registrationId;
/**
* 关联申请ID
*/
private Long applicationId;
/**
* 姓名(模糊查询)
*/
private String name;
/**
* 性别
*/
private String gender;
/**
* 政治面貌
*/
private String politicalStatus;
/**
* 身份证号(模糊查询)
*/
private String idNumber;
/**
* 工作部门(模糊查询)
*/
private String department;
/**
* 职务职称(模糊查询)
*/
private String position;
/**
* 行政级别
*/
private String administrativeLevel;
/**
* 人员类别代码
*/
private String personnelCategory;
/**
* 前往国家(模糊查询)
*/
private String destinationCountry;
/**
* 登记单位(模糊查询)
*/
private String registrationUnit;
/**
* 出国类型
*/
private String travelType;
/**
* 状态
*/
private String status;
/**
* 出国日期-开始
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date departureDateStart;
/**
* 出国日期-结束
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date departureDateEnd;
/**
* 登记时间-开始(前端传参)
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date registrationTimeStart;
/**
* 登记时间-结束(前端传参)
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date registrationTimeEnd;
/**
* 提交时间-开始
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date submitTimeStart;
/**
* 提交时间-结束
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date submitTimeEnd;
/**
* 创建者(用于查询我的登记)
*/
private Long createBy;
}
@@ -0,0 +1,68 @@
package org.dromara.rs.domain.query;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.util.Date;
/**
* 出国党籍保留查询对象 rs_party_membership_retention
*
* @author weizzzz
* @date 2025-10-05
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class RsPartyMembershipRetentionQuery extends PageQuery {
private static final long serialVersionUID = 1L;
/**
* 姓名(模糊查询)
*/
private String name;
/**
* 身份证号(精确查询)
*/
private String idCard;
/**
* 目的地(模糊查询)
*/
private String destination;
/**
* 党支部名称(模糊查询)
*/
private String partyBranch;
/**
* 状态:0-草稿,1-待审核,2-已通过,3-已拒绝,4-已撤回
*/
private String status;
/**
* 关联出国登记ID
*/
private Long registrationId;
/**
* 关联出国申请ID
*/
private Long applicationId;
/**
* 申请时间开始
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applicationTimeStart;
/**
* 申请时间结束
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applicationTimeEnd;
}
@@ -0,0 +1,55 @@
package org.dromara.rs.domain.vo;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* 登记表PDF生成响应VO
*
* @author weizzzz
* @date 2025-01-03
*/
@Data
public class RegistrationFormVO {
/**
* 文档唯一ID
*/
private String documentId;
/**
* 文件名
*/
private String fileName;
/**
* 下载链接
*/
private String downloadUrl;
/**
* 预览链接
*/
private String previewUrl;
/**
* 文件大小(字节)
*/
private Long fileSize;
/**
* 生成时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private String generateTime;
/**
* 文档编号
*/
private String documentNumber;
/**
* 申请ID
*/
private Long applicationId;
}
@@ -0,0 +1,112 @@
package org.dromara.rs.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* 出国申请附件VO
*
* @author weizzzz
* @date 2025-10-10
*/
@Data
public class RsOverseasApplicationAttachmentVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 附件ID
*/
private Long attachmentId;
/**
* 出国申请ID
*/
private Long applicationId;
/**
* 附件标题
*/
private String attachmentTitle;
/**
* 附件内容说明
*/
private String attachmentContent;
/**
* 文件名称
*/
private String fileName;
/**
* 文件原始名称
*/
private String fileOriginalName;
/**
* 文件存储路径/OSS URL
*/
private String filePath;
/**
* 文件大小(字节)
*/
private Long fileSize;
/**
* 文件大小(格式化)
*/
private String fileSizeFormatted;
/**
* 文件类型
*/
private String fileType;
/**
* MIME类型
*/
private String mimeType;
/**
* 上传时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date uploadTime;
/**
* 上传人ID
*/
private Long uploadUserId;
/**
* 上传人姓名
*/
private String uploadUserName;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,271 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsOverseasItineraryChange;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serializable;
import java.util.Date;
/**
* 出国行程变更视图对象 rs_overseas_itinerary_change
*
* @author zlwu
* @date 2025-01-13
*/
@Data
@AutoMapper(target = RsOverseasItineraryChange.class)
public class RsOverseasItineraryChangeVo implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 变更ID(主键,雪花ID)
*/
private Long changeId;
/**
* 关联备案记录ID(外键,关联rs_overseas_entry_exit_record.record_id)
*/
private Long recordId;
/**
* 关联出国申请ID(外键,关联rs_overseas_application.application_id,可为空)
*/
private Long applicationId;
/**
* 申请人ID(外键,关联sys_user.user_id)
*/
private Long applicantId;
/**
* 申请人姓名
*/
private String applicantName;
/**
* 性别:0-男,1-女
*/
private String gender;
/**
* 出生日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
/**
* 政治面貌
*/
private String politicalStatus;
/**
* 职称
*/
private String professionalTitle;
/**
* 职务
*/
private String position;
/**
* 申领证件名称
*/
private String documentName;
/**
* 证件号码
*/
private String documentNumber;
/**
* 部门
*/
private String department;
/**
* 原目的地国家
*/
private String originalDestination;
/**
* 原目的地城市
*/
private String originalDestinationCity;
/**
* 原出境事由
*/
private String originalExitReason;
/**
* 原计划开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date originalStartDate;
/**
* 原计划结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date originalEndDate;
/**
* 原计划天数
*/
private Integer originalDuration;
/**
* 原行程安排(详细)
*/
private String originalItinerary;
/**
* 原住宿计划
*/
private String originalAccommodationPlan;
/**
* 原交通计划
*/
private String originalTransportationPlan;
/**
* 变更类型(字典:rs_itinerary_change_type)
*/
private String changeType;
/**
* 变更类型文本(字典翻译)
*/
private String changeTypeText;
/**
* 新目的地国家
*/
private String newDestination;
/**
* 新目的地城市
*/
private String newDestinationCity;
/**
* 新出境事由
*/
private String newExitReason;
/**
* 新计划开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date newStartDate;
/**
* 新计划结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date newEndDate;
/**
* 新计划天数
*/
private Integer newDuration;
/**
* 新行程安排(详细)
*/
private String newItinerary;
/**
* 新住宿计划
*/
private String newAccommodationPlan;
/**
* 新交通计划
*/
private String newTransportationPlan;
/**
* 变更原因(必填)
*/
private String changeReason;
/**
* 备注
*/
private String remark;
/**
* 审批状态(字典:rs_itinerary_change_status,0:待审核 1:已通过 2:已拒绝 3:已撤回)
*/
private String status;
/**
* 审批状态文本(字典翻译)
*/
private String statusText;
/**
* 审批意见
*/
private String approvalOpinion;
/**
* 审批时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date approvalTime;
/**
* 审批人ID
*/
private Long approverId;
/**
* 审批人姓名
*/
private String approverName;
/**
* 租户编号
*/
private String tenantId;
/**
* 删除标志(0代表存在 1代表删除)
*/
private Integer deleted;
/**
* 创建部门
*/
private Long createDept;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,231 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsOverseasRegistration;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 出国登记VO对象 rs_overseas_registration
*
* @author weizzzz
* @date 2025-09-30
*/
@Data
@AutoMapper(target = RsOverseasRegistration.class)
public class RsOverseasRegistrationVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 登记ID
*/
private Long registrationId;
/**
* 关联申请ID
*/
private Long applicationId;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private String gender;
/**
* 性别文本
*/
private String genderText;
/**
* 身份证号
*/
private String idNumber;
/**
* 出生日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date birthDate;
/**
* 户籍所在地
*/
private String registeredAddress;
/**
* 工作部门
*/
private String department;
/**
* 职务职称
*/
private String position;
/**
* 行政职级
*/
private String administrativeLevel;
/**
* 行政职级文本
*/
private String administrativeLevelText;
/**
* 人员类别代码
*/
private String personnelCategory;
/**
* 人员类别文本
*/
private String personnelCategoryText;
/**
* 照片地址
*/
private String photo;
/**
* 政治面貌
*/
private String politicalStatus;
/**
* 政治面貌文本
*/
private String politicalStatusText;
/**
* 工作单位及职务
*/
private String workUnitPosition;
/**
* 配偶姓名
*/
private String spouse;
/**
* 联系电话
*/
private String contactPhone;
/**
* 出国日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date departureDate;
/**
* 预定返回日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date returnDate;
/**
* 停留天数
*/
private Integer stayDays;
/**
* 前往国家
*/
private String destinationCountry;
/**
* 前往国家详细信息
*/
private String destinationDetail;
/**
* 事由
*/
private String reason;
/**
* 登记单位
*/
private String registrationUnit;
/**
* 出国类型
*/
private String travelType;
/**
* 出国类型文本
*/
private String travelTypeText;
/**
* 状态
*/
private String status;
/**
* 状态文本
*/
private String statusText;
/**
* 提交时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date submitTime;
/**
* 登记时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date registrationTime;
/**
* 备注
*/
private String remarks;
/**
* 租户编号
*/
private String tenantId;
/**
* 删除标志
*/
private String deleted;
/**
* 创建者(统一基础字段)
*/
private Long createBy;
/**
* 创建时间(统一基础字段)
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者(统一基础字段)
*/
private Long updateBy;
/**
* 更新时间(统一基础字段)
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
}
@@ -0,0 +1,180 @@
package org.dromara.rs.domain.vo;
import org.dromara.rs.domain.RsPartyMembershipRetention;
import io.github.linpeilie.annotations.AutoMapper;
import lombok.Data;
import com.fasterxml.jackson.annotation.JsonFormat;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 出国党籍保留VO对象 rs_party_membership_retention
*
* @author weizzzz
* @date 2025-10-05
*/
@Data
@AutoMapper(target = RsPartyMembershipRetention.class)
public class RsPartyMembershipRetentionVo implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 党籍保留ID(主键)
*/
private Long retentionId;
/**
* 关联出国申请ID
*/
private Long applicationId;
/**
* 关联出国登记ID
*/
private Long registrationId;
/**
* 我支部同志(姓名)
*/
private String name;
/**
* 身份证号(18位)
*/
private String idCard;
/**
* 现任职务
*/
private String position;
/**
* 偕配偶姓名
*/
private String spouse;
/**
* 亲属姓名(探望对象)
*/
private String relativeName;
/**
* 拟出国开始日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date travelStartDate;
/**
* 拟出国结束日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date travelEndDate;
/**
* 在外停留天数
*/
private Integer stayDays;
/**
* 因私赴XX(国家或地区)
*/
private String destination;
/**
* 出国目的详细说明
*/
private String purpose;
/**
* 出国原因(简要)
*/
private String travelReason;
/**
* 费用自理:1-是,0-否
*/
private String selfFunded;
/**
* 费用自理文本
*/
private String selfFundedText;
/**
* 请求审批意见
*/
private String requestApproval;
/**
* 最终审批意见
*/
private String finalApproval;
/**
* 党支部名称
*/
private String partyBranch;
/**
* 申请日期
*/
@JsonFormat(pattern = "yyyy-MM-dd")
private Date applicationDate;
/**
* 状态:0-草稿,1-待审核,2-已通过,3-已拒绝,4-已撤回
*/
private String status;
/**
* 状态文本
*/
private String statusText;
/**
* 申请时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date applicationTime;
/**
* 备注
*/
private String remarks;
/**
* 租户编号
*/
private String tenantId;
/**
* 创建者
*/
private Long createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
/**
* 更新者
*/
private Long updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date updateTime;
/**
* 删除标志(0代表存在 1代表删除)
*/
private String deleted;
}
@@ -0,0 +1,35 @@
package org.dromara.rs.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.dromara.rs.domain.RsOverseasApplicationAttachment;
import org.dromara.rs.domain.vo.RsOverseasApplicationAttachmentVo;
import java.util.List;
/**
* 出国申请附件Mapper接口
*
* @author weizzzz
* @date 2025-10-10
*/
@Mapper
public interface RsOverseasApplicationAttachmentMapper extends BaseMapper<RsOverseasApplicationAttachment> {
/**
* 查询出国申请附件列表
*
* @param applicationId 申请ID
* @return 附件列表
*/
List<RsOverseasApplicationAttachmentVo> selectAttachmentList(Long applicationId);
/**
* 查询附件详情
*
* @param attachmentId 附件ID
* @return 附件信息
*/
RsOverseasApplicationAttachmentVo selectAttachmentById(Long attachmentId);
}
@@ -0,0 +1,42 @@
package org.dromara.rs.mapper;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.rs.domain.RsOverseasItineraryChange;
import org.dromara.rs.domain.vo.RsOverseasItineraryChangeVo;
import org.dromara.rs.domain.query.RsOverseasItineraryChangeQuery;
import java.util.List;
/**
* 出国行程变更申请Mapper接口
*
* @author weizzzz
* @date 2025-10-13
*/
public interface RsOverseasItineraryChangeMapper extends BaseMapperPlus<RsOverseasItineraryChange, RsOverseasItineraryChangeVo> {
/**
* 查询出国行程变更申请列表
*
* @param query 查询条件
* @return 出国行程变更申请集合
*/
List<RsOverseasItineraryChangeVo> selectChangeList(RsOverseasItineraryChangeQuery query);
/**
* 查询出国行程变更申请详情
*
* @param changeId 变更ID
* @return 出国行程变更申请
*/
RsOverseasItineraryChangeVo selectChangeById(Long changeId);
/**
* 根据出国记录ID查询变更记录列表
*
* @param recordId 出国记录ID
* @return 变更记录列表
*/
List<RsOverseasItineraryChangeVo> selectChangeListByRecordId(Long recordId);
}
@@ -0,0 +1,73 @@
package org.dromara.rs.mapper;
import java.util.List;
import java.util.Map;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.rs.domain.RsOverseasRegistration;
import org.dromara.rs.domain.vo.RsOverseasRegistrationVo;
import org.dromara.rs.domain.query.RsOverseasRegistrationQuery;
/**
* 出国登记Mapper接口
*
* @author weizzzz
* @date 2025-09-30
*/
public interface RsOverseasRegistrationMapper extends BaseMapperPlus<RsOverseasRegistration, RsOverseasRegistrationVo> {
/**
* 查询出国登记列表
*
* @param query 查询条件
* @return 出国登记集合
*/
List<RsOverseasRegistrationVo> selectRegistrationList(RsOverseasRegistrationQuery query);
/**
* 查询我的出国登记列表
*
* @param query 查询条件
* @return 出国登记集合
*/
List<RsOverseasRegistrationVo> selectMyRegistrationList(RsOverseasRegistrationQuery query);
/**
* 查询出国登记详细信息
*
* @param registrationId 出国登记主键
* @return 出国登记
*/
RsOverseasRegistrationVo selectRegistrationById(Long registrationId);
/**
* 根据申请ID查询出国登记信息
*
* @param applicationId 申请ID
* @return 出国登记
*/
RsOverseasRegistrationVo selectRegistrationByApplicationId(Long applicationId);
/**
* 根据状态查询出国登记列表
*
* @param status 状态
* @return 出国登记集合
*/
List<RsOverseasRegistrationVo> selectRegistrationsByStatus(String status);
/**
* 根据姓名查询出国登记列表
*
* @param name 姓名
* @return 出国登记集合
*/
List<RsOverseasRegistrationVo> selectRegistrationsByName(String name);
/**
* 统计各状态的登记数量
*
* @param query 查询条件
* @return 统计结果
*/
List<Map<String, Object>> selectStatusStatistics(RsOverseasRegistrationQuery query);
}
@@ -0,0 +1,26 @@
package org.dromara.rs.mapper;
import org.dromara.rs.domain.RsPartyMembershipRetention;
import org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo;
import org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import java.util.List;
/**
* 出国党籍保留Mapper接口
*
* @author weizzzz
* @date 2025-10-05
*/
public interface RsPartyMembershipRetentionMapper extends BaseMapperPlus<RsPartyMembershipRetention, RsPartyMembershipRetentionVo> {
/**
* 查询出国党籍保留列表(包含扩展字段如statusText)
*
* @param query 查询条件
* @return 出国党籍保留列表
*/
List<RsPartyMembershipRetentionVo> selectVoList(RsPartyMembershipRetentionQuery query);
}
@@ -0,0 +1,52 @@
package org.dromara.rs.service;
import org.dromara.rs.domain.bo.GenerateRegistrationFormBo;
import org.dromara.rs.domain.vo.RegistrationFormVO;
import jakarta.servlet.http.HttpServletResponse;
/**
* 登记表PDF生成服务接口
*
* @author weizzzz
* @date 2025-01-03
*/
public interface IRegistrationFormService {
/**
* 生成出国登记表PDF
*
* @param bo 生成请求参数
* @return 生成结果
*/
RegistrationFormVO generateRegistrationFormPdf(GenerateRegistrationFormBo bo);
/**
* 下载生成的PDF文档
*
* @param documentId 文档ID
* @param response HTTP响应
*/
void downloadPdf(String documentId, HttpServletResponse response);
/**
* 获取PDF预览链接
*
* @param documentId 文档ID
* @return 预览链接
*/
String getPreviewUrl(String documentId);
/**
* 下载登记备案表模板
*
* @param response HTTP响应
*/
void downloadRegistrationTemplate(HttpServletResponse response);
/**
* 下载保留党籍申请模板
*
* @param response HTTP响应
*/
void downloadPartyMembershipTemplate(HttpServletResponse response);
}
@@ -0,0 +1,70 @@
package org.dromara.rs.service;
import org.dromara.rs.domain.bo.RsOverseasApplicationAttachmentBo;
import org.dromara.rs.domain.vo.RsOverseasApplicationAttachmentVo;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.List;
/**
* 出国申请附件Service接口
*
* @author weizzzz
* @date 2025-10-10
*/
public interface IRsOverseasApplicationAttachmentService {
/**
* 查询出国申请的附件列表
*
* @param applicationId 申请ID
* @return 附件列表
*/
List<RsOverseasApplicationAttachmentVo> selectAttachmentList(Long applicationId);
/**
* 查询附件详情
*
* @param attachmentId 附件ID
* @return 附件信息
*/
RsOverseasApplicationAttachmentVo selectAttachmentById(Long attachmentId);
/**
* 上传附件
*
* @param applicationId 申请ID
* @param title 附件标题
* @param content 附件内容说明
* @param file 文件
* @return 附件信息
* @throws IOException IO异常
*/
RsOverseasApplicationAttachmentVo uploadAttachment(Long applicationId, String title, String content, MultipartFile file) throws IOException;
/**
* 更新附件信息(标题和内容)
*
* @param bo 附件业务对象
* @return 是否成功
*/
boolean updateAttachment(RsOverseasApplicationAttachmentBo bo);
/**
* 删除附件
*
* @param attachmentId 附件ID
* @return 是否成功
*/
boolean deleteAttachment(Long attachmentId);
/**
* 批量删除附件
*
* @param attachmentIds 附件ID数组
* @return 是否成功
*/
boolean deleteAttachments(Long[] attachmentIds);
}
@@ -0,0 +1,108 @@
package org.dromara.rs.service;
import java.util.List;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.rs.domain.bo.RsOverseasItineraryChangeBo;
import org.dromara.rs.domain.vo.RsOverseasItineraryChangeVo;
import org.dromara.rs.domain.query.RsOverseasItineraryChangeQuery;
/**
* 出国行程变更申请Service接口
*
* @author weizzzz
* @date 2025-10-13
*/
public interface IRsOverseasItineraryChangeService {
/**
* 查询出国行程变更申请分页列表
*
* @param query 查询条件
* @return 分页结果
*/
TableDataInfo<RsOverseasItineraryChangeVo> queryPageList(RsOverseasItineraryChangeQuery query);
/**
* 查询出国行程变更申请列表
*
* @param query 查询条件
* @return 列表结果
*/
List<RsOverseasItineraryChangeVo> queryList(RsOverseasItineraryChangeQuery query);
/**
* 查询出国行程变更申请详情
*
* @param changeId 变更ID
* @return 详情数据
*/
RsOverseasItineraryChangeVo queryById(Long changeId);
/**
* 查询我的变更记录列表(当前登录用户的已通过变更申请)
* 说明:变更记录Tab只显示已通过审批的变更申请,这些是真正生效的变更记录
*
* @param query 查询条件
* @return 分页结果
*/
TableDataInfo<RsOverseasItineraryChangeVo> queryMyChangeList(RsOverseasItineraryChangeQuery query);
/**
* 查询待审批的变更申请列表
*
* @param query 查询条件
* @return 分页结果
*/
TableDataInfo<RsOverseasItineraryChangeVo> queryPendingList(RsOverseasItineraryChangeQuery query);
/**
* 新增出国行程变更申请
*
* @param bo 业务对象
* @return 是否成功
*/
Boolean insertByBo(RsOverseasItineraryChangeBo bo);
/**
* 修改出国行程变更申请(重新提交)
*
* @param bo 业务对象
* @return 是否成功
*/
Boolean updateByBo(RsOverseasItineraryChangeBo bo);
/**
* 删除出国行程变更申请
*
* @param changeIds 变更ID列表
* @return 是否成功
*/
Boolean deleteByIds(List<Long> changeIds);
/**
* 撤回变更申请
*
* @param changeId 变更ID
* @return 是否成功
*/
Boolean cancelChange(Long changeId);
/**
* 审批变更申请
*
* @param changeId 变更ID
* @param approved 是否通过
* @param opinion 审批意见
* @return 是否成功
*/
Boolean approveChange(Long changeId, Boolean approved, String opinion);
/**
* 根据出国记录ID查询变更记录列表
*
* @param recordId 出国记录ID
* @return 变更记录列表
*/
List<RsOverseasItineraryChangeVo> queryByRecordId(Long recordId);
}
@@ -0,0 +1,377 @@
package org.dromara.rs.service;
import java.util.List;
import java.util.Collection;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.rs.domain.bo.RsOverseasRegistrationBo;
import org.dromara.rs.domain.vo.RsOverseasRegistrationVo;
import org.dromara.rs.domain.query.RsOverseasRegistrationQuery;
/**
* 出国登记Service接口
*
* @author weizzzz
* @date 2025-09-30
*/
public interface IRsOverseasRegistrationService {
/**
* 查询出国登记分页列表
*
* @param query 查询条件
* @return 出国登记分页集合
*/
TableDataInfo<RsOverseasRegistrationVo> queryPageList(RsOverseasRegistrationQuery query);
/**
* 查询我的出国登记分页列表
*
* @param query 查询条件
* @return 出国登记分页集合
*/
TableDataInfo<RsOverseasRegistrationVo> queryMyRegistrationList(RsOverseasRegistrationQuery query);
/**
* 查询出国登记列表
*
* @param query 查询条件
* @return 出国登记集合
*/
List<RsOverseasRegistrationVo> queryList(RsOverseasRegistrationQuery query);
/**
* 查询出国登记详细信息
*
* @param registrationId 出国登记主键
* @return 出国登记
*/
RsOverseasRegistrationVo queryById(Long registrationId);
/**
* 根据申请ID查询出国登记信息
*
* @param applicationId 申请ID
* @return 出国登记
*/
RsOverseasRegistrationVo queryByApplicationId(Long applicationId);
/**
* 根据状态查询出国登记列表
*
* @param status 状态
* @return 出国登记集合
*/
List<RsOverseasRegistrationVo> queryByStatus(String status);
/**
* 根据姓名查询出国登记列表
*
* @param name 姓名
* @return 出国登记集合
*/
List<RsOverseasRegistrationVo> queryByName(String name);
/**
* 新增出国登记
*
* @param bo 出国登记业务对象
* @return 是否成功
*/
Boolean insertByBo(RsOverseasRegistrationBo bo);
/**
* 新增并提交出国登记
*
* @param bo 出国登记业务对象
* @return 是否成功
*/
Boolean addAndSubmit(RsOverseasRegistrationBo bo);
/**
* 修改出国登记
*
* @param bo 出国登记业务对象
* @return 是否成功
*/
Boolean updateByBo(RsOverseasRegistrationBo bo);
/**
* 校验并批量删除出国登记信息
*
* @param ids 主键集合
* @param isValid 是否校验,true-删除前校验,false-不校验
* @return 是否成功
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 提交出国登记
*
* @param registrationId 登记ID
* @return 是否成功
*/
Boolean submitRegistration(Long registrationId);
/**
* 确认出国登记
*
* @param registrationId 登记ID
* @return 是否成功
*/
Boolean confirmRegistration(Long registrationId);
/**
* 撤回出国登记
*
* @param registrationId 登记ID
* @return 是否成功
*/
Boolean withdrawRegistration(Long registrationId);
/**
* 基于出国申请创建登记记录
*
* @param applicationId 申请ID
* @return 是否成功
*/
Boolean createFromApplication(Long applicationId);
/**
* 统计各状态的登记数量
*
* @param query 查询条件
* @return 统计结果
*/
List<java.util.Map<String, Object>> getStatusStatistics(RsOverseasRegistrationQuery query);
/**
* 批量提交出国登记
*
* @param registrationIds 登记ID集合
* @return 是否成功
*/
Boolean batchSubmitRegistrations(List<Long> registrationIds);
/**
* 批量确认出国登记
*
* @param registrationIds 登记ID集合
* @return 是否成功
*/
Boolean batchConfirmRegistrations(List<Long> registrationIds);
/**
* 获取可用的登记选项(字典数据)
*
* @return 选项集合
*/
java.util.Map<String, List<java.util.Map<String, Object>>> getRegistrationOptions();
/**
* 批量导出出国登记数据
*
* @param query 查询条件
* @param response 响应对象
*/
void exportRegistrations(RsOverseasRegistrationQuery query, jakarta.servlet.http.HttpServletResponse response);
/**
* 按部门统计出国登记数量
*
* @param query 查询条件
* @return 统计结果
*/
List<java.util.Map<String, Object>> statisticsByDepartment(RsOverseasRegistrationQuery query);
/**
* 按目的地国家统计出国登记数量
*
* @param query 查询条件
* @return 统计结果
*/
List<java.util.Map<String, Object>> statisticsByCountry(RsOverseasRegistrationQuery query);
/**
* 按出国类型统计出国登记数量
*
* @param query 查询条件
* @return 统计结果
*/
List<java.util.Map<String, Object>> statisticsByTravelType(RsOverseasRegistrationQuery query);
/**
* 按人员类别统计出国登记数量
*
* @param query 查询条件
* @return 统计结果
*/
List<java.util.Map<String, Object>> statisticsByPersonnelCategory(RsOverseasRegistrationQuery query);
/**
* 检查是否存在重复的出国登记(同一人同一时间段)
*
* @param registration 登记信息
* @return 是否重复
*/
Boolean checkDuplicateRegistration(RsOverseasRegistrationVo registration);
/**
* 获取即将到期的出国登记列表
*
* @param days 天数阈值
* @return 登记列表
*/
List<RsOverseasRegistrationVo> getExpiringRegistrations(Integer days);
/**
* 获取已过期的出国登记列表
*
* @return 登记列表
*/
List<RsOverseasRegistrationVo> getExpiredRegistrations();
/**
* 按时间段统计出国登记趋势
*
* @param startDate 开始日期
* @param endDate 结束日期
* @return 统计结果
*/
List<java.util.Map<String, Object>> getTrendStatistics(java.util.Date startDate, java.util.Date endDate);
/**
* 获取出国登记年度报告
*
* @param year 年份
* @return 报告数据
*/
java.util.Map<String, Object> getAnnualReport(Integer year);
/**
* 更新登记状态
*
* @param registrationId 登记ID
* @param status 新状态
* @return 是否成功
*/
Boolean updateStatus(Long registrationId, String status);
/**
* 获取我的出国登记统计
*
* @return 统计数据
*/
java.util.Map<String, Object> getMyStatistics();
/**
* 获取趋势数据
*
* @return 趋势数据
*/
java.util.Map<String, Object> getTrendData();
/**
* 获取状态选项列表
*
* @return 状态选项
*/
List<java.util.Map<String, Object>> getStatusOptions();
/**
* 获取出国类型选项列表
*
* @return 出国类型选项
*/
List<java.util.Map<String, Object>> getTravelTypeOptions();
/**
* 获取完成状态选项列表
*
* @return 完成状态选项
*/
List<java.util.Map<String, Object>> getCompletionStatusOptions();
/**
* 生成出国证明文件
*
* @param registrationId 登记ID
* @param response 响应对象
* @throws Exception 异常
*/
void generateCertificate(Long registrationId, jakarta.servlet.http.HttpServletResponse response) throws Exception;
/**
* 查询历史记录分页列表
*
* @param query 查询条件
* @return 历史记录分页集合
*/
TableDataInfo<RsOverseasRegistrationVo> queryHistoryPageList(RsOverseasRegistrationQuery query);
/**
* 查询历史记录列表
*
* @param query 查询条件
* @return 历史记录集合
*/
List<RsOverseasRegistrationVo> queryHistoryList(RsOverseasRegistrationQuery query);
/**
* 查询历史记录详细信息
*
* @param historyId 历史记录ID
* @return 历史记录
*/
RsOverseasRegistrationVo queryHistoryById(Long historyId);
/**
* 新增历史记录
*
* @param bo 历史记录业务对象
* @return 是否成功
*/
Boolean insertHistoryByBo(RsOverseasRegistrationBo bo);
/**
* 修改历史记录
*
* @param bo 历史记录业务对象
* @return 是否成功
*/
Boolean updateHistoryByBo(RsOverseasRegistrationBo bo);
/**
* 删除历史记录
*
* @param historyIds 历史记录ID集合
* @return 是否成功
*/
Boolean deleteHistoryWithValidByIds(List<Long> historyIds);
/**
* 完成登记
*
* @param registrationId 登记ID
* @param data 完成数据
* @return 是否成功
*/
Boolean completeRegistration(Long registrationId, java.util.Map<String, Object> data);
/**
* 批准出国登记
*
* @param registrationId 登记ID
* @param approveNotes 审批意见
* @return 是否成功
*/
Boolean approveRegistration(Long registrationId, String approveNotes);
/**
* 拒绝出国登记
*
* @param registrationId 登记ID
* @param rejectReason 拒绝原因
* @return 是否成功
*/
Boolean rejectRegistration(Long registrationId, String rejectReason);
}
@@ -0,0 +1,79 @@
package org.dromara.rs.service;
import org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo;
import org.dromara.rs.domain.bo.RsPartyMembershipRetentionBo;
import org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import java.util.Collection;
import java.util.List;
/**
* 出国党籍保留Service接口
*
* @author weizzzz
* @date 2025-10-05
*/
public interface IRsPartyMembershipRetentionService {
/**
* 查询出国党籍保留
*/
RsPartyMembershipRetentionVo queryById(Long retentionId);
/**
* 查询出国党籍保留列表
*/
TableDataInfo<RsPartyMembershipRetentionVo> queryPageList(RsPartyMembershipRetentionQuery query, PageQuery pageQuery);
/**
* 查询出国党籍保留列表
*/
List<RsPartyMembershipRetentionVo> queryList(RsPartyMembershipRetentionQuery query);
/**
* 新增出国党籍保留
*/
Boolean insertByBo(RsPartyMembershipRetentionBo bo);
/**
* 修改出国党籍保留
*/
Boolean updateByBo(RsPartyMembershipRetentionBo bo);
/**
* 校验并批量删除出国党籍保留信息
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 保存并提交出国党籍保留
*/
Boolean saveAndSubmit(RsPartyMembershipRetentionBo bo);
/**
* 提交出国党籍保留
*/
Boolean submit(Long retentionId);
/**
* 撤回出国党籍保留
*/
Boolean withdraw(Long retentionId);
/**
* 审批出国党籍保留
*/
Boolean approve(Long retentionId, String approvalOpinion, Boolean approved);
/**
* 根据登记ID查询党籍保留信息
*/
RsPartyMembershipRetentionVo queryByRegistrationId(Long registrationId);
/**
* 根据申请ID查询党籍保留信息
*/
RsPartyMembershipRetentionVo queryByApplicationId(Long applicationId);
}
@@ -0,0 +1,609 @@
package org.dromara.rs.service.impl;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.DocumentInfo;
import org.dromara.rs.domain.bo.GenerateRegistrationFormBo;
import org.dromara.rs.domain.vo.RegistrationFormVO;
import org.dromara.rs.service.IRegistrationFormService;
import org.dromara.rs.service.IRsOverseasApplicationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Value;
import jakarta.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;
/**
* 登记表PDF生成服务实现类
*
* @author weizzzz
* @date 2025-01-03
*/
@Slf4j
@Service
public class RegistrationFormServiceImpl implements IRegistrationFormService {
@Autowired
private IRsOverseasApplicationService applicationService;
@Value("${ruoyi.profile:./uploadPath}")
private String uploadPath;
private static final String REGISTRATION_FORMS_DIR = "registration-forms";
private static final String PDF_EXTENSION = ".pdf";
// 简化实现:内存存储文档信息(实际项目中应存储到数据库)
private static final Map<String, DocumentInfo> DOCUMENT_CACHE = new ConcurrentHashMap<>();
@Override
public RegistrationFormVO generateRegistrationFormPdf(GenerateRegistrationFormBo bo) {
try {
// 1. 查询申请数据
RsOverseasApplicationVo application = applicationService.queryById(bo.getApplicationId());
if (application == null) {
throw new ServiceException("申请记录不存在");
}
// 2. 生成文档编号
String documentNumber = bo.getDocumentNumber();
if (documentNumber == null || documentNumber.trim().isEmpty()) {
documentNumber = generateDocumentNumber();
}
// 3. 创建存储目录
String dateDir = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
Path storageDir = Paths.get(uploadPath, REGISTRATION_FORMS_DIR, dateDir);
Files.createDirectories(storageDir);
// 4. 生成文件名和路径
String documentId = UUID.randomUUID().toString();
String fileName = String.format("registration_form_%d_%s%s",
application.getApplicationId(),
System.currentTimeMillis(),
PDF_EXTENSION);
Path filePath = storageDir.resolve(fileName);
// 5. 生成PDF文档
generatePdfDocument(application, documentNumber, filePath);
// 6. 构建返回结果
RegistrationFormVO result = new RegistrationFormVO();
result.setDocumentId(documentId);
result.setFileName(fileName);
result.setDownloadUrl("/rs/overseas/application/downloadPdf/" + documentId);
result.setPreviewUrl("/rs/overseas/application/previewPdf/" + documentId);
result.setFileSize(Files.size(filePath));
result.setGenerateTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
result.setDocumentNumber(documentNumber);
result.setApplicationId(application.getApplicationId());
// 7. 存储文档信息(这里简化处理,实际项目中可能需要存储到数据库)
storeDocumentInfo(documentId, filePath.toString(), result);
log.info("成功生成登记表PDF,申请ID: {}, 文档ID: {}", bo.getApplicationId(), documentId);
return result;
} catch (Exception e) {
log.error("生成登记表PDF失败,申请ID: {}", bo.getApplicationId(), e);
throw new ServiceException("生成PDF文档失败: " + e.getMessage());
}
}
/**
* 生成PDF文档 - 严格按照登记表图片格式
*/
private void generatePdfDocument(RsOverseasApplicationVo application, String documentNumber, Path filePath)
throws DocumentException, IOException {
Document document = new Document(PageSize.A4, 25, 25, 25, 25);
PdfWriter writer = PdfWriter.getInstance(document, Files.newOutputStream(filePath));
document.open();
try {
// 创建中文字体
BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
Font titleFont = new Font(baseFont, 16, Font.BOLD);
Font normalFont = new Font(baseFont, 12, Font.NORMAL);
Font smallFont = new Font(baseFont, 10, Font.NORMAL);
// 1. 标题
Paragraph title = new Paragraph("登记报备人员因私事出国(境)登记表", titleFont);
title.setAlignment(Element.ALIGN_CENTER);
title.setSpacingAfter(10);
document.add(title);
// 2. 编号
Paragraph number = new Paragraph("编号:" + documentNumber, normalFont);
number.setAlignment(Element.ALIGN_RIGHT);
number.setSpacingAfter(15);
document.add(number);
// 3. 创建主表格 - 严格按照图片结构
PdfPTable mainTable = new PdfPTable(7); // 7列表格
mainTable.setWidthPercentage(100);
mainTable.setWidths(new float[]{8, 15, 8, 10, 10, 15, 20}); // 列宽比例
// 第一行:姓名、性别、出生日期、照片
addTableCell(mainTable, "姓名", normalFont, true);
addTableCell(mainTable, application.getApplicantName(), normalFont, false);
addTableCell(mainTable, "性别", normalFont, true);
addTableCell(mainTable, application.getGender(), normalFont, false);
addTableCell(mainTable, "出生日期", normalFont, true);
addTableCell(mainTable, formatBirthDate(application.getBirthDate()), normalFont, false);
// 照片区域 - 跨7行
PdfPCell photoCell = new PdfPCell(new Phrase("照片\n(2寸免冠白\n色或淡蓝色\n底彩照\n32MM×26MM)", normalFont));
photoCell.setRowspan(7);
photoCell.setHorizontalAlignment(Element.ALIGN_CENTER);
photoCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
photoCell.setMinimumHeight(120f);
mainTable.addCell(photoCell);
// 第二行:公民身份号码、政治面貌
addTableCell(mainTable, "公民身份号码", normalFont, true);
PdfPCell idCell = new PdfPCell(new Phrase("(请填写18位公民身份\n号码)\n" +
(application.getIdNumber() != null ? application.getIdNumber() : ""), normalFont));
idCell.setColspan(2);
mainTable.addCell(idCell);
addTableCell(mainTable, "政治\n面貌", normalFont, true);
PdfPCell politicalCell = new PdfPCell(new Phrase(application.getPoliticalStatus(), normalFont));
politicalCell.setColspan(2);
mainTable.addCell(politicalCell);
// 第三行:户籍所在地
addTableCell(mainTable, "户籍所在地", normalFont, true);
PdfPCell addressCell = new PdfPCell(new Phrase("(请填写至县(市、区))\n" +
(application.getRegisteredAddress() != null ? application.getRegisteredAddress() : ""), normalFont));
addressCell.setColspan(5);
mainTable.addCell(addressCell);
// 第四行:工作部门
addTableCell(mainTable, "工作部门\n(教学系部)", normalFont, true);
PdfPCell deptCell = new PdfPCell(new Phrase("(请填写全称)\n" +
(application.getDepartment() != null ? application.getDepartment() : ""), normalFont));
deptCell.setColspan(5);
mainTable.addCell(deptCell);
// 第五行:职务职称、行政职级、人员类别代码
addTableCell(mainTable, "职务职称", normalFont, true);
addTableCell(mainTable, application.getPosition(), normalFont, false);
addTableCell(mainTable, "行政\n职级", normalFont, true);
addTableCell(mainTable, application.getAdministrativeLevel(), normalFont, false);
addTableCell(mainTable, "人员类\n别代码", normalFont, true);
addTableCell(mainTable, application.getPersonnelCategory(), normalFont, false);
// 第六行:登记备案责任单位(第一部分)
PdfPCell unitCell1 = new PdfPCell(new Phrase("登记备案\n责任单位", normalFont));
unitCell1.setRowspan(2);
unitCell1.setVerticalAlignment(Element.ALIGN_MIDDLE);
mainTable.addCell(unitCell1);
PdfPCell stampCell = new PdfPCell(new Phrase("盖 章", normalFont));
stampCell.setColspan(2);
stampCell.setHorizontalAlignment(Element.ALIGN_CENTER);
stampCell.setMinimumHeight(40f);
mainTable.addCell(stampCell);
PdfPCell leaderCell = new PdfPCell(new Phrase("登记备案责任单位\n负责人签名:\n" +
(application.getUnitLeaderSignature() != null ? application.getUnitLeaderSignature() : ""), normalFont));
leaderCell.setColspan(3);
mainTable.addCell(leaderCell);
// 第七行:联系电话
addTableCell(mainTable, "联系电话", normalFont, true);
addTableCell(mainTable, "办公电话:\n" +
(application.getOfficePhone() != null ? application.getOfficePhone() : ""), normalFont, false);
addTableCell(mainTable, "移动电话:\n" +
(application.getMobilePhone() != null ? application.getMobilePhone() : ""), normalFont, false);
// 填充剩余列
addTableCell(mainTable, "", normalFont, false);
addTableCell(mainTable, "", normalFont, false);
document.add(mainTable);
// 4. 填表说明
Paragraph note = new Paragraph("填表说明:人员类别代码根据报备人员属于报备人员范围中的情形项,填写所属项的序号即可。", smallFont);
note.setSpacingBefore(20);
document.add(note);
} finally {
document.close();
}
}
/**
* 添加表格单元格
*/
private void addTableCell(PdfPTable table, String content, Font font, boolean isHeader) {
PdfPCell cell = new PdfPCell(new Phrase(content != null ? content : "", font));
if (isHeader) {
cell.setBackgroundColor(BaseColor.LIGHT_GRAY);
}
cell.setHorizontalAlignment(Element.ALIGN_CENTER);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setMinimumHeight(25f);
table.addCell(cell);
}
/**
* 格式化出生日期
*/
private String formatBirthDate(Date birthDate) {
if (birthDate == null) {
return "年 月 日";
}
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日");
return sdf.format(birthDate);
}
/**
* 生成文档编号
*/
private String generateDocumentNumber() {
return "CG" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
}
/**
* 存储文档信息(简化实现)
*/
private void storeDocumentInfo(String documentId, String filePath, RegistrationFormVO result) {
DocumentInfo docInfo = new DocumentInfo();
docInfo.setDocumentId(documentId);
docInfo.setFilePath(filePath);
docInfo.setFileName(result.getFileName());
docInfo.setApplicationId(result.getApplicationId());
docInfo.setDocumentNumber(result.getDocumentNumber());
docInfo.setFileSize(result.getFileSize());
docInfo.setCreateTime(LocalDateTime.now());
DOCUMENT_CACHE.put(documentId, docInfo);
log.info("存储文档信息: documentId={}, filePath={}", documentId, filePath);
}
@Override
public void downloadPdf(String documentId, HttpServletResponse response) {
try {
// 从缓存中获取文档信息
DocumentInfo docInfo = DOCUMENT_CACHE.get(documentId);
if (docInfo == null) {
throw new ServiceException("文档不存在或已过期");
}
// 检查文件是否存在
Path filePath = Paths.get(docInfo.getFilePath());
if (!Files.exists(filePath)) {
throw new ServiceException("文件不存在");
}
// 设置响应头
response.setContentType("application/pdf");
response.setHeader("Content-Disposition",
"attachment; filename=\"" + docInfo.getFileName() + "\"");
response.setHeader("Content-Length", String.valueOf(docInfo.getFileSize()));
// 输出文件内容
try (InputStream inputStream = Files.newInputStream(filePath);
OutputStream outputStream = response.getOutputStream()) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
}
log.info("成功下载PDF文档: documentId={}, fileName={}", documentId, docInfo.getFileName());
} catch (Exception e) {
log.error("下载PDF文档失败: documentId={}", documentId, e);
throw new ServiceException("下载文档失败: " + e.getMessage());
}
}
@Override
public String getPreviewUrl(String documentId) {
return "/rs/overseas/application/previewPdf/" + documentId;
}
@Override
public void downloadRegistrationTemplate(HttpServletResponse response) {
log.info("开始生成登记备案表模板PDF");
try {
// 生成登记备案表模板PDF
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Document document = new Document(PageSize.A4, 50, 50, 50, 50);
PdfWriter writer = PdfWriter.getInstance(document, baos);
document.open();
// 创建支持中文的字体
BaseFont baseFont;
try {
// 使用iText内置的中文字体支持 - 需要itext-asian依赖
baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
log.info("✅ 成功加载中文字体: STSong-Light");
} catch (Exception e) {
log.warn("⚠️ 无法加载STSong-Light字体: {}", e.getMessage());
try {
// 备选方案:使用宋体标准版
baseFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
log.info("✅ 成功加载中文字体: STSongStd-Light");
} catch (Exception e2) {
log.error("❌ 无法加载任何中文字体,PDF将显示空白");
log.error("💡 解决方案: 确保已添加itext-asian依赖并重启服务");
// 抛出异常,不生成空白PDF
throw new ServiceException("无法加载中文字体,请检查itext-asian依赖配置");
}
}
Font titleFont = new Font(baseFont, 16, Font.BOLD);
Font headerFont = new Font(baseFont, 12, Font.BOLD);
Font normalFont = new Font(baseFont, 10, Font.NORMAL);
Font smallFont = new Font(baseFont, 8, Font.NORMAL);
// 标题 - 修改为纯中文版本
Paragraph title = new Paragraph("登记报备人员因私事出国(境)登记表", titleFont);
title.setAlignment(Element.ALIGN_CENTER);
title.setSpacingAfter(30f);
document.add(title);
// 编号
Paragraph codeP = new Paragraph("编号:_______________", normalFont);
codeP.setAlignment(Element.ALIGN_RIGHT);
codeP.setSpacingAfter(15f);
document.add(codeP);
// 创建表格
PdfPTable table = new PdfPTable(4);
table.setWidthPercentage(100);
table.setWidths(new float[]{2f, 3f, 2f, 3f});
// 基本信息部分
addFormCell(table, "姓名", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "性别", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "出生日期", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "身份证号", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "政治面貌", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "户籍", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "工作部门", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "职务职称", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "行政职级", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "人员类别", headerFont);
addFormCell(table, "", normalFont);
// 责任单位信息
PdfPCell responsibleUnitCell = new PdfPCell(new Phrase("登记备案责任单位", headerFont));
responsibleUnitCell.setColspan(2);
responsibleUnitCell.setMinimumHeight(40f);
responsibleUnitCell.setPadding(8f);
responsibleUnitCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(responsibleUnitCell);
PdfPCell sealCell = new PdfPCell(new Phrase("盖章", headerFont));
sealCell.setColspan(2);
sealCell.setMinimumHeight(40f);
sealCell.setPadding(8f);
sealCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
table.addCell(sealCell);
// 联系信息
addFormCell(table, "联系人", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "联系电话", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "负责人签名", headerFont);
addFormCell(table, "", normalFont);
addFormCell(table, "日期", headerFont);
addFormCell(table, "", normalFont);
document.add(table);
// 照片区域
Paragraph photoNote = new Paragraph("照片粘贴处(2寸彩色证件照,白色或浅蓝色背景,32MM×26MM)", smallFont);
photoNote.setAlignment(Element.ALIGN_RIGHT);
photoNote.setSpacingBefore(15f);
photoNote.setSpacingAfter(15f);
document.add(photoNote);
// 说明
Paragraph instructions = new Paragraph("填表说明:人员类别代码根据报备人员属于报备人员范围中的情况,填写所属项的序号即可。", smallFont);
instructions.setSpacingBefore(10f);
document.add(instructions);
document.close();
// 获取PDF字节数组
byte[] pdfBytes = baos.toByteArray();
if (pdfBytes.length == 0) {
throw new ServiceException("生成的PDF文件为空");
}
log.info("PDF文件生成成功,大小: {} bytes", pdfBytes.length);
// 设置响应头
response.setContentType("application/pdf");
String fileName = "登记报备人员因私事出国(境)登记表.pdf";
String encodedFileName = java.net.URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
response.setContentLength(pdfBytes.length);
// 输出PDF
response.getOutputStream().write(pdfBytes);
response.getOutputStream().flush();
log.info("登记备案表模板下载成功,文件大小: {} bytes", pdfBytes.length);
} catch (Exception e) {
log.error("下载登记备案表模板失败", e);
try {
if (!response.isCommitted()) {
response.reset();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":500,\"msg\":\"下载模板失败: " + e.getMessage() + "\"}");
}
} catch (IOException ioEx) {
log.error("发送错误响应失败", ioEx);
}
throw new ServiceException("下载模板失败: " + e.getMessage());
}
}
@Override
public void downloadPartyMembershipTemplate(HttpServletResponse response) {
log.info("开始生成保留党籍申请模板PDF");
try {
// 生成保留党籍申请模板PDF
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Document document = new Document(PageSize.A4, 50, 50, 50, 50);
PdfWriter writer = PdfWriter.getInstance(document, baos);
document.open();
// 创建支持中文的字体
BaseFont baseFont;
try {
// 使用iText内置的中文字体支持 - 需要itext-asian依赖
baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
log.info("✅ 成功加载中文字体: STSong-Light");
} catch (Exception e) {
log.warn("⚠️ 无法加载STSong-Light字体: {}", e.getMessage());
try {
// 备选方案:使用宋体标准版
baseFont = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
log.info("✅ 成功加载中文字体: STSongStd-Light");
} catch (Exception e2) {
log.error("❌ 无法加载任何中文字体,PDF将显示空白");
log.error("💡 解决方案: 确保已添加itext-asian依赖并重启服务");
// 抛出异常,不生成空白PDF
throw new ServiceException("无法加载中文字体,请检查itext-asian依赖配置");
}
}
Font titleFont = new Font(baseFont, 16, Font.BOLD);
Font normalFont = new Font(baseFont, 12, Font.NORMAL);
Font smallFont = new Font(baseFont, 10, Font.NORMAL);
// 标题
Paragraph title = new Paragraph("关于因私赴 ______ 探亲保留党籍的申请", titleFont);
title.setAlignment(Element.ALIGN_CENTER);
title.setSpacingAfter(40f);
document.add(title);
// 收件人
Paragraph recipient = new Paragraph("________________________ 支部:", normalFont);
recipient.setSpacingAfter(30f);
document.add(recipient);
// 正文段落1
String paragraph1 = " 本人 ______ (身份证号),现任 ______ 。计划于 ______ 年 ______ 月 ______ 日至 ______ 月 ______ 日,借配偶 ______ ,因私赴 ______ 探望在 ______ 的工作(留学)的孙女 ______ ,在外停留 ______ 天(含离、抵我国国境当日)。现申请保留 ______ 年 ______ 月 ______ 日至 ______ 年 ______ 月 ______ 日期间的党籍。";
Paragraph content1 = new Paragraph(paragraph1, normalFont);
content1.setSpacingAfter(30f);
content1.setAlignment(Element.ALIGN_JUSTIFIED);
content1.setIndentationLeft(0f);
document.add(content1);
// 正文段落2
Paragraph content2 = new Paragraph(" 特此申请,望予批准。", normalFont);
content2.setSpacingAfter(80f);
content2.setAlignment(Element.ALIGN_JUSTIFIED);
document.add(content2);
// 申请人署名区域
Paragraph applicant = new Paragraph("申请人:", normalFont);
applicant.setAlignment(Element.ALIGN_RIGHT);
applicant.setSpacingAfter(30f);
document.add(applicant);
// 日期
Paragraph date = new Paragraph("______ 年 ______ 月 ______ 日", normalFont);
date.setAlignment(Element.ALIGN_RIGHT);
document.add(date);
document.close();
// 获取PDF字节数组
byte[] pdfBytes = baos.toByteArray();
if (pdfBytes.length == 0) {
throw new ServiceException("生成的PDF文件为空");
}
log.info("保留党籍申请PDF文件生成成功,大小: {} bytes", pdfBytes.length);
// 设置响应头
response.setContentType("application/pdf");
String fileName = "关于因私赴XXX探亲保留党籍的申请.pdf";
String encodedFileName = java.net.URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
response.setContentLength(pdfBytes.length);
// 输出PDF
response.getOutputStream().write(pdfBytes);
response.getOutputStream().flush();
log.info("保留党籍申请模板下载成功,文件大小: {} bytes", pdfBytes.length);
} catch (Exception e) {
log.error("下载保留党籍申请模板失败", e);
try {
if (!response.isCommitted()) {
response.reset();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":500,\"msg\":\"下载模板失败: " + e.getMessage() + "\"}");
}
} catch (IOException ioEx) {
log.error("发送错误响应失败", ioEx);
}
throw new ServiceException("下载模板失败: " + e.getMessage());
}
}
private void addFormCell(PdfPTable table, String text, Font font) {
PdfPCell cell = new PdfPCell(new Phrase(text, font));
cell.setHorizontalAlignment(Element.ALIGN_LEFT);
cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
cell.setPadding(8f);
cell.setMinimumHeight(30f);
cell.setBorder(Rectangle.BOX);
cell.setBorderWidth(0.5f);
table.addCell(cell);
}
}
@@ -0,0 +1,212 @@
package org.dromara.rs.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.oss.core.OssClient;
import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.factory.OssFactory;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.rs.domain.RsOverseasApplicationAttachment;
import org.dromara.rs.domain.bo.RsOverseasApplicationAttachmentBo;
import org.dromara.rs.domain.vo.RsOverseasApplicationAttachmentVo;
import org.dromara.rs.mapper.RsOverseasApplicationAttachmentMapper;
import org.dromara.rs.service.IRsOverseasApplicationAttachmentService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 出国申请附件Service实现
*
* @author weizzzz
* @date 2025-10-10
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class RsOverseasApplicationAttachmentServiceImpl implements IRsOverseasApplicationAttachmentService {
private final RsOverseasApplicationAttachmentMapper attachmentMapper;
/**
* 查询出国申请的附件列表
*/
@Override
public List<RsOverseasApplicationAttachmentVo> selectAttachmentList(Long applicationId) {
List<RsOverseasApplicationAttachmentVo> list = attachmentMapper.selectAttachmentList(applicationId);
// 格式化文件大小
list.forEach(vo -> vo.setFileSizeFormatted(formatFileSize(vo.getFileSize())));
return list;
}
/**
* 查询附件详情
*/
@Override
public RsOverseasApplicationAttachmentVo selectAttachmentById(Long attachmentId) {
RsOverseasApplicationAttachmentVo vo = attachmentMapper.selectAttachmentById(attachmentId);
if (vo != null) {
vo.setFileSizeFormatted(formatFileSize(vo.getFileSize()));
}
return vo;
}
/**
* 上传附件
*/
@Override
@Transactional(rollbackFor = Exception.class)
public RsOverseasApplicationAttachmentVo uploadAttachment(Long applicationId, String title, String content,
MultipartFile file) throws IOException {
if (file == null || file.isEmpty()) {
throw new ServiceException("上传文件不能为空");
}
if (StringUtils.isBlank(title)) {
throw new ServiceException("附件标题不能为空");
}
// 验证文件大小(限制50MB)
long maxSize = 50 * 1024 * 1024; // 50MB
if (file.getSize() > maxSize) {
throw new ServiceException("文件大小超过50MB限制");
}
// 获取文件信息
String originalFilename = file.getOriginalFilename();
String fileExtension = getFileExtension(originalFilename);
String contentType = file.getContentType();
// 生成文件名
String fileName = "overseas_attachment_" + applicationId + "_" + System.currentTimeMillis()
+ "." + fileExtension;
// 上传到OSS
OssClient ossClient = OssFactory.instance();
String filePath = "overseas/attachments/" + fileName;
UploadResult uploadResult = ossClient.upload(file.getInputStream(), filePath,
file.getSize(), contentType);
// 保存附件信息到数据库
RsOverseasApplicationAttachment attachment = new RsOverseasApplicationAttachment();
attachment.setApplicationId(applicationId);
attachment.setAttachmentTitle(title);
attachment.setAttachmentContent(content);
attachment.setFileName(fileName);
attachment.setFileOriginalName(originalFilename);
attachment.setFilePath(uploadResult.getUrl());
attachment.setFileSize(file.getSize());
attachment.setFileType(fileExtension);
attachment.setMimeType(contentType);
attachment.setUploadTime(new Date());
// 设置上传人信息
try {
Long userId = LoginHelper.getUserId();
String username = LoginHelper.getUsername();
attachment.setUploadUserId(userId);
attachment.setUploadUserName(username);
} catch (Exception e) {
log.warn("获取用户信息失败: {}", e.getMessage());
}
attachmentMapper.insert(attachment);
// 返回VO
RsOverseasApplicationAttachmentVo vo = BeanUtil.copyProperties(attachment, RsOverseasApplicationAttachmentVo.class);
vo.setFileSizeFormatted(formatFileSize(file.getSize()));
return vo;
}
/**
* 更新附件信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateAttachment(RsOverseasApplicationAttachmentBo bo) {
RsOverseasApplicationAttachment attachment = BeanUtil.copyProperties(bo, RsOverseasApplicationAttachment.class);
return attachmentMapper.updateById(attachment) > 0;
}
/**
* 删除附件
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteAttachment(Long attachmentId) {
// 查询附件信息
RsOverseasApplicationAttachment attachment = attachmentMapper.selectById(attachmentId);
if (attachment == null) {
throw new ServiceException("附件不存在");
}
// 删除OSS文件(可选,根据业务需求决定是否物理删除)
try {
OssClient ossClient = OssFactory.instance();
// ossClient.delete(attachment.getFilePath());
} catch (Exception e) {
log.warn("删除OSS文件失败: {}", e.getMessage());
}
// 逻辑删除数据库记录
return attachmentMapper.deleteById(attachmentId) > 0;
}
/**
* 批量删除附件
*/
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteAttachments(Long[] attachmentIds) {
if (attachmentIds == null || attachmentIds.length == 0) {
return false;
}
return attachmentMapper.deleteBatchIds(Arrays.asList(attachmentIds)) > 0;
}
/**
* 获取文件扩展名
*/
private String getFileExtension(String filename) {
if (StrUtil.isBlank(filename)) {
return "";
}
int lastDotIndex = filename.lastIndexOf(".");
if (lastDotIndex > 0 && lastDotIndex < filename.length() - 1) {
return filename.substring(lastDotIndex + 1).toLowerCase();
}
return "";
}
/**
* 格式化文件大小
*/
private String formatFileSize(Long size) {
if (size == null || size == 0) {
return "0 B";
}
DecimalFormat df = new DecimalFormat("#.##");
if (size < 1024) {
return size + " B";
} else if (size < 1024 * 1024) {
return df.format(size / 1024.0) + " KB";
} else if (size < 1024 * 1024 * 1024) {
return df.format(size / (1024.0 * 1024.0)) + " MB";
} else {
return df.format(size / (1024.0 * 1024.0 * 1024.0)) + " GB";
}
}
}
@@ -0,0 +1,265 @@
package org.dromara.rs.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.rs.domain.RsOverseasItineraryChange;
import org.dromara.rs.domain.bo.RsOverseasItineraryChangeBo;
import org.dromara.rs.domain.query.RsOverseasItineraryChangeQuery;
import org.dromara.rs.domain.vo.RsOverseasItineraryChangeVo;
import org.dromara.rs.mapper.RsOverseasItineraryChangeMapper;
import org.dromara.rs.service.IRsOverseasItineraryChangeService;
import org.dromara.rs.service.IRsOverseasApplicationService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 出国行程变更申请Service业务层处理
*
* @author weizzzz
* @date 2025-10-13
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class RsOverseasItineraryChangeServiceImpl implements IRsOverseasItineraryChangeService {
private final RsOverseasItineraryChangeMapper changeMapper;
private final IRsOverseasApplicationService applicationService;
/**
* 查询出国行程变更申请分页列表
*/
@Override
public TableDataInfo<RsOverseasItineraryChangeVo> queryPageList(RsOverseasItineraryChangeQuery query) {
List<RsOverseasItineraryChangeVo> list = changeMapper.selectChangeList(query);
return TableDataInfo.build(list);
}
/**
* 查询出国行程变更申请列表
*/
@Override
public List<RsOverseasItineraryChangeVo> queryList(RsOverseasItineraryChangeQuery query) {
return changeMapper.selectChangeList(query);
}
/**
* 查询出国行程变更申请详情
*/
@Override
public RsOverseasItineraryChangeVo queryById(Long changeId) {
return changeMapper.selectChangeById(changeId);
}
/**
* 查询我的变更记录列表(当前登录用户的已通过变更申请)
* 说明:变更记录Tab只显示已通过审批的变更申请,这些是真正生效的变更记录
*/
@Override
public TableDataInfo<RsOverseasItineraryChangeVo> queryMyChangeList(RsOverseasItineraryChangeQuery query) {
Long userId = LoginHelper.getUserId();
query.setApplicantId(userId);
query.setStatus("1"); // 只查询已通过的变更申请
return queryPageList(query);
}
/**
* 查询待审批的变更申请列表
*/
@Override
public TableDataInfo<RsOverseasItineraryChangeVo> queryPendingList(RsOverseasItineraryChangeQuery query) {
query.setStatus("0"); // 待审核状态
return queryPageList(query);
}
/**
* 新增出国行程变更申请
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(RsOverseasItineraryChangeBo bo) {
// 查询出国申请信息
org.dromara.rs.domain.vo.RsOverseasApplicationVo application = applicationService.queryById(bo.getApplicationId());
if (application == null) {
throw new ServiceException("出国申请不存在");
}
// 检查申请状态是否为已通过
if (!"1".equals(application.getStatus())) {
throw new ServiceException("只能为已通过审批的出国申请提交变更申请");
}
// 检查是否有未完成的变更申请
LambdaQueryWrapper<RsOverseasItineraryChange> wrapper = Wrappers.lambdaQuery();
wrapper.eq(RsOverseasItineraryChange::getApplicationId, bo.getApplicationId())
.in(RsOverseasItineraryChange::getStatus, Arrays.asList("0")) // 待审核
.eq(RsOverseasItineraryChange::getDeleted, 0);
Long count = changeMapper.selectCount(wrapper);
if (count > 0) {
throw new ServiceException("该出国申请已有待审核的变更申请,请先处理");
}
// 转换BO到Entity
RsOverseasItineraryChange entity = BeanUtil.toBean(bo, RsOverseasItineraryChange.class);
// 设置申请人信息
Long userId = LoginHelper.getUserId();
entity.setApplicantId(userId);
entity.setApplicantName(LoginHelper.getUsername());
entity.setApplicationId(bo.getApplicationId());
// 从出国申请中复制原始信息
entity.setOriginalDestination(application.getDestination());
entity.setOriginalDestinationCity(application.getDestinationCity());
entity.setOriginalExitReason(application.getExitReason() != null ? application.getExitReason() : application.getPurposeDetail());
entity.setOriginalStartDate(application.getStartDate());
entity.setOriginalEndDate(application.getEndDate());
entity.setOriginalDuration(application.getDuration());
entity.setOriginalItinerary(application.getItinerary());
entity.setOriginalAccommodationPlan(application.getAccommodationPlan());
entity.setOriginalTransportationPlan(application.getTransportationPlan());
// 设置初始状态为待审核
entity.setStatus("0");
entity.setTenantId(LoginHelper.getTenantId());
return changeMapper.insert(entity) > 0;
}
/**
* 修改出国行程变更申请(重新提交)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(RsOverseasItineraryChangeBo bo) {
// 查询原变更记录
RsOverseasItineraryChange original = changeMapper.selectById(bo.getChangeId());
if (original == null) {
throw new ServiceException("变更申请不存在");
}
// 只有待审核或已拒绝的申请可以修改
if (!"0".equals(original.getStatus()) && !"2".equals(original.getStatus())) {
throw new ServiceException("只有待审核或已拒绝的变更申请可以修改");
}
// 检查权限:只能修改自己的申请
Long userId = LoginHelper.getUserId();
if (!userId.equals(original.getApplicantId())) {
throw new ServiceException("只能修改自己的变更申请");
}
// 转换BO到Entity
RsOverseasItineraryChange entity = BeanUtil.toBean(bo, RsOverseasItineraryChange.class);
// 重新提交时,状态改为待审核
entity.setStatus("0");
entity.setApprovalOpinion(null);
entity.setApprovalTime(null);
entity.setApproverId(null);
entity.setApproverName(null);
return changeMapper.updateById(entity) > 0;
}
/**
* 删除出国行程变更申请
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteByIds(List<Long> changeIds) {
Long userId = LoginHelper.getUserId();
for (Long changeId : changeIds) {
RsOverseasItineraryChange change = changeMapper.selectById(changeId);
if (change == null) {
continue;
}
// 检查权限:只能删除自己的申请
if (!userId.equals(change.getApplicantId())) {
throw new ServiceException("只能删除自己的变更申请");
}
// 只有待审核或已拒绝的申请可以删除
if (!"0".equals(change.getStatus()) && !"2".equals(change.getStatus())) {
throw new ServiceException("只有待审核或已拒绝的变更申请可以删除");
}
}
return changeMapper.deleteBatchIds(changeIds) > 0;
}
/**
* 撤回变更申请
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean cancelChange(Long changeId) {
RsOverseasItineraryChange change = changeMapper.selectById(changeId);
if (change == null) {
throw new ServiceException("变更申请不存在");
}
// 检查权限
Long userId = LoginHelper.getUserId();
if (!userId.equals(change.getApplicantId())) {
throw new ServiceException("只能撤回自己的变更申请");
}
// 只有待审核的申请可以撤回
if (!"0".equals(change.getStatus())) {
throw new ServiceException("只有待审核的变更申请可以撤回");
}
// 更新状态为已撤回
change.setStatus("3");
return changeMapper.updateById(change) > 0;
}
/**
* 审批变更申请
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean approveChange(Long changeId, Boolean approved, String opinion) {
RsOverseasItineraryChange change = changeMapper.selectById(changeId);
if (change == null) {
throw new ServiceException("变更申请不存在");
}
// 只有待审核的申请可以审批
if (!"0".equals(change.getStatus())) {
throw new ServiceException("只有待审核的变更申请可以审批");
}
// 更新审批信息
Long userId = LoginHelper.getUserId();
change.setStatus(approved ? "1" : "2"); // 1=已通过,2=已拒绝
change.setApprovalOpinion(opinion);
change.setApprovalTime(new Date());
change.setApproverId(userId);
change.setApproverName(LoginHelper.getUsername());
return changeMapper.updateById(change) > 0;
}
/**
* 根据出国记录ID查询变更记录列表
*/
@Override
public List<RsOverseasItineraryChangeVo> queryByRecordId(Long recordId) {
return changeMapper.selectChangeListByRecordId(recordId);
}
}
@@ -0,0 +1,844 @@
package org.dromara.rs.service.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import jakarta.servlet.http.HttpServletResponse;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.rs.mapper.RsOverseasRegistrationMapper;
import org.dromara.rs.mapper.RsOverseasApplicationMapper;
import org.dromara.rs.domain.RsOverseasRegistration;
import org.dromara.rs.domain.bo.RsOverseasRegistrationBo;
import org.dromara.rs.domain.vo.RsOverseasRegistrationVo;
import org.dromara.rs.domain.vo.RsOverseasApplicationVo;
import org.dromara.rs.domain.query.RsOverseasRegistrationQuery;
import org.dromara.rs.service.IRsOverseasRegistrationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 出国登记Service业务层处理
*
* @author weizzzz
* @date 2025-09-30
*/
@Service
public class RsOverseasRegistrationServiceImpl implements IRsOverseasRegistrationService {
private static final Logger log = LoggerFactory.getLogger(RsOverseasRegistrationServiceImpl.class);
@Autowired
private RsOverseasRegistrationMapper baseMapper;
@Autowired
private RsOverseasApplicationMapper applicationMapper;
/**
* 查询出国登记分页列表
*/
@Override
public TableDataInfo<RsOverseasRegistrationVo> queryPageList(RsOverseasRegistrationQuery query) {
log.info("============= 出国登记列表查询开始 =============");
log.info("查询参数: pageNum={}, pageSize={}, name={}, status={}",
query.getPageNum(), query.getPageSize(), query.getName(), query.getStatus());
List<RsOverseasRegistrationVo> list = baseMapper.selectRegistrationList(query);
log.info("查询结果: 总记录数={}", list != null ? list.size() : 0);
if (list != null && !list.isEmpty()) {
log.info("第一条记录: registrationId={}, name={}, status={}, tenantId={}",
list.get(0).getRegistrationId(),
list.get(0).getName(),
list.get(0).getStatus(),
list.get(0).getTenantId());
}
// 手动分页处理
int pageNum = query.getPageNum();
int pageSize = query.getPageSize();
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, list.size());
List<RsOverseasRegistrationVo> pageList;
if (start > list.size()) {
pageList = Arrays.asList();
} else {
pageList = list.subList(start, end);
}
log.info("分页结果: start={}, end={}, pageListSize={}", start, end, pageList.size());
Page<RsOverseasRegistrationVo> page = new Page<>(pageNum, pageSize);
page.setRecords(pageList);
page.setTotal(list.size());
TableDataInfo<RsOverseasRegistrationVo> result = TableDataInfo.build(page);
log.info("最终返回: total={}, rows.size={}", result.getTotal(), result.getRows() != null ? result.getRows().size() : 0);
log.info("============= 出国登记列表查询结束 =============");
return result;
}
/**
* 查询我的出国登记分页列表
*/
@Override
public TableDataInfo<RsOverseasRegistrationVo> queryMyRegistrationList(RsOverseasRegistrationQuery query) {
// 设置当前用户ID
query.setCreateBy(LoginHelper.getUserId());
List<RsOverseasRegistrationVo> list = baseMapper.selectMyRegistrationList(query);
// 手动分页处理
int pageNum = query.getPageNum();
int pageSize = query.getPageSize();
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, list.size());
List<RsOverseasRegistrationVo> pageList;
if (start > list.size()) {
pageList = Arrays.asList();
} else {
pageList = list.subList(start, end);
}
Page<RsOverseasRegistrationVo> page = new Page<>(pageNum, pageSize);
page.setRecords(pageList);
page.setTotal(list.size());
return TableDataInfo.build(page);
}
/**
* 查询出国登记列表
*/
@Override
public List<RsOverseasRegistrationVo> queryList(RsOverseasRegistrationQuery query) {
return baseMapper.selectRegistrationList(query);
}
/**
* 查询出国登记详细信息
*/
@Override
public RsOverseasRegistrationVo queryById(Long registrationId) {
return baseMapper.selectRegistrationById(registrationId);
}
/**
* 根据申请ID查询出国登记信息
*/
@Override
public RsOverseasRegistrationVo queryByApplicationId(Long applicationId) {
return baseMapper.selectRegistrationByApplicationId(applicationId);
}
/**
* 根据状态查询出国登记列表
*/
@Override
public List<RsOverseasRegistrationVo> queryByStatus(String status) {
return baseMapper.selectRegistrationsByStatus(status);
}
/**
* 根据姓名查询出国登记列表
*/
@Override
public List<RsOverseasRegistrationVo> queryByName(String name) {
return baseMapper.selectRegistrationsByName(name);
}
/**
* 新增出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(RsOverseasRegistrationBo bo) {
// 🔍 调试日志:检查Bo中的基础字段
log.info("========== 出国登记新增 - 调试信息 ==========");
log.info("Bo中的createBy: {}", bo.getCreateBy());
log.info("Bo中的createTime: {}", bo.getCreateTime());
log.info("Bo中的createDept: {}", bo.getCreateDept());
// ⚠️ 强制清空Bo中的基础字段,让MyBatis-Plus自动填充
bo.setCreateBy(null);
bo.setCreateTime(null);
bo.setUpdateBy(null);
bo.setUpdateTime(null);
// createDept保留,因为自动填充逻辑会使用它
RsOverseasRegistration add = MapstructUtils.convert(bo, RsOverseasRegistration.class);
log.info("转换后Entity中的createBy: {}", add.getCreateBy());
log.info("转换后Entity中的createTime: {}", add.getCreateTime());
log.info("转换后Entity中的createDept: {}", add.getCreateDept());
// 设置状态为草稿
if (StringUtils.isEmpty(add.getStatus())) {
add.setStatus("0");
}
// 🔧 修复:设置登记时间为当前时间(修复列表不显示数据的问题)
if (add.getSubmitTime() == null) {
add.setSubmitTime(new Date());
}
// 计算停留天数
if (add.getDepartureDate() != null && add.getReturnDate() != null) {
long days = (add.getReturnDate().getTime() - add.getDepartureDate().getTime()) / (1000 * 60 * 60 * 24);
add.setStayDays((int) days);
}
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setRegistrationId(add.getRegistrationId());
log.info("插入成功后Entity中的createBy: {}", add.getCreateBy());
log.info("插入成功后Entity中的createTime: {}", add.getCreateTime());
log.info("插入成功后Entity中的submitTime: {}", add.getSubmitTime());
}
log.info("==========================================");
return flag;
}
/**
* 新增并提交出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean addAndSubmit(RsOverseasRegistrationBo bo) {
// ⚠️ 强制清空Bo中的基础字段,让MyBatis-Plus自动填充
bo.setCreateBy(null);
bo.setCreateTime(null);
bo.setUpdateBy(null);
bo.setUpdateTime(null);
// 1. 先新增登记记录
RsOverseasRegistration add = MapstructUtils.convert(bo, RsOverseasRegistration.class);
// 直接设置为待审核状态
add.setStatus("1"); // 待审核
add.setSubmitTime(new Date());
// 计算停留天数
if (add.getDepartureDate() != null && add.getReturnDate() != null) {
long days = (add.getReturnDate().getTime() - add.getDepartureDate().getTime()) / (1000 * 60 * 60 * 24);
add.setStayDays((int) days);
}
boolean flag = baseMapper.insert(add) > 0;
if (!flag) {
throw new RuntimeException("保存出国登记失败!");
}
bo.setRegistrationId(add.getRegistrationId());
log.info("新增并提交成功,registrationId: {}, createBy: {}, createTime: {}, submitTime: {}",
add.getRegistrationId(), add.getCreateBy(), add.getCreateTime(), add.getSubmitTime());
return true;
}
/**
* 修改出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(RsOverseasRegistrationBo bo) {
RsOverseasRegistration update = MapstructUtils.convert(bo, RsOverseasRegistration.class);
// 计算停留天数
if (update.getDepartureDate() != null && update.getReturnDate() != null) {
long days = (update.getReturnDate().getTime() - update.getDepartureDate().getTime()) / (1000 * 60 * 60 * 24);
update.setStayDays((int) days);
}
return baseMapper.updateById(update) > 0;
}
/**
* 校验并批量删除出国登记信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 校验:不能删除待审核或已通过的登记
for (Long id : ids) {
RsOverseasRegistrationVo vo = baseMapper.selectRegistrationById(id);
if (vo != null && ("1".equals(vo.getStatus()) || "2".equals(vo.getStatus()))) {
throw new RuntimeException("不能删除待审核或已通过的出国登记!");
}
}
}
// 逻辑删除
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 提交出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean submitRegistration(Long registrationId) {
RsOverseasRegistrationVo vo = baseMapper.selectRegistrationById(registrationId);
if (vo == null) {
throw new RuntimeException("出国登记不存在!");
}
if (!"0".equals(vo.getStatus()) && !"3".equals(vo.getStatus())) {
throw new RuntimeException("只有草稿或已拒绝状态的登记才能提交!");
}
RsOverseasRegistration update = new RsOverseasRegistration();
update.setRegistrationId(registrationId);
update.setStatus("1");
update.setSubmitTime(new Date());
return baseMapper.updateById(update) > 0;
}
/**
* 确认出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean confirmRegistration(Long registrationId) {
RsOverseasRegistrationVo vo = baseMapper.selectRegistrationById(registrationId);
if (vo == null) {
throw new RuntimeException("出国登记不存在!");
}
if (!"1".equals(vo.getStatus())) {
throw new RuntimeException("只有待审核状态的登记才能确认(通过)!");
}
RsOverseasRegistration update = new RsOverseasRegistration();
update.setRegistrationId(registrationId);
update.setStatus("2");
return baseMapper.updateById(update) > 0;
}
/**
* 撤回出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean withdrawRegistration(Long registrationId) {
RsOverseasRegistrationVo vo = baseMapper.selectRegistrationById(registrationId);
if (vo == null) {
throw new RuntimeException("出国登记不存在!");
}
if (!"1".equals(vo.getStatus())) {
throw new RuntimeException("只有待审核状态的登记才能撤回!");
}
RsOverseasRegistration update = new RsOverseasRegistration();
update.setRegistrationId(registrationId);
update.setStatus("3");
return baseMapper.updateById(update) > 0;
}
/**
* 基于出国申请创建登记记录
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean createFromApplication(Long applicationId) {
// 查询出国申请信息
RsOverseasApplicationVo application = applicationMapper.selectOverseasApplicationById(applicationId);
if (application == null) {
throw new RuntimeException("出国申请不存在!");
}
// 检查申请状态是否为已通过
if (!"1".equals(application.getStatus())) {
throw new RuntimeException("只有已通过的申请才能创建登记记录!");
}
// 检查是否已创建登记记录
RsOverseasRegistrationVo existing = baseMapper.selectRegistrationByApplicationId(applicationId);
if (existing != null) {
throw new RuntimeException("该申请已经创建过登记记录!");
}
// 创建登记记录
RsOverseasRegistration registration = new RsOverseasRegistration();
registration.setApplicationId(applicationId);
registration.setName(application.getApplicantName());
registration.setGender(application.getGender());
registration.setBirthDate(application.getBirthDate());
registration.setPoliticalStatus(application.getPoliticalStatus());
registration.setWorkUnitPosition(application.getDepartment() + " " + application.getPosition());
registration.setContactPhone(application.getMobilePhone());
registration.setDepartureDate(application.getStartDate());
registration.setReturnDate(application.getEndDate());
registration.setStayDays(application.getDuration());
registration.setDestinationCountry(application.getDestination());
registration.setDestinationDetail(application.getDestinationCity());
registration.setReason(application.getPurposeDetail());
registration.setRegistrationUnit(application.getRegistrationUnit());
registration.setTravelType("1"); // 默认因公出国
registration.setStatus("0"); // 草稿状态
return baseMapper.insert(registration) > 0;
}
/**
* 统计各状态的登记数量
*/
@Override
public List<Map<String, Object>> getStatusStatistics(RsOverseasRegistrationQuery query) {
return baseMapper.selectStatusStatistics(query);
}
/**
* 批量提交出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchSubmitRegistrations(List<Long> registrationIds) {
for (Long registrationId : registrationIds) {
submitRegistration(registrationId);
}
return true;
}
/**
* 批量确认出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean batchConfirmRegistrations(List<Long> registrationIds) {
for (Long registrationId : registrationIds) {
confirmRegistration(registrationId);
}
return true;
}
/**
* 获取可用的登记选项(字典数据)
*/
@Override
public Map<String, List<Map<String, Object>>> getRegistrationOptions() {
Map<String, List<Map<String, Object>>> options = new HashMap<>();
// 性别选项
List<Map<String, Object>> genderOptions = new ArrayList<>();
genderOptions.add(Map.of("value", "0", "label", "男"));
genderOptions.add(Map.of("value", "1", "label", "女"));
options.put("gender", genderOptions);
// 政治面貌选项
List<Map<String, Object>> politicalOptions = new ArrayList<>();
politicalOptions.add(Map.of("value", "中共党员", "label", "中共党员"));
politicalOptions.add(Map.of("value", "中共预备党员", "label", "中共预备党员"));
politicalOptions.add(Map.of("value", "共青团员", "label", "共青团员"));
politicalOptions.add(Map.of("value", "民革党员", "label", "民革党员"));
politicalOptions.add(Map.of("value", "民盟盟员", "label", "民盟盟员"));
politicalOptions.add(Map.of("value", "民建会员", "label", "民建会员"));
politicalOptions.add(Map.of("value", "民进会员", "label", "民进会员"));
politicalOptions.add(Map.of("value", "农工党党员", "label", "农工党党员"));
politicalOptions.add(Map.of("value", "致公党党员", "label", "致公党党员"));
politicalOptions.add(Map.of("value", "九三学社社员", "label", "九三学社社员"));
politicalOptions.add(Map.of("value", "台盟盟员", "label", "台盟盟员"));
politicalOptions.add(Map.of("value", "无党派人士", "label", "无党派人士"));
politicalOptions.add(Map.of("value", "群众", "label", "群众"));
options.put("politicalStatus", politicalOptions);
// 行政职级选项
List<Map<String, Object>> levelOptions = new ArrayList<>();
levelOptions.add(Map.of("value", "正厅级", "label", "正厅级"));
levelOptions.add(Map.of("value", "副厅级", "label", "副厅级"));
levelOptions.add(Map.of("value", "正处级", "label", "正处级"));
levelOptions.add(Map.of("value", "副处级", "label", "副处级"));
levelOptions.add(Map.of("value", "正科级", "label", "正科级"));
levelOptions.add(Map.of("value", "副科级", "label", "副科级"));
levelOptions.add(Map.of("value", "科员", "label", "科员"));
levelOptions.add(Map.of("value", "办事员", "label", "办事员"));
options.put("administrativeLevel", levelOptions);
// 人员类别代码选项
List<Map<String, Object>> categoryOptions = new ArrayList<>();
categoryOptions.add(Map.of("value", "A", "label", "A-厅级干部"));
categoryOptions.add(Map.of("value", "B", "label", "B-处级干部"));
categoryOptions.add(Map.of("value", "C", "label", "C-科级干部"));
categoryOptions.add(Map.of("value", "D", "label", "D-一般干部"));
categoryOptions.add(Map.of("value", "E", "label", "E-专业技术人员"));
categoryOptions.add(Map.of("value", "F", "label", "F-工勤人员"));
options.put("personnelCategory", categoryOptions);
// 登记状态选项(参考出国申请状态)
List<Map<String, Object>> statusOptions = new ArrayList<>();
statusOptions.add(Map.of("value", "0", "label", "草稿"));
statusOptions.add(Map.of("value", "1", "label", "待审核"));
statusOptions.add(Map.of("value", "2", "label", "已通过"));
statusOptions.add(Map.of("value", "3", "label", "已拒绝"));
options.put("status", statusOptions);
return options;
}
/**
* 转换字典值为文本
*/
private void convertDictToText(RsOverseasRegistrationVo vo) {
// 性别转换
if ("0".equals(vo.getGender())) {
vo.setGenderText("男");
} else if ("1".equals(vo.getGender())) {
vo.setGenderText("女");
}
// 政治面貌转换
vo.setPoliticalStatusText(vo.getPoliticalStatus());
// 行政职级转换
vo.setAdministrativeLevelText(vo.getAdministrativeLevel());
// 人员类别代码转换
if (StringUtils.isNotEmpty(vo.getPersonnelCategory())) {
switch (vo.getPersonnelCategory()) {
case "A" -> vo.setPersonnelCategoryText("A-厅级干部");
case "B" -> vo.setPersonnelCategoryText("B-处级干部");
case "C" -> vo.setPersonnelCategoryText("C-科级干部");
case "D" -> vo.setPersonnelCategoryText("D-一般干部");
case "E" -> vo.setPersonnelCategoryText("E-专业技术人员");
case "F" -> vo.setPersonnelCategoryText("F-工勤人员");
default -> vo.setPersonnelCategoryText(vo.getPersonnelCategory());
}
}
// 状态转换
if (StringUtils.isNotEmpty(vo.getStatus())) {
switch (vo.getStatus()) {
case "0" -> vo.setStatusText("草稿");
case "1" -> vo.setStatusText("待审核");
case "2" -> vo.setStatusText("已通过");
case "3" -> vo.setStatusText("已拒绝");
default -> vo.setStatusText(vo.getStatus());
}
}
}
@Override
public Map<String, Object> getTrendData() {
Map<String, Object> result = new HashMap<>();
// TODO: 实现趋势数据统计逻辑
result.put("monthlyTrend", new ArrayList<>());
result.put("departmentTrend", new ArrayList<>());
return result;
}
@Override
public List<Map<String, Object>> getStatusOptions() {
List<Map<String, Object>> options = new ArrayList<>();
options.add(Map.of("value", "0", "label", "草稿"));
options.add(Map.of("value", "1", "label", "待审核"));
options.add(Map.of("value", "2", "label", "已通过"));
options.add(Map.of("value", "3", "label", "已拒绝"));
return options;
}
@Override
public List<Map<String, Object>> getTravelTypeOptions() {
List<Map<String, Object>> options = new ArrayList<>();
options.add(Map.of("value", "business", "label", "商务出行"));
options.add(Map.of("value", "academic", "label", "学术交流"));
options.add(Map.of("value", "training", "label", "培训学习"));
options.add(Map.of("value", "other", "label", "其他"));
return options;
}
@Override
public List<Map<String, Object>> getCompletionStatusOptions() {
List<Map<String, Object>> options = new ArrayList<>();
options.add(Map.of("value", "completed", "label", "已完成"));
options.add(Map.of("value", "cancelled", "label", "已取消"));
options.add(Map.of("value", "pending", "label", "进行中"));
return options;
}
@Override
public void generateCertificate(Long registrationId, HttpServletResponse response) throws Exception {
// TODO: 实现证明生成逻辑
// 1. 查询登记信息
RsOverseasRegistrationVo registration = queryById(registrationId);
if (registration == null) {
throw new RuntimeException("登记记录不存在");
}
// 2. 生成PDF证明文件(这里先简单处理)
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=" +
java.net.URLEncoder.encode("出国证明_" + registration.getName() + ".pdf", "UTF-8"));
// TODO: 实际的PDF生成逻辑
String content = "出国证明\n\n姓名:" + registration.getName() + "\n身份证号:" + registration.getIdNumber();
response.getOutputStream().write(content.getBytes("UTF-8"));
}
@Override
public TableDataInfo<RsOverseasRegistrationVo> queryHistoryPageList(RsOverseasRegistrationQuery query) {
// TODO: 实现历史记录分页查询
// 这里暂时返回空结果,实际应该查询历史表
Page<RsOverseasRegistrationVo> page = new Page<>();
page.setRecords(new ArrayList<>());
return TableDataInfo.build(page);
}
@Override
public List<RsOverseasRegistrationVo> queryHistoryList(RsOverseasRegistrationQuery query) {
// TODO: 实现历史记录查询
return new ArrayList<>();
}
@Override
public RsOverseasRegistrationVo queryHistoryById(Long historyId) {
// TODO: 实现历史记录详情查询
return null;
}
@Override
public Boolean insertHistoryByBo(RsOverseasRegistrationBo bo) {
// TODO: 实现历史记录新增
return true;
}
@Override
public Boolean updateHistoryByBo(RsOverseasRegistrationBo bo) {
// TODO: 实现历史记录修改
return true;
}
@Override
public Boolean deleteHistoryWithValidByIds(List<Long> historyIds) {
// TODO: 实现历史记录删除
return true;
}
@Override
public Boolean completeRegistration(Long registrationId, Map<String, Object> data) {
// TODO: 实现登记完成逻辑
// 1. 更新登记状态为已完成
// 2. 将记录移动到历史表(如果有的话)
RsOverseasRegistrationBo bo = new RsOverseasRegistrationBo();
bo.setRegistrationId(registrationId);
bo.setStatus("completed");
return updateByBo(bo);
}
@Override
public Map<String, Object> getMyStatistics() {
Map<String, Object> result = new HashMap<>();
Long userId = LoginHelper.getUserId();
// 统计当前用户的出国登记数据
RsOverseasRegistrationQuery query = new RsOverseasRegistrationQuery();
query.setCreateBy(userId);
List<RsOverseasRegistrationVo> myRegistrations = queryList(query);
// 总数
result.put("total", myRegistrations.size());
// 按状态统计
long draftCount = myRegistrations.stream().filter(r -> "0".equals(r.getStatus())).count();
long submittedCount = myRegistrations.stream().filter(r -> "1".equals(r.getStatus())).count();
long confirmedCount = myRegistrations.stream().filter(r -> "2".equals(r.getStatus())).count();
long withdrawnCount = myRegistrations.stream().filter(r -> "3".equals(r.getStatus())).count();
result.put("draftCount", draftCount);
result.put("submittedCount", submittedCount);
result.put("confirmedCount", confirmedCount);
result.put("withdrawnCount", withdrawnCount);
// 本月新增
long thisMonthCount = myRegistrations.stream()
.filter(r -> {
Date createTime = r.getCreateTime();
if (createTime == null) return false;
java.util.Calendar cal = java.util.Calendar.getInstance();
int currentMonth = cal.get(java.util.Calendar.MONTH);
int currentYear = cal.get(java.util.Calendar.YEAR);
cal.setTime(createTime);
return cal.get(java.util.Calendar.MONTH) == currentMonth
&& cal.get(java.util.Calendar.YEAR) == currentYear;
})
.count();
result.put("thisMonthCount", thisMonthCount);
return result;
}
@Override
public Boolean updateStatus(Long registrationId, String status) {
RsOverseasRegistration registration = baseMapper.selectById(registrationId);
if (registration == null) {
throw new RuntimeException("登记记录不存在");
}
registration.setStatus(status);
registration.setUpdateTime(new Date());
registration.setUpdateBy(LoginHelper.getUserId());
return baseMapper.updateById(registration) > 0;
}
@Override
public List<Map<String, Object>> statisticsByDepartment(RsOverseasRegistrationQuery query) {
// TODO: 实现按部门统计
return new ArrayList<>();
}
@Override
public List<Map<String, Object>> statisticsByCountry(RsOverseasRegistrationQuery query) {
// TODO: 实现按国家统计
return new ArrayList<>();
}
@Override
public List<Map<String, Object>> statisticsByTravelType(RsOverseasRegistrationQuery query) {
// TODO: 实现按出国类型统计
return new ArrayList<>();
}
@Override
public List<Map<String, Object>> statisticsByPersonnelCategory(RsOverseasRegistrationQuery query) {
// TODO: 实现按人员类别统计
return new ArrayList<>();
}
@Override
public Boolean checkDuplicateRegistration(RsOverseasRegistrationVo registration) {
// TODO: 实现重复检查
return false;
}
@Override
public List<RsOverseasRegistrationVo> getExpiringRegistrations(Integer days) {
// TODO: 实现即将到期查询
return new ArrayList<>();
}
@Override
public List<RsOverseasRegistrationVo> getExpiredRegistrations() {
// TODO: 实现已过期查询
return new ArrayList<>();
}
@Override
public List<Map<String, Object>> getTrendStatistics(Date startDate, Date endDate) {
// TODO: 实现趋势统计
return new ArrayList<>();
}
@Override
public Map<String, Object> getAnnualReport(Integer year) {
// TODO: 实现年度报告
Map<String, Object> result = new HashMap<>();
result.put("year", year);
result.put("totalCount", 0);
result.put("monthlyData", new ArrayList<>());
return result;
}
@Override
public void exportRegistrations(RsOverseasRegistrationQuery query, HttpServletResponse response) {
// TODO: 实现导出功能
List<RsOverseasRegistrationVo> list = queryList(query);
// 这里应该使用 ExcelUtil 导出,暂时简单处理
log.info("导出出国登记数据,共{}条", list.size());
}
/**
* 批准出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean approveRegistration(Long registrationId, String approveNotes) {
log.info("============= 批准出国登记开始 =============");
log.info("登记ID: {}, 审批意见: {}", registrationId, approveNotes);
// 1. 查询登记信息
RsOverseasRegistrationVo registration = queryById(registrationId);
if (registration == null) {
log.error("登记信息不存在: registrationId={}", registrationId);
throw new RuntimeException("登记信息不存在");
}
// 2. 验证当前状态是否为已提交
if (!"1".equals(registration.getStatus())) {
log.error("登记状态不是已提交,无法批准: registrationId={}, currentStatus={}",
registrationId, registration.getStatus());
throw new RuntimeException("只能批准已提交的登记");
}
// 3. 更新状态为已确认(2)
RsOverseasRegistrationBo bo = new RsOverseasRegistrationBo();
bo.setRegistrationId(registrationId);
bo.setStatus("2"); // 已确认
bo.setRemarks(approveNotes); // 将审批意见保存到备注字段
boolean result = updateByBo(bo);
log.info("批准出国登记结果: {}", result);
log.info("============= 批准出国登记结束 =============");
return result;
}
/**
* 拒绝出国登记
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean rejectRegistration(Long registrationId, String rejectReason) {
log.info("============= 拒绝出国登记开始 =============");
log.info("登记ID: {}, 拒绝原因: {}", registrationId, rejectReason);
// 1. 查询登记信息
RsOverseasRegistrationVo registration = queryById(registrationId);
if (registration == null) {
log.error("登记信息不存在: registrationId={}", registrationId);
throw new RuntimeException("登记信息不存在");
}
// 2. 验证当前状态是否为已提交
if (!"1".equals(registration.getStatus())) {
log.error("登记状态不是已提交,无法拒绝: registrationId={}, currentStatus={}",
registrationId, registration.getStatus());
throw new RuntimeException("只能拒绝已提交的登记");
}
// 3. 更新状态为已拒绝(3)
RsOverseasRegistrationBo bo = new RsOverseasRegistrationBo();
bo.setRegistrationId(registrationId);
bo.setStatus("3"); // 已拒绝
bo.setRemarks(rejectReason); // 将拒绝原因保存到备注字段
boolean result = updateByBo(bo);
log.info("拒绝出国登记结果: {}", result);
log.info("============= 拒绝出国登记结束 =============");
return result;
}
}
@@ -0,0 +1,288 @@
package org.dromara.rs.service.impl;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.dromara.rs.mapper.RsPartyMembershipRetentionMapper;
import org.dromara.rs.domain.RsPartyMembershipRetention;
import org.dromara.rs.domain.bo.RsPartyMembershipRetentionBo;
import org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo;
import org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery;
import org.dromara.rs.service.IRsPartyMembershipRetentionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 出国党籍保留Service业务层处理
*
* @author weizzzz
* @date 2025-10-05
*/
@Service
public class RsPartyMembershipRetentionServiceImpl implements IRsPartyMembershipRetentionService {
private static final Logger log = LoggerFactory.getLogger(RsPartyMembershipRetentionServiceImpl.class);
@Autowired
private RsPartyMembershipRetentionMapper baseMapper;
/**
* 查询出国党籍保留
*/
@Override
public RsPartyMembershipRetentionVo queryById(Long retentionId) {
return baseMapper.selectVoById(retentionId);
}
/**
* 查询出国党籍保留列表(使用自定义SQL,包含statusText映射)
*/
@Override
public TableDataInfo<RsPartyMembershipRetentionVo> queryPageList(RsPartyMembershipRetentionQuery query, PageQuery pageQuery) {
// 1. 调用自定义SQL查询(包含statusText映射)
List<RsPartyMembershipRetentionVo> list = baseMapper.selectVoList(query);
// 2. 手动分页处理
int pageNum = pageQuery.getPageNum();
int pageSize = pageQuery.getPageSize();
int start = (pageNum - 1) * pageSize;
int end = Math.min(start + pageSize, list.size());
// 如果起始位置超出了列表大小,返回空结果
List<RsPartyMembershipRetentionVo> pageList = start > list.size() ?
List.of() : list.subList(start, end);
// 3. 构建分页对象
Page<RsPartyMembershipRetentionVo> page = new Page<>(pageNum, pageSize);
page.setRecords(pageList);
page.setTotal(list.size());
return TableDataInfo.build(page);
}
/**
* 查询出国党籍保留列表
*/
@Override
public List<RsPartyMembershipRetentionVo> queryList(RsPartyMembershipRetentionQuery query) {
return baseMapper.selectVoList(buildQueryWrapper(query));
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<RsPartyMembershipRetention> buildQueryWrapper(RsPartyMembershipRetentionQuery query) {
LambdaQueryWrapper<RsPartyMembershipRetention> lqw = Wrappers.lambdaQuery();
lqw.like(StringUtils.isNotBlank(query.getName()), RsPartyMembershipRetention::getName, query.getName());
lqw.like(StringUtils.isNotBlank(query.getDestination()), RsPartyMembershipRetention::getDestination, query.getDestination());
lqw.like(StringUtils.isNotBlank(query.getPartyBranch()), RsPartyMembershipRetention::getPartyBranch, query.getPartyBranch());
lqw.eq(StringUtils.isNotBlank(query.getStatus()), RsPartyMembershipRetention::getStatus, query.getStatus());
lqw.eq(query.getRegistrationId() != null, RsPartyMembershipRetention::getRegistrationId, query.getRegistrationId());
lqw.eq(query.getApplicationId() != null, RsPartyMembershipRetention::getApplicationId, query.getApplicationId());
lqw.between(query.getApplicationTimeStart() != null && query.getApplicationTimeEnd() != null,
RsPartyMembershipRetention::getApplicationTime, query.getApplicationTimeStart(), query.getApplicationTimeEnd());
lqw.orderByDesc(RsPartyMembershipRetention::getCreateTime);
return lqw;
}
/**
* 新增出国党籍保留
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBo(RsPartyMembershipRetentionBo bo) {
RsPartyMembershipRetention add = MapstructUtils.convert(bo, RsPartyMembershipRetention.class);
// ⚠️ 重要:在validEntityBeforeSave之前设置默认值,确保不会因为NOT NULL约束报错
// 自动设置申请日期为当前日期(如果前端未提供)
if (add.getApplicationDate() == null) {
add.setApplicationDate(new Date());
}
// 自动设置费用自理为"是"(如果前端未提供) - 数据库字段为NOT NULL
if (StringUtils.isBlank(add.getSelfFunded())) {
add.setSelfFunded("1");
}
// 设置默认状态为草稿
if (StringUtils.isBlank(add.getStatus())) {
add.setStatus("0");
}
// 执行数据校验和自动计算
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setRetentionId(add.getRetentionId());
}
return flag;
}
/**
* 修改出国党籍保留
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean updateByBo(RsPartyMembershipRetentionBo bo) {
RsPartyMembershipRetention update = MapstructUtils.convert(bo, RsPartyMembershipRetention.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存实体之前的数据校验
*/
private void validEntityBeforeSave(RsPartyMembershipRetention entity) {
// 计算停留天数
if (entity.getTravelStartDate() != null && entity.getTravelEndDate() != null) {
long diff = entity.getTravelEndDate().getTime() - entity.getTravelStartDate().getTime();
entity.setStayDays((int) (diff / (1000 * 60 * 60 * 24)));
}
}
/**
* 校验并批量删除出国党籍保留信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
// 做一些业务上的校验,判断是否允许删除
// 例如:已提交或已审批的不允许删除
for (Long id : ids) {
RsPartyMembershipRetentionVo vo = baseMapper.selectVoById(id);
if (vo != null && !"0".equals(vo.getStatus())) {
throw new RuntimeException("只能删除草稿状态的记录");
}
}
}
return baseMapper.deleteByIds(ids) > 0;
}
/**
* 保存并提交出国党籍保留
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean saveAndSubmit(RsPartyMembershipRetentionBo bo) {
// 先保存
boolean saved;
if (bo.getRetentionId() != null) {
saved = updateByBo(bo);
} else {
saved = insertByBo(bo);
}
// 再提交
if (saved && bo.getRetentionId() != null) {
return submit(bo.getRetentionId());
}
return false;
}
/**
* 提交出国党籍保留
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean submit(Long retentionId) {
RsPartyMembershipRetentionVo vo = baseMapper.selectVoById(retentionId);
if (vo == null) {
throw new RuntimeException("记录不存在");
}
if (!"0".equals(vo.getStatus()) && !"4".equals(vo.getStatus())) {
throw new RuntimeException("只有草稿或已撤回状态才能提交");
}
RsPartyMembershipRetention update = new RsPartyMembershipRetention();
update.setRetentionId(retentionId);
update.setStatus("1"); // 待审核
update.setApplicationTime(new Date());
return baseMapper.updateById(update) > 0;
}
/**
* 撤回出国党籍保留
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean withdraw(Long retentionId) {
RsPartyMembershipRetentionVo vo = baseMapper.selectVoById(retentionId);
if (vo == null) {
throw new RuntimeException("记录不存在");
}
if (!"1".equals(vo.getStatus())) {
throw new RuntimeException("只有待审核状态才能撤回");
}
RsPartyMembershipRetention update = new RsPartyMembershipRetention();
update.setRetentionId(retentionId);
update.setStatus("4"); // 已撤回
return baseMapper.updateById(update) > 0;
}
/**
* 审批出国党籍保留
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean approve(Long retentionId, String approvalOpinion, Boolean approved) {
RsPartyMembershipRetentionVo vo = baseMapper.selectVoById(retentionId);
if (vo == null) {
throw new RuntimeException("记录不存在");
}
if (!"1".equals(vo.getStatus())) {
throw new RuntimeException("只有待审核状态才能审批");
}
RsPartyMembershipRetention update = new RsPartyMembershipRetention();
update.setRetentionId(retentionId);
update.setStatus(approved ? "2" : "3"); // 2-已通过,3-已拒绝
update.setFinalApproval(approvalOpinion);
return baseMapper.updateById(update) > 0;
}
/**
* 根据登记ID查询党籍保留信息
*/
@Override
public RsPartyMembershipRetentionVo queryByRegistrationId(Long registrationId) {
LambdaQueryWrapper<RsPartyMembershipRetention> lqw = Wrappers.lambdaQuery();
lqw.eq(RsPartyMembershipRetention::getRegistrationId, registrationId);
lqw.orderByDesc(RsPartyMembershipRetention::getCreateTime);
lqw.last("LIMIT 1");
return baseMapper.selectVoOne(lqw);
}
/**
* 根据申请ID查询党籍保留信息
*/
@Override
public RsPartyMembershipRetentionVo queryByApplicationId(Long applicationId) {
LambdaQueryWrapper<RsPartyMembershipRetention> lqw = Wrappers.lambdaQuery();
lqw.eq(RsPartyMembershipRetention::getApplicationId, applicationId);
lqw.orderByDesc(RsPartyMembershipRetention::getCreateTime);
lqw.last("LIMIT 1");
return baseMapper.selectVoOne(lqw);
}
}
@@ -0,0 +1,201 @@
package org.dromara.rs.util;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 因私出国(境)情况回执PDF生成工具类
*
* @author weizzzz
* @date 2025-01-08
*/
@Slf4j
public class OverseasReceiptPdfUtil {
/**
* 生成因私出国(境)情况回执PDF
*
* @param record 出国记录信息
* @return PDF字节数组
*/
public static byte[] generateReceiptPdf(RsOverseasRecordVo record) throws DocumentException, IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Document document = new Document(PageSize.A4, 40, 40, 40, 40);
PdfWriter writer = PdfWriter.getInstance(document, baos);
document.open();
try {
// 创建中文字体
BaseFont baseFont = createChineseFont();
Font titleFont = new Font(baseFont, 14, Font.BOLD);
Font headerFont = new Font(baseFont, 12, Font.BOLD);
Font normalFont = new Font(baseFont, 10, Font.NORMAL);
Font smallFont = new Font(baseFont, 9, Font.NORMAL);
// 1. 附件标题
Paragraph attachmentTitle = new Paragraph("附件 6", smallFont);
attachmentTitle.setAlignment(Element.ALIGN_LEFT);
attachmentTitle.setSpacingAfter(5);
document.add(attachmentTitle);
// 2. 主标题
Paragraph mainTitle = new Paragraph("杭州第一技师学院教职工因私出国(境)情况回执", titleFont);
mainTitle.setAlignment(Element.ALIGN_CENTER);
mainTitle.setSpacingAfter(20);
document.add(mainTitle);
// 3. 创建表格
PdfPTable table = new PdfPTable(2);
table.setWidthPercentage(100);
table.setWidths(new float[]{20, 80});
table.setSpacingBefore(10);
// 设置表格边框
table.getDefaultCell().setBorderWidth(1);
table.getDefaultCell().setPadding(8);
// 第一行:申请行程
addSectionRow(table, "申请\n行程", getApplicationItinerary(record), headerFont, normalFont);
// 第二行:温馨提醒
addSectionRow(table, "温馨\n\n提醒", getWarmReminder(), headerFont, smallFont);
// 第三行:实际行程
addSectionRow(table, "实际\n行程", getActualItinerary(), headerFont, normalFont);
// 第四行:执纪情况
addSectionRow(table, "执纪\n情况", getDisciplineStatement(), headerFont, normalFont);
// 第五行:有关情况说明
addSectionRow(table, "有关情\n况说明", "(实际行程与申请行程有出入及其他情况的,请简要说明)", headerFont, normalFont);
// 第六行:申请人意见
addSectionRow(table, "申请人\n\n意见", getApplicantOpinion(), headerFont, normalFont);
document.add(table);
// 页码
Paragraph pageNumber = new Paragraph("- 16 -", smallFont);
pageNumber.setAlignment(Element.ALIGN_CENTER);
pageNumber.setSpacingBefore(20);
document.add(pageNumber);
} catch (Exception e) {
log.error("生成因私出国情况回执PDF失败", e);
throw new ServiceException("生成PDF失败: " + e.getMessage());
} finally {
document.close();
}
return baos.toByteArray();
}
/**
* 创建中文字体
*/
private static BaseFont createChineseFont() throws DocumentException, IOException {
try {
return BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e) {
log.warn("无法加载STSong-Light字体,尝试备选字体");
try {
return BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
} catch (Exception e2) {
throw new ServiceException("无法加载中文字体,请检查itext-asian依赖配置");
}
}
}
/**
* 添加表格行
*/
private static void addSectionRow(PdfPTable table, String label, String content, Font labelFont, Font contentFont) {
// 左侧标签单元格
PdfPCell labelCell = new PdfPCell(new Phrase(label, labelFont));
labelCell.setHorizontalAlignment(Element.ALIGN_CENTER);
labelCell.setVerticalAlignment(Element.ALIGN_MIDDLE);
labelCell.setPadding(8);
labelCell.setMinimumHeight(80);
table.addCell(labelCell);
// 右侧内容单元格
PdfPCell contentCell = new PdfPCell(new Phrase(content, contentFont));
contentCell.setHorizontalAlignment(Element.ALIGN_LEFT);
contentCell.setVerticalAlignment(Element.ALIGN_TOP);
contentCell.setPadding(8);
contentCell.setMinimumHeight(80);
table.addCell(contentCell);
}
/**
* 获取申请行程内容
*/
private static String getApplicationItinerary(RsOverseasRecordVo record) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy年MM月dd日");
// 申请人姓名
String applicantName = record.getApplicantName() != null ?
record.getApplicantName() : "____";
String startDate = record.getActualStartDate() != null ?
dateFormat.format(record.getActualStartDate()) : "____年__月__日";
String endDate = record.getActualEndDate() != null ?
dateFormat.format(record.getActualEndDate()) : "____年__月__日";
String country = record.getDestinationCountry() != null ?
record.getDestinationCountry() : "__________";
String exitReason = record.getExitReason() != null ?
record.getExitReason() : "________";
Integer duration = record.getActualDuration();
String durationStr = duration != null ? String.valueOf(duration) : "____";
return String.format(" %s同志,申请于%s至%s,赴%s旅游(探亲),在外停留%s天,费用自理。",
applicantName, startDate, endDate, country, durationStr);
}
/**
* 获取温馨提醒内容
*/
private static String getWarmReminder() {
return " 1. 请自觉遵守我国和当地法律法规,尊重当地民族风俗和宗教信仰,注意人身及财产安全。\n\n" +
" 2. 出国(境)期间不准为企业和个人办理经营业务;不准接受外商或住国(境)外中资机构(企业)的资助,不准在任何单位报销出国(境)费用。\n\n" +
" 3. 出国(境)时间、天数、目的地、事由等情况有变化的,须在出国(境)前向学院人事(组织)处报备。\n\n" +
" 4. 请于回国 10 日内,将出国(境)证件和本回执通过本单位组织人事(管理)部门转交学院人事(组织)处集中保管。\n\n" +
" 5. 若有违反《中国共产党纪律处分条例》和出国(境)有关文件规定的,将视情节轻重给予相应处理。";
}
/**
* 获取实际行程内容
*/
private static String getActualItinerary() {
return " 本人于______年___月___日至______年___月___日,赴_______________旅游(探亲),在外停留_____天,费用(是□否□)自理。";
}
/**
* 获取执纪情况内容
*/
private static String getDisciplineStatement() {
return " 本人因私出国(境)期间,(是□否□)严格遵守保密纪律、外事纪律,(是□否□)自觉遵守所在国家法律,(是□否□)严格执行国家、省、市、局和学院因私出国(境)管理有关规定,(有□无□)违规违纪行为。出国(境)证件已于___月___日 按规定上缴______统一保管。\n\n" +
"(证件编号:_______________)";
}
/**
* 获取申请人意见内容
*/
private static String getApplicantOpinion() {
return " 本人保证填写信息真实。\n\n\n" +
" 签名:\n" +
" 年 月 日";
}
}
@@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsOverseasApplicationAttachmentMapper">
<resultMap type="org.dromara.rs.domain.vo.RsOverseasApplicationAttachmentVo" id="RsOverseasApplicationAttachmentResult">
<result property="attachmentId" column="attachment_id" />
<result property="applicationId" column="application_id" />
<result property="attachmentTitle" column="attachment_title" />
<result property="attachmentContent" column="attachment_content" />
<result property="fileName" column="file_name" />
<result property="fileOriginalName" column="file_original_name" />
<result property="filePath" column="file_path" />
<result property="fileSize" column="file_size" />
<result property="fileType" column="file_type" />
<result property="mimeType" column="mime_type" />
<result property="uploadTime" column="upload_time" />
<result property="uploadUserId" column="upload_user_id" />
<result property="uploadUserName" column="upload_user_name" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
</resultMap>
<sql id="selectAttachmentVo">
select attachment_id, application_id, attachment_title, attachment_content,
file_name, file_original_name, file_path, file_size, file_type, mime_type,
upload_time, upload_user_id, upload_user_name,
create_by, create_time, update_by, update_time
from rs_overseas_application_attachment
</sql>
<!-- 查询出国申请附件列表 -->
<select id="selectAttachmentList" parameterType="Long" resultMap="RsOverseasApplicationAttachmentResult">
<include refid="selectAttachmentVo"/>
where application_id = #{applicationId}
and deleted = '0'
order by upload_time desc
</select>
<!-- 查询附件详情 -->
<select id="selectAttachmentById" parameterType="Long" resultMap="RsOverseasApplicationAttachmentResult">
<include refid="selectAttachmentVo"/>
where attachment_id = #{attachmentId}
and deleted = '0'
</select>
</mapper>
@@ -0,0 +1,173 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsOverseasItineraryChangeMapper">
<resultMap type="org.dromara.rs.domain.vo.RsOverseasItineraryChangeVo" id="RsOverseasItineraryChangeResult">
<result property="changeId" column="change_id"/>
<result property="recordId" column="record_id"/>
<result property="applicationId" column="application_id"/>
<result property="applicantId" column="applicant_id"/>
<result property="applicantName" column="applicant_name"/>
<result property="department" column="department"/>
<!-- 原记录信息 -->
<result property="originalDestination" column="original_destination"/>
<result property="originalDestinationCity" column="original_destination_city"/>
<result property="originalExitReason" column="original_exit_reason"/>
<result property="originalStartDate" column="original_start_date"/>
<result property="originalEndDate" column="original_end_date"/>
<result property="originalDuration" column="original_duration"/>
<result property="originalItinerary" column="original_itinerary"/>
<result property="originalAccommodationPlan" column="original_accommodation_plan"/>
<result property="originalTransportationPlan" column="original_transportation_plan"/>
<!-- 变更信息 -->
<result property="changeType" column="change_type"/>
<result property="changeTypeText" column="change_type_text"/>
<result property="newDestination" column="new_destination"/>
<result property="newDestinationCity" column="new_destination_city"/>
<result property="newExitReason" column="new_exit_reason"/>
<result property="newStartDate" column="new_start_date"/>
<result property="newEndDate" column="new_end_date"/>
<result property="newDuration" column="new_duration"/>
<result property="newItinerary" column="new_itinerary"/>
<result property="newAccommodationPlan" column="new_accommodation_plan"/>
<result property="newTransportationPlan" column="new_transportation_plan"/>
<!-- 变更原因和备注 -->
<result property="changeReason" column="change_reason"/>
<result property="remark" column="remark"/>
<!-- 审批信息 -->
<result property="status" column="status"/>
<result property="statusText" column="status_text"/>
<result property="approvalOpinion" column="approval_opinion"/>
<result property="approvalTime" column="approval_time"/>
<result property="approverId" column="approver_id"/>
<result property="approverName" column="approver_name"/>
<!-- 基础字段 -->
<result property="tenantId" column="tenant_id"/>
<result property="createDept" column="create_dept"/>
<result property="deleted" column="deleted"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
</resultMap>
<sql id="selectChangeVo">
SELECT
c.change_id,
c.record_id,
c.application_id,
c.applicant_id,
c.applicant_name,
c.department,
c.original_destination,
c.original_destination_city,
c.original_exit_reason,
c.original_start_date,
c.original_end_date,
c.original_duration,
c.original_itinerary,
c.original_accommodation_plan,
c.original_transportation_plan,
c.change_type,
c.new_destination,
c.new_destination_city,
c.new_exit_reason,
c.new_start_date,
c.new_end_date,
c.new_duration,
c.new_itinerary,
c.new_accommodation_plan,
c.new_transportation_plan,
c.change_reason,
c.remark,
c.status,
c.approval_opinion,
c.approval_time,
c.approver_id,
c.approver_name,
c.tenant_id,
c.create_dept,
c.deleted,
c.create_by,
c.create_time,
c.update_by,
c.update_time,
dict_status.dict_label AS status_text
FROM rs_overseas_itinerary_change c
LEFT JOIN sys_dict_data dict_status ON dict_status.dict_value = c.status AND dict_status.dict_type = 'rs_itinerary_change_status'
</sql>
<select id="selectChangeList" parameterType="org.dromara.rs.domain.query.RsOverseasItineraryChangeQuery" resultMap="RsOverseasItineraryChangeResult">
<include refid="selectChangeVo"/>
<where>
c.deleted = 0
<if test="recordId != null">
AND c.record_id = #{recordId}
</if>
<if test="applicationId != null">
AND c.application_id = #{applicationId}
</if>
<if test="applicantId != null">
AND c.applicant_id = #{applicantId}
</if>
<if test="applicantName != null and applicantName != ''">
AND c.applicant_name LIKE CONCAT('%', #{applicantName}, '%')
</if>
<if test="department != null and department != ''">
AND c.department LIKE CONCAT('%', #{department}, '%')
</if>
<if test="status != null and status != ''">
<!-- 支持逗号分隔的多状态查询,如 '1,2',使用 FIND_IN_SET 函数 -->
<choose>
<!-- 判断是否包含逗号 -->
<when test='status.indexOf(",") > 0'>
AND FIND_IN_SET(c.status, #{status})
</when>
<!-- 单个状态精确匹配 -->
<otherwise>
AND c.status = #{status}
</otherwise>
</choose>
</if>
<if test="changeType != null and changeType != ''">
AND c.change_type LIKE CONCAT('%', #{changeType}, '%')
</if>
<if test="createTimeStart != null">
AND c.create_time &gt;= #{createTimeStart}
</if>
<if test="createTimeEnd != null">
AND c.create_time &lt;= #{createTimeEnd}
</if>
<if test="approvalTimeStart != null">
AND c.approval_time &gt;= #{approvalTimeStart}
</if>
<if test="approvalTimeEnd != null">
AND c.approval_time &lt;= #{approvalTimeEnd}
</if>
<if test="tenantId != null and tenantId != ''">
AND c.tenant_id = #{tenantId}
</if>
</where>
ORDER BY c.create_time DESC
</select>
<select id="selectChangeById" parameterType="Long" resultMap="RsOverseasItineraryChangeResult">
<include refid="selectChangeVo"/>
WHERE c.change_id = #{changeId} AND c.deleted = 0
</select>
<select id="selectChangeListByRecordId" parameterType="Long" resultMap="RsOverseasItineraryChangeResult">
<include refid="selectChangeVo"/>
WHERE c.record_id = #{recordId} AND c.deleted = 0
ORDER BY c.create_time DESC
</select>
</mapper>
@@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsOverseasRegistrationMapper">
<resultMap type="RsOverseasRegistration" id="RsOverseasRegistrationResult">
<id property="registrationId" column="registration_id"/>
<result property="applicationId" column="application_id"/>
<result property="name" column="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="position" column="position"/>
<result property="administrativeLevel" column="administrative_level"/>
<result property="personnelCategory" column="personnel_category"/>
<result property="status" column="status"/>
<result property="submitTime" column="submit_time"/>
<result property="remarks" column="remarks"/>
<result property="tenantId" column="tenant_id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="deleted" column="deleted"/>
</resultMap>
<resultMap id="RsOverseasRegistrationVoResult" type="org.dromara.rs.domain.vo.RsOverseasRegistrationVo" extends="RsOverseasRegistrationResult">
<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"/>
<result property="statusText" column="status_text"/>
</resultMap>
<sql id="selectRegistrationVo">
select r.registration_id, r.application_id, r.name, r.gender, r.birth_date,
r.photo, r.id_number, r.political_status, r.registered_address,
r.department, r.position, r.administrative_level, r.personnel_category,
r.status, r.registration_time as submit_time, r.remarks,
r.tenant_id, r.create_by, r.create_time, r.update_by, r.update_time, r.deleted,
CASE r.gender
WHEN '0' THEN '男'
WHEN '1' THEN '女'
ELSE '未知'
END as gender_text,
r.political_status as political_status_text,
r.administrative_level as administrative_level_text,
CASE r.personnel_category
WHEN 'A' THEN 'A-厅级干部'
WHEN 'B' THEN 'B-处级干部'
WHEN 'C' THEN 'C-科级干部'
WHEN 'D' THEN 'D-一般干部'
WHEN 'E' THEN 'E-专业技术人员'
WHEN 'F' THEN 'F-工勤人员'
ELSE r.personnel_category
END as personnel_category_text,
CASE r.status
WHEN '0' THEN '草稿'
WHEN '1' THEN '待审核'
WHEN '2' THEN '已通过'
WHEN '3' THEN '已拒绝'
ELSE '未知状态'
END as status_text
from rs_overseas_registration r
</sql>
<select id="selectRegistrationList" parameterType="org.dromara.rs.domain.query.RsOverseasRegistrationQuery" resultMap="RsOverseasRegistrationVoResult">
<include refid="selectRegistrationVo"/>
<where>
r.deleted = '0'
<if test="name != null and name != ''">
AND r.name like concat('%', #{name}, '%')
</if>
<if test="gender != null and gender != ''">
AND r.gender = #{gender}
</if>
<if test="politicalStatus != null and politicalStatus != ''">
AND r.political_status = #{politicalStatus}
</if>
<if test="department != null and department != ''">
AND r.department like concat('%', #{department}, '%')
</if>
<if test="position != null and position != ''">
AND r.position like concat('%', #{position}, '%')
</if>
<if test="idNumber != null and idNumber != ''">
AND r.id_number = #{idNumber}
</if>
<if test="administrativeLevel != null and administrativeLevel != ''">
AND r.administrative_level = #{administrativeLevel}
</if>
<if test="personnelCategory != null and personnelCategory != ''">
AND r.personnel_category = #{personnelCategory}
</if>
<if test="status != null and status != ''">
AND r.status = #{status}
</if>
<if test="applicationId != null">
AND r.application_id = #{applicationId}
</if>
<if test="registrationTimeStart != null">
AND r.registration_time >= #{registrationTimeStart}
</if>
<if test="registrationTimeEnd != null">
AND r.registration_time &lt;= #{registrationTimeEnd}
</if>
</where>
order by COALESCE(r.registration_time, r.create_time) desc, r.create_time desc
</select>
<select id="selectMyRegistrationList" parameterType="org.dromara.rs.domain.query.RsOverseasRegistrationQuery" resultMap="RsOverseasRegistrationVoResult">
<include refid="selectRegistrationVo"/>
<where>
r.deleted = '0'
<if test="createBy != null">
AND r.create_by = #{createBy}
</if>
<if test="name != null and name != ''">
AND r.name like concat('%', #{name}, '%')
</if>
<if test="status != null and status != ''">
AND r.status = #{status}
</if>
<if test="department != null and department != ''">
AND r.department like concat('%', #{department}, '%')
</if>
<if test="registrationTimeStart != null">
AND r.registration_time >= #{registrationTimeStart}
</if>
<if test="registrationTimeEnd != null">
AND r.registration_time &lt;= #{registrationTimeEnd}
</if>
</where>
order by COALESCE(r.registration_time, r.create_time) desc, r.create_time desc
</select>
<select id="selectRegistrationById" parameterType="Long" resultMap="RsOverseasRegistrationVoResult">
<include refid="selectRegistrationVo"/>
where r.registration_id = #{registrationId} and r.deleted = '0'
</select>
<select id="selectRegistrationByApplicationId" parameterType="Long" resultMap="RsOverseasRegistrationVoResult">
<include refid="selectRegistrationVo"/>
where r.application_id = #{applicationId} and r.deleted = '0'
order by r.create_time desc
limit 1
</select>
<select id="selectRegistrationsByStatus" parameterType="String" resultMap="RsOverseasRegistrationVoResult">
<include refid="selectRegistrationVo"/>
where r.status = #{status} and r.deleted = '0'
order by COALESCE(r.registration_time, r.create_time) desc, r.create_time desc
</select>
<select id="selectRegistrationsByName" parameterType="String" resultMap="RsOverseasRegistrationVoResult">
<include refid="selectRegistrationVo"/>
where r.name like concat('%', #{name}, '%') and r.deleted = '0'
order by COALESCE(r.registration_time, r.create_time) desc, r.create_time desc
</select>
</mapper>
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.dromara.rs.mapper.RsPartyMembershipRetentionMapper">
<resultMap type="RsPartyMembershipRetention" id="RsPartyMembershipRetentionResult">
<id property="retentionId" column="retention_id"/>
<result property="applicationId" column="application_id"/>
<result property="registrationId" column="registration_id"/>
<result property="name" column="name"/>
<result property="idCard" column="id_card"/>
<result property="position" column="position"/>
<result property="spouse" column="spouse"/>
<result property="relativeName" column="relative_name"/>
<result property="travelStartDate" column="travel_start_date"/>
<result property="travelEndDate" column="travel_end_date"/>
<result property="stayDays" column="stay_days"/>
<result property="destination" column="destination"/>
<result property="purpose" column="purpose"/>
<result property="travelReason" column="travel_reason"/>
<result property="selfFunded" column="self_funded"/>
<result property="requestApproval" column="request_approval"/>
<result property="finalApproval" column="final_approval"/>
<result property="partyBranch" column="party_branch"/>
<result property="applicationDate" column="application_date"/>
<result property="status" column="status"/>
<result property="applicationTime" column="application_time"/>
<result property="remarks" column="remarks"/>
<result property="tenantId" column="tenant_id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="deleted" column="deleted"/>
</resultMap>
<resultMap id="RsPartyMembershipRetentionVoResult" type="org.dromara.rs.domain.vo.RsPartyMembershipRetentionVo" extends="RsPartyMembershipRetentionResult">
<result property="selfFundedText" column="self_funded_text"/>
<result property="statusText" column="status_text"/>
</resultMap>
<sql id="selectRetentionVo">
select r.retention_id, r.application_id, r.registration_id, r.name, r.id_card, r.position, r.spouse, r.relative_name,
r.travel_start_date, r.travel_end_date, r.stay_days, r.destination, r.purpose,
r.travel_reason, r.self_funded, r.request_approval, r.final_approval,
r.party_branch, r.application_date, r.status, r.application_time, r.remarks,
r.tenant_id, r.create_by, r.create_time, r.update_by, r.update_time, r.deleted,
CASE r.self_funded
WHEN '1' THEN '是'
WHEN '0' THEN '否'
ELSE '未知'
END as self_funded_text,
CASE r.status
WHEN '0' THEN '草稿'
WHEN '1' THEN '待审核'
WHEN '2' THEN '已通过'
WHEN '3' THEN '已拒绝'
WHEN '4' THEN '已撤回'
ELSE '未知状态'
END as status_text
from rs_party_membership_retention r
</sql>
<select id="selectVoById" parameterType="Long" resultMap="RsPartyMembershipRetentionVoResult">
<include refid="selectRetentionVo"/>
where r.retention_id = #{retentionId} and r.deleted = '0'
</select>
<select id="selectVoList" parameterType="org.dromara.rs.domain.query.RsPartyMembershipRetentionQuery" resultMap="RsPartyMembershipRetentionVoResult">
<include refid="selectRetentionVo"/>
<where>
r.deleted = '0'
<if test="name != null and name != ''">
AND r.name like concat('%', #{name}, '%')
</if>
<if test="idCard != null and idCard != ''">
AND r.id_card = #{idCard}
</if>
<if test="destination != null and destination != ''">
AND r.destination like concat('%', #{destination}, '%')
</if>
<if test="partyBranch != null and partyBranch != ''">
AND r.party_branch like concat('%', #{partyBranch}, '%')
</if>
<if test="status != null and status != ''">
AND r.status = #{status}
</if>
<if test="registrationId != null">
AND r.registration_id = #{registrationId}
</if>
<if test="applicationId != null">
AND r.application_id = #{applicationId}
</if>
<if test="applicationTimeStart != null and applicationTimeEnd != null">
AND r.application_time between #{applicationTimeStart} and #{applicationTimeEnd}
</if>
</where>
order by r.create_time desc
</select>
</mapper>
@@ -0,0 +1,156 @@
package org.dromara.zsjy.domain.bo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Positive;
import java.io.Serial;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;
/**
* 实习信息变更业务对象 InternshipInfoChangeBO
* <p>用于实习信息变更功能,将原实习记录设置为"已变更"状态,并新增一条新的实习记录</p>
*
* @author system
* @date 2025-01-03
*/
@Data
@Schema(description = "实习信息变更业务对象")
public class InternshipInfoChangeBO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 原实习信息ID(要变更的实习记录ID)
*/
@Schema(description = "原实习信息ID")
@NotNull(message = "原实习信息ID不能为空")
private Long internshipId;
/**
* 学生ID(用于验证权限)
*/
@Schema(description = "学生ID")
@NotNull(message = "学生ID不能为空")
private Long studentId;
/**
* 变更原因(必填)
*/
@Schema(description = "变更原因")
@NotBlank(message = "变更原因不能为空")
private String changeReason;
/**
* 实习状态:1-进行中,2-已完成,3-已暂停,4-已变更
*/
@Schema(description = "实习状态:1-进行中,2-已完成,3-已暂停,4-已变更")
@NotBlank(message = "实习状态不能为空")
@Pattern(regexp = "^[1-4]$", message = "实习状态只能是1-进行中,2-已完成,3-已暂停,4-已变更")
private String internshipStatus;
/**
* 实习单位名称
*/
@Schema(description = "实习单位名称")
@NotBlank(message = "实习单位名称不能为空")
private String companyName;
/**
* 实习单位地址
*/
@Schema(description = "实习单位地址")
private String companyAddress;
/**
* 单位联系人
*/
@Schema(description = "单位联系人")
private String companyContact;
/**
* 联系人电话
*/
@Schema(description = "联系人电话")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "联系人电话格式不正确")
private String companyPhone;
/**
* 行业类型
*/
@Schema(description = "行业类型")
private String companyIndustry;
/**
* 单位规模
*/
@Schema(description = "单位规模")
private String companyScale;
/**
* 实习岗位名称
*/
@Schema(description = "实习岗位名称")
@NotBlank(message = "实习岗位名称不能为空")
private String positionName;
/**
* 岗位职责
*/
@Schema(description = "岗位职责")
private String positionDuties;
/**
* 实习补贴/薪资
*/
@Schema(description = "实习补贴/薪资")
@Positive(message = "实习补贴/薪资必须大于0")
private BigDecimal internshipAllowance;
/**
* 工作时长
*/
@Schema(description = "工作时长")
private String workHours;
/**
* 住宿地址
*/
@Schema(description = "住宿地址")
private String accommodationAddress;
/**
* 住宿类型
*/
@Schema(description = "住宿类型")
private String accommodationType;
/**
* 住宿费用
*/
@Schema(description = "住宿费用")
private BigDecimal accommodationCost;
/**
* 实习开始时间
*/
@Schema(description = "实习开始时间")
@JsonFormat(pattern = "yyyy-MM-dd")
@NotNull(message = "实习开始时间不能为空")
private Date startDate;
/**
* 实习结束时间
*/
@Schema(description = "实习结束时间")
@JsonFormat(pattern = "yyyy-MM-dd")
private Date endDate;
}
+1
View File
@@ -137,3 +137,4 @@ mvn spring-boot:run