This commit is contained in:
2026-07-14 16:28:22 +08:00
parent 24e2d9e826
commit f39735f1f2
37 changed files with 468 additions and 251 deletions
@@ -80,8 +80,9 @@ public class ProposalUnderTakeReplyController {
public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval, boolean transfer) {
/*
* 承办单位答复列表需要按“承办单位”维度展示。
* 同一个任务可能挂了多个承办单位负责人,若只按 taskId 分组会把多条待审核合并成一条
* 因此这里优先使用任务参与人的承办单位信息拆行,历史数据缺失时再回退到流程变量。
* 新流程生成的会签任务可能未写入 underTakeName、underTakeId、underTakeIsMaster
* 因此优先读取任务变量,变量缺失时再按“提案ID + 任务参与人单位ID”关联承办单位记录兜底,
* 兼容已经生成的历史待办,同时保持原有任务变量的数据优先级。
*/
Sql sql = Sqls.create("""
SELECT
@@ -107,8 +108,13 @@ public class ProposalUnderTakeReplyController {
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName),'结束') curTaskName,
IFNULL(GROUP_CONCAT(DISTINCT nt.taskName),'结束') curTaskKey,
CASE WHEN t.taskState = 20 AND ( rt.id IS NULL OR rt.taskState = 10 ) THEN 1 ELSE 0 END AS canRevoke,
t.variable->>'$.underTakeName' AS underTakeName,
IF(JSON_EXTRACT(t.variable, '$.underTakeIsMaster') = true, 1, 0) AS underTakeIsMaster,
COALESCE(NULLIF(t.variable->>'$.underTakeName', ''), replyUnit.unitName) AS underTakeName,
COALESCE(NULLIF(t.variable->>'$.underTakeId', ''), replyUnit.unitId) AS underTakeId,
CASE
WHEN JSON_EXTRACT(t.variable, '$.underTakeIsMaster') IS NOT NULL
THEN IF(JSON_EXTRACT(t.variable, '$.underTakeIsMaster') = true, 1, 0)
ELSE IFNULL(replyUnit.isMaster, 0)
END AS underTakeIsMaster,
t.variable->>'$.tf_transferUserId' AS tf_transferUserId,
transferUser.username AS transferUserName,
GROUP_CONCAT(DISTINCT ta.actorName, '(', ta.actorAccount, ')' ) AS auditUser
@@ -119,6 +125,7 @@ public class ProposalUnderTakeReplyController {
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
LEFT JOIN proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_reply_unit replyUnit ON replyUnit.proposalId = info.id AND replyUnit.unitId = ta.actorUnitId
LEFT JOIN proposal_type type on type.id = info.typeId
LEFT JOIN proposal_merge mer ON mer.proposalId = info.id
LEFT JOIN teacher_congress_session tcs on tcs.id = info.sessionId
@@ -6,6 +6,7 @@ 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.constant.FlowConst;
import com.budwk.app.flow.engine.AssignmentHandler;
import com.budwk.app.flow.engine.core.Execution;
import com.budwk.app.flow.engine.core.ServiceContext;
@@ -36,12 +37,7 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class);
String proposalId = execution.getProcessInstance().getBusinessNo();
// 当前节点需要同时处理主办和协办负责人。
// 这里不能按单位ID去重,必须保留每个承办单位对应的一条分派记录,
// 否则同一用户负责多个单位时,会被错误合并成更少的待办。
List<String> unitIds = new ArrayList<>();
unitIds.addAll(resolveMasterUnitIds(execution, dao, proposalId));
unitIds.addAll(resolveSlaveUnitIds(execution, dao, proposalId));
List<String> unitIds = resolveUnitIds(model, execution, dao, proposalId);
if (unitIds.isEmpty()) {
throw new BaseException("提案没有配置承办单位");
@@ -76,6 +72,25 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
return assignee;
}
/**
* 确定当前答复节点需要分派的单位范围。
* 二次答复页面已明确提交 tf_secondReplyUnitIds 时,该字段就是唯一分派范围;
* 未选择的主办或协办单位不能再从历史承办单位记录中补回。
* 旧流程没有该字段时,继续按主办、协办配置回退,保证历史流程可以正常运行。
*/
private List<String> resolveUnitIds(TaskModel model, Execution execution, Dao dao, String proposalId) {
String secondReplyUnitIdsKey = FlowConst.TASK_FORM_DATA_PREFIX + "secondReplyUnitIds";
if ("two_unit_reply".equals(model.getName()) && execution.getArgs().containsKey(secondReplyUnitIdsKey)) {
return getStringList(execution.getArgs(), secondReplyUnitIdsKey);
}
// 初次答复需要同时处理主办和协办负责人,旧二次答复流程也沿用该兼容逻辑。
List<String> unitIds = new ArrayList<>();
unitIds.addAll(resolveMasterUnitIds(execution, dao, proposalId));
unitIds.addAll(resolveSlaveUnitIds(execution, dao, proposalId));
return unitIds;
}
/**
* 优先从当前流程变量读取主办单位,读不到时回落到承办单位记录表,兼容重新流转场景。
*/
@@ -33,6 +33,11 @@ public class ProposalInfo extends BaseModel {
@ColDefine(type = ColType.VARCHAR, width = 20)
private String code;
@Column
@Comment("立案编号")
@ColDefine(type = ColType.VARCHAR, width = 20)
private String caseFilingCode;
@Column
@Comment("提案名称")
@ColDefine(type = ColType.VARCHAR, width = 100)
@@ -76,6 +76,8 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
throw new BaseException("没有进行中的流程任务");
}
validateAndNormalizeCaseFilingCode(args);
String proposalId = args.getStr("proposalId");
List<String> proposalIds = proposalCommonService.mergeProposal(proposalId);
if (ObjectUtil.isEmpty(proposalIds)) {
@@ -98,16 +100,44 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
syncCommitteeFilingUnitData(proposalIds, args);
}
/**
* old-flow 页面提交后,需要把立案结果以及主协办单位同步落业务表。
* 校验并规范化立案编号。确定立案时必须传入不超过 20 个字符的编号;
* 其他立案结果不保存编号,避免切换选项后把历史输入带入业务表和流程变量。
*
* @param args 流程提交参数,其中 tf_caseFilingResult 为立案结果,tf_caseFilingCode 为手工填写的立案编号
*/
private void validateAndNormalizeCaseFilingCode(Dict args) {
String caseFilingResultKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult";
String caseFilingCodeKey = FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingCode";
if (!"CONFIRM_FILING".equals(args.getStr(caseFilingResultKey))) {
args.put(caseFilingCodeKey, "");
return;
}
String caseFilingCode = StrUtil.trim(args.getStr(caseFilingCodeKey));
if (StrUtil.isBlank(caseFilingCode)) {
throw new BaseException("立案编号不能为空");
}
if (caseFilingCode.length() > 20) {
throw new BaseException("立案编号不能超过20个字符");
}
args.put(caseFilingCodeKey, caseFilingCode);
}
/**
* old-flow 页面提交后,需要把立案编号、立案结果以及主协办单位同步落业务表。
* 若这次改成“不予立案”等无需承办单位的结果,也要先清掉旧的承办单位记录,避免脏数据残留。
*/
private void syncCommitteeFilingUnitData(List<String> proposalIds, Dict args) {
String caseFilingCode = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingCode");
String caseFilingResult = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult");
String caseFilingType = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingType");
dao().update(ProposalInfo.class,
Chain.make("caseFilingResult", caseFilingResult).add("caseFilingType", caseFilingType),
Chain.make("caseFilingCode", caseFilingCode)
.add("caseFilingResult", caseFilingResult)
.add("caseFilingType", caseFilingType),
Cnd.where(ProposalInfo::getId, "in", proposalIds));
dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds));
@@ -83,15 +83,18 @@ public class TeacherCongressInstitutionController {
}
/**
* 表单中的树结构
* 获取新增机构表单使用的固定机构字典树。
*
* @param sessionId
* @return
* @param sessionId 当前教代会届次ID,用于保持接口参数与机构管理页面上下文一致
* @return Resultdata.treeList 为级联组件树结构,data.treeFlat 为名称和代码回填使用的扁平字典列表
*/
@At
@SaCheckPermission("tc.institution")
public Result formTree(@Valid String sessionId) {
Sys_dict institutionFixedDict = sysDictService.fetch(Cnd.where(Sys_dict::getCode, "=", "TEACHER_CONGRESS_INSTITUTION_FIXED"));
if (ObjectUtil.isEmpty(institutionFixedDict)) {
return Result.error("未配置双代会固定机构字典");
}
List<Sys_dict> children = sysDictService.query(Cnd.where(Sys_dict::getPath, "like", institutionFixedDict.getPath() + "%"));
List<TreeNode<String>> treeNodes = children.stream().map(sysDict -> {
TreeNode<String> treeNode = new TreeNode<>(sysDict.getId(), sysDict.getParentId(), sysDict.getName(), sysDict.getLocation());
@@ -134,19 +137,24 @@ public class TeacherCongressInstitutionController {
}
/**
* 添加机构
* 添加当前届次的机构
*
* @param institution
* @return
* @param institution 机构数据,包含 sessionId、name、code、parentId、location 和 introduceparentId 可能是固定字典ID
* @return Result,机构重复或父级不存在时返回错误,否则返回成功
*/
@At
@SaCheckPermission("tc.institution")
@Aop(TransAop.READ_COMMITTED)
public Result insert(Teacher_congress_institution institution) {
if (dao.count(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getCode, "=", institution.getCode()).and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId())) > 0) {
return Result.error("机构已存在");
}
if (StrUtil.isNotBlank(institution.getParentId()) && !institution.getParentId().equals("0") && dao.count(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getId, "=", institution.getParentId()).and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId())) == 0) {
// 级联框提交的是固定字典父级ID,需要按机构代码匹配当前届次的实际父机构。
Sys_dict sysDict = sysDictService.fetch(Cnd.where(Sys_dict::getId, "=", institution.getParentId()));
if (ObjectUtil.isEmpty(sysDict)) {
return Result.error("父级机构不存在");
}
Teacher_congress_institution parentInstitution = dao.fetch(Teacher_congress_institution.class,
Cnd.where(Teacher_congress_institution::getCode, "=", sysDict.getCode())
.and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId())
@@ -162,13 +170,14 @@ public class TeacherCongressInstitutionController {
}
/**
* 删除机构
* 更新机构名称、描述和排序。
*
* @param id
* @return
* @param institution 机构数据,id 标识待更新机构,name、introduce、location 为可更新内容
* @return Result,机构不存在时返回错误,否则返回成功
*/
@At
@SaCheckPermission("tc.institution")
@Aop(TransAop.READ_COMMITTED)
public Result update(Teacher_congress_institution institution) {
Teacher_congress_institution dbInstitution = dao.fetch(Teacher_congress_institution.class, institution.getId());
if (ObjectUtil.isEmpty(dbInstitution)) {
@@ -267,6 +276,7 @@ public class TeacherCongressInstitutionController {
*/
@At
@SaCheckPermission("tc.institution")
@Aop(TransAop.READ_COMMITTED)
public Result userDelete(@Valid String id) {
teacherCongressInstitutionUserService.userDelete(id);
return Result.success();
@@ -61,6 +61,18 @@ public class TeacherCongressSessionController {
return Result.success(pagination);
}
/**
* 查询历史届次已保存的提案类型,不需要请求参数。
*
* @return VO 列表,每项的 proposalType 字段表示一个下拉选项
*/
@At
@SaCheckPermission("tc.prepare.session")
@ApiOperation(value = "查询届次提案类型选项")
public Result listProposalTypes() {
return Result.success(teacherCongressSessionService.listProposalTypes());
}
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tc.prepare.session")
@@ -74,7 +86,7 @@ public class TeacherCongressSessionController {
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tc.prepare.session")
@ApiOperation(value = "修改届次信息")
public Result update(Teacher_congress_session session) {
public Result update(@Valid Teacher_congress_session session) {
int count = dao.count(Teacher_congress_session.class,
Cnd.where(Teacher_congress_session::getJ, "=", session.getJ())
.and(Teacher_congress_session::getC, "=", session.getC())
@@ -43,6 +43,13 @@ public class Teacher_congress_session extends BaseModel {
@ColDefine(type = ColType.VARCHAR, width = 20)
private String fullName;
@Column
@Comment("提案类型")
@ColDefine(type = ColType.VARCHAR, width = 50)
@NotBlank(message = "提案类型不能为空")
@Size(max = 50, message = "提案类型最多50个字")
private String proposalType;
@Column
@Comment("教代会开启时间")
@ColDefine(type = ColType.DATE)
@@ -1,13 +1,23 @@
package com.budwk.app.zhgh.democratic.teachercongress.prepare.service;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.controller.vo.TeacherCongressSessionProposalTypeVO;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.models.Teacher_congress_session;
import java.util.List;
/**
* 教代会届次
*/
public interface TeacherCongressSessionService extends BaseService<Teacher_congress_session> {
/**
* 查询届次中已经保存过的提案类型,供可输入下拉框复用。
*
* @return 提案类型 VO 列表,每项的 proposalType 字段为一个可选类型
*/
List<TeacherCongressSessionProposalTypeVO> listProposalTypes();
void insertSession(Teacher_congress_session session, boolean isExtend);
void deleteSession(String id);
@@ -24,12 +24,15 @@ import com.budwk.app.zhgh.democratic.teachercongress.institution.models.Teacher_
import com.budwk.app.zhgh.democratic.teachercongress.institution.models.Teacher_congress_institution_user;
import com.budwk.app.zhgh.democratic.teachercongress.meetings.models.Teacher_congress_meeting;
import com.budwk.app.zhgh.democratic.teachercongress.meetings.models.Teacher_congress_meeting_file;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.controller.vo.TeacherCongressSessionProposalTypeVO;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.models.Teacher_congress_prepare_file;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.models.Teacher_congress_session;
import com.budwk.app.zhgh.democratic.teachercongress.prepare.service.TeacherCongressSessionService;
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;
@@ -52,6 +55,21 @@ public class TeacherCongressSessionServiceImpl extends BaseServiceImpl<Teacher_c
super(dao);
}
/**
* 读取历史届次中非空的提案类型并去重,用户既可复用已有类型,也可在前端输入新类型。
*
* @return 提案类型 VO 列表,每项只包含 proposalType 字段
*/
@Override
public List<TeacherCongressSessionProposalTypeVO> listProposalTypes() {
Sql sql = Sqls.create("SELECT DISTINCT proposalType FROM teacher_congress_session " +
"WHERE proposalType IS NOT NULL AND TRIM(proposalType) <> '' ORDER BY proposalType");
sql.setCallback(Sqls.callback.strList());
dao().execute(sql);
return sql.getList(String.class).stream()
.map(TeacherCongressSessionProposalTypeVO::new)
.toList();
}
@Override
@Aop(TransAop.READ_COMMITTED)