This commit is contained in:
2026-05-14 19:15:40 +08:00
parent 652c647644
commit 6682bca13d
7 changed files with 103 additions and 36 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<packaging>jar</packaging>
<groupId>com.budwk</groupId>
<artifactId>zhgh_hdc</artifactId>
<artifactId>mini</artifactId>
<version>5.6.0-plus</version>
<properties>
<nutzboot.version>2.6.0-SNAPSHOT</nutzboot.version>
@@ -108,9 +108,10 @@ public class ProposalSecondedController {
Cnd cnd = Cnd.NEW();
cnd.and("t.taskName", "=", "second");
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
/* if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
}
}*/
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
if (approval) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
@@ -151,9 +152,10 @@ public class ProposalSecondedController {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
cnd.andEX("info.sessionId", "=", sessionId);
if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
/* if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
}
}*/
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
sql.setCondition(cnd);
return Result.success(baseService.count(sql));
@@ -7,7 +7,7 @@ import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
public interface ProposalSecondedService extends BaseService<ProposalSecond> {
/**
* 保存受邀附议办理结果,并在“受邀附议 + 主动附议”总同意人数达到 2 人时自动推进到团长审核。
* 保存受邀附议办理结果,并在受邀附议同意人数达到配置数量时自动推进到团长审核。
*
* @param proposalId 提案ID
* @param taskActorUserId 受邀附议办理人ID
@@ -18,7 +18,8 @@ public interface ProposalSecondedService extends BaseService<ProposalSecond> {
Result updateInviteSecondInfo(String proposalId, String taskActorUserId, String opinion, Integer submitType);
/**
* 保存主动附议结果,并在“受邀附议 + 主动附议”总同意人数达到 2 人时自动推进到团长审核。
* 保存主动附议结果。
* 主动附议只记录业务附议人信息,不参与工作流会签统计,也不触发流程自动推进。
*
* @param proposalId 提案ID
* @param opinion 附议意见
@@ -1,6 +1,7 @@
package com.budwk.app.zhgh.democratic.proposal.service.common;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
@@ -15,6 +16,7 @@ import com.budwk.app.flow.engine.FlowEngine;
import com.budwk.app.flow.engine.util.FlowUtil;
import com.budwk.app.flow.entity.ProcessInstance;
import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.vo.ProcessTaskVO;
import com.budwk.app.sys.models.Sys_dict;
@@ -143,7 +145,9 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
List<ProposalSecond> positiveSecondList = dao().query(
ProposalSecond.class,
Cnd.where(ProposalSecond::getProposalId, "=", id).and(ProposalSecond::getMode, "=", false)
Cnd.where(ProposalSecond::getProposalId, "=", id)
.and(ProposalSecond::getMode, "=", false)
.asc(ProposalSecond::getSecondedTime)
);
info.put("positiveSecondList", positiveSecondList);
return info;
@@ -217,6 +221,20 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
// 按任务节点分组
Map<String, List<ProcessTaskVO>> taskGroups = doneTaskVos.stream().collect(Collectors.groupingBy(ProcessTaskVO::getDisplayName));
// 主动附议不进入工作流,但 Word 模板的附议人明细仍使用“提案附议”分组渲染。
// 这里把 proposal_second 中的主动附议业务记录转换成模板可识别的任务数据,和受邀附议一起导出。
List<ProposalSecond> positiveSecondList = dao().query(
ProposalSecond.class,
Cnd.where(ProposalSecond::getProposalId, "=", id)
.and(ProposalSecond::getMode, "=", false)
.asc(ProposalSecond::getSecondedTime)
);
if (Lang.isNotEmpty(positiveSecondList)) {
List<ProcessTaskVO> proposalSecondTasks = taskGroups.computeIfAbsent("提案附议", key -> new ArrayList<>());
for (ProposalSecond positiveSecond : positiveSecondList) {
proposalSecondTasks.add(buildPositiveSecondTaskVO(processInstance.getId(), positiveSecond));
}
}
docData.putAll(taskGroups);
LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
@@ -238,6 +256,37 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
}
}
/**
* 将主动附议业务记录转换成 Word 模板已支持的流程任务结构。
*
* @param processInstanceId 流程实例 ID,用于保持导出数据与当前提案流程关联
* @param positiveSecond 主动附议记录,包含附议人、附议时间、附议意见和附议结果
* @return ProcessTaskVO,放入“提案附议”模板分组后可复用现有 Word 表格渲染
*/
private ProcessTaskVO buildPositiveSecondTaskVO(Long processInstanceId, ProposalSecond positiveSecond) {
ProcessTaskVO taskVO = new ProcessTaskVO();
taskVO.setProcessInstanceId(processInstanceId);
taskVO.setTaskName("second");
taskVO.setDisplayName("提案附议");
taskVO.setTaskState(ProcessTaskStateEnum.FINISHED.getCode());
taskVO.setOperator(positiveSecond.getSeconderId());
taskVO.setFinishTime(positiveSecond.getSecondedTime());
Dict variable = Dict.create();
variable.put(FlowConst.SUBMIT_TYPE, Boolean.TRUE.equals(positiveSecond.getIsAgree())
? ProcessSubmitTypeEnum.AGREE.getCode()
: ProcessSubmitTypeEnum.COUNTERSIGN_DISAGREE.getCode());
variable.put(FlowConst.TASK_FORM_DATA_PREFIX + "userName", positiveSecond.getUserName());
variable.put(FlowConst.TASK_FORM_DATA_PREFIX + "loginName", positiveSecond.getLoginName());
variable.put(FlowConst.TASK_FORM_DATA_PREFIX + "unitName", positiveSecond.getUnitName());
variable.put(FlowConst.TASK_FORM_DATA_PREFIX + "unionName", positiveSecond.getUnionName());
variable.put(FlowConst.TASK_FORM_DATA_PREFIX + "delegationName", positiveSecond.getDelegationName());
variable.put(FlowConst.TASK_FORM_DATA_PREFIX + "opinion", positiveSecond.getOpinion());
variable.put(FlowConst.TASK_FORM_DATA_PREFIX + "secondedMode", "主动附议");
taskVO.setVariable(Json.toJson(variable));
return taskVO;
}
@Override
public void exportProposalFeedBackAsDocx(String id, ByteArrayOutputStream byteArrayOutputStream) {
if (id == null || id.isEmpty()) {
@@ -19,6 +19,7 @@ import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.web.commons.base.Globals;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
import com.budwk.app.zhgh.democratic.proposal.param.ProposalQueryComprehensiveParam;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalExportService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
@@ -196,24 +197,7 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
list.get(i).put("brief", HtmlUtil.cleanHtmlTag(list.get(i).getString("brief")));
list.get(i).put("measures", HtmlUtil.cleanHtmlTag(list.get(i).getString("measures")));
// 流程实例
ProcessInstance instance = dao().fetch(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "=", list.get(i).getString("id")));
// 查询附议人信息
ProcessTask inviteTask = dao().fetch(ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "=", instance.getId())
.and(ProcessTask::getTaskName, "=", "invite")
.and(ProcessTask::getTaskState, "in",
List.of(ProcessTaskStateEnum.DOING.getCode(), ProcessTaskStateEnum.FINISHED.getCode()))
.desc(ProcessTask::getCreatedAt));
if (inviteTask != null) {
NutMap variable = Json.fromJson(NutMap.class, inviteTask.getVariable());
List<NutMap> seconders = variable.getAsList(FlowConst.TASK_FORM_DATA_PREFIX + "seconder", NutMap.class);
if (Lang.isNotEmpty(seconders)) {
list.get(i).put("secondedUserNames", seconders.stream().map(v -> v.getString("userName")).collect(Collectors.joining(",")));
}
}
list.get(i).put("secondedUserNames", buildSecondedUserNames(list.get(i).getString("id")));
}
List<ExcelExportEntity> exportEntities = new ArrayList<>();
@@ -225,7 +209,7 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
exportEntities.add(new ExcelExportEntity("代表建议措施", "measures", 40));
exportEntities.add(new ExcelExportEntity("提案人", "createUserName", 20));
exportEntities.add(new ExcelExportEntity("提案人电话", "mobile", 20));
exportEntities.add(new ExcelExportEntity("附议人(两名)", "secondedUserNames", 20));
exportEntities.add(new ExcelExportEntity("附议人", "secondedUserNames", 20));
exportEntities.add(new ExcelExportEntity("提案单位", "unitName", 20));
// exportEntities.add(new ExcelExportEntity("拟交办单位", "", 20));
@@ -239,6 +223,30 @@ public class ProposalExportServiceImpl extends BaseServiceImpl<ProposalInfo> imp
CommonDownloadUtil.download(title + ".xlsx", workbook, response);
}
/**
* 汇总表附议人统一从 proposal_second 获取。
* 邀请附议和主动附议都会写入该表,这里只负责导出附议人姓名,不再依赖流程任务变量。
*
* @param proposalId 提案 ID
* @return 附议人姓名字符串,多个附议人用逗号分隔;无附议人时返回空字符串
*/
private String buildSecondedUserNames(String proposalId) {
List<ProposalSecond> seconds = dao().query(
ProposalSecond.class,
Cnd.where(ProposalSecond::getProposalId, "=", proposalId)
.asc(ProposalSecond::getInviteTime)
.asc(ProposalSecond::getSecondedTime)
);
if (Lang.isEmpty(seconds)) {
return "";
}
return seconds.stream()
.map(ProposalSecond::getUserName)
.filter(StrUtil::isNotBlank)
.distinct()
.collect(Collectors.joining(","));
}
@Override
public void exportProposalRegisterSummaryAsExcel(ProposalQueryComprehensiveParam pageForm, HttpServletResponse response) {
Sql sql = exportComprehensiveSql(pageForm);
@@ -12,6 +12,7 @@ import com.budwk.app.flow.entity.ProcessTask;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.sys.views.View_user;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalSecond;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalSecondedService;
@@ -44,6 +45,7 @@ public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond>
ProposalSecond.class,
Cnd.where(ProposalSecond::getProposalId, "=", proposalId)
.and(ProposalSecond::getSeconderId, "=", taskActorUserId)
.and(ProposalSecond::getMode, "=", true)
);
if (proposalSecond == null) {
return Result.error("未查询到附议记录");
@@ -113,15 +115,13 @@ public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond>
seconded.setIsAgree(ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode()));
dao().insert(seconded);
if (ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode())) {
autoAdvanceToDelegationWhenEnoughSeconders(proposalId);
}
// 主动附议只做业务留痕,不参与 second 会签统计,也不触发流程自动流转。
return Result.success();
}
/**
* 统一按 proposal_second 里的“同意附议”人数判断是否已满足 2 人附议。
* 一旦满足 2 人,就用系统自动执行一个仍处于办理中的 second 任务来触发流程引擎会签合并,
* 只按受邀附议记录判断是否达到配置的附议人数。
* 一旦满足配置数量,就用系统自动执行一个仍处于办理中的 second 任务来触发流程引擎会签合并,
* 这样 second 节点会自动废弃其余未办任务,并按流程定义流转到 delegation 节点。
*/
private void autoAdvanceToDelegationWhenEnoughSeconders(String proposalId) {
@@ -130,12 +130,18 @@ public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond>
return;
}
ProposalConfig proposalConfig = dao().fetch(ProposalConfig.class, Cnd.NEW());
if (proposalConfig == null || proposalConfig.getSeconderNum() == null) {
return;
}
long agreeCount = dao().count(
ProposalSecond.class,
Cnd.where(ProposalSecond::getProposalId, "=", proposalId)
.and(ProposalSecond::getMode, "=", true)
.and(ProposalSecond::getIsAgree, "=", true)
);
if (agreeCount < 2) {
if (agreeCount < proposalConfig.getSeconderNum()) {
return;
}
int mergedCompletedCount = (int) Math.max(agreeCount - 1, 0);
@@ -145,8 +151,8 @@ public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond>
return;
}
// 当前流程仍停留在 second 会签节点时,把“已同意附议人数”桥接到会签变量里。
// 这样系统自动完成一个 second 任务后,会签节点会按“累计 2 个同意”直接合并到团长审核。
// 当前流程仍停留在 second 会签节点时,把“受邀同意附议人数”桥接到会签变量里。
// 这样系统自动完成一个 second 任务后,会签节点会按配置的受邀附议人数合并到团长审核。
String prefix = FlowConst.COUNTERSIGN_VARIABLE_PREFIX + "second_";
Dict instanceVariable = Dict.create();
instanceVariable.put(prefix + FlowConst.NR_OF_COMPLETED_INSTANCES, mergedCompletedCount);
@@ -146,7 +146,8 @@ const PROPOSAL_INFO = {
</el-table>
</el-descriptions-item>
<el-descriptions-item label="主动附议" :span="3" v-if="task.taskName === 'invite'">
<el-table v-if="viewData.positiveSecondList.length > 0" :data="viewData.positiveSecondList">
<el-table v-if="viewData.positiveSecondList && viewData.positiveSecondList.length > 0"
:data="viewData.positiveSecondList">
<el-table-column label="姓名" prop="userName"></el-table-column>
<el-table-column label="工号" prop="loginName"></el-table-column>
<el-table-column label="单位" prop="unitName"></el-table-column>