This commit is contained in:
2026-05-20 08:56:48 +08:00
parent 6682bca13d
commit 8c467328f1
5 changed files with 219 additions and 36 deletions
@@ -22,6 +22,7 @@ import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
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;
@@ -92,14 +93,23 @@ public class ProposalSecondedController {
ta.actorId taskActorUserId,
ta.actorName taskActorName,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke
IF(rt.id IS NOT NULL, 1, 0) AS canRevoke,
CASE
WHEN t.taskState = 10 THEN 1
WHEN t.taskState = 99 AND dt.id IS NOT NULL AND ft.id IS NULL AND ps.isAgree IS NULL THEN 1
ELSE 0
END AS canAudit,
IF(t.taskState = 99, 1, 0) AS abandonedSecondTask
FROM
wf_process_task t
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
LEFT JOIN wf_process_task rt ON rt.processInstanceId = ins.id AND rt.taskParentId = t.id AND rt.taskState = 10
LEFT JOIN wf_process_task dt ON dt.processInstanceId = ins.id AND dt.taskName = 'delegation' AND dt.taskState = 10
LEFT JOIN wf_process_task ft ON ft.processInstanceId = ins.id AND ft.taskName = 'delegation' AND ft.taskState = 20
LEFT JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_second ps ON ps.proposalId = info.id AND ps.seconderId = ta.actorId AND ps.mode = 1
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
LEFT JOIN teacher_congress_delegation tcd on tcd.id = info.delegationId
@@ -116,7 +126,20 @@ public class ProposalSecondedController {
if (approval) {
cnd.and("t.taskState", "in", List.of(ProcessTaskStateEnum.FINISHED.getCode(), ProcessTaskStateEnum.INTERRUPT.getCode()));
} else {
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
// 未附议列表包含两类数据:
// 1. second 仍在进行中的正常受邀附议待办;
// 2. second 已因人数达标被系统废弃,但 delegation 团长审核还未办理,此时允许受邀人补审并写入流程记录。
cnd.and(new Static("""
(
t.taskState = 10
OR (
t.taskState = 99
AND dt.id IS NOT NULL
AND ft.id IS NULL
AND ps.isAgree IS NULL
)
)
"""));
}
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
@@ -145,11 +168,25 @@ public class ProposalSecondedController {
LEFT JOIN wf_process_instance ins ON ins.id = t.processInstanceId
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = t.id
LEFT JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_second ps ON ps.proposalId = info.id AND ps.seconderId = ta.actorId AND ps.mode = 1
LEFT JOIN wf_process_task dt ON dt.processInstanceId = ins.id AND dt.taskName = 'delegation' AND dt.taskState = 10
LEFT JOIN wf_process_task ft ON ft.processInstanceId = ins.id AND ft.taskName = 'delegation' AND ft.taskState = 20
$condition
""");
Cnd cnd = Cnd.NEW();
cnd.and("t.taskName", "=", "second");
cnd.and("t.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
// 待办数量与列表口径保持一致:正常待办 + delegation 未审前可补审的废弃 second 任务。
cnd.and(new Static("""
(
t.taskState = 10
OR (
t.taskState = 99
AND dt.id IS NOT NULL
AND ft.id IS NULL
AND ps.isAgree IS NULL
)
)
"""));
cnd.andEX("info.sessionId", "=", sessionId);
/* if (!AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
@@ -167,10 +204,21 @@ public class ProposalSecondedController {
@ApiOperation("修改提案附议信息")
@SLog(tag = "提案管理系统-附议提案", msg = "修改提案附议信息")
public Result updateInfo(@Param("proposalId") String proposalId,
@Param("taskId") Long taskId,
@Param("taskActorUserId") String taskActorUserId,
@Param("opinion") String opinion,
@Param("submitType") Integer submitType) {
return proposalSecondedService.updateInviteSecondInfo(proposalId, taskActorUserId, opinion, submitType);
/*
* 参数说明:
* proposalId:提案ID,用于定位 proposal_second 业务附议记录。
* taskId:second 流程任务ID。正常待办先由前端执行流程任务,再调用本接口同步业务记录;
* 如果 second 已被系统废弃但 delegation 还未审核,本接口会用该 taskId 补写流程审批记录。
* taskActorUserId:受邀附议人用户ID,必须与 proposal_second.seconderId 和任务参与人一致。
* opinion:受邀附议意见,写入业务表;废弃任务补审时也写入流程任务变量。
* submitType:提交类型,1 表示同意,20 表示不同意。
* 返回值:Result,code=0 表示保存成功;废弃任务补审成功时,流程记录和业务附议记录都会同步完成。
*/
return proposalSecondedService.updateInviteSecondInfo(proposalId, taskId, taskActorUserId, opinion, submitType);
}
}
@@ -10,12 +10,13 @@ public interface ProposalSecondedService extends BaseService<ProposalSecond> {
* 保存受邀附议办理结果,并在受邀附议同意人数达到配置数量时自动推进到团长审核。
*
* @param proposalId 提案ID
* @param taskId second 流程任务ID;正常待办提交后用于业务记录同步,废弃任务补审时用于写入流程审批记录
* @param taskActorUserId 受邀附议办理人ID
* @param opinion 附议意见
* @param submitType 提交类型
* @return 处理结果
*/
Result updateInviteSecondInfo(String proposalId, String taskActorUserId, String opinion, Integer submitType);
Result updateInviteSecondInfo(String proposalId, Long taskId, String taskActorUserId, String opinion, Integer submitType);
/**
* 保存主动附议结果。
@@ -3,13 +3,17 @@ package com.budwk.app.zhgh.democratic.proposal.service.impl;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.flow.constant.FlowConst;
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.entity.ProcessTaskActor;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
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;
@@ -40,7 +44,11 @@ public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond>
@Override
@Aop(TransAop.READ_COMMITTED)
public Result updateInviteSecondInfo(String proposalId, String taskActorUserId, String opinion, Integer submitType) {
public Result updateInviteSecondInfo(String proposalId, Long taskId, String taskActorUserId, String opinion, Integer submitType) {
if (!ObjectUtil.equals(taskActorUserId, SecurityUtil.getUserId())) {
return Result.error("只能办理当前登录人的受邀附议任务");
}
ProposalSecond proposalSecond = dao().fetch(
ProposalSecond.class,
Cnd.where(ProposalSecond::getProposalId, "=", proposalId)
@@ -50,6 +58,18 @@ public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond>
if (proposalSecond == null) {
return Result.error("未查询到附议记录");
}
if (proposalSecond.getIsAgree() != null) {
return Result.error("该受邀附议已办理,请勿重复提交");
}
ProcessTask secondTask = taskId == null ? null : dao().fetch(ProcessTask.class, taskId);
if (secondTask != null && ProcessTaskStateEnum.ABANDON.getCode().equals(secondTask.getTaskState())) {
Result supplementResult = supplementAbandonedSecondTask(proposalId, secondTask, taskActorUserId, opinion, submitType);
if (supplementResult.getCode() != 0) {
return supplementResult;
}
}
proposalSecond.setIsAgree(ObjectUtil.equals(submitType, ProcessSubmitTypeEnum.AGREE.getCode()));
proposalSecond.setOpinion(opinion);
proposalSecond.setSecondedTime(DateUtil.date());
@@ -119,6 +139,80 @@ public class ProposalSecondedServiceImpl extends BaseServiceImpl<ProposalSecond>
return Result.success();
}
/**
* delegation 团长审核未办理前,允许受邀人补审已经被系统废弃的 second 会签任务。
* 这里不能再调用流程引擎执行任务,因为引擎只允许执行 DOING 状态任务;补审只负责把废弃任务补写成
* FINISHED 审批记录,并保存本次附议意见,不再触发流程继续流转。
*/
private Result supplementAbandonedSecondTask(String proposalId, ProcessTask secondTask, String taskActorUserId, String opinion, Integer submitType) {
if (!"second".equals(secondTask.getTaskName())) {
return Result.error("当前任务不是受邀附议任务");
}
ProcessInstance processInstance = dao().fetch(ProcessInstance.class, Cnd.where(ProcessInstance::getBusinessNo, "=", proposalId));
if (processInstance == null || !ObjectUtil.equals(processInstance.getId(), secondTask.getProcessInstanceId())) {
return Result.error("未查询到提案流程信息");
}
int taskActorCount = dao().count(
ProcessTaskActor.class,
Cnd.where(ProcessTaskActor::getProcessTaskId, "=", secondTask.getId())
.and(ProcessTaskActor::getActorId, "=", taskActorUserId)
);
if (taskActorCount == 0) {
return Result.error("当前用户不是该受邀附议任务办理人");
}
int delegationDoneCount = dao().count(
ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstance.getId())
.and(ProcessTask::getTaskName, "=", "delegation")
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode())
);
if (delegationDoneCount > 0) {
return Result.error("团长已审核,不能再补充受邀附议");
}
int delegationDoingCount = dao().count(
ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "=", processInstance.getId())
.and(ProcessTask::getTaskName, "=", "delegation")
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode())
);
if (delegationDoingCount == 0) {
return Result.error("当前提案不在团长审核待办阶段,不能补充受邀附议");
}
Dict taskVariable = FlowUtil.variableToDict(secondTask.getVariable());
taskVariable.put(FlowConst.SUBMIT_TYPE, submitType);
taskVariable.put(FlowConst.TASK_FORM_DATA_PREFIX + "opinion", opinion);
fillTaskOperatorInfo(taskActorUserId, taskVariable);
FlowUtil.addUserInfoToArgs(taskActorUserId, taskVariable);
secondTask.setTaskState(ProcessTaskStateEnum.FINISHED.getCode());
secondTask.setOperator(taskActorUserId);
secondTask.setFinishTime(DateUtil.date());
secondTask.setUpdatedAt(System.currentTimeMillis());
secondTask.setUpdatedBy(taskActorUserId);
secondTask.setVariable(JSONUtil.toJsonStr(taskVariable));
dao().update(secondTask, "taskState|operator|finishTime|updatedAt|updatedBy|variable");
return Result.success();
}
/**
* 补写流程记录时,补齐与 /flow/common/executeTask 正常审批一致的任务表单用户信息。
* 这些字段用于审批记录展示和后续导出模板读取,避免补审记录只有意见、没有办理人组织信息。
*/
private void fillTaskOperatorInfo(String taskActorUserId, Dict taskVariable) {
View_user user = dao().fetch(View_user.class, Cnd.where("id", "=", taskActorUserId));
taskVariable.put(FlowConst.TASK_FORM_DATA_PREFIX + "userName", user == null ? "" : user.getUsername());
taskVariable.put(FlowConst.TASK_FORM_DATA_PREFIX + "loginName", user == null ? "" : user.getLoginname());
taskVariable.put(FlowConst.TASK_FORM_DATA_PREFIX + "unitId", user == null ? "" : user.getUnitId());
taskVariable.put(FlowConst.TASK_FORM_DATA_PREFIX + "unitName", user == null ? "" : user.getUnitName());
taskVariable.put(FlowConst.TASK_FORM_DATA_PREFIX + "unionId", user == null ? "" : user.getUnionId());
taskVariable.put(FlowConst.TASK_FORM_DATA_PREFIX + "unionName", user == null ? "" : user.getUnionName());
}
/**
* 只按受邀附议记录判断是否达到配置的附议人数。
* 一旦满足配置数量,就用系统自动执行一个仍处于办理中的 second 任务来触发流程引擎会签合并,
@@ -275,7 +275,7 @@ layout("/layouts/platform.html"){
showAuditButton: function (row) {
if (this.activeTab === "inviteSeconded") {
return row.taskState === 10
return row.canAudit === 1
}
return row.count === 0
},
@@ -287,13 +287,19 @@ layout("/layouts/platform.html"){
})
},
openAudit: async function (row) {
openAudit: function (row) {
if (this.activeTab === "positiveSeconded") {
var validatePass = await this.validateCanSeconded(row.id)
if (!validatePass) {
return
}
this.validateCanSeconded(row.id).then((validatePass) => {
if (validatePass) {
this.openAuditForm(row)
}
})
return
}
this.openAuditForm(row)
},
openAuditForm: function (row) {
this.$set(this, "row", row)
this.$refs.guava.edit(() => {
this.$set(this, "showApprovalForm", true)
@@ -314,7 +320,6 @@ layout("/layouts/platform.html"){
}
this.$refs.proposalInfoRef.onOpen(row)
})
},
handleTaskAction: function (val) {
@@ -335,6 +340,17 @@ layout("/layouts/platform.html"){
},
handleInviteSeconded: function (val) {
if (this.row.abandonedSecondTask === 1) {
this.updateSecond(val).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg)
this.doSearch()
this.listInviteSecondedTodoCount()
}
})
return
}
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
@@ -342,18 +358,22 @@ layout("/layouts/platform.html"){
})
}).then((res) => {
if (res.code === 0) {
this.updateSecond(val)
this.$refs.guava.index()
this.$message.success(res.msg)
this.doSearch()
this.listInviteSecondedTodoCount()
this.updateSecond(val).then((updateRes) => {
if (updateRes.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg)
this.doSearch()
this.listInviteSecondedTodoCount()
}
})
}
})
},
updateSecond: function (submitType) {
this.$axios.post("/platform/proposal/seconded/updateInfo", {
return this.$axios.post("/platform/proposal/seconded/updateInfo", {
proposalId: this.row.id,
taskId: this.row.taskId,
taskActorUserId: this.row.taskActorUserId,
opinion: this.formData.tf_opinion,
submitType: submitType,
@@ -245,7 +245,7 @@ layout("/layouts/platform_h5.html"){
},
showApprovalButton(row) {
if (this.activeTab === "inviteSeconded") {
return row.taskState === 10
return row.canAudit === 1
}
return row.count === 0
},
@@ -255,13 +255,19 @@ layout("/layouts/platform_h5.html"){
this.$refs.proposalInfoRef.onOpen(row)
},
async onApproval(row) {
onApproval(row) {
if (this.activeTab === "positiveSeconded") {
const validatePass = await this.validateCanSeconded(row.id)
if (!validatePass) {
return
}
this.validateCanSeconded(row.id).then((validatePass) => {
if (validatePass) {
this.openApprovalForm(row)
}
})
return
}
this.openApprovalForm(row)
},
openApprovalForm(row) {
this.$set(this, "row", row)
this.$set(this, "showApprovalForm", true)
this.$refs.proposalInfoRef.onOpen(row)
@@ -294,9 +300,8 @@ layout("/layouts/platform_h5.html"){
})
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate()
handleTaskAction(val) {
this.$refs.formRef.validate().then(() => {
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
@@ -309,12 +314,23 @@ layout("/layouts/platform_h5.html"){
}).catch(() => {
})
} catch (error) {
}).catch(() => {
}
})
},
handleInviteSeconded(val) {
if (this.row.abandonedSecondTask === 1) {
this.updateSecond(val).then((res) => {
if (res.code === 0) {
this.$refs.proposalInfoRef.onClose()
this.$toast.success(res.msg)
this.doSearch()
this.listInviteSecondedTodoCount()
}
})
return
}
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
@@ -322,11 +338,14 @@ layout("/layouts/platform_h5.html"){
})
}).then((res) => {
if (res.code === 0) {
this.$refs.proposalInfoRef.onClose()
this.$toast.success(res.msg)
this.doSearch()
this.updateSecond(val)
this.listInviteSecondedTodoCount()
this.updateSecond(val).then((updateRes) => {
if (updateRes.code === 0) {
this.$refs.proposalInfoRef.onClose()
this.$toast.success(res.msg)
this.doSearch()
this.listInviteSecondedTodoCount()
}
})
}
})
},
@@ -346,8 +365,9 @@ layout("/layouts/platform_h5.html"){
},
updateSecond(submitType) {
this.$axios.post("/platform/proposal/seconded/updateInfo", {
return this.$axios.post("/platform/proposal/seconded/updateInfo", {
proposalId: this.row.id,
taskId: this.row.taskId,
taskActorUserId: this.row.taskActorUserId,
opinion: this.formData.tf_opinion,
submitType: submitType,