提案导出

This commit is contained in:
2026-10-23 11:35:20 +08:00
parent 2ebc204803
commit 0bde359113
3 changed files with 131 additions and 43 deletions
@@ -82,16 +82,7 @@ import java.util.stream.Collectors;
@IocBean(args = {"refer:dao"})
public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> implements ProposalCommonService {
/** 确定立案提案的 Word 模板编码。 */
private static final String PROPOSAL_CONFIRM_FILING_TEMPLATE_CODE = "proposal_confirm_filing";
/** 工作建议提案的 Word 模板编码。 */
private static final String PROPOSAL_SUGGESTION_TEMPLATE_CODE = "proposal_suggestion";
/** 不予立案提案的 Word 模板编码。 */
private static final String PROPOSAL_NOT_TEMPLATE_CODE = "proposal_not";
/** 历史提案立案结果为空或无法识别时使用的原 Word 模板编码。 */
/** 所有立案结果统一使用重庆工贸提案征集表模板。 */
private static final String PROPOSAL_DEFAULT_TEMPLATE_CODE = "proposal";
/** PC端提案列表页面的排序字段白名单,页面代码由对应Controller固定传入。 */
@@ -1367,6 +1358,11 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
execute(sql);
NutMap docData = (NutMap) sql.getResult();
// 提案可能已被删除,避免在读取模板字段时产生空指针。
if (docData == null) {
throw new BaseException("提案信息不存在");
}
//处理下富文本
String brief = docData.getString("brief");
String measures = docData.getString("measures");
@@ -1381,12 +1377,23 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
ProcessInstance processInstance = dao().fetch(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "=", id));
List<ProcessTaskVO> doneTaskVos = new ArrayList<>();
List<ProcessTask> doneTaskList = flowEngine.processTaskService().getDoneTaskList(processInstance.getId(), null);
for (ProcessTask doneTask : doneTaskList) {
ProcessTaskVO taskVO = flowEngine.processTaskService().findById(doneTask.getId());
doneTaskVos.add(taskVO);
// 尚未启动或缺少 WF 实例的提案仍导出基础信息,附议和审批栏保持为空。
if (processInstance != null) {
List<ProcessTask> doneTaskList = flowEngine.processTaskService().getDoneTaskList(processInstance.getId(), null);
for (ProcessTask doneTask : doneTaskList) {
ProcessTaskVO taskVO = flowEngine.processTaskService().findById(doneTask.getId());
if (taskVO != null && taskVO.getExt() != null) {
doneTaskVos.add(taskVO);
}
}
}
// 附议人单元格仅展示已完成附议任务中的姓名,多人以顿号分隔并去重。
docData.put("secondNames", doneTaskVos.stream()
.filter(task -> "second".equals(task.getTaskName()))
.map(task -> task.getTaskFormData().getStr("userName"))
.filter(StrUtil::isNotBlank).distinct().collect(Collectors.joining("、")));
// 按任务节点分组
Map<String, List<ProcessTaskVO>> taskGroups = doneTaskVos.stream()
.filter(Objects::nonNull)
@@ -1394,6 +1401,17 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
docData.putAll(taskGroups);
putProposalExportTaskAliases(id, taskGroups, docData);
// 中文区块名称保持与现有模板一致,实际审核记录按 WF 节点编码匹配。
// 区块内继续读取 taskFormData 的意见、姓名和日期;无记录时写入空列表,避免同名节点误填。
Map<String, String> approvalTemplateNodes = Map.of(
"分工会主席审批", "delegation",
"校工会主席审批", "schoolUnionChairApproval",
"分管校领导审批", "school_leader");
approvalTemplateNodes.forEach((templateKey, taskName) -> docData.put(templateKey, doneTaskVos.stream()
.filter(task -> taskName.equals(task.getTaskName()))
.sorted(Comparator.comparing(ProcessTaskVO::getFinishTime, Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.toList())));
// 提案附议
List<NutMap> secondInfos = new ArrayList<>();
List<ProcessTaskVO> secondTaskVos = taskGroups.get("提案附议");
@@ -1475,29 +1493,13 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
docData.put("secondedList", secondInfos);
docData.put("delegationAuditList", delegationAuditList);
String templateCode = getProposalTemplateCode(docData.getString("caseFilingResult"));
try {
XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate(templateCode), config).render(docData).writeAndClose(byteArrayOutputStream);
XWPFTemplate.compile(sysOfficeTemplateUtil.getTemplate(PROPOSAL_DEFAULT_TEMPLATE_CODE), config).render(docData).writeAndClose(byteArrayOutputStream);
} catch (IOException e) {
log.error("提案导出失败{},提案id:{}", e.getMessage(), id);
}
}
/**
* 根据立案结果选择数据库办公模板编码。
*
* @param caseFilingResult 立案结果字典编码:CONFIRM_FILING、SUGGESTION 或 NOT
* @return 对应的 Word 模板编码;历史空值或未知编码返回 proposal
*/
private String getProposalTemplateCode(String caseFilingResult) {
return switch (StrUtil.nullToEmpty(caseFilingResult)) {
case "CONFIRM_FILING" -> PROPOSAL_CONFIRM_FILING_TEMPLATE_CODE;
case "SUGGESTION" -> PROPOSAL_SUGGESTION_TEMPLATE_CODE;
case "NOT" -> PROPOSAL_NOT_TEMPLATE_CODE;
default -> PROPOSAL_DEFAULT_TEMPLATE_CODE;
};
}
/**
* 将现行工作流节点映射到历史提案表模板使用的区块名称,并按主协办单位拆分办理答复。
* 模板仍使用旧节点名称时,避免已完成的意见和答复因键名不一致而无法渲染。
+89 -9
View File
@@ -1,17 +1,85 @@
<?xml version="1.0" encoding="UTF-8" ?>
<configuration scan="false" scanPeriod="60000" debug="false">
<!-- 定义日志文件的存储路径 -->
<!-- 日志存储路径 -->
<property name="LOG_HOME" value="./logs"/>
<!-- 控制台输出配置 -->
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- INFO级别日志文件配置 -->
<!--
#################################################################################
# #
# 全文输出日志(一个文件存储)start #
# #
#################################################################################
-->
<!-- 所有级别日志写入同一个文件 -->
<appender name="ALL_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/app.log</file>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/app-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>300</maxHistory>
</rollingPolicy>
</appender>
<!-- 框架日志级别(按需调整) -->
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="org.quartz" level="INFO"/>
<logger name="org.nutz" level="DEBUG"/>
<!-- root logger:DEBUG 级别,输出到控制台和全量文件 -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="ALL_FILE"/>
</root>
<!--
#################################################################################
# #
# 全文输出日志(一个文件存储)end #
# #
#################################################################################
-->
<!--
#################################################################################
# #
# debug、info、error分文件输出 start #
# #
#################################################################################
-->
<!-- DEBUG级别日志文件配置 -->
<!--<appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/debug.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>DEBUG</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/debug-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>300</maxHistory>
</rollingPolicy>
</appender>
&lt;!&ndash; INFO级别日志文件配置 &ndash;&gt;
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/info.log</file>
<encoder>
@@ -24,11 +92,11 @@
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/info-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<maxHistory>300</maxHistory>
</rollingPolicy>
</appender>
<!-- ERROR级别日志文件配置 -->
&lt;!&ndash; ERROR级别日志文件配置 &ndash;&gt;
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOG_HOME}/error.log</file>
<encoder>
@@ -41,19 +109,31 @@
</filter>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${LOG_HOME}/error-%d{yyyy-MM-dd}.log</fileNamePattern>
<maxHistory>30</maxHistory>
<maxHistory>300</maxHistory>
</rollingPolicy>
</appender>
<logger name="java" additivity="false" />
&lt;!&ndash; 框架日志级别 &ndash;&gt;
&lt;!&ndash; <logger name="java" additivity="false" />&ndash;&gt;
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="org.quartz" level="INFO"/>
<logger name="org.nutz" level="DEBUG"/>
<!-- 日志级别和appender的关联 -->
&lt;!&ndash; 日志级别和appender的关联 &ndash;&gt;
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="DEBUG_FILE"/>
<appender-ref ref="INFO_FILE"/>
<appender-ref ref="ERROR_FILE"/>
</root>
</root>-->
<!--
#################################################################################
# #
# debug、info、error分文件输出 end #
# #
#################################################################################
-->
</configuration>
@@ -88,7 +88,8 @@ layout("/layouts/platform.html"){
</template>
<template #edit>
<el-form :model="formData" :rules="welfareFormRules" label-width="150px" ref="form" v-loading="formLoading">
<el-form :model="formData" :rules="welfareFormRules" :validate-on-rule-change="false"
@submit.native.prevent label-width="150px" ref="form" v-loading="formLoading">
<el-form-item label="沿用往期福利" v-if="!formData.id">
<el-select clearable filterable placeholder="请选择往期福利" style="width: 100%"
v-model="reuseProjectId" @change="reuseHistoryProject">
@@ -294,10 +295,12 @@ layout("/layouts/platform.html"){
welfareFormRules() {
// 慰问金只要求项目名称和单个发放时间,其他发放形式沿用原有校验。
const requiredForOtherMode = this.formData.provideMode !== 99
// 慰问金在提交时统一校验,输入项目名称或切换表单项时不提前提示。
const requiredTrigger = requiredForOtherMode ? ["blur", "change"] : "submit"
return {
name: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
name: [{ required: true, message: "必填", trigger: requiredTrigger }],
festival: [{ required: requiredForOtherMode, message: "必选", trigger: ["blur", "change"] }],
provideTime: [{ required: true, message: "必选", trigger: ["blur", "change"] }],
provideTime: [{ required: true, message: "必选", trigger: requiredTrigger }],
choiceTime: [{ required: requiredForOtherMode, message: "必选", trigger: ["blur", "change"] }],
provideAddress: [{ required: requiredForOtherMode, message: "必选", trigger: ["blur", "change"] }],
provideMode: [{ required: true, message: "必选", trigger: ["blur", "change"] }],
@@ -410,6 +413,9 @@ layout("/layouts/platform.html"){
this.$set(this, "reuseProjectId", null)
if (this.$refs.form) this.$refs.form.resetFields()
this.$set(this, "formData", {
name: "",
choiceTime: [],
provideTime: null,
provideMode: 99,
isPushHome: false,
signMode: 4,