From 476dc19590c0cd3024d16158e68cebf2ed26ff23 Mon Sep 17 00:00:00 2001 From: zhouhefeng Date: Thu, 9 Apr 2026 14:55:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E6=A1=88=E6=95=B4=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sms/impl/jshvc/SmsJshvcServiceImpl.java | 8 +- .../transact/ProposalCaseCheckController.java | 10 + .../ProposalCommitteeFilingController.java | 36 +- ...ProposalCommitteeFilingUnitController.java | 24 +- ...roposalSchoolLeaderApprovalController.java | 95 +++- .../ProposalUnderTakeReplyController.java | 105 ++-- .../ProposalUnitAssignmentHandler.java | 172 +++++++ .../ProposalCaseFilingInterceptor.java | 57 +-- .../ProposalFgxldPrefixInterceptor.java | 9 +- .../ProposalCommitteeFilingService.java | 18 +- .../ProposalCommitteeFilingUnitService.java | 11 +- .../ProposalUnderTakeReplyService.java | 43 +- .../ProposalCommitteeFilingServiceImpl.java | 202 ++------ ...roposalCommitteeFilingUnitServiceImpl.java | 233 +++++---- .../ProposalUnderTakeReplyServiceImpl.java | 470 +++++++++--------- .../zhgh/democratic/proposal/common/info.js | 24 +- .../proposal/export/comprehensive/index.html | 34 +- .../proposal/query/comprehensive/index.html | 34 +- .../proposal/query/history/index.html | 35 +- .../proposal/query/yearReport/index.html | 35 +- .../proposal/transact/allFinished/index.html | 208 ++++---- .../proposal/transact/caseCheck/index.html | 100 +++- .../transact/committeeFiling/index.html | 62 ++- .../transact/committeeFiling/merge.js | 117 +++-- .../transact/committeeFilingUnit/index.html | 105 +++- .../transact/schoolLeaderApproval/index.html | 35 +- .../proposal/transact/unitReply/index.html | 88 +++- .../zhghh5/democratic/proposal/common/info.js | 20 +- .../transact/schoolLeaderApproval/index.html | 2 +- .../views/platform/zhghh5/sys/home/todo.js | 2 +- 30 files changed, 1502 insertions(+), 892 deletions(-) create mode 100644 src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalUnitAssignmentHandler.java diff --git a/src/main/java/com/budwk/app/base/sms/impl/jshvc/SmsJshvcServiceImpl.java b/src/main/java/com/budwk/app/base/sms/impl/jshvc/SmsJshvcServiceImpl.java index 9eeb4bba..1b0408b5 100644 --- a/src/main/java/com/budwk/app/base/sms/impl/jshvc/SmsJshvcServiceImpl.java +++ b/src/main/java/com/budwk/app/base/sms/impl/jshvc/SmsJshvcServiceImpl.java @@ -27,10 +27,10 @@ import java.util.Map; @IocBean public class SmsJshvcServiceImpl implements SmsService { - private static final String APPID = "1430576717768122368"; - private static final String APP_SECRET = "19A0ACB03FBQHL4R4XQD"; - private static final String TOKEN_URL = "https://gateway.jshvc.edu.cn/token/gateway/accessToken"; - private static final String MSG_URL = "https://gateway.jshvc.edu.cn/mp/restful/v2/message/send"; + private static final String APPID = ""; + private static final String APP_SECRET = ""; + private static final String TOKEN_URL = ""; + private static final String MSG_URL = ""; private static final String REDIS_KEY_MSG_ACCESS_TOKEN = "msg:token:"; @Inject diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCaseCheckController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCaseCheckController.java index 98e6f06c..eb9f5661 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCaseCheckController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCaseCheckController.java @@ -9,6 +9,7 @@ import com.budwk.app.flow.enums.ProcessTaskStateEnum; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam; +import com.budwk.app.zhgh.democratic.proposal.service.ProposalCaseCheckService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @@ -39,6 +40,8 @@ public class ProposalCaseCheckController { @Inject private BaseService baseService; + @Inject + private ProposalCaseCheckService proposalCaseCheckService; @At("") @Ok("beetl:/platform/zhgh/democratic/proposal/transact/caseCheck/index.html") @@ -103,4 +106,11 @@ public class ProposalCaseCheckController { Pagination pagination = baseService.listPageMap(pageForm.getPageNumber(),pageForm.getPageSize(), sql); return Result.success(pagination); } + + @At + @SaCheckPermission("proposal.caseCheck") + @ApiOperation("查询当前提案承办单位") + public Result listReplyUnits(String proposalId) { + return Result.success(proposalCaseCheckService.listReplyUnits(proposalId)); + } } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingController.java index c411adc2..b8d2ae19 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingController.java @@ -5,12 +5,9 @@ import cn.hutool.core.lang.Dict; import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; -import com.budwk.app.flow.constant.FlowConst; import com.budwk.app.flow.enums.ProcessTaskStateEnum; -import com.budwk.app.flow.service.FlowCommonService; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; -import com.budwk.app.zhgh.democratic.proposal.models.ProposalMerge; import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam; import com.budwk.app.zhgh.democratic.proposal.service.ProposalCommitteeFilingService; import io.swagger.annotations.Api; @@ -25,7 +22,6 @@ import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.json.Json; -import org.nutz.lang.random.R; import org.nutz.mvc.annotation.At; import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; @@ -44,9 +40,6 @@ public class ProposalCommitteeFilingController { private Dao dao; @Inject private ProposalCommitteeFilingService proposalCommitteeFilingService; - @Inject - private FlowCommonService flowCommonService; - @At("") @Ok("beetl:/platform/zhgh/democratic/proposal/transact/committeeFiling/index.html") @@ -118,27 +111,16 @@ public class ProposalCommitteeFilingController { @Aop(TransAop.READ_COMMITTED) public Result merge(@Param("data") String data) { Dict args = Json.fromJson(Dict.class, data); + proposalCommitteeFilingService.merge(args); + return Result.success(); + } - List taskIds = (List) args.get("processTaskIds"); - List proposalIds = (List) args.get("proposalIds"); - - // 记录提案并案 - dao.clear(ProposalMerge.class, Cnd.where(ProposalMerge::getProposalId, "in", proposalIds)); - String groupId = R.UU32(); - List merges = proposalIds.stream().map(proposalId -> { - ProposalMerge merge = new ProposalMerge(); - merge.setProposalId(proposalId); - merge.setGroupId(groupId); - return merge; - }).toList(); - dao.insert(merges); - - for (Integer taskId : taskIds) { - Dict taskArgs = args.clone(); - taskArgs.put(FlowConst.PROCESS_TASK_ID_KEY, taskId); - flowCommonService.executeTask(taskArgs); - } - + @At + @SaCheckPermission("proposal.committeeFiling") + @ApiOperation("撤回任务") + @Aop(TransAop.READ_COMMITTED) + public Result revokeTask(@Param("taskId") Long taskId, @Param("proposalId") String proposalId) { + proposalCommitteeFilingService.revokeTask(taskId, proposalId); return Result.success(); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingUnitController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingUnitController.java index 283360e7..c95f3fd5 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingUnitController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalCommitteeFilingUnitController.java @@ -8,7 +8,6 @@ import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; import com.budwk.app.base.service.BaseService; -import com.budwk.app.flow.constant.FlowConst; import com.budwk.app.flow.entity.ProcessInstance; import com.budwk.app.flow.entity.ProcessTask; import com.budwk.app.flow.entity.ProcessTaskActor; @@ -128,28 +127,7 @@ public class ProposalCommitteeFilingUnitController { @Aop(TransAop.READ_COMMITTED) public Result executeTask(@Param("data") String data) { Dict args = Json.fromJson(Dict.class, data); - String proposalId = args.getStr("proposalId"); - - // 查询是否提案 - - // 单条审核 - List mergeProposalIds = proposalCommonService.mergeProposal(proposalId); - if (ObjectUtil.isEmpty(mergeProposalIds)) { - flowCommonService.executeTask(args); - return Result.success(); - } - - // 并案审核 - ProcessTask thisTask = baseService.dao().fetch(ProcessTask.class, args.getLong(FlowConst.PROCESS_TASK_ID_KEY)); - List mergeTasks = processTaskService.getDoingTaskByBizIdTaskName(mergeProposalIds, thisTask.getTaskName()); - for (ProcessTask mergeTask : mergeTasks) { - Dict cloneArgs = args.clone(); - cloneArgs.put(FlowConst.PROCESS_TASK_ID_KEY, mergeTask.getId()); - flowCommonService.executeTask(cloneArgs); - } - - ProposalInfo info = proposalCommonService.fetch(proposalId); - info.setCaseFilingResult(args.getStr("caseFilingResult")); + proposalCommitteeFilingUnitService.executeFlowTask(args); return Result.success(); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSchoolLeaderApprovalController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSchoolLeaderApprovalController.java index 197d8489..637535aa 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSchoolLeaderApprovalController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalSchoolLeaderApprovalController.java @@ -3,6 +3,7 @@ package com.budwk.app.zhgh.democratic.proposal.controller.transact; import cn.dev33.satoken.annotation.SaCheckPermission; import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONUtil; import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.page.Pagination; @@ -23,10 +24,12 @@ import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonServi import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; +import org.nutz.aop.interceptor.ioc.TransAop; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; import org.nutz.dao.Sqls; import org.nutz.dao.sql.Sql; +import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.json.Json; @@ -35,6 +38,7 @@ import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; import javax.validation.Valid; +import java.util.ArrayList; import java.util.List; @IocBean @@ -95,6 +99,8 @@ public class ProposalSchoolLeaderApprovalController { t.finishTime, t.taskParentId, t.variable taskVariable, + GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 1 THEN pru.unitName END) AS masterUnitName, + GROUP_CONCAT(DISTINCT CASE WHEN pru.isMaster = 0 THEN pru.unitName END) AS slaveUnitNames, IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName, IF(rt.id IS NOT NULL, 1, 0) AS canRevoke, GROUP_CONCAT(DISTINCT ta.actorName, '(', ta.actorAccount, ')' ) AS auditUser @@ -107,6 +113,7 @@ public class ProposalSchoolLeaderApprovalController { LEFT JOIN proposal_info info ON info.id = ins.businessNo LEFT JOIN proposal_type type on type.id = info.typeId LEFT JOIN proposal_merge mer ON mer.proposalId = info.id + LEFT JOIN proposal_reply_unit pru ON pru.proposalId = info.id LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId $condition @@ -142,6 +149,7 @@ public class ProposalSchoolLeaderApprovalController { @At @SaCheckPermission("proposal.schoolLeaderApproval") @ApiOperation("执行任务") + @Aop(TransAop.READ_COMMITTED) public Result executeTask(@Param("data") String data) { Dict args = Json.fromJson(Dict.class, data); @@ -156,18 +164,34 @@ public class ProposalSchoolLeaderApprovalController { ProcessTask thisTask = dao.fetch(ProcessTask.class, args.getLong(FlowConst.PROCESS_TASK_ID_KEY)); ProcessTask parentTask = dao.fetch(ProcessTask.class, Cnd.where("id", "=", thisTask.getTaskParentId())); - Dict dict = Json.fromJson(Dict.class, parentTask.getVariable()); - - args.put("tf_caseFilingResult", dict.getStr("tf_caseFilingResult")); - args.put("tf_helpunitreply", dict.getStr("tf_helpunitreply")); - args.put("tf_masterUnitId", dict.getStr("tf_masterUnitId")); - args.put("tf_slaveUnitIds", dict.getObj("tf_slaveUnitIds")); + Dict dict = ObjectUtil.defaultIfNull(Json.fromJson(Dict.class, parentTask.getVariable()), Dict.create()); ProcessInstance instance = dao.fetch(ProcessInstance.class, Cnd.where(ProcessInstance::getId, "=", thisTask.getProcessInstanceId())); - Dict instanceDict = Json.fromJson(Dict.class, instance.getVariable()); - instanceDict.put("tf_helpunitreply", dict.getStr("tf_helpunitreply")); - instanceDict.put("tf_masterUnitId", dict.getStr("tf_masterUnitId")); - instanceDict.put("tf_slaveUnitIds", dict.getObj("tf_slaveUnitIds")); + Dict instanceDict = ObjectUtil.defaultIfNull(Json.fromJson(Dict.class, instance.getVariable()), Dict.create()); + + // 分管领导审批需要把提委会立案环节的承办单位信息继续透传,兼容主办单位由单个改为多个的场景。 + Object masterUnitIds = getProcessVariable(dict, instanceDict, "tf_masterUnitIds"); + String masterUnitId = getProcessVariableStr(dict, instanceDict, "tf_masterUnitId"); + if (ObjectUtil.isEmpty(masterUnitIds) && StrUtil.isNotBlank(masterUnitId)) { + masterUnitIds = List.of(masterUnitId); + } + if (StrUtil.isBlank(masterUnitId)) { + List masterUnitIdList = toStringList(masterUnitIds); + if (ObjectUtil.isNotEmpty(masterUnitIdList)) { + masterUnitId = masterUnitIdList.get(0); + } + } + + args.put("tf_caseFilingResult", getProcessVariableStr(dict, instanceDict, "tf_caseFilingResult")); + args.put("tf_helpunitreply", getProcessVariableStr(dict, instanceDict, "tf_helpunitreply")); + args.put("tf_masterUnitId", masterUnitId); + args.put("tf_masterUnitIds", masterUnitIds); + args.put("tf_slaveUnitIds", getProcessVariable(dict, instanceDict, "tf_slaveUnitIds")); + + instanceDict.put("tf_helpunitreply", getProcessVariableStr(dict, instanceDict, "tf_helpunitreply")); + instanceDict.put("tf_masterUnitId", masterUnitId); + instanceDict.put("tf_masterUnitIds", masterUnitIds); + instanceDict.put("tf_slaveUnitIds", getProcessVariable(dict, instanceDict, "tf_slaveUnitIds")); instance.setVariable(JSONUtil.toJsonStr(instanceDict)); dao.update(instance); @@ -190,4 +214,55 @@ public class ProposalSchoolLeaderApprovalController { } return Result.success(); } + + /** + * 优先取当前父任务变量,没有时回退到流程实例变量,避免审批流转时丢失承办单位信息。 + */ + private Object getProcessVariable(Dict taskDict, Dict instanceDict, String key) { + Object taskValue = taskDict.getObj(key); + if (ObjectUtil.isNotEmpty(taskValue)) { + return taskValue; + } + return instanceDict.getObj(key); + } + + /** + * 字符串类型流程变量统一做空值兼容,便于兼容老流程里单主办字段的读取。 + */ + private String getProcessVariableStr(Dict taskDict, Dict instanceDict, String key) { + Object value = getProcessVariable(taskDict, instanceDict, key); + return ObjectUtil.isEmpty(value) ? "" : String.valueOf(value); + } + + /** + * 主办单位可能以集合、数组、JSON 字符串或逗号分隔字符串保存,这里统一转换为字符串列表。 + */ + private List toStringList(Object value) { + if (ObjectUtil.isEmpty(value)) { + return List.of(); + } + if (value instanceof List list) { + return list.stream().filter(ObjectUtil::isNotEmpty).map(String::valueOf).toList(); + } + if (value instanceof Object[] array) { + List result = new ArrayList<>(); + for (Object item : array) { + if (ObjectUtil.isNotEmpty(item)) { + result.add(String.valueOf(item)); + } + } + return result; + } + if (value instanceof String strValue) { + if (StrUtil.isBlank(strValue)) { + return List.of(); + } + if (JSONUtil.isTypeJSONArray(strValue)) { + return JSONUtil.parseArray(strValue).toList(String.class); + } + return StrUtil.splitTrim(strValue, ","); + } + return List.of(String.valueOf(value)); + } + } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalUnderTakeReplyController.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalUnderTakeReplyController.java index 74f627d5..a8fa6146 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalUnderTakeReplyController.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/controller/transact/ProposalUnderTakeReplyController.java @@ -6,10 +6,8 @@ import cn.hutool.core.util.ObjectUtil; import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.result.Result; -import com.budwk.app.flow.constant.FlowConst; import com.budwk.app.flow.entity.ProcessTask; import com.budwk.app.flow.enums.ProcessTaskStateEnum; -import com.budwk.app.flow.service.FlowCommonService; import com.budwk.app.flow.service.ProcessTaskService; import com.budwk.app.sys.models.Sys_role; import com.budwk.app.sys.models.Sys_user_role; @@ -18,6 +16,7 @@ import com.budwk.app.sys.services.SysUserService; import com.budwk.app.web.commons.auth.utils.AuthUtil; import com.budwk.app.web.commons.auth.utils.SecurityUtil; import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam; +import com.budwk.app.zhgh.democratic.proposal.service.ProposalUnderTakeReplyService; import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -38,6 +37,8 @@ import org.nutz.mvc.annotation.Ok; import org.nutz.mvc.annotation.Param; import javax.validation.Valid; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; import java.util.List; import java.util.stream.Stream; @@ -53,7 +54,7 @@ public class ProposalUnderTakeReplyController { @Inject private ProposalCommonService proposalCommonService; @Inject - private FlowCommonService flowCommonService; + private ProposalUnderTakeReplyService proposalUnderTakeReplyService; @Inject private ProcessTaskService processTaskService; @Inject @@ -77,6 +78,11 @@ public class ProposalUnderTakeReplyController { @SaCheckPermission("proposal.unitReply") @ApiOperation("分页列表") public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval, boolean transfer) { + /* + * 承办单位答复列表需要按“承办单位”维度展示。 + * 同一个任务可能挂了多个承办单位负责人,若只按 taskId 分组会把多条待审核合并成一条, + * 因此这里优先使用任务参与人的承办单位信息拆行,历史数据缺失时再回退到流程变量。 + */ Sql sql = Sqls.create(""" SELECT info.*, @@ -121,10 +127,11 @@ public class ProposalUnderTakeReplyController { $condition """); Cnd cnd = Cnd.NEW(); - cnd.and("t.taskName", "in", List.of("master_reply", "slave_reply", "opinion_master_reply")); +// cnd.and("t.taskName", "in", List.of("master_reply", "slave_reply", "opinion_master_reply")); + cnd.and("t.taskName", "in", List.of("unit_reply", "two_unit_reply", "opinion_unit_reply")); - if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { - cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId())); + if (!SecurityUtil.getUserLoginname().equals("superadmin")) { + cnd.and("ta.actorId", "=", SecurityUtil.getUserId()); } if (approval) { @@ -140,45 +147,85 @@ public class ProposalUnderTakeReplyController { List list = pagination.getList(); for (NutMap row : list) { - if (row.getInt("taskIsMaster") == 1) { - boolean b = Stream.of("master_reply", "slave_reply", "school_leader") - .anyMatch(a -> row.getString("curTaskKey").contains(a)); - row.setv("canRevoke", b && row.getInt("taskState") == ProcessTaskStateEnum.FINISHED.getCode()); - } else { - boolean b = Stream.of("master_reply", "slave_reply").anyMatch(a -> row.getString("curTaskKey").contains(a)); - row.setv("canRevoke", b && row.getInt("taskState") == ProcessTaskStateEnum.FINISHED.getCode()); - } + /* + * 承办单位答复撤回必须按“当前整组待办节点”统一判断。 + * 这里不能再用 contains,只要出现过 unit_reply/two_unit_reply 就会误判可撤回; + * 正确规则是:curTaskKey 必须精确等于某一个允许撤回的节点编码, + * 像 unit_reply,two_unit_reply 这种混合状态一律不允许撤回。 + */ + row.setv("canRevoke", isReplyTaskGroupRevocable(row)); } pagination.setList(list); return Result.success(pagination); } + /** + * 承办单位答复节点撤回需要基于整组当前节点判断。 + * 只有“当前流程正在办理的节点”和“这条列表记录所属节点”完全一致时,才允许撤回; + * 这样流程已经从 unit_reply 走到 two_unit_reply 后,历史 unit_reply 记录会自动失去撤回资格。 + * 主办只能撤回 unit_reply、two_unit_reply、school_leader; + * 协办只能撤回 unit_reply、two_unit_reply。 + */ + private boolean isReplyTaskGroupRevocable(NutMap row) { + if (!ObjectUtil.equal(row.getInt("taskState"), ProcessTaskStateEnum.FINISHED.getCode())) { + return false; + } + String taskKey = row.getString("taskKey"); + String curTaskKey = row.getString("curTaskKey"); + if (!ObjectUtil.equal(taskKey, curTaskKey)) { + return false; + } + if (row.getInt("underTakeIsMaster") == 1) { + return Stream.of("unit_reply", "two_unit_reply", "school_leader") + .anyMatch(a -> ObjectUtil.equal(a, taskKey)); + } + return Stream.of("unit_reply", "two_unit_reply") + .anyMatch(a -> ObjectUtil.equal(a, taskKey)); + } + + + + + @At @Aop(TransAop.READ_COMMITTED) @SaCheckPermission("proposal.unitReply") @ApiOperation("执行任务") public Result executeTask(@Param("data") String data) { Dict args = Json.fromJson(Dict.class, data); - String proposalId = args.getStr("proposalId"); + proposalUnderTakeReplyService.executeFlowTask(args); + return Result.success(); + } - // 查询是否提案 - - // 单条审核 - List mergeProposalIds = proposalCommonService.mergeProposal(proposalId); - if (ObjectUtil.isEmpty(mergeProposalIds)) { - flowCommonService.executeTask(args); - return Result.success(); + @At + @SaCheckPermission("proposal.unitReply") + @ApiOperation("查询历史答复内容") + public Result getReplyOpinion(@Param("taskId") @NotNull(message = "任务ID不能为空") Long taskId) { + /* + * 页面上的“编辑答复内容”入口只对超级管理员开放。 + * 这里继续在服务端做一次角色兜底,避免前端绕过按钮显隐后越权修改历史答复。 + */ + if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { + return Result.error("只有超级管理员可以编辑答复内容"); } + return Result.success(proposalUnderTakeReplyService.getReplyOpinion(taskId)); + } - // 并案审核 - ProcessTask thisTask = dao.fetch(ProcessTask.class, args.getLong(FlowConst.PROCESS_TASK_ID_KEY)); - List mergeTasks = processTaskService.getDoingTaskByBizIdTaskName(mergeProposalIds, thisTask.getTaskName()); - for (ProcessTask mergeTask : mergeTasks) { - Dict cloneArgs = args.clone(); - cloneArgs.put(FlowConst.PROCESS_TASK_ID_KEY, mergeTask.getId()); - flowCommonService.executeTask(cloneArgs); + @At + @SaCheckPermission("proposal.unitReply") + @ApiOperation("更新历史答复内容") + @Aop(TransAop.READ_COMMITTED) + public Result updateReplyOpinion(@Param("taskId") @NotNull(message = "任务ID不能为空") Long taskId, + @Param("opinion") @NotBlank(message = "答复内容不能为空") String opinion) { + /* + * 这里只允许超级管理员改历史答复内容,且只改 tf_opinion 字段。 + * 具体的任务范围和状态校验放在 service 层统一处理,避免控制器承载业务逻辑。 + */ + if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) { + return Result.error("只有超级管理员可以编辑答复内容"); } + proposalUnderTakeReplyService.updateReplyOpinion(taskId, opinion); return Result.success(); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalUnitAssignmentHandler.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalUnitAssignmentHandler.java new file mode 100644 index 00000000..4425574f --- /dev/null +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/handler/ProposalUnitAssignmentHandler.java @@ -0,0 +1,172 @@ +package com.budwk.app.zhgh.democratic.proposal.handler; + +import cn.hutool.core.lang.Dict; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; +import com.budwk.app.base.constant.RoleConstant; +import com.budwk.app.base.exception.BaseException; +import com.budwk.app.flow.engine.AssignmentHandler; +import com.budwk.app.flow.engine.core.Execution; +import com.budwk.app.flow.engine.core.ServiceContext; +import com.budwk.app.flow.engine.model.TaskModel; +import com.budwk.app.sys.models.Sys_role; +import com.budwk.app.sys.models.Sys_user_role; +import com.budwk.app.sys.services.SysRoleService; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalReplyUnit; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake; +import org.nutz.aop.interceptor.ioc.TransAop; +import org.nutz.dao.Cnd; +import org.nutz.dao.Dao; +import org.nutz.ioc.aop.Aop; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * 提案承办单位处理人 + */ +public class ProposalUnitAssignmentHandler implements AssignmentHandler { + @Override + @Aop(TransAop.READ_COMMITTED) + public List assign(TaskModel model, Execution execution) { + Dao dao = ServiceContext.find(Dao.class); + SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class); + String proposalId = execution.getProcessInstance().getBusinessNo(); + + // 当前节点需要同时处理主办和协办负责人。 + // 这里不能按单位ID去重,必须保留每个承办单位对应的一条分派记录, + // 否则同一用户负责多个单位时,会被错误合并成更少的待办。 + List unitIds = new ArrayList<>(); + unitIds.addAll(resolveMasterUnitIds(execution, dao, proposalId)); + unitIds.addAll(resolveSlaveUnitIds(execution, dao, proposalId)); + + if (unitIds.isEmpty()) { + throw new BaseException("提案没有配置承办单位"); + } + + Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER); + if (sysRole == null) { + throw new BaseException("提案承办单位领导角色不存在"); + } + + List assignee = new ArrayList<>(); + for (String unitId : unitIds) { + ProposalUndertake undertake = dao.fetch(ProposalUndertake.class, unitId); + if (undertake == null) { + throw new BaseException("承办单位不存在"); + } + + // 只要存在单位 ID,就必须找到该单位负责人,否则流程无法继续往下分派。 + List sysUserRoles = dao.query(Sys_user_role.class, + Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId()) + .and(Sys_user_role::getUnderTakeId, "=", undertake.getId())); + if (ObjectUtil.isEmpty(sysUserRoles)) { + throw new BaseException(undertake.getName() + "单位没有设置负责人!"); + } + /* + * 会签需要按“承办单位”创建实例。 + * 同一个用户若同时负责主办、协办多个承办单位,不能在这里跨单位去重; + * 否则流程会把多个承办单位合并成一个会签实例,导致“全部承办单位答复完”判断失真。 + */ + assignee.addAll(sysUserRoles.stream().map(Sys_user_role::getUserId).toList()); + } + return assignee; + } + + /** + * 优先从当前流程变量读取主办单位,读不到时回落到承办单位记录表,兼容重新流转场景。 + */ + private List resolveMasterUnitIds(Execution execution, Dao dao, String proposalId) { + List masterUnitIds = new ArrayList<>(); + + /* + * 页面会同时提交 tf_masterUnitId 和 tf_masterUnitIds: + * 前者是给旧流程兼容的“第一个主办单位”,后者才是当前页面真实的主办单位列表。 + * 这里必须优先使用列表字段;只有列表为空时,才回退到单值字段, + * 否则单主办场景会把同一个单位重复加入两次,最终多生成一条会签任务。 + */ + List masterUnitIdList = getStringList(execution.getArgs(), "tf_masterUnitIds"); + if (ObjectUtil.isNotEmpty(masterUnitIdList)) { + masterUnitIds.addAll(masterUnitIdList); + } else { + String masterUnitId = execution.getArgs().getStr("tf_masterUnitId"); + if (StrUtil.isNotBlank(masterUnitId)) { + masterUnitIds.add(masterUnitId); + } + } + + if (masterUnitIds.isEmpty()) { + List masterUnits = dao.query(ProposalReplyUnit.class, + Cnd.where(ProposalReplyUnit::getProposalId, "=", proposalId) + .and(ProposalReplyUnit::getIsMaster, "=", true)); + if (ObjectUtil.isNotEmpty(masterUnits)) { + masterUnitIds.addAll(masterUnits.stream().map(ProposalReplyUnit::getUnitId).toList()); + } + } + return masterUnitIds; + } + + /** + * 协办单位不区分是否需要答复,只要流程变量或记录表里存在协办单位 ID,就分派给对应负责人。 + */ + private List resolveSlaveUnitIds(Execution execution, Dao dao, String proposalId) { + List slaveUnitIds = new ArrayList<>(); + + List slaveUnitIdList = getStringList(execution.getArgs(), "tf_slaveUnitIds"); + if (ObjectUtil.isNotEmpty(slaveUnitIdList)) { + slaveUnitIds.addAll(slaveUnitIdList); + } + + if (slaveUnitIds.isEmpty()) { + List slaveUnits = dao.query(ProposalReplyUnit.class, + Cnd.where(ProposalReplyUnit::getProposalId, "=", proposalId) + .and(ProposalReplyUnit::getIsMaster, "=", false)); + if (ObjectUtil.isNotEmpty(slaveUnits)) { + slaveUnitIds.addAll(slaveUnits.stream().map(ProposalReplyUnit::getUnitId).toList()); + } + } + return slaveUnitIds; + } + + /** + * 流程变量里的单位ID可能是 List、数组、JSON 字符串或逗号分隔字符串,这里统一转成字符串列表。 + */ + private List getStringList(Dict args, String key) { + Object value = args.getObj(key); + if (ObjectUtil.isEmpty(value)) { + return List.of(); + } + if (value instanceof Collection collection) { + return collection.stream().filter(ObjectUtil::isNotEmpty).map(String::valueOf).toList(); + } + if (value instanceof Object[] array) { + return Arrays.stream(array).filter(ObjectUtil::isNotEmpty).map(String::valueOf).toList(); + } + if (value instanceof String strValue) { + if (StrUtil.isBlank(strValue)) { + return List.of(); + } + if (JSONUtil.isTypeJSONArray(strValue)) { + return JSONUtil.parseArray(strValue).toList(String.class); + } + return Arrays.stream(strValue.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .toList(); + } + return List.of(String.valueOf(value)); + } + + @Override + public String getMessage() { + return "提案单位处理人"; + } + + @Override + public int getOrder() { + return 210; + } +} diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/interceptor/ProposalCaseFilingInterceptor.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/interceptor/ProposalCaseFilingInterceptor.java index 958e9456..5c3a1553 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/interceptor/ProposalCaseFilingInterceptor.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/interceptor/ProposalCaseFilingInterceptor.java @@ -26,6 +26,9 @@ import org.nutz.ioc.aop.Aop; import org.nutz.json.Json; import org.nutz.lang.util.NutMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; /** @@ -36,62 +39,26 @@ public class ProposalCaseFilingInterceptor implements FlowInterceptor { @Override @Aop(TransAop.READ_COMMITTED) public void intercept(Execution execution) { + if ("0".equals(execution.getArgs().getStr("tf_caseCheckNeedSecondReply"))){ + return; + } Dao dao = ServiceContext.find(Dao.class); // 提案ID String proposalId = execution.getProcessInstance().getBusinessNo(); - - // 立案结果 - String caseFilingResult = execution.getArgs().getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult"); - - // 储存承办单位 - if (StrUtil.isNotBlank(caseFilingResult) && List.of("CONFIRM_FILING", "SUGGESTION").contains(caseFilingResult)) { - // 删除原来的记录 - dao.clear(ProposalReplyUnit.class,Cnd.where(ProposalReplyUnit::getProposalId, "=", proposalId)); - - // 主办单位 - String masterUnitId = execution.getArgs().getStr(FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitId"); - ProposalUndertake masterUnit = dao.fetch(ProposalUndertake.class, masterUnitId); - ProposalReplyUnit masterReplyUnit = new ProposalReplyUnit(); - masterReplyUnit.setProposalId(proposalId); - masterReplyUnit.setUnitId(masterUnit.getId()); - masterReplyUnit.setUnitCode(masterUnit.getCode()); - masterReplyUnit.setUnitName(masterUnit.getName()); - masterReplyUnit.setIsMaster(true); - dao.insert(masterReplyUnit); - - // 协办单位 - List helpUnitIds = (List) execution.getArgs().getObj(FlowConst.TASK_FORM_DATA_PREFIX + "slaveUnitIds"); - if(ObjectUtil.isNotEmpty(helpUnitIds)){ - for (String helpUnitId : helpUnitIds) { - ProposalUndertake helpUnit = dao.fetch(ProposalUndertake.class, helpUnitId); - ProposalReplyUnit helpReplyUnit = new ProposalReplyUnit(); - helpReplyUnit.setProposalId(proposalId); - helpReplyUnit.setUnitId(helpUnit.getId()); - helpReplyUnit.setUnitCode(helpUnit.getCode()); - helpReplyUnit.setUnitName(helpUnit.getName()); - helpReplyUnit.setIsMaster(false); - dao.insert(helpReplyUnit); - } - } - } - - // 更新立案结果、立案类型 - ProcessInstance processInstance = execution.getProcessInstance(); - String businessNo = processInstance.getBusinessNo(); - - Chain chain = Chain.make("caseFilingResult", execution.getArgs().getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult")); - chain.add("caseFilingType", execution.getArgs().getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingType")); - dao.update(ProposalInfo.class, chain, Cnd.where(ProposalInfo::getId, "=", businessNo)); - - // 当前任务 List processTaskList = execution.getProcessTaskList(); for (ProcessTask task : processTaskList) { // 办理人 ProcessTaskActor taskActor = dao.fetch(ProcessTaskActor.class, Cnd.where(ProcessTaskActor::getProcessTaskId, "=", task.getId())); + if (taskActor == null) { + continue; + } String actorUnitId = taskActor.getActorUnitId(); ProposalReplyUnit replyUnit = dao.fetch(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "=", proposalId).and(ProposalReplyUnit::getUnitId, "=", actorUnitId)); + if (replyUnit == null) { + continue; + } Dict variable = FlowUtil.variableToDict(task.getVariable()); variable.set("underTakeName", replyUnit.getUnitName()); diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/interceptor/ProposalFgxldPrefixInterceptor.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/interceptor/ProposalFgxldPrefixInterceptor.java index ec9eb40d..c5e5b948 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/interceptor/ProposalFgxldPrefixInterceptor.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/interceptor/ProposalFgxldPrefixInterceptor.java @@ -1,5 +1,6 @@ package com.budwk.app.zhgh.democratic.proposal.interceptor; +import cn.hutool.core.lang.Dict; import cn.hutool.core.util.StrUtil; import com.budwk.app.flow.constant.FlowConst; import com.budwk.app.flow.engine.FlowInterceptor; @@ -13,7 +14,7 @@ import org.nutz.dao.Dao; /** * 提案分管领导审批前缀拦截器 - * 用于设置主办单位变量 tf_masterUnitId + * 用于设置主办单位变量,兼容主办单位由单个改为多个的场景 */ public class ProposalFgxldPrefixInterceptor implements FlowInterceptor { @Override @@ -27,8 +28,12 @@ public class ProposalFgxldPrefixInterceptor implements FlowInterceptor { .and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode()) .desc(ProcessTask::getCreatedAt) ); - String masterUnitId = FlowUtil.variableToDict(task.getVariable()).getStr(FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitId"); + Dict variable = FlowUtil.variableToDict(task.getVariable()); + String masterUnitId = variable.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitId"); execution.getArgs().set(FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitId", masterUnitId); + // 退回提委会时保留主办单位集合,避免多主办场景下页面无法完整回显。 + execution.getArgs().set(FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitIds", + variable.getObj(FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitIds")); } } } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalCommitteeFilingService.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalCommitteeFilingService.java index e90ed231..e7402f76 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalCommitteeFilingService.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalCommitteeFilingService.java @@ -1,12 +1,24 @@ package com.budwk.app.zhgh.democratic.proposal.service; import com.budwk.app.base.service.BaseService; +import cn.hutool.core.lang.Dict; import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; -import com.budwk.app.zhgh.democratic.proposal.param.ProposalCommitteeFilingApprovalParam; public interface ProposalCommitteeFilingService extends BaseService { - void approval(ProposalCommitteeFilingApprovalParam approvalParam); + /** + * 提案委员会并案审核。 + * + * @param args 页面提交的流程参数 + */ + void merge(Dict args); - void revoke(String taskId); + /** + * 提案委员会立案撤回。 + * 单条提案只撤回当前任务;并案提案需要整组一起撤回,确保流程状态一致。 + * + * @param taskId 当前已办任务ID + * @param proposalId 当前提案ID + */ + void revokeTask(Long taskId, String proposalId); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalCommitteeFilingUnitService.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalCommitteeFilingUnitService.java index 24845cc2..f0ac7e7c 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalCommitteeFilingUnitService.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalCommitteeFilingUnitService.java @@ -1,12 +1,19 @@ package com.budwk.app.zhgh.democratic.proposal.service; +import cn.hutool.core.lang.Dict; import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; import com.budwk.app.zhgh.democratic.proposal.param.ProposalCommitteeFilingUnitApprovalParam; public interface ProposalCommitteeFilingUnitService extends BaseService { - void approval(ProposalCommitteeFilingUnitApprovalParam approvalParam); - void revoke(String taskId); + /** + * old-flow 提案委员会确认承办单位执行。 + * 执行流程任务后,同步落库立案结果以及主协办单位配置。 + * + * @param args 页面提交的流程参数 + */ + void executeFlowTask(Dict args); + } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalUnderTakeReplyService.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalUnderTakeReplyService.java index e40eea9e..458077ce 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalUnderTakeReplyService.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/ProposalUnderTakeReplyService.java @@ -1,10 +1,10 @@ package com.budwk.app.zhgh.democratic.proposal.service; +import cn.hutool.core.lang.Dict; import com.budwk.app.base.page.Pagination; import com.budwk.app.base.service.BaseService; import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; import com.budwk.app.zhgh.democratic.proposal.param.ProposalSearchParam; -import com.budwk.app.zhgh.democratic.proposal.param.ProposalUnderTakeReplyApprovalParam; import com.budwk.app.zhgh.democratic.proposal.param.ProposalUnderTakeReplyTransferParam; import org.nutz.lang.util.NutMap; @@ -15,7 +15,15 @@ public interface ProposalUnderTakeReplyService extends BaseService Pagination pageData(ProposalSearchParam param); - void approval(ProposalUnderTakeReplyApprovalParam param); + + /** + * old-flow 提案承办单位答复执行。 + * 同一批次存在多个 wf_process_task 时,前面的任务只完结当前待办, + * 最后一条待办才真正驱动流程流转到下一节点。 + * + * @param args 页面提交的流程参数 + */ + void executeFlowTask(Dict args); /** * 查询本单位可转办用户 @@ -24,26 +32,21 @@ public interface ProposalUnderTakeReplyService extends BaseService List queryTransferUser(String keyword); /** - * 转办 - * @param param + * 查询历史承办单位答复任务中的答复内容。 + * 该能力仅用于超级管理员在页面上回显“编辑答复内容”弹窗, + * 因此只返回当前编辑场景所需的最小字段,避免误改其他流程数据。 + * + * @param taskId 历史承办单位答复任务ID + * @return 包含任务ID和 tf_opinion 的回显数据 */ - void transfer(ProposalUnderTakeReplyTransferParam param); + NutMap getReplyOpinion(Long taskId); /** - * 撤回 - * @param taskId + * 更新历史承办单位答复任务中的答复内容。 + * 这里只允许修改流程任务变量里的 tf_opinion,其他字段如落实情况、附件、流程状态均保持不变。 + * + * @param taskId 历史承办单位答复任务ID + * @param opinion 最新答复内容 */ - void revokeTask(@Valid String taskId); - - /** - * 检查是否可以办理 - * @param taskId - */ - String checkCanReply(String taskId); - - /** - * 撤回转办 - * @param taskId - */ - void revokeTransfer(String taskId); + void updateReplyOpinion(Long taskId, String opinion); } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalCommitteeFilingServiceImpl.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalCommitteeFilingServiceImpl.java index 9b64e29c..8adcdd63 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalCommitteeFilingServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalCommitteeFilingServiceImpl.java @@ -1,40 +1,26 @@ package com.budwk.app.zhgh.democratic.proposal.service.impl; -import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjectUtil; -import cn.hutool.core.util.StrUtil; -import cn.hutool.json.JSONObject; -import com.budwk.app.base.constant.RoleConstant; -import com.budwk.app.base.exception.BaseException; import com.budwk.app.base.service.impl.BaseServiceImpl; -import com.budwk.app.bpm.dto.BpmAssignmentDto; -import com.budwk.app.bpm.enums.BpmTaskApprovalTypeEnum; -import com.budwk.app.bpm.models.BpmProcessInstance; -import com.budwk.app.bpm.models.BpmProcessTask; -import com.budwk.app.bpm.service.BpmService; -import com.budwk.app.sys.models.Sys_user_role; -import com.budwk.app.sys.services.SysMsgService; -import com.budwk.app.web.commons.auth.utils.SecurityUtil; -import com.budwk.app.web.controllers.open.commons.service.CommonService; +import com.budwk.app.flow.constant.FlowConst; +import com.budwk.app.flow.entity.ProcessTask; +import com.budwk.app.flow.service.FlowCommonService; +import com.budwk.app.flow.service.ProcessTaskService; import com.budwk.app.zhgh.democratic.proposal.models.ProposalConsolidation; import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; -import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake; -import com.budwk.app.zhgh.democratic.proposal.param.ProposalCommitteeFilingApprovalParam; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalMerge; import com.budwk.app.zhgh.democratic.proposal.service.ProposalCommitteeFilingService; import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService; import org.nutz.aop.interceptor.ioc.TransAop; -import org.nutz.dao.Chain; import org.nutz.dao.Cnd; import org.nutz.dao.Dao; -import org.nutz.dao.util.cri.Static; import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; +import org.nutz.lang.random.R; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; -import java.util.Map; @IocBean(args = {"refer:dao"}) public class ProposalCommitteeFilingServiceImpl extends BaseServiceImpl implements ProposalCommitteeFilingService { @@ -42,155 +28,61 @@ public class ProposalCommitteeFilingServiceImpl extends BaseServiceImpl variables = BeanUtil.beanToMap(approvalParam); - variables.put("proposalIds", approvalParam.getProposalIds()); - variables.put("hostUnitId", approvalParam.getHostUnitId()); - variables.put("helpUnitIds", approvalParam.getHelpUnitIds()); - variables.put("caseFilingResult", approvalParam.getCaseFilingResult()); - variables.put("consolidationIds", approvalParam.getProposalIds()); + @Aop(TransAop.READ_COMMITTED) + public void merge(Dict args) { + List taskIds = (List) args.get("processTaskIds"); + List proposalIds = (List) args.get("proposalIds"); - ProposalUndertake hostUnit = null; - List helpUnits = null; - //承办单位答复人初步阅览 - List nextApprovals = new ArrayList<>(); + // 记录提案并案,后续撤回、查询等场景都依赖这组并案关系。 + dao().clear(ProposalMerge.class, Cnd.where(ProposalMerge::getProposalId, "in", proposalIds)); + ProposalConsolidation proposalConsolidation = new ProposalConsolidation(); + proposalConsolidation.setConsolidationIds(proposalIds); + dao().insert(proposalConsolidation); - if (approvalParam.getBpmTaskApprovalTypeEnum().equals(BpmTaskApprovalTypeEnum.DYNAMIC)) { - if (List.of("CONFIRM_FILING", "SUGGESTION").contains(approvalParam.getCaseFilingResult())) { - hostUnit = dao().fetch(ProposalUndertake.class, approvalParam.getHostUnitId()); - helpUnits = dao().query(ProposalUndertake.class, Cnd.where(ProposalUndertake::getId, "in", approvalParam.getHelpUnitIds())); - variables.put("hostUnit", hostUnit.getName()); - variables.put("helpUnits", helpUnits.stream().map(ProposalUndertake::getName).toArray(String[]::new)); + String groupId = R.UU32(); + List merges = proposalIds.stream().map(proposalId -> { + ProposalMerge merge = new ProposalMerge(); + merge.setProposalId(proposalId); + merge.setGroupId(groupId); + return merge; + }).toList(); + dao().insert(merges); - List units = new ArrayList<>(); - units.add(new JSONObject() - .set("isMaster",true) - .set("underTakeId", hostUnit.getId()) - .set("underTakeName", hostUnit.getName()) - .set("underTakeCode", hostUnit.getCode()) - ); - - if(ObjectUtil.isNotEmpty(helpUnits)){ - for (ProposalUndertake helpUnit : helpUnits) { - units.add(new JSONObject() - .set("isMaster",false) - .set("underTakeId", helpUnit.getId()) - .set("underTakeName", helpUnit.getName()) - .set("underTakeCode", helpUnit.getCode()) - ); - } - } - variables.put("units",units); - } - - //并案审核 - if (approvalParam.getProposalIds().length > 1) { - ProposalConsolidation proposalConsolidation = new ProposalConsolidation(); - proposalConsolidation.setConsolidationIds(Arrays.asList(approvalParam.getProposalIds())); - dao().insert(proposalConsolidation); - } - - //立案和作为意见会有承办单位 - if (approvalParam.getCaseFilingResult().equals("CONFIRM_FILING") || approvalParam.getCaseFilingResult().equals("SUGGESTION")) { - List units = new ArrayList<>(); - units.add(hostUnit); - if (ObjectUtil.isNotNull(helpUnits)) { - units.addAll(helpUnits); - } - for (ProposalUndertake unit : units) { - String approvalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.PROPOSAL_UNIT_LEADER, Cnd.where(Sys_user_role::getUnderTakeId, "=", unit.getId())); - if (StrUtil.isBlank(approvalLoginName)) { - throw new BaseException("{}答复人没有配置,请到承办单位管理页面设置!", unit.getName()); - } - - assert hostUnit != null; - nextApprovals.add(new BpmAssignmentDto() - .setLoginName(approvalLoginName) - .setJsonObject(new JSONObject() - .set("isMaster", hostUnit.getId().equals(unit.getId())) - .set("underTakeId", unit.getId()) - .set("underTakeName", unit.getName()) - .set("underTakeCode", unit.getCode()) - ) - ); - } - } - - //下一个审批人 - String nextApprovalLoginName = proposalCommonService.getCommitteeDirectorByProposalId(approvalParam.getProcessInstanceBusinessId()); - - //提交并案 - for (String consolidationProposalId : approvalParam.getProposalIds()) { - BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, Cnd.where(BpmProcessInstance::getProcessInstanceBusinessId, "=", consolidationProposalId)); -// bpmService.completeTaskWithNextTaskVariable(instance.getProcessInstanceTaskIds().get(0), approvalParam.getBpmTaskApprovalTypeEnum(), variables, nextApprovals); - bpmService.completeTask(instance.getProcessInstanceTaskIds().get(0), approvalParam.getBpmTaskApprovalTypeEnum(), variables, List.of(nextApprovalLoginName)); - } - - //修改提案立案结果 - dao().update(ProposalInfo.class, Chain.make("caseFilingResult", approvalParam.getCaseFilingResult()), Cnd.where(ProposalInfo::getId, "in", approvalParam.getProposalIds())); - - List proposalInfos = dao().query(ProposalInfo.class, Cnd.where(ProposalInfo::getId, "in", approvalParam.getProposalIds())); - for (ProposalInfo proposalInfo : proposalInfos) { - //给代表发送消息 - if("CONFIRM_FILING".equals(approvalParam.getCaseFilingResult())){ - String template = "{}代表您好!您的提案《{}》提案委员会已立案审核,审核结果为准予立案,您可登陆智慧校园下的暖心工会查看详情。"; - String content = StrUtil.format(template, proposalInfo.getCreateUserName(), proposalInfo.getName()); - sysMsgService.sendMsg(proposalInfo.getCreateUserLoginName(), "提案委员会立案审核", content, SecurityUtil.getUserId()); - }else if("SUGGESTION".equals(approvalParam.getCaseFilingResult())){ - - }else if("NOT".equals(approvalParam.getCaseFilingResult())){ - String template = "{}代表您好!您的提案《{}》提案委员会已立案审核,审核结果为不予立案,您可登陆智慧校园下的暖心工会查看详情。"; - String content = StrUtil.format(template, proposalInfo.getCreateUserName(), proposalInfo.getName()); - sysMsgService.sendMsg(proposalInfo.getCreateUserLoginName(), "提案委员会立案审核", content, SecurityUtil.getUserId()); - } - - //给承办单位发送消息 - if("CONFIRM_FILING".equals(approvalParam.getCaseFilingResult())){ - for (BpmAssignmentDto nextApproval : nextApprovals) { - String template = "经提案委员会立案审核,提案《{}》由{}作为{}单位,您可登陆智慧校园下的暖心工会查看详情,感谢您对教代会提案工作的大力支持!。"; - String underTakeName = nextApproval.getJsonObject().getStr("underTakeName"); - Boolean isMaster = nextApproval.getJsonObject().getBool("isMaster", false); - String content = StrUtil.format(template, proposalInfo.getName(), underTakeName,isMaster?"主办":"协办"); - sysMsgService.sendMsg(nextApproval.getLoginName(), "承办单位初步意见", content, SecurityUtil.getUserId()); - } - } - } + // 并案审核需要把同一批提案的当前节点一起提交,保持整组流程同步往下走。 + for (Integer taskId : taskIds) { + Dict taskArgs = args.clone(); + taskArgs.put(FlowConst.PROCESS_TASK_ID_KEY, taskId); + flowCommonService.executeTask(taskArgs); } } @Override @Aop(TransAop.READ_COMMITTED) - public void revoke(String taskId) { - BpmProcessTask task = dao().fetch(BpmProcessTask.class, taskId); - String instanceId = task.getProcessInstanceId(); - BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, instanceId); - String instanceBusinessId = instance.getProcessInstanceBusinessId(); - List consolidationIds = proposalCommonService.getConsolidation(instanceBusinessId); - for (String consolidationId : consolidationIds) { - BpmProcessInstance consolidationProposalInstance = dao().fetch(BpmProcessInstance.class, Cnd.where(BpmProcessInstance::getProcessInstanceBusinessId, "=", consolidationId)); - if (ObjectUtil.isNotEmpty(consolidationProposalInstance.getProcessInstanceTaskIds())) { - //还有待处理的任务 那么走的就是确定立案 - String activeTaskId = consolidationProposalInstance.getProcessInstanceTaskIds().get(0); - BpmProcessTask activeTask = dao().fetch(BpmProcessTask.class, Cnd.where(BpmProcessTask::getId, "=", activeTaskId)); - bpmService.revokeTask(activeTask.getPrevTaskId()); - }else{ - //不予立案后撤回 - bpmService.revokeTask(taskId); - } + public void revokeTask(Long taskId, String proposalId) { + /* + * 提委会立案撤回统一走 flowCommonService。 + * 非并案时直接撤回当前任务;并案时按当前节点找出整组已办任务,逐条撤回,避免只回退一部分提案。 + */ + List mergeProposalIds = proposalCommonService.mergeProposal(proposalId); + if (ObjectUtil.isEmpty(mergeProposalIds)) { + flowCommonService.revokeTask(taskId); + return; + } + + ProcessTask thisTask = dao().fetch(ProcessTask.class, taskId); + List mergeTasks = processTaskService.getDoneTaskByBizIdTaskName(mergeProposalIds, thisTask.getTaskName()); + for (ProcessTask mergeTask : mergeTasks) { + flowCommonService.revokeTask(mergeTask.getId()); } - dao().update(ProposalInfo.class, Chain.make("caseFilingResult", null), Cnd.where(ProposalInfo::getId, "in", consolidationIds)); - dao().clear(ProposalConsolidation.class, Cnd.where(new Static("JSON_CONTAINS(consolidationIds,'\"" + instanceBusinessId + "\"')"))); } + } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalCommitteeFilingUnitServiceImpl.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalCommitteeFilingUnitServiceImpl.java index 4865009d..fba790be 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalCommitteeFilingUnitServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalCommitteeFilingUnitServiceImpl.java @@ -1,8 +1,10 @@ package com.budwk.app.zhgh.democratic.proposal.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.Dict; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import cn.hutool.json.JSONObject; import com.budwk.app.base.constant.RoleConstant; import com.budwk.app.base.exception.BaseException; @@ -12,6 +14,10 @@ import com.budwk.app.bpm.enums.BpmProcessTaskStatusEnum; import com.budwk.app.bpm.models.BpmProcessInstance; import com.budwk.app.bpm.models.BpmProcessTask; import com.budwk.app.bpm.service.BpmService; +import com.budwk.app.flow.constant.FlowConst; +import com.budwk.app.flow.entity.ProcessTask; +import com.budwk.app.flow.service.FlowCommonService; +import com.budwk.app.flow.service.ProcessTaskService; import com.budwk.app.sys.models.Sys_user_role; import com.budwk.app.sys.services.SysMsgService; import com.budwk.app.web.commons.auth.utils.SecurityUtil; @@ -19,6 +25,7 @@ import com.budwk.app.web.controllers.open.commons.service.CommonService; import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig; import com.budwk.app.zhgh.democratic.proposal.models.ProposalConsolidation; import com.budwk.app.zhgh.democratic.proposal.models.ProposalInfo; +import com.budwk.app.zhgh.democratic.proposal.models.ProposalReplyUnit; import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake; import com.budwk.app.zhgh.democratic.proposal.param.ProposalCommitteeFilingUnitApprovalParam; import com.budwk.app.zhgh.democratic.proposal.service.ProposalCommitteeFilingUnitService; @@ -33,9 +40,12 @@ import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import java.util.ArrayList; +import java.util.Collection; import java.util.Arrays; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @IocBean(args = {"refer:dao"}) public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl implements ProposalCommitteeFilingUnitService { @@ -48,6 +58,10 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl variables = BeanUtil.beanToMap(approvalParam); - - //审批变量 - variables.put("proposalIds", approvalParam.getProposalIds()); - variables.put("hostUnitId", approvalParam.getHostUnitId()); - variables.put("helpUnitIds", approvalParam.getHelpUnitIds()); - variables.put("caseFilingResult", approvalParam.getCaseFilingResult()); - - //承办单位 - ProposalUndertake hostUnit = dao().fetch(ProposalUndertake.class, approvalParam.getHostUnitId()); - List helpUnits = dao().query(ProposalUndertake.class, Cnd.where(ProposalUndertake::getId, "in", approvalParam.getHelpUnitIds())); - variables.put("hostUnit", hostUnit.getName()); - variables.put("helpUnits", helpUnits.stream().map(ProposalUndertake::getName).toArray(String[]::new)); - - //查询提案配置协办单位是否需要答复 - ProposalConfig proposalConfig = dao().fetch(ProposalConfig.class, Cnd.NEW()); - Boolean slaveUnitNeedReply = proposalConfig.getSlaveUnitNeedReply(); - if (ObjectUtil.isNull(slaveUnitNeedReply)) { - throw new BaseException("协办是否需要答复提案配置缺失,请联系校工会!"); + public void executeFlowTask(Dict args) { + Long processTaskId = args.getLong(FlowConst.PROCESS_TASK_ID_KEY); + ProcessTask currentTask = dao().fetch(ProcessTask.class, processTaskId); + if (currentTask == null) { + throw new BaseException("没有进行中的流程任务"); } - //重新并案审核 - if (approvalParam.getProposalIds().length > 1) { - for (String proposalId : approvalParam.getProposalIds()) { - dao().clear(ProposalConsolidation.class, Cnd.where(new Static("JSON_CONTAINS(consolidationIds,'\"%s\"')".formatted(proposalId)))); - } - ProposalConsolidation proposalConsolidation = new ProposalConsolidation(); - proposalConsolidation.setConsolidationIds(Arrays.asList(approvalParam.getProposalIds())); - dao().insert(proposalConsolidation); + String proposalId = args.getStr("proposalId"); + List proposalIds = proposalCommonService.mergeProposal(proposalId); + if (ObjectUtil.isEmpty(proposalIds)) { + proposalIds = List.of(proposalId); } - List proposalUndertakes = new ArrayList<>(); - proposalUndertakes.add(hostUnit); - if (ObjectUtil.isNotEmpty(helpUnits) && slaveUnitNeedReply) { - proposalUndertakes.addAll(helpUnits); + /* + * 并案场景下,同一个节点会在每条提案实例上各有一条待办。 + * 这里统一按 taskName 找出整组正在办理中的任务,逐条执行,确保并案提案的流程状态保持一致。 + */ + List executeTasks = processTaskService.getDoingTaskByBizIdTaskName(proposalIds, currentTask.getTaskName()); + if (ObjectUtil.isEmpty(executeTasks)) { + executeTasks = List.of(currentTask); + } + for (ProcessTask executeTask : executeTasks) { + Dict cloneArgs = args.clone(); + cloneArgs.put(FlowConst.PROCESS_TASK_ID_KEY, executeTask.getId()); + flowCommonService.executeTask(cloneArgs); } - //承办单位答复人 - List nextApprovals = new ArrayList<>(); -// if (approvalParam.getCaseFilingResult().equals("CONFIRM_FILING") || approvalParam.getCaseFilingResult().equals("SUGGESTION")) { - //南邮 只有立案需要答复 - if (approvalParam.getCaseFilingResult().equals("CONFIRM_FILING")) { - for (ProposalUndertake undertake : proposalUndertakes) { - String approvalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.PROPOSAL_UNIT_LEADER, Cnd.where(Sys_user_role::getUnderTakeId, "=", undertake.getId())); - if (StrUtil.isBlank(approvalLoginName)) { - throw new BaseException("{}答复人没有配置,请到承办单位管理页面进行配置。", undertake.getName()); - } + syncCommitteeFilingUnitData(proposalIds, args); + } + /** + * old-flow 页面提交后,需要把立案结果以及主协办单位同步落业务表。 + * 若这次改成“不予立案”等无需承办单位的结果,也要先清掉旧的承办单位记录,避免脏数据残留。 + */ + private void syncCommitteeFilingUnitData(List proposalIds, Dict args) { + String caseFilingResult = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult"); + String caseFilingType = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingType"); - nextApprovals.add(new BpmAssignmentDto() - .setLoginName(approvalLoginName) - .setJsonObject(new JSONObject() - .set("isMaster", hostUnit.getId().equals(undertake.getId())) - .set("underTakeId", undertake.getId()) - .set("underTakeName", undertake.getName()) - .set("underTakeCode", undertake.getCode()) - ) - ); + dao().update(ProposalInfo.class, + Chain.make("caseFilingResult", caseFilingResult).add("caseFilingType", caseFilingType), + Cnd.where(ProposalInfo::getId, "in", proposalIds)); + + dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds)); + if (!List.of("CONFIRM_FILING", "SUGGESTION").contains(caseFilingResult)) { + return; + } + + List masterUnitIds = getUnitIdList(args, FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitIds"); + if (ObjectUtil.isEmpty(masterUnitIds)) { + String masterUnitId = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "masterUnitId"); + if (StrUtil.isNotBlank(masterUnitId)) { + masterUnitIds = List.of(masterUnitId); } } + List slaveUnitIds = getUnitIdList(args, FlowConst.TASK_FORM_DATA_PREFIX + "slaveUnitIds"); - for (String consolidationProposalId : approvalParam.getProposalIds()) { - BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, Cnd.where(BpmProcessInstance::getProcessInstanceBusinessId, "=", consolidationProposalId)); - BpmProcessTask caseUnitTask = dao().fetch(BpmProcessTask.class, Cnd.where(BpmProcessTask::getProcessInstanceId, "=", instance.getId()) - .and(BpmProcessTask::getProcessTaskNodeCode, "=", 70) - .and(BpmProcessTask::getTaskStatus, "=", BpmProcessTaskStatusEnum.ACTIVE.name()) - .and(BpmProcessTask::getDelFlag, "=", 0) - .desc(BpmProcessTask::getCreatedOn) - ); - bpmService.completeTaskWithNextTaskVariable(caseUnitTask.getId(), approvalParam.getBpmTaskApprovalTypeEnum(), variables, nextApprovals); + List allUnitIds = new ArrayList<>(); + allUnitIds.addAll(masterUnitIds); + allUnitIds.addAll(slaveUnitIds); + if (allUnitIds.isEmpty()) { + return; } - //修改提案立案结果 - dao().update(ProposalInfo.class, Chain.make("caseFilingResult", approvalParam.getCaseFilingResult()) - , Cnd.where(ProposalInfo::getId, "in", approvalParam.getProposalIds())); + Map undertakeMap = dao().query(ProposalUndertake.class, Cnd.where(ProposalUndertake::getId, "in", allUnitIds)) + .stream() + .collect(Collectors.toMap(ProposalUndertake::getId, v -> v)); - List proposalInfos = dao().query(ProposalInfo.class, Cnd.where(ProposalInfo::getId, "in", approvalParam.getProposalIds())); - for (ProposalInfo proposalInfo : proposalInfos) { - //给代表发送消息 -// if("CONFIRM_FILING".equals(approvalParam.getCaseFilingResult())){ -// String template = "{}代表您好!您的提案《{}》提案委员会已立案审核,审核结果为准予立案,您可登陆智慧校园下的暖心工会查看详情。"; -// String content = StrUtil.format(template, proposalInfo.getCreateUserName(), proposalInfo.getName()); -// sysMsgService.sendMsg(proposalInfo.getCreateUserLoginName(), "提案委员会立案审核", content, SecurityUtil.getUserId()); -// }else if("SUGGESTION".equals(approvalParam.getCaseFilingResult())){ -// -// }else if("NOT".equals(approvalParam.getCaseFilingResult())){ -// String template = "{}代表您好!您的提案《{}》提案委员会已立案审核,审核结果为不予立案,您可登陆智慧校园下的暖心工会查看详情。"; -// String content = StrUtil.format(template, proposalInfo.getCreateUserName(), proposalInfo.getName()); -// sysMsgService.sendMsg(proposalInfo.getCreateUserLoginName(), "提案委员会立案审核", content, SecurityUtil.getUserId()); -// } - - //给承办单位发送消息 - if("CONFIRM_FILING".equals(approvalParam.getCaseFilingResult())) { - for (BpmAssignmentDto nextApproval : nextApprovals) { - String template = "经提案委员会立案审核,提案《{}》最终由{}作为{}单位,您可登陆智慧校园下的暖心工会进行答复,感谢您对教代会提案工作的大力支持!。"; - String underTakeName = nextApproval.getJsonObject().getStr("underTakeName"); - Boolean isMaster = nextApproval.getJsonObject().getBool("isMaster", false); - String content = StrUtil.format(template, proposalInfo.getName(), underTakeName, isMaster ? "主办" : "协办"); - sysMsgService.sendMsg(nextApproval.getLoginName(), "承办单位答复", content, SecurityUtil.getUserId()); - } - } + List replyUnits = new ArrayList<>(); + for (String proposalId : proposalIds) { + replyUnits.addAll(buildReplyUnits(proposalId, masterUnitIds, undertakeMap, true)); + replyUnits.addAll(buildReplyUnits(proposalId, slaveUnitIds, undertakeMap, false)); + } + if (!replyUnits.isEmpty()) { + dao().insert(replyUnits); } - - - } - @Override - @Aop(TransAop.READ_COMMITTED) - public void revoke(String taskId) { - BpmProcessTask task = dao().fetch(BpmProcessTask.class, taskId); - String instanceId = task.getProcessInstanceId(); - BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, instanceId); - String instanceBusinessId = instance.getProcessInstanceBusinessId(); - List consolidationIds = proposalCommonService.getConsolidation(instanceBusinessId); - for (String consolidationId : consolidationIds) { - BpmProcessInstance consolidationProposalInstance = dao().fetch(BpmProcessInstance.class, Cnd.where(BpmProcessInstance::getProcessInstanceBusinessId, "=", consolidationId)); - String activeTaskId = consolidationProposalInstance.getProcessInstanceTaskIds().get(0); - BpmProcessTask activeTask = dao().fetch(BpmProcessTask.class, Cnd.where(BpmProcessTask::getId, "=", activeTaskId)); - bpmService.revokeTask(activeTask.getPrevTaskId()); + /** + * 一个提案可以配置多个主办单位和多个协办单位。 + * 这里按页面提交顺序逐条生成承办单位记录,兼容多主办的业务场景。 + */ + private List buildReplyUnits(String proposalId, List unitIds, Map undertakeMap, boolean isMaster) { + List replyUnits = new ArrayList<>(); + for (String unitId : unitIds) { + ProposalUndertake undertake = undertakeMap.get(unitId); + if (undertake == null) { + continue; + } + ProposalReplyUnit replyUnit = new ProposalReplyUnit(); + replyUnit.setProposalId(proposalId); + replyUnit.setUnitId(undertake.getId()); + replyUnit.setUnitCode(undertake.getCode()); + replyUnit.setUnitName(undertake.getName()); + replyUnit.setIsMaster(isMaster); + replyUnits.add(replyUnit); } - dao().update(ProposalInfo.class, Chain.make("caseFilingResult", null), Cnd.where(ProposalInfo::getId, "in", consolidationIds)); - dao().clear(ProposalConsolidation.class, Cnd.where(new Static("JSON_CONTAINS(consolidationIds,'\"" + instanceBusinessId + "\"')"))); + return replyUnits; + } + + /** + * 页面里的单位字段可能是数组、JSON 字符串或逗号分隔字符串,这里统一转成去空去重后的列表。 + */ + private List getUnitIdList(Dict args, String key) { + Object value = args.get(key); + if (ObjectUtil.isEmpty(value)) { + return List.of(); + } + List unitIds = new ArrayList<>(); + if (value instanceof Collection collection) { + collection.forEach(item -> { + if (ObjectUtil.isNotEmpty(item)) { + unitIds.add(String.valueOf(item)); + } + }); + } else if (value instanceof String strValue) { + if (StrUtil.isBlank(strValue)) { + return List.of(); + } + if (JSONUtil.isTypeJSONArray(strValue)) { + unitIds.addAll(JSONUtil.parseArray(strValue).toList(String.class)); + } else { + Arrays.stream(strValue.split(",")) + .map(String::trim) + .filter(StrUtil::isNotBlank) + .forEach(unitIds::add); + } + } else { + unitIds.add(String.valueOf(value)); + } + return new ArrayList<>(new LinkedHashSet<>(unitIds)); } } diff --git a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalUnderTakeReplyServiceImpl.java b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalUnderTakeReplyServiceImpl.java index af16949f..f1aae3f0 100644 --- a/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalUnderTakeReplyServiceImpl.java +++ b/src/main/java/com/budwk/app/zhgh/democratic/proposal/service/impl/ProposalUnderTakeReplyServiceImpl.java @@ -1,6 +1,7 @@ package com.budwk.app.zhgh.democratic.proposal.service.impl; import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.lang.Dict; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import com.budwk.app.base.constant.BpmProcessConstant; @@ -16,6 +17,16 @@ import com.budwk.app.bpm.models.BpmProcessInstance; import com.budwk.app.bpm.models.BpmProcessNodeDefine; import com.budwk.app.bpm.models.BpmProcessTask; import com.budwk.app.bpm.service.BpmService; +import com.budwk.app.flow.constant.FlowConst; +import com.budwk.app.flow.entity.ProcessInstance; +import com.budwk.app.flow.entity.ProcessTask; +import com.budwk.app.flow.engine.util.FlowUtil; +import com.budwk.app.flow.enums.ProcessSubmitTypeEnum; +import com.budwk.app.flow.enums.ProcessTaskPerformTypeEnum; +import com.budwk.app.flow.enums.ProcessTaskStateEnum; +import com.budwk.app.flow.service.FlowCommonService; +import com.budwk.app.flow.service.ProcessInstanceService; +import com.budwk.app.flow.service.ProcessTaskService; import com.budwk.app.sys.models.Sys_role; import com.budwk.app.sys.models.Sys_user; import com.budwk.app.sys.models.Sys_user_role; @@ -43,6 +54,7 @@ import org.nutz.ioc.aop.Aop; import org.nutz.ioc.loader.annotation.Inject; import org.nutz.ioc.loader.annotation.IocBean; import org.nutz.lang.util.NutMap; +import org.nutz.json.Json; import java.util.ArrayList; import java.util.List; @@ -65,6 +77,12 @@ public class ProposalUnderTakeReplyServiceImpl extends BaseServiceImpl variables = BeanUtil.beanToMap(approvalParam); - - String processInstanceBusinessId = approvalParam.getProcessInstanceBusinessId(); - ProposalInfo proposalInfo = dao().fetch(ProposalInfo.class, processInstanceBusinessId); - - //查询是否都答复完成 - boolean ALL_REPLY = false; - BpmProcessTask task = dao().fetch(BpmProcessTask.class, approvalParam.getProcessInstanceTaskId()); - String prevTaskId = task.getPrevTaskId(); - //查询一同分配的承办单位的答复记录 - int otherUnderTakeTasks = dao().count(BpmProcessTask.class, Cnd.where("prevTaskId", "=", prevTaskId) - .and(BpmProcessTask::getProcessInstanceId, "=", approvalParam.getProcessInstanceId()) - .and(BpmProcessTask::getId, "!=", task.getId()) - .and(BpmProcessTask::getTaskStatus, "=", BpmProcessTaskStatusEnum.ACTIVE.name()) - .and(BpmProcessTask::getDelFlag, "=", 0) - ); - if (otherUnderTakeTasks == 0) { - //说明其他单位都答复完成了 - ALL_REPLY = true; - } - variables.put("ALL_REPLY", ALL_REPLY); - variables.put("caseFilingResult", proposalInfo.getCaseFilingResult()); - - //查询提案配置 - ProposalConfig proposalConfig = dao().fetch(ProposalConfig.class, Cnd.NEW()); - Boolean slaveUnitLeaderNeedApprove = proposalConfig.getSlaveUnitLeaderNeedApprove(); - - //查询出分配的承办单位 - BpmProcessDefine proposalProcessDefine = dao().fetch(BpmProcessDefine.class, Cnd.where(BpmProcessDefine::getCode, "=", BpmProcessConstant.PROPOSAL.name())); - BpmProcessNodeDefine caseUnitNode = dao().fetch(BpmProcessNodeDefine.class, Cnd.where(BpmProcessNodeDefine::getProcessDefineId, "=", proposalProcessDefine.getId()) - .and(BpmProcessNodeDefine::getNodeCode, "=", 70)); - BpmProcessTask caseUnitTask = dao().fetch(BpmProcessTask.class, Cnd.where(BpmProcessTask::getProcessInstanceId, "=", approvalParam.getProcessInstanceId()) - .and(BpmProcessTask::getTaskStatus, "=", BpmProcessTaskStatusEnum.COMPLETE.name()) - .and(BpmProcessTask::getDelFlag, "=", 0) - .and(BpmProcessTask::getProcessTaskNodeId, "=", caseUnitNode.getId()) - ); - JSONObject caseUnitExtVariable = caseUnitTask.getExtVariable(); - List helpUnitIds = caseUnitExtVariable.getBeanList("helpUnitIds", String.class); - String hostUnitId = caseUnitExtVariable.getStr("hostUnitId"); - - //任务接收人 - List assignmentDTOS = new ArrayList<>(); - - if (ALL_REPLY) { - String masterApprovalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER, Cnd.where(Sys_user_role::getUnderTakeId, "=", hostUnitId)); - if (StrUtil.isBlank(masterApprovalLoginName)) { - throw new BaseException("主办单位分管校领导没有配置!"); - } - ProposalUndertake masterUndertake = dao().fetch(ProposalUndertake.class, hostUnitId); - assignmentDTOS.add(new BpmAssignmentDto() - .setLoginName(masterApprovalLoginName) - .setJsonObject(new JSONObject() - .set("isMaster", true) - .set("underTakeId", masterUndertake.getId()) - .set("underTakeName", masterUndertake.getName()) - .set("underTakeCode", masterUndertake.getCode()) - ) - - ); - //如果需要协办领导审批 - if (slaveUnitLeaderNeedApprove) { - for (String helpUnitId : helpUnitIds) { - String slaveApprovalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER, Cnd.where(Sys_user_role::getUnderTakeId, "=", helpUnitId)); - if (StrUtil.isBlank(slaveApprovalLoginName)) { - throw new BaseException("协办单位分管校领导没有配置!"); - } - ProposalUndertake slaveUndertake = dao().fetch(ProposalUndertake.class, helpUnitId); - assignmentDTOS.add(new BpmAssignmentDto() - .setLoginName(slaveApprovalLoginName) - .setJsonObject(new JSONObject() - .set("isMaster", false) - .set("underTakeId", slaveUndertake.getId()) - .set("underTakeName", slaveUndertake.getName()) - .set("underTakeCode", slaveUndertake.getCode()) - ) - - ); - } - } + public void executeFlowTask(Dict args) { + String proposalId = args.getStr("proposalId"); + List mergeProposalIds = proposalCommonService.mergeProposal(proposalId); + if (mergeProposalIds == null || mergeProposalIds.isEmpty()) { + executeSingleFlowTask(args); + return; } - //查询并案的提案 - List consolidationProposalIds = proposalCommonService.getConsolidation(proposalInfo.getId()); - //完成本单位的任务 - for (String consolidationProposalId : consolidationProposalIds) { - BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, Cnd.where(BpmProcessInstance::getProcessInstanceBusinessId, "=", consolidationProposalId)); - BpmProcessTask otherTask = dao().fetch(BpmProcessTask.class, Cnd.where(BpmProcessTask::getProcessInstanceId, "=", instance.getId()) - .and(BpmProcessTask::getTaskStatus, "=", BpmProcessTaskStatusEnum.ACTIVE.name()) - .and(BpmProcessTask::getProcessTaskNodeCode, "=", task.getProcessTaskNodeCode()) - .and("JSON_EXTRACT(createVariable, '$.underTakeId')", "=", approvalParam.getUnderTakeId()) - .and(BpmProcessTask::getDelFlag, "=", 0) - ); - bpmService.completeTaskWithNextTaskVariable(otherTask.getId(), BpmTaskApprovalTypeEnum.DYNAMIC, variables, assignmentDTOS); + ProcessTask currentTask = dao().fetch(ProcessTask.class, args.getLong(FlowConst.PROCESS_TASK_ID_KEY)); + if (currentTask == null) { + throw new BaseException("没有进行中的流程任务"); } - //全部答复完成发送消息到校领导 - if(ALL_REPLY){ - List proposalInfos = dao().query(ProposalInfo.class, Cnd.where(ProposalInfo::getId, "in", consolidationProposalIds)); - for (ProposalInfo info : proposalInfos) { - for (BpmAssignmentDto assignmentDTO : assignmentDTOS) { - String template = "尊敬的校领导您好!提案《{}》承办单位已经全部答复完毕,请您登陆智慧校园下的暖心工会进行审批,感谢您对教代会提案工作的大力支持!。"; - String content = StrUtil.format(template, info.getName()); - sysMsgService.sendMsg(assignmentDTO.getLoginName(), "分管校领导审批", content, SecurityUtil.getUserId()); - } - } + List mergeTasks = processTaskService.getDoingTaskByBizIdTaskName(mergeProposalIds, currentTask.getTaskName()); + for (ProcessTask mergeTask : mergeTasks) { + Dict cloneArgs = args.clone(); + cloneArgs.put(FlowConst.PROCESS_TASK_ID_KEY, mergeTask.getId()); + executeSingleFlowTask(cloneArgs); } - } @Override @@ -203,154 +131,218 @@ public class ProposalUnderTakeReplyServiceImpl extends BaseServiceImpl consolidation = proposalCommonService.getConsolidation(param.getProposalId()); - for (String proposalId : consolidation) { - BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, Cnd.where(BpmProcessInstance::getProcessInstanceBusinessId, "=", proposalId)); - //查询出其他并案提案的任务ID - BpmProcessTask otherTask = dao().fetch(BpmProcessTask.class, Cnd.where(BpmProcessTask::getProcessInstanceId, "=", instance.getId()) - .and(BpmProcessTask::getTaskStatus, "=", BpmProcessTaskStatusEnum.ACTIVE.name()) - .and(BpmProcessTask::getProcessTaskNodeId, "=", task.getProcessTaskNodeId()) - .and("JSON_EXTRACT(createVariable, '$.underTakeId')", "=", param.getUnderTakeId()) - .and(BpmProcessTask::getDelFlag, "=", 0) - ); - bpmService.transferTask(otherTask.getId(), param.getTransferUserLoginName(), BeanUtil.beanToMap(param)); - //分配权限 - FieldFilter fieldFilter = FieldFilter.create(Sys_user.class, "^id$"); - Sys_user proxyUser = Daos.ext(dao(), fieldFilter).fetch(Sys_user.class, Cnd.where(Sys_user::getLoginname, "=", param.getTransferUserLoginName())); - - Sys_user_role sysUserRole = new Sys_user_role(); - sysUserRole.setUserId(proxyUser.getId()); - sysUserRole.setRoleId(sysRole.getId()); - dao().insert(sysUserRole); - sysRoleService.clearCache(); - sysUserService.deleteCache(proxyUser.getId()); - } - - //发送消息 - List proposalInfos = dao().query(ProposalInfo.class, Cnd.where(ProposalInfo::getId, "in", consolidation)); - for (ProposalInfo info : proposalInfos) { - BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, Cnd.where(BpmProcessInstance::getProcessInstanceBusinessId, "=", info.getId())); - //查询出其他并案提案的任务ID - BpmProcessTask otherTask = dao().fetch(BpmProcessTask.class, Cnd.where(BpmProcessTask::getProcessInstanceId, "=", instance.getId()) - .and(BpmProcessTask::getProcessTaskNodeId, "=", task.getProcessTaskNodeId()) - .and("JSON_EXTRACT(createVariable, '$.underTakeId')", "=", param.getUnderTakeId()) - .and(BpmProcessTask::getDelFlag, "=", 0) - ); - String underTakeName = otherTask.getCreateVariable().getStr("underTakeName"); - - String template = "您好!提案《{}》{}单位的答复工作已由{}转交给您,请您登陆智慧校园下的暖心工会进行答复,感谢您对教代会提案工作的大力支持!。"; - String content = StrUtil.format(template, info.getName(),underTakeName,SecurityUtil.getUserUsername()); - sysMsgService.sendMsg(param.getTransferUserLoginName(), "承办单位答复", content, SecurityUtil.getUserId()); - } - - + public void updateReplyOpinion(Long taskId, String opinion) { + ProcessTask replyTask = getEditableReplyTask(taskId); + Dict variable = FlowUtil.variableToDict(replyTask.getVariable()); + /* + * 超级管理员编辑入口只允许修改答复内容。 + * 因此这里仅覆盖 tf_opinion,避免把落实情况、附件或办理人信息一并改掉。 + */ + variable.put("tf_opinion", opinion); + replyTask.setVariable(Json.toJson(variable)); + dao().update(replyTask, "variable"); } - @Override - @Aop({TransAop.READ_COMMITTED}) - public void revokeTask(String taskId) { - BpmProcessTask task = dao().fetch(BpmProcessTask.class, taskId); - String instanceId = task.getProcessInstanceId(); - BpmProcessInstance instance = dao().fetch(BpmProcessInstance.class, instanceId); - String instanceBusinessId = instance.getProcessInstanceBusinessId(); - List consolidationIds = proposalCommonService.getConsolidation(instanceBusinessId); - for (String consolidationId : consolidationIds) { - BpmProcessInstance consolidationProposalInstance = dao().fetch(BpmProcessInstance.class, Cnd.where(BpmProcessInstance::getProcessInstanceBusinessId, "=", consolidationId)); - - BpmProcessTask caseUnitTask = dao().fetch(BpmProcessTask.class, - Cnd.where(BpmProcessTask::getProcessInstanceId, "=", consolidationProposalInstance.getId()) - .and(BpmProcessTask::getProcessTaskNodeCode, "=", 70) - .and(BpmProcessTask::getDelFlag, "=", 0) - .desc(BpmProcessTask::getCreatedOn) - ); - - BpmProcessTask replyTask = dao().fetch(BpmProcessTask.class, - Cnd.where(BpmProcessTask::getPrevTaskId, "=", caseUnitTask.getId()) - .and("JSON_EXTRACT(createVariable, '$.underTakeId')", "=", task.getCreateVariable().getStr("underTakeId")) - .and(BpmProcessTask::getTaskStatus, "=", BpmProcessTaskStatusEnum.COMPLETE.name()) - .and(BpmProcessTask::getDelFlag, "=", 0) - ); - - Boolean isMaster = replyTask.getCreateVariable().getBool("isMaster", false); - if (isMaster) { - bpmService.revokeTask(replyTask.getId()); - }else{ - //判断主办是否已答复完 -// int masterActiveTaskCount = dao().count(BpmProcessTask.class, Cnd.where(BpmProcessTask::getPrevTaskId, "=", caseUnitTask.getId()) -// .and("JSON_EXTRACT(createVariable, '$.isMaster')", "=", task.getCreateVariable().getBool("isMaster") ? "true" : "false") -// .and(BpmProcessTask::getTaskStatus, "=", BpmProcessTaskStatusEnum.ACTIVE.name()) -// .and(BpmProcessTask::getDelFlag, "=", 0) -// ); - - - Sql sql = Sqls.create(""" - SELECT - COUNT(*)\s - FROM - bpm_process_task\s - WHERE - prevTaskId = @prevTaskId - AND JSON_EXTRACT( createVariable, '$.isMaster' )= TRUE\s - AND taskStatus = 'ACTIVE'\s - AND processTaskNodeCode = 80\s - AND delFlag =0 - """); - sql.setParam("prevTaskId",caseUnitTask.getId()); - sql.setCallback(Sqls.callback.integer()); - execute(sql); - int anInt = sql.getInt(); - - if(anInt == 0){ - throw new BaseException("主办单位已经答复完成,无法撤回,如需修改答复内容请联系校工会"); - }else{ - bpmService.revokeTask(replyTask.getId()); - } - - } + /** + * 同一批次承办单位答复任务未全部完成时,只完结当前任务,不触发流程流转。 + * 等最后一条 reply 任务提交时,再交给流程引擎继续往下走。 + */ + private void executeSingleFlowTask(Dict args) { + Long processTaskId = args.getLong(FlowConst.PROCESS_TASK_ID_KEY); + ProcessTask currentTask = dao().fetch(ProcessTask.class, processTaskId); + if (currentTask == null || !ProcessTaskStateEnum.DOING.getCode().equals(currentTask.getTaskState())) { + throw new BaseException("没有进行中的流程任务"); } + + String operator = resolveFlowExecuteOperator(args); + if (!processTaskService.isAllowed(currentTask, operator)) { + throw new BaseException("当前参与者不能执行该流程任务"); + } + + Dict executeArgs = buildFlowExecuteArgs(args); + if (shouldOnlyFinishCurrentReplyTask(currentTask, executeArgs)) { + /* + * 主办单位可能先于协办单位提交,此时校领导候选人要先写入流程实例变量, + * 这样最后一条 reply 任务触发流转时,下一节点仍能拿到主办单位选择的办理人。 + */ + persistSharedReplyArgs(currentTask.getProcessInstanceId(), executeArgs); + /* + * 会签节点如果配置了完成条件,最后一条任务真正走引擎时仍会读取流程实例里的计数器。 + * 因此前面这些“只完结不流转”的任务,也要同步补齐会签统计,避免最后一条被误判退回。 + */ + persistCountersignProgress(currentTask, executeArgs); + processTaskService.finishProcessTask(currentTask.getId(), operator, executeArgs); + return; + } + flowCommonService.executeTask(args); } - @Override - public String checkCanReply(String taskId) { - // 获取提案配置 - ProposalConfig config = dao().fetch(ProposalConfig.class, Cnd.NEW()); - // 如果不需要协办单位先答复,直接返回null - if (!Boolean.TRUE.equals(config.getSlaveUnitFirstReply())) { - return null; + /** + * 仅在“正常提交”场景下拦截流程继续往后流转。 + * 只要同一批次还有别的承办单位 reply 待办,就让当前任务先单独完结。 + */ + private boolean shouldOnlyFinishCurrentReplyTask(ProcessTask currentTask, Dict executeArgs) { + Integer submitType = executeArgs.getInt(FlowConst.SUBMIT_TYPE); + if (!ProcessSubmitTypeEnum.AGREE.getCode().equals(submitType)) { + return false; } - BpmProcessTask task = dao().fetch(BpmProcessTask.class, taskId); - // 如果不是主办单位,直接返回null - if (!task.getCreateVariable().getBool("isMaster", false)) { - return null; + Long batchParentTaskId = resolveReplyBatchParentTaskId(currentTask, currentTask.getTaskName()); + if (batchParentTaskId == null || batchParentTaskId <= 0) { + return false; } - - // 查询同一批次的其他活动任务 - List activeTasks = dao().query(BpmProcessTask.class, - Cnd.where("prevTaskId", "=", task.getPrevTaskId()) - .and("id", "!=", task.getId()) - .and("taskStatus", "=", BpmProcessTaskStatusEnum.ACTIVE.name()) - .and("delFlag", "=", 0) + List doingReplyTasks = dao().query( + ProcessTask.class, + Cnd.where("processInstanceId", "=", currentTask.getProcessInstanceId()) + .and("taskState", "=", ProcessTaskStateEnum.DOING.getCode()) + .and("taskName", "=", currentTask.getTaskName()) ); - - // 如果还有未完成的协办任务,返回提示信息 - return activeTasks.isEmpty() ? null : - activeTasks.stream() - .map(t -> t.getCreateVariable().getStr("underTakeName")) - .filter(Objects::nonNull) - .collect(Collectors.joining(",")) + "协办单位还未答复完成,暂时无法操作,您可以点击查看按钮关注协办单位办理进度。
如果您已经线下和协办单位交流沟通完成,请点击答复按钮进行答复。"; + return doingReplyTasks.stream() + .filter(task -> !task.getId().equals(currentTask.getId())) + .anyMatch(task -> batchParentTaskId.equals(resolveReplyBatchParentTaskId(task, currentTask.getTaskName()))); } - @Override - public void revokeTransfer(String taskId) { - bpmService.revokeTask(taskId); + /** + * 转办会把新任务的 parentId 指向原 reply 任务,因此这里要一直向上追溯, + * 找到本批次承办单位答复任务共同归属的上游节点ID,作为“同一批次”的判定依据。 + */ + private Long resolveReplyBatchParentTaskId(ProcessTask task, String taskName) { + Long batchParentTaskId = task.getTaskParentId(); + Long parentTaskId = task.getTaskParentId(); + while (parentTaskId != null && parentTaskId > 0) { + ProcessTask parentTask = dao().fetch(ProcessTask.class, parentTaskId); + if (parentTask == null || !taskName.equals(parentTask.getTaskName())) { + break; + } + batchParentTaskId = parentTask.getTaskParentId(); + parentTaskId = parentTask.getTaskParentId(); + } + return batchParentTaskId; } + + /** + * old-flow 的 executeTask 会补齐办理人信息并移除 processTaskId。 + * 前置完结当前任务时,也要保持同样的数据结构,避免任务变量格式不一致。 + */ + private Dict buildFlowExecuteArgs(Dict args) { + Dict executeArgs = args.clone(); + executeArgs.remove(FlowConst.PROCESS_TASK_ID_KEY); + Integer submitType = executeArgs.getInt(FlowConst.SUBMIT_TYPE); + if (submitType == null) { + submitType = ProcessSubmitTypeEnum.AGREE.getCode(); + } + executeArgs.put(FlowConst.SUBMIT_TYPE, submitType); + executeArgs.put(FlowConst.TASK_FORM_DATA_PREFIX + "userName", SecurityUtil.getUserUsername()); + executeArgs.put(FlowConst.TASK_FORM_DATA_PREFIX + "loginName", SecurityUtil.getUserLoginname()); + executeArgs.put(FlowConst.TASK_FORM_DATA_PREFIX + "unitId", SecurityUtil.getUnitId()); + executeArgs.put(FlowConst.TASK_FORM_DATA_PREFIX + "unitName", SecurityUtil.getUnitId()); + executeArgs.put(FlowConst.TASK_FORM_DATA_PREFIX + "unionId", SecurityUtil.getUnionId()); + return executeArgs; + } + + /** + * 承办单位答复环节真正往下流转时,下一节点可能依赖主办单位预先选择的候选人。 + * 这里只把跨任务共享所需的字段写入流程实例,避免把每个单位自己的答复内容互相覆盖。 + */ + private void persistSharedReplyArgs(Long processInstanceId, Dict executeArgs) { + Dict sharedArgs = Dict.create(); + if (StrUtil.isNotBlank(executeArgs.getStr(FlowConst.NEXT_NODE_OPERATOR))) { + sharedArgs.put(FlowConst.NEXT_NODE_OPERATOR, executeArgs.getStr(FlowConst.NEXT_NODE_OPERATOR)); + } + if (StrUtil.isNotBlank(executeArgs.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "nextNodeOperatorName"))) { + sharedArgs.put(FlowConst.TASK_FORM_DATA_PREFIX + "nextNodeOperatorName", executeArgs.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "nextNodeOperatorName")); + } + if (!sharedArgs.isEmpty()) { + processInstanceService.addVariable(processInstanceId, sharedArgs); + } + } + + /** + * 前面的 reply 任务虽然不触发节点流转,但依然属于会签实例的一部分。 + * 这里补齐流程实例上的会签计数,保证最后一条任务进入引擎时,计数与真实完结数一致。 + */ + private void persistCountersignProgress(ProcessTask currentTask, Dict executeArgs) { + if (!ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode().equals(currentTask.getPerformType())) { + return; + } + + String prefix = FlowConst.COUNTERSIGN_VARIABLE_PREFIX + currentTask.getTaskName() + "_"; + List doingSameNameTasks = dao().query( + ProcessTask.class, + Cnd.where("processInstanceId", "=", currentTask.getProcessInstanceId()) + .and("taskState", "=", ProcessTaskStateEnum.DOING.getCode()) + .and("taskName", "=", currentTask.getTaskName()) + ); + Dict addVariable = Dict.create(); + addVariable.put(prefix + FlowConst.NR_OF_COMPLETED_INSTANCES, + getProcessInstanceIntValue(currentTask.getProcessInstanceId(), prefix + FlowConst.NR_OF_COMPLETED_INSTANCES) + 1); + if (!executeArgs.containsKey(FlowConst.COUNTERSIGN_DISAGREE_FLAG)) { + addVariable.put(prefix + FlowConst.NR_OF_COMPLETED_AGREE_INSTANCES, + getProcessInstanceIntValue(currentTask.getProcessInstanceId(), prefix + FlowConst.NR_OF_COMPLETED_AGREE_INSTANCES) + 1); + } + addVariable.put(prefix + FlowConst.NR_OF_ACTIVATE_INSTANCES, + Math.max(doingSameNameTasks.size() - 1, 0)); + processInstanceService.addVariable(currentTask.getProcessInstanceId(), addVariable); + } + + /** + * old-flow 会签计数统一保存在流程实例变量里,这里读取当前值并转成整型,便于补写增量。 + */ + private int getProcessInstanceIntValue(Long processInstanceId, String key) { + ProcessInstance processInstance = processInstanceService.getById(processInstanceId); + if (processInstance == null || StrUtil.isBlank(processInstance.getVariable())) { + return 0; + } + Dict variable = cn.hutool.json.JSONUtil.toBean(processInstance.getVariable(), Dict.class); + /* + * 当前项目使用的 Hutool Dict 不支持 getInt(key, defaultValue) 这种重载。 + * 会签计数键在首次提交前可能还没写入流程实例变量,这里需要先显式判空, + * 避免 Integer 为 null 时自动拆箱触发空指针。 + */ + Integer value = variable.getInt(key); + return value == null ? 0 : value; + } + + /** + * 与 old-flow 公共执行逻辑保持一致,支持开启扩展执行人时以管理员身份提交。 + */ + private String resolveFlowExecuteOperator(Dict args) { + String operator = SecurityUtil.getUserId(); + if (args.containsKey(FlowConst.EXT_ENABLE_OPERATOR) && args.getBool(FlowConst.EXT_ENABLE_OPERATOR)) { + operator = FlowConst.ADMIN_ID; + } + return operator; + } + + /** + * “编辑答复内容”只能针对已经完成的承办单位答复任务。 + * 这里统一校验任务存在性、节点范围和任务状态,避免页面把其他流程节点误传进来。 + */ + private ProcessTask getEditableReplyTask(Long taskId) { + ProcessTask replyTask = dao().fetch(ProcessTask.class, taskId); + if (replyTask == null) { + throw new BaseException("未找到答复任务"); + } + if (!List.of("unit_reply", "two_unit_reply", "opinion_master_reply").contains(replyTask.getTaskName())) { + throw new BaseException("当前任务不支持编辑答复内容"); + } + if (!Objects.equals(ProcessTaskStateEnum.FINISHED.getCode(), replyTask.getTaskState())) { + throw new BaseException("只有已完成的答复任务才能编辑内容"); + } + return replyTask; + } + } diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/common/info.js b/src/main/resources/views/platform/zhgh/democratic/proposal/common/info.js index 101fddec..5cce1600 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/common/info.js +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/common/info.js @@ -21,10 +21,10 @@ const PROPOSAL_INFO = { {{viewData.typeName}} - + @@ -144,7 +144,7 @@ const PROPOSAL_INFO = { + v-else-if="task.taskName === 'committee'||task.taskName === 'committeeFilingUnit'"> {{ task.taskFormData.userName }}({{task.taskFormData.loginName}}) @@ -162,10 +162,16 @@ const PROPOSAL_INFO = { - {{task.ext.tf_masterUnitName}} - - {{task?.ext?.tf_slaveUnitNameStr}} - +
@@ -173,7 +179,7 @@ const PROPOSAL_INFO = { + v-if="['opinion_unit_reply','unit_reply','two_unit_reply','master_reply','slave_reply'].includes(task.taskName)" :span="3"> {{task.ext.underTakeName}} @@ -244,6 +250,8 @@ const PROPOSAL_INFO = { this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => { if (res.code === 0) { this.doneTasks = res.data + // 通知外层页面已办节点数据已加载完成,便于当前环节做表单回显。 + this.$emit("done-tasks", res.data) } }) }, diff --git a/src/main/resources/views/platform/zhgh/democratic/proposal/export/comprehensive/index.html b/src/main/resources/views/platform/zhgh/democratic/proposal/export/comprehensive/index.html index ffea23ed..b1baf488 100644 --- a/src/main/resources/views/platform/zhgh/democratic/proposal/export/comprehensive/index.html +++ b/src/main/resources/views/platform/zhgh/democratic/proposal/export/comprehensive/index.html @@ -156,6 +156,9 @@ layout("/layouts/platform.html"){ + + - - + + + - - + + +