“完成出国申请审批流程11.3”

This commit is contained in:
weizhong committed 2025-11-03 15:32:19 +08:00
1 parent 4277e2fca8
commit 9af67076a1
12 files changed
+1338 -979

No files matched your search

@@ -550,6 +550,79 @@ public class RsOverseasApplicationController extends BaseController {
}
}
// ==================== 党籍保留附件上传接口 ====================
/**
* 上传党籍保留附件
*/
@SaCheckPermission("rs:overseas:add")
@PostMapping("/uploadPartyRetentionFile")
@Log(title = "上传党籍保留附件", businessType = BusinessType.OTHER)
public R<Map<String, Object>> uploadPartyRetentionFile(@RequestParam("file") MultipartFile file,
@RequestParam("applicationId") Long applicationId) {
try {
// 验证文件格式
String contentType = file.getContentType();
if (!isValidFileType(contentType)) {
return R.fail("不支持的文件格式,请上传PDF、JPG或PNG文件");
}
// 验证文件大小(10MB)
long maxSize = 10 * 1024 * 1024; // 10MB
if (file.getSize() > maxSize) {
return R.fail("文件大小超过10MB限制");
}
// 使用系统OSS服务上传文件(自动保存到sys_oss表)
SysOssVo ossResult = ossService.upload(file);
// 更新申请记录 - 存储OSS ID和文件路径
applicationService.updatePartyRetentionFileInfo(
applicationId,
ossResult.getUrl(), // 文件URL
ossResult.getOriginalName(), // 原始文件名
new Date(), // 上传时间
"1", // 上传状态:已上传
ossResult.getOssId() // OSS文件ID
);
// 返回结果
Map<String, Object> result = new HashMap<>();
result.put("filePath", ossResult.getUrl());
result.put("fileName", ossResult.getOriginalName());
result.put("uploadTime", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
result.put("ossId", ossResult.getOssId());
return R.ok("党籍保留附件上传成功", result);
} catch (Exception e) {
e.printStackTrace();
return R.fail("系统异常:" + e.getMessage());
}
}
/**
* 下载党籍保留附件(安全下载)
*/
@SaCheckPermission("rs:overseas:query")
@GetMapping("/downloadPartyRetentionFile/{applicationId}")
public void downloadPartyRetentionFile(@PathVariable Long applicationId, HttpServletResponse response) {
try {
RsOverseasApplicationVo application = applicationService.queryById(applicationId);
if (application == null || application.getPartyRetentionFileOssId() == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":404,\"msg\":\"文件不存在\"}");
return;
}
// 使用系统OSS服务进行安全下载
ossService.secureDownload(application.getPartyRetentionFileOssId(), response);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 验证文件类型
*/
@@ -30,6 +30,7 @@ import org.dromara.rs.domain.bo.RsOverseasRecordBo;
import org.dromara.rs.domain.vo.RsOverseasRecordVo;
import org.dromara.rs.domain.query.RsOverseasRecordQuery;
import org.dromara.rs.service.IRsOverseasRecordService;
import org.dromara.rs.service.IRegistrationFormService;
import org.dromara.rs.util.OverseasReceiptPdfUtil;
import java.io.IOException;
@@ -50,6 +51,9 @@ public class RsOverseasRecordController extends BaseController {
@Autowired
private IRsOverseasRecordService recordService;
@Autowired
private IRegistrationFormService registrationFormService;
/**
* 查询出国记录列表
*/
@@ -100,6 +104,17 @@ public class RsOverseasRecordController extends BaseController {
ExcelUtil.exportExcel(list, "出国记录数据", RsOverseasRecordVo.class, response);
}
/**
* 下载党籍保留申请PDF模板
* 注意:此接口必须放在 /{recordId} 之前,避免路由冲突
*/
@SaCheckPermission("rs:overseas:query")
@Log(title = "下载党籍保留申请模板", businessType = BusinessType.EXPORT)
@GetMapping("/downloadPartyRetention")
public void downloadPartyRetention(HttpServletResponse response) {
registrationFormService.downloadPartyMembershipTemplate(response);
}
/**
* 获取出国记录详细信息
*/
@@ -315,6 +315,12 @@ public class RsOverseasApplication extends BaseEntity {
@ExcelProperty(value = "应急联系人及电话")
private String emergencyContact;
/**
* 附件OSS ID列表(逗号分隔)
*/
@ExcelProperty(value = "附件ID列表")
private String attachmentIds;
/**
* 手写签字(base64格式)
*/
@@ -251,6 +251,11 @@ public class RsOverseasApplicationBo extends BaseEntity {
*/
private String emergencyContact;
/**
* 附件OSS ID列表(逗号分隔)
*/
private String attachmentIds;
/**
* 手写签字(base64格式)
*/
@@ -33,6 +33,11 @@ public class RsOverseasApplicationQuery extends PageQuery {
*/
private String purpose;
/**
* 出境事由
*/
private String exitReason;
/**
* 目的地国家
*/
@@ -297,6 +297,11 @@ public class RsOverseasApplicationVo implements Serializable {
*/
private String emergencyContact;
/**
* 附件OSS ID列表(逗号分隔)
*/
private String attachmentIds;
/**
* 手写签字(base64格式)
*/
@@ -195,6 +195,20 @@ public interface IRsOverseasApplicationService {
Boolean updateStampedFileInfo(Long applicationId, String filePath, String fileName,
java.util.Date uploadTime, String uploadStatus, Long ossId);
/**
* 更新党籍保留附件信息
*
* @param applicationId 申请ID
* @param filePath 文件路径
* @param fileName 文件名
* @param uploadTime 上传时间
* @param uploadStatus 上传状态
* @param ossId OSS文件ID
* @return 是否成功
*/
Boolean updatePartyRetentionFileInfo(Long applicationId, String filePath, String fileName,
java.util.Date uploadTime, String uploadStatus, Long ossId);
/**
* 校验出国登记前置条件
* 检查近3个月内是否有已通过的出国登记记录
@@ -983,6 +983,23 @@ public class RsOverseasApplicationServiceImpl extends ServiceImpl<RsOverseasAppl
.set(RsOverseasApplication::getStampedFileOssId, ossId)) > 0;
}
/**
* 更新党籍保留附件信息
*/
@Override
@Transactional
public Boolean updatePartyRetentionFileInfo(Long applicationId, String filePath, String fileName,
java.util.Date uploadTime, String uploadStatus, Long ossId) {
return baseMapper.update(null,
new LambdaUpdateWrapper<RsOverseasApplication>()
.eq(RsOverseasApplication::getApplicationId, applicationId)
.set(RsOverseasApplication::getPartyRetentionFilePath, filePath)
.set(RsOverseasApplication::getPartyRetentionFileName, fileName)
.set(RsOverseasApplication::getPartyRetentionUploadTime, uploadTime)
.set(RsOverseasApplication::getPartyRetentionUploadStatus, uploadStatus)
.set(RsOverseasApplication::getPartyRetentionFileOssId, ossId)) > 0;
}
/**
* 校验出国登记前置条件
* 检查当前自然年内是否有已通过的出国登记记录
@@ -52,12 +52,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="adminRemark" column="admin_remark"/>
<result property="applicantRemark" column="applicant_remark"/>
<result property="emergencyContact" column="emergency_contact"/>
<result property="attachmentIds" column="attachment_ids"/>
<result property="signature" column="signature"/>
<result property="stampedRegistrationFilePath" column="stamped_registration_file_path"/>
<result property="stampedRegistrationFileName" column="stamped_registration_file_name"/>
<result property="stampedFileOssId" column="stamped_file_oss_id"/>
<result property="stampedFileUploadTime" column="stamped_file_upload_time"/>
<result property="stampedFileUploadStatus" column="stamped_file_upload_status"/>
<result property="partyRetentionFilePath" column="party_retention_file_path"/>
<result property="partyRetentionFileName" column="party_retention_file_name"/>
<result property="partyRetentionUploadTime" column="party_retention_upload_time"/>
<result property="partyRetentionUploadStatus" column="party_retention_upload_status"/>
<result property="partyRetentionFileOssId" column="party_retention_file_oss_id"/>
<result property="tenantId" column="tenant_id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
@@ -89,9 +95,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
oa.start_date, oa.end_date, oa.duration, oa.invitation, oa.itinerary,
oa.accommodation_plan, oa.transportation_plan, oa.status, oa.approval_status, oa.current_approver, oa.approval_level,
oa.apply_time, oa.approval_time, oa.admin_remark, oa.applicant_remark,
oa.emergency_contact, oa.signature,
oa.emergency_contact, oa.attachment_ids, oa.signature,
oa.stamped_registration_file_path, oa.stamped_registration_file_name,
oa.stamped_file_upload_time, oa.stamped_file_upload_status, oa.stamped_file_oss_id,
oa.party_retention_file_path, oa.party_retention_file_name,
oa.party_retention_upload_time, oa.party_retention_upload_status, oa.party_retention_file_oss_id,
oa.tenant_id, oa.create_by, oa.create_time, oa.update_by, oa.update_time, oa.deleted,
CASE
WHEN oa.status IS NULL OR oa.status = '' THEN '草稿'
@@ -199,6 +207,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="purpose != null and purpose != ''">
AND oa.purpose = #{purpose}
</if>
<if test="exitReason != null and exitReason != ''">
AND oa.exit_reason = #{exitReason}
</if>
<if test="destination != null and destination != ''">
AND oa.destination like concat('%', #{destination}, '%')
</if>
@@ -257,6 +268,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="purpose != null and purpose != ''">
AND oa.purpose = #{purpose}
</if>
<if test="exitReason != null and exitReason != ''">
AND oa.exit_reason = #{exitReason}
</if>
<if test="destination != null and destination != ''">
AND oa.destination like concat('%', #{destination}, '%')
</if>
+5 -1
View File
@@ -6,7 +6,7 @@ export interface OverseasApplicationQuery {
pageSize: number;
applicantName?: string;
department?: string;
purpose?: string;
exitReason?: string;
destination?: string;
status?: string;
approvalLevel?: string;
@@ -96,6 +96,8 @@ export interface OverseasApplicationVO {
approvalStatus?: string; // 工作流审批状态:draft/waiting/finish/back/cancel
approvalStatusText?: string; // 工作流审批状态文本
taskId?: string; // 当前待办任务ID(用于审批跳转)
// 附件相关字段
attachmentIds?: string; // 附件OSS ID列表(逗号分隔)
}
// 出国申请表单类型
@@ -150,6 +152,8 @@ export interface OverseasApplicationForm {
applicantRemark?: string;
// 工作流相关字段
approvalStatus?: string; // 工作流审批状态
// 附件相关字段
attachmentIds?: string; // 附件OSS ID列表(逗号分隔)
}
// 审批请求类型(支持标准工作流模式)
+25 -48
View File
@@ -7,39 +7,20 @@
<el-card shadow="hover">
<el-form ref="queryForm" :model="queryParams" label-width="100px">
<el-row :gutter="20">
<el-col :span="5">
<el-form-item label="申请人" prop="applicantName">
<el-input
v-model="queryParams.applicantName"
placeholder="请输入申请人姓名"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="所属部门" prop="department">
<el-input v-model="queryParams.department" placeholder="请输入所属部门" clearable @keyup.enter="handleQuery" />
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="目的地" prop="destination">
<el-col :span="6">
<el-form-item label="前往国家" prop="destination">
<el-input
v-model="queryParams.destination"
placeholder="请输入目的地国家"
placeholder="请输入前往国家"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
</el-col>
<el-col :span="5">
<el-form-item label="申请状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择申请状态" clearable style="width: 100%">
<el-option label="待审核" value="0" />
<el-option label="已通过" value="1" />
<el-option label="已拒绝" value="2" />
<el-option label="已取消" value="3" />
<el-option label="已完成" value="4" />
<el-col :span="6">
<el-form-item label="出境事由" prop="exitReason">
<el-select v-model="queryParams.exitReason" placeholder="请选择出境事由" clearable style="width: 100%">
<el-option v-for="dict in exitReasonOptions" :key="dict.label" :label="dict.label" :value="dict.label" />
</el-select>
</el-form-item>
</el-col>
@@ -79,8 +60,14 @@
>
<el-table-column label="申请人" align="center" prop="applicantName" min-width="100" show-overflow-tooltip />
<el-table-column label="部门" align="center" prop="department" min-width="140" show-overflow-tooltip />
<el-table-column label="目的地" align="center" prop="destination" min-width="120" show-overflow-tooltip />
<el-table-column label="出国事由" align="center" prop="purpose" min-width="140" show-overflow-tooltip />
<el-table-column label="政治面貌" align="center" prop="politicalStatusText" min-width="100" show-overflow-tooltip />
<el-table-column label="前往国家" align="center" prop="destination" min-width="120" show-overflow-tooltip />
<el-table-column label="出境事由" align="center" prop="exitReason" min-width="140" show-overflow-tooltip />
<el-table-column label="出国天数" align="center" prop="duration" min-width="100">
<template #default="scope">
<span>{{ scope.row.duration || 0 }}天</span>
</template>
</el-table-column>
<el-table-column label="申请时间" align="center" prop="applyTime" min-width="170">
<template #default="scope">
<span>{{ scope.row.applyTime ? new Date(scope.row.applyTime).toLocaleString() : '-' }}</span>
@@ -392,12 +379,15 @@ const state = reactive({
queryParams: {
pageNum: 1,
pageSize: 10,
applicantName: '',
department: '',
destination: '',
status: ''
exitReason: ''
} as OverseasApplicationQuery,
// 出境事由选项
exitReasonOptions: [
{ label: '旅游', value: 'tourism' },
{ label: '探亲', value: 'visiting_relatives' }
],
// 查看详情
viewDialog: {
@@ -427,6 +417,7 @@ const {
total,
showSearch,
queryParams,
exitReasonOptions,
viewDialog,
viewData,
approvalDialog,
@@ -469,27 +460,15 @@ const applyFilterAndPagination = () => {
let filteredList = [...state.allApplicationList];
// 🔴 应用搜索过滤
if (state.queryParams.applicantName) {
filteredList = filteredList.filter(item =>
item.applicantName?.includes(state.queryParams.applicantName || '')
);
}
if (state.queryParams.department) {
filteredList = filteredList.filter(item =>
item.department?.includes(state.queryParams.department || '')
);
}
if (state.queryParams.destination) {
filteredList = filteredList.filter(item =>
item.destination?.includes(state.queryParams.destination || '')
);
}
if (state.queryParams.status) {
if (state.queryParams.exitReason) {
filteredList = filteredList.filter(item =>
item.status === state.queryParams.status
item.exitReason === state.queryParams.exitReason
);
}
@@ -518,10 +497,8 @@ const resetQuery = () => {
Object.assign(state.queryParams, {
pageNum: 1,
pageSize: 10,
applicantName: '',
department: '',
destination: '',
status: ''
exitReason: ''
});
const form = proxy.$refs.queryForm;
if (form && typeof form.resetFields === 'function') {
File diff suppressed because it is too large. Load diff