This commit is contained in:
=
2026-07-24 10:31:32 +08:00
parent 71da51c7f8
commit 46afff880c
34 changed files with 969 additions and 256 deletions
@@ -14,11 +14,9 @@ import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.services.SysDictService;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.sys.services.SysUnitService;
import com.budwk.app.sys.services.SysUserService;
@@ -26,6 +24,7 @@ import com.budwk.app.sys.views.View_user;
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.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Chain;
@@ -67,8 +66,6 @@ public class SysUnitController {
@Inject
private SysUserService sysUserService;
@Inject
private SysDictService sysDictService;
@Inject
private SysRoleService sysRoleService;
@At("")
@@ -115,37 +112,7 @@ public class SysUnitController {
@Ok("json")
@SaCheckLogin
public Object leaderPageData(PageForm pageForm, String unitId) {
List<Sys_dict> unitRoles = sysDictService.getSubListByCode("UNIT_ROLES");
if (Lang.isEmpty(unitRoles)) {
return Result.success();
}
List<String> unitRoleCodes = unitRoles.stream().map(Sys_dict::getCode).toList();
Sql sql = Sqls.create("""
SELECT
t1.userId,
t2.username,
t2.loginname,
t3.`code` AS roleCode,
t3.`name` AS roleName
FROM
sys_user_role t1
LEFT JOIN sys_user t2 ON t2.id = t1.userId
LEFT JOIN sys_role t3 ON t3.id = t1.roleId
$condition
""");
Cnd cnd = Cnd.where("t3.`code`", "in", unitRoleCodes);
cnd.and("t1.unitId", "=", unitId);
if (StrUtil.isAllNotBlank(pageForm.getSearchName(), pageForm.getSearchKeyword())) {
switch (pageForm.getSearchName()) {
case "loginname" -> cnd.where().andLike("t2.loginname", pageForm.getSearchKeyword());
case "username" -> cnd.where().andLike("t2.username", pageForm.getSearchKeyword());
case "roleName" -> cnd.where().andLike("t3.`name`", pageForm.getSearchKeyword());
}
}
cnd.asc("t2.username");
sql.setCondition(cnd);
Pagination pagination = sysUserService.listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
return Result.success(pagination);
return Result.success(sysUnitService.getUnitLeaderPageData(pageForm, unitId));
}
@At
@@ -182,6 +149,25 @@ public class SysUnitController {
@Ok("json")
@Aop(TransAop.READ_COMMITTED)
public Result insertUnitUserRole(String userId, String roleCode, String unitId) {
// 党委书记按分工会归属维护,单位页仅用于展示,避免把分工会角色错误写成单个单位角色。
if (RoleConstant.UNIT_PARTY_SECRETARY.name().equals(roleCode)) {
return Result.error("单位党委书记请在分工会管理中设置");
}
// 页面选择“单位分管校领导”时,维护提案流程实际使用的承办单位角色数据。
if ("UNIT_SCHOOL_LEADER".equals(roleCode)) {
ProposalUndertake undertake = getProposalUndertakeByUnitId(unitId);
Sys_role schoolLeaderRole = sysRoleService.fetch(Cnd.where("code", "=", RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name()));
if (Lang.isEmpty(schoolLeaderRole)) {
throw new BaseException("未找到单位分管校领导角色");
}
sysRoleService.clear("sys_user_role", Cnd.where("roleId", "=", schoolLeaderRole.getId())
.and("userId", "=", userId).and("underTakeId", "=", undertake.getId()));
sysRoleService.insert("sys_user_role", Chain.make("roleId", schoolLeaderRole.getId())
.add("userId", userId).add("underTakeId", undertake.getId()));
sysRoleService.clearCache();
sysUserService.clearCache();
return Result.success();
}
Sys_role role = sysRoleService.fetch(Cnd.where("code", "=", roleCode));
if (Lang.isEmpty(role)) {
throw new BaseException("无法找到{}对应编码的角色", roleCode);
@@ -197,6 +183,23 @@ public class SysUnitController {
@Ok("json")
@Aop(TransAop.READ_COMMITTED)
public Result deleteUnitUserRole(String userId, String roleCode, String unitId) {
// 党委书记按分工会归属维护,单位页只展示其现有设置。
if (RoleConstant.UNIT_PARTY_SECRETARY.name().equals(roleCode)) {
return Result.error("单位党委书记请在分工会管理中设置");
}
// 分管校领导列表返回的是提案流程实际角色,删除时按承办单位绑定维度清理。
if (RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name().equals(roleCode)) {
ProposalUndertake undertake = getProposalUndertakeByUnitId(unitId);
Sys_role schoolLeaderRole = sysRoleService.fetch(Cnd.where("code", "=", RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name()));
if (Lang.isEmpty(schoolLeaderRole)) {
throw new BaseException("未找到单位分管校领导角色");
}
sysRoleService.clear("sys_user_role", Cnd.where("roleId", "=", schoolLeaderRole.getId())
.and("userId", "=", userId).and("underTakeId", "=", undertake.getId()));
sysRoleService.clearCache();
sysUserService.clearCache();
return Result.success();
}
Sys_role role = sysRoleService.fetch(Cnd.where("code", "=", roleCode));
if (Lang.isEmpty(role)) {
throw new BaseException("无法找到{}对应编码的角色", roleCode);
@@ -207,6 +210,25 @@ public class SysUnitController {
return Result.success();
}
/**
* 根据系统单位编码找到对应提案承办单位,保证单位页维护的分管校领导直接用于提案流程。
*
* @param unitId 系统单位 ID
* @return 对应的提案承办单位
*/
private ProposalUndertake getProposalUndertakeByUnitId(String unitId) {
Sys_unit unit = sysUnitService.fetch(unitId);
if (unit == null || StrUtil.isBlank(unit.getUnitcode())) {
throw new BaseException("当前单位不存在或未设置单位编码");
}
ProposalUndertake undertake = sysUnitService.dao().fetch(ProposalUndertake.class,
Cnd.where("code", "=", unit.getUnitcode()));
if (undertake == null) {
throw new BaseException("当前单位未同步为提案承办单位,无法设置分管校领导");
}
return undertake;
}
@At("/child")
@Ok("json")
@SaCheckLogin
@@ -1,5 +1,7 @@
package com.budwk.app.sys.services;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_unit;
@@ -22,6 +24,16 @@ public interface SysUnitService extends BaseService<Sys_unit> {
*/
void deleteAndChild(Sys_unit unit);
/**
* 查询单位干部。单位党委书记按分工会归属展示,提案分管校领导按承办单位归属展示,
* 避免把两类已有业务角色误当作直接绑定在 sys_unit 上的角色。
*
* @param pageForm 分页及查询条件
* @param unitId 当前系统单位 ID
* @return 单位干部分页数据,包含数据来源及是否允许在本页维护的标记
*/
Pagination getUnitLeaderPageData(PageForm pageForm, String unitId);
/**
* 清空缓存
*/
@@ -2,6 +2,9 @@ package com.budwk.app.sys.services.impl;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RedisConstant;
import com.budwk.app.base.constant.RoleConstant;
import com.budwk.app.base.page.Pagination;
import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.service.impl.BaseServiceImpl;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.sys.services.SysUnitService;
@@ -10,6 +13,7 @@ import org.nutz.dao.Chain;
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.IocBean;
import org.nutz.lang.Strings;
@@ -69,6 +73,79 @@ public class SysUnitServiceImpl extends BaseServiceImpl<Sys_unit> implements Sys
}
}
/**
* 查询单位干部,并按角色的实际维护维度关联数据。
* 单位党委书记由分工会干部维护,提案分管校领导由提案承办单位维护,二者均不应按
* sys_user_role.unitId 直接过滤,否则迁移和现有配置的数据都无法在单位页展示。
*
* @param pageForm 分页及查询条件
* @param unitId 当前系统单位 ID
* @return 含角色来源和页面维护权限的分页结果
*/
@Override
public Pagination getUnitLeaderPageData(PageForm pageForm, String unitId) {
String searchCondition = switch (pageForm.getSearchName()) {
case "loginname" -> " AND t2.loginname LIKE CONCAT('%', @searchKeyword, '%')";
case "username" -> " AND t2.username LIKE CONCAT('%', @searchKeyword, '%')";
case "roleName" -> " AND (t3.name LIKE CONCAT('%', @searchKeyword, '%') "
+ "OR t3.code = '" + RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name() + "')";
default -> "";
};
if (StrUtil.isBlank(pageForm.getSearchKeyword())) {
searchCondition = "";
}
Sql sql = Sqls.create("""
SELECT
t1.userId,
t2.username,
t2.loginname,
t3.`code` AS roleCode,
CASE t3.`code`
WHEN 'PROPOSAL_BRANCH_SCHOOL_LEADER' THEN '单位分管校领导'
ELSE t3.`name`
END AS roleName,
CASE
WHEN t3.`code` = 'UNIT_PARTY_SECRETARY' THEN 0
ELSE 1
END AS canManage
FROM sys_user_role t1
LEFT JOIN sys_user t2 ON t2.id = t1.userId
LEFT JOIN sys_role t3 ON t3.id = t1.roleId
LEFT JOIN sys_unit currentUnit ON currentUnit.id = @unitId
LEFT JOIN proposal_undertake undertake ON undertake.id = t1.underTakeId
WHERE currentUnit.id = @unitId
AND (
(
t3.`code` IN (
SELECT dictRole.`code`
FROM sys_dict dictRole
WHERE dictRole.parentId = (
SELECT dictParent.id FROM sys_dict dictParent
WHERE dictParent.`code` = 'UNIT_ROLES' LIMIT 1
)
)
AND (
t1.unitId = currentUnit.id
OR (
t3.`code` = 'UNIT_PARTY_SECRETARY'
AND t1.unionId = currentUnit.unionId
)
)
)
OR (
t3.`code` = 'PROPOSAL_BRANCH_SCHOOL_LEADER'
AND undertake.`code` = currentUnit.unitcode
)
)
""" + searchCondition + " ORDER BY t2.username");
sql.setParam("unitId", unitId);
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
sql.setParam("searchKeyword", pageForm.getSearchKeyword());
}
return listPageMap(pageForm.getPageNumber(), pageForm.getPageSize(), sql);
}
@CacheRemoveAll
public void clearCache() {
@@ -13,10 +13,10 @@ import com.budwk.app.base.param.PageForm;
import com.budwk.app.base.result.Result;
import com.budwk.app.base.service.BaseService;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalUndertakeConfigService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
@@ -37,6 +37,7 @@ import org.nutz.mvc.annotation.Param;
import javax.validation.Valid;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -82,26 +83,34 @@ public class ProposalConfigUnitController {
Sql sql = Sqls.create("""
SELECT
t1.*,
u1.username AS branchSchoolLeader,
u2.username AS unitLeader
branchLeader.branchSchoolLeader,
unitManager.unitLeader
FROM
proposal_undertake t1
LEFT JOIN sys_user_role sur1 ON sur1.underTakeId = t1.id
AND sur1.roleId = ( SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_BRANCH_SCHOOL_LEADER )
LEFT JOIN sys_user u1 ON u1.id = sur1.userId
LEFT JOIN sys_user_role sur2 ON sur2.underTakeId = t1.id
AND sur2.roleId = ( SELECT r.id FROM sys_role r WHERE r.CODE = @PROPOSAL_UNIT_LEADER)
LEFT JOIN sys_user u2 ON u2.id = sur2.userId
proposal_undertake t1
LEFT JOIN sys_unit sysUnit ON sysUnit.unitcode = t1.code
LEFT JOIN (
SELECT sur.underTakeId,
GROUP_CONCAT(DISTINCT u.username ORDER BY u.username SEPARATOR '、') AS branchSchoolLeader
FROM sys_user_role sur
JOIN sys_role r ON r.id = sur.roleId AND r.CODE = @PROPOSAL_BRANCH_SCHOOL_LEADER
LEFT JOIN sys_user u ON u.id = sur.userId
GROUP BY sur.underTakeId
) branchLeader ON branchLeader.underTakeId = t1.id
LEFT JOIN (
SELECT sur.unitId,
GROUP_CONCAT(DISTINCT u.username ORDER BY u.username SEPARATOR '、') AS unitLeader
FROM sys_user_role sur
JOIN sys_role r ON r.id = sur.roleId AND r.CODE = 'UNIT_MANAGER'
LEFT JOIN sys_user u ON u.id = sur.userId
GROUP BY sur.unitId
) unitManager ON unitManager.unitId = sysUnit.id
$condition
""");
sql.setParam("PROPOSAL_BRANCH_SCHOOL_LEADER", RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name());
sql.setParam("PROPOSAL_UNIT_LEADER", RoleConstant.PROPOSAL_UNIT_LEADER.name());
Cnd cnd = Cnd.NEW();
if (StrUtil.isNotBlank(pageForm.getSearchKeyword())) {
cnd.where().orLike("u1.username", pageForm.getSearchKeyword());
cnd.where().orLike("u1.loginname", pageForm.getSearchKeyword());
cnd.where().orLike("u2.username", pageForm.getSearchKeyword());
cnd.where().orLike("u2.loginname", pageForm.getSearchKeyword());
cnd.where().orLike("branchLeader.branchSchoolLeader", pageForm.getSearchKeyword());
cnd.where().orLike("unitManager.unitLeader", pageForm.getSearchKeyword());
cnd.where().orLike("t1.name", pageForm.getSearchKeyword());
}
// 承办单位列表未指定有效排序时继续按单位编码升序。
@@ -167,15 +176,18 @@ public class ProposalConfigUnitController {
@SLog(tag = "提案", msg = "设置承办单位领导")
@Aop(TransAop.READ_COMMITTED)
public Result updateUnitLeader(@Valid String id, @Param("userIds") String[] userIds) {
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER);
//删除原来的用户
Sys_unit unit = getSystemUnitByUndertakeId(id);
Sys_role sysRole = sysRoleService.getByCode("UNIT_MANAGER");
// 系统单位负责人是提案承办单位答复的唯一人员来源,按系统单位维度整体覆盖。
dao.clear(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId())
.and(Sys_user_role::getUnderTakeId, "=", id));
.and(Sys_user_role::getUnitId, "=", unit.getId()));
List<Sys_user_role> sysUserRoles = Arrays.stream(userIds).map(userId -> {
Sys_user_role sysUserRole = new Sys_user_role();
sysUserRole.setRoleId(sysRole.getId());
sysUserRole.setUserId(userId);
sysUserRole.setUnderTakeId(id);
sysUserRole.setUnitId(unit.getId());
// 与单位管理页保持同一角色关联结构,确保两页均可维护同一位负责人。
sysUserRole.setUnderTakeId(unit.getId());
return sysUserRole;
}).toList();
dao.insert(sysUserRoles);
@@ -191,25 +203,21 @@ public class ProposalConfigUnitController {
@At
@SaCheckPermission("proposal.config.unit")
public Result branchSchoolLeaderTransferData(@Valid String id) {
//已设置的领导
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER);
List<Sys_user_role> sysUserRoles = dao.query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId()).and(Sys_user_role::getUnderTakeId, "=", id));
List<String> selectUserIds = sysUserRoles.stream().map(Sys_user_role::getUserId).toList();
return Result.success(createBranchSchoolLeaderTransferData(id, null));
}
//查询全部的校领导
Sql sql = Sqls.create("select id,username,loginname,unitname,concat(username,'(',loginname,')') label from vw_user where unitId in (@unitId)");
ProposalConfig proposalConfig = dao.fetch(ProposalConfig.class, Cnd.NEW());
if(ObjectUtil.isNull(proposalConfig)){
return Result.error("提案全局配置未配置,请在提案管理-基础配置-提案配置页面进行配置。");
}
if(ObjectUtil.isEmpty(proposalConfig.getSchoolLeaderUnitIds())){
return Result.error("校领导单位未配置,请在提案管理-基础配置-提案配置页面进行配置。");
}
sql.setParam("unitId", proposalConfig.getSchoolLeaderUnitIds());
List<NutMap> allUsers = baseService.listMap(sql);
return Result.success(Map.of("allUsers", allUsers, "selectUserIds", selectUserIds));
/**
* 单位分管校领导候选人员。首次仅返回前五十人;按姓名或工号搜索时由服务端重新查询。
* 已选人员始终补入候选集合,保证不在当前搜索结果中的人员也能回显。
*
* @param id 承办单位 ID
* @param keyword 姓名或工号关键字
* @return 候选人员、已选人员和总数
*/
@At
@SaCheckPermission("proposal.config.unit")
public Result branchSchoolLeaderUserPageData(@Valid String id, String keyword) {
return Result.success(createBranchSchoolLeaderTransferData(id, keyword));
}
/**
@@ -221,18 +229,20 @@ public class ProposalConfigUnitController {
@At
@SaCheckPermission("proposal.config.unit")
public Result unitUsersTransferData(@Valid String id) {
//已设置的单位领导
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER);
List<Sys_user_role> sysUserRoles = dao.query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId()).and(Sys_user_role::getUnderTakeId, "=", id));
// 已设置的系统单位负责人。
Sys_unit unit = getSystemUnitByUndertakeId(id);
Sys_role sysRole = sysRoleService.getByCode("UNIT_MANAGER");
List<Sys_user_role> sysUserRoles = dao.query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId()).and(Sys_user_role::getUnitId, "=", unit.getId()));
List<String> selectUserIds = sysUserRoles.stream().map(Sys_user_role::getUserId).toList();
//查询该单位下全部的用户
ProposalUndertake proposalUndertake = dao.fetch(ProposalUndertake.class, id);
Sql sql = Sqls.create("select id,username,loginname,unitname,concat(username,'(',loginname,')') label from vw_user $condition");
Cnd cnd = Cnd.NEW();
cnd.and("unitCode","=",proposalUndertake.getCode());
cnd.orEX("id","in",selectUserIds);
cnd.and("unitCode","=",unit.getUnitcode());
if (ObjectUtil.isNotEmpty(selectUserIds)) {
// 已设置负责人可能不属于当前单位,仍要放入候选集合,才能在穿梭框回显。
cnd.orEX("id","in",selectUserIds);
}
sql.setCondition(cnd);
// sql.setParam("unitCode", proposalUndertake.getCode());
// sql.setParam("selectUserIds", selectUserIds);
@@ -241,6 +251,56 @@ public class ProposalConfigUnitController {
return Result.success(Map.of("allUsers", allUsers, "selectUserIds", selectUserIds));
}
/** 根据承办单位编码查询系统单位,保证配置页面和流程处理人使用同一单位数据。 */
private Sys_unit getSystemUnitByUndertakeId(String undertakeId) {
ProposalUndertake undertake = dao.fetch(ProposalUndertake.class, undertakeId);
if (undertake == null || StrUtil.isBlank(undertake.getCode())) {
throw new IllegalArgumentException("承办单位不存在或未设置单位编码");
}
Sys_unit unit = dao.fetch(Sys_unit.class, Cnd.where(Sys_unit::getUnitcode, "=", undertake.getCode()));
if (unit == null) {
throw new IllegalArgumentException("承办单位未关联系统单位,请先同步系统单位");
}
return unit;
}
/**
* 组装分管校领导穿梭框数据。默认或搜索时均固定返回前五十条候选人,并合并已选人员。
*/
@SuppressWarnings("unchecked")
private Map<String, Object> createBranchSchoolLeaderTransferData(String undertakeId, String keyword) {
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER);
List<String> selectUserIds = dao.query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId())
.and(Sys_user_role::getUnderTakeId, "=", undertakeId))
.stream().map(Sys_user_role::getUserId).toList();
Sql sql = Sqls.create("select id,username,loginname,unitname,concat(username,'(',loginname,')') label from vw_user $condition");
Cnd cnd = Cnd.NEW();
if (StrUtil.isNotBlank(keyword)) {
cnd.where().orLike("username", keyword);
cnd.where().orLike("loginname", keyword);
}
// 候选人员按所属分工会归类展示,同一分工会内再按姓名、工号排序。
cnd.asc("unionName").asc("username").asc("loginname");
sql.setCondition(cnd);
Pagination<NutMap> page = (Pagination<NutMap>) baseService.listPageMap(1, 50, sql);
LinkedHashMap<String, NutMap> userMap = new LinkedHashMap<>();
for (NutMap user : page.getList()) {
userMap.put(user.getString("id"), user);
}
if (ObjectUtil.isNotEmpty(selectUserIds)) {
Sql selectedSql = Sqls.create("select id,username,loginname,unitname,concat(username,'(',loginname,')') label from vw_user where id in (@userIds)");
selectedSql.setParam("userIds", selectUserIds);
List<NutMap> selectedUsers = (List<NutMap>) baseService.listMap(selectedSql);
for (NutMap user : selectedUsers) {
userMap.putIfAbsent(user.getString("id"), user);
}
}
return Map.of("allUsers", new ArrayList<>(userMap.values()), "selectUserIds", selectUserIds,
"totalCount", page.getTotalCount());
}
@At
@SaCheckPermission("proposal.config.unit")
@Aop(TransAop.READ_COMMITTED)
@@ -137,6 +137,19 @@ public class ProposalCommitteeFilingController {
return pageData(pageForm, approval);
}
/**
* 提案委员会立案 H5 撤回。
* 复用 PC 端撤回服务,确保并案提案仍按整组撤回处理。
*/
@At("/h5/revokeTask")
@SaCheckPermission("h5.proposal.committeeFiling")
@ApiOperation("提案委员会立案 H5 撤回任务")
@Aop(TransAop.READ_COMMITTED)
public Result h5RevokeTask(@Param("taskId") Long taskId, @Param("proposalId") String proposalId) {
proposalCommitteeFilingService.revokeTask(taskId, proposalId);
return Result.success();
}
@At
@Ok("void")
@SaCheckPermission("proposal.committeeFiling")
@@ -150,6 +150,18 @@ public class ProposalCommitteeFilingUnitController {
return pageData(pageForm, approval);
}
/**
* 提案委员会确认承办单位 H5 撤回。
* 与 PC 端共用现有撤回规则,保证并案提案会整组回退。
*/
@At("/h5/revokeTask")
@SaCheckPermission("h5.proposal.committeeFilingUnit")
@ApiOperation("提案委员会确认承办单位 H5 撤回任务")
@Aop(TransAop.READ_COMMITTED)
public Result h5RevokeTask(@Param("taskId") Long taskId, @Param("proposalId") String proposalId) {
return revokeTask(taskId, proposalId);
}
@At
@Ok("void")
@SaCheckPermission("proposal.committeeFilingUnit")
@@ -5,11 +5,11 @@ 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.enums.ProcessTaskPerformTypeEnum;
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.ProposalControlService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@@ -36,6 +36,8 @@ public class ProposalControlController {
private BaseService baseService;
@Inject
private ProposalCommonService proposalCommonService;
@Inject
private ProposalControlService proposalControlService;
@At("")
@Ok("beetl:/platform/zhgh/democratic/proposal/transact/control/index.html")
@@ -58,20 +60,12 @@ public class ProposalControlController {
ins.state instanceState,
ins.variable instanceVariable,
ins.processDefineId instanceProcessDefineId,
t.id taskId,
t.taskName AS taskKey,
t.displayName taskName,
t.taskType,
t.performType taskPerformType,
t.taskState,
t.finishTime,
t.taskParentId,
t.variable taskVariable,
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') curTaskName
MIN(t.id) taskId,
GROUP_CONCAT(DISTINCT t.taskName ORDER BY t.id) taskKeys,
IFNULL(GROUP_CONCAT(DISTINCT t.displayName ORDER BY t.id), '结束') curTaskName
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 proposal_info info ON info.id = ins.businessNo
LEFT JOIN proposal_type type ON type.id = info.typeId
LEFT JOIN teacher_congress_session tcs ON tcs.id = info.sessionId
@@ -86,7 +80,8 @@ public class ProposalControlController {
}
ProposalSearchParam.buildSearch(cnd, pageForm);
cnd.groupBy("t.id");
// 一个提案实例可能有多个并行承办单位答复待办;状态调整按实例聚合,只展示一条提案记录。
cnd.groupBy("ins.id");
// 状态调整列表未指定有效排序时保留任务创建时间倒序。
if (!proposalCommonService.applySafeProposalListOrder(cnd, pageForm, "control")) {
cnd.desc("t.createdAt");
@@ -100,25 +95,17 @@ public class ProposalControlController {
@SaCheckPermission("proposal.control")
@ApiOperation("获取可跳转节点")
public Result jumpNodes(Long instanceId) {
if (instanceId == null) {
return Result.error("参数错误");
}
Sql sql = Sqls.create("""
SELECT
t.taskName AS value,
MAX(t.displayName) AS label,
MIN(t.id) AS seq
FROM wf_process_task t
WHERE t.processInstanceId = @instanceId
AND t.taskState IN (@doing, @finish)
AND t.performType != @countersign
GROUP BY t.taskName
ORDER BY seq ASC
""");
sql.params().set("instanceId", instanceId);
sql.params().set("doing", ProcessTaskStateEnum.DOING.getCode());
sql.params().set("finish", ProcessTaskStateEnum.FINISHED.getCode());
sql.params().set("countersign", ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode());
return Result.success(baseService.listMap(sql));
return Result.success(proposalControlService.listJumpNodes(instanceId));
}
/**
* 按整个提案流程实例跳转节点,统一清理并行待办后再创建目标节点待办。
*/
@At
@SaCheckPermission("proposal.control")
@ApiOperation("跳转提案流程节点")
public Result jump(Long instanceId, String targetTaskName) {
proposalControlService.jump(instanceId, targetTaskName);
return Result.success();
}
}
@@ -116,6 +116,14 @@ public class ProposalMineController {
@SaCheckPermission("proposal.mine")
@Aop(TransAop.READ_COMMITTED)
public Result pageData(@Valid ProposalSearchParam pageForm, String sessionId) {
return queryPageData(pageForm, sessionId);
}
/**
* 查询当前登录人的提案分页数据。
* PC 与 H5 入口共用该私有方法,避免 H5 请求进入带 PC 权限注解的方法。
*/
private Result queryPageData(ProposalSearchParam pageForm, String sessionId) {
Sql sql = Sqls.create("""
SELECT
info.*,
@@ -163,6 +171,16 @@ public class ProposalMineController {
return Result.success(pagination);
}
/**
* 我的提案 H5 分页列表。
* H5 菜单权限与 PC 权限独立,列表查询仍复用 PC 的同一数据口径。
*/
@At("/h5/pageData")
@SaCheckPermission("h5.proposal.mine")
public Result h5PageData(@Valid ProposalSearchParam pageForm, String sessionId) {
return queryPageData(pageForm, sessionId);
}
@At
@SaCheckPermission("proposal.mine")
@Aop(TransAop.READ_COMMITTED)
@@ -91,6 +91,14 @@ public class ProposalSecondedController {
@At
@SaCheckPermission("proposal.seconded")
public Result pageData(@Valid ProposalSearchParam pageForm, boolean approval) {
return queryPageData(pageForm, approval);
}
/**
* 查询当前登录附议人的提案分页数据。
* PC 与 H5 入口共用该私有方法,避免 H5 请求进入带 PC 权限注解的方法。
*/
private Result queryPageData(ProposalSearchParam pageForm, boolean approval) {
Sql sql = Sqls.create("""
SELECT
info.*,
@@ -165,6 +173,16 @@ public class ProposalSecondedController {
return Result.success(pagination);
}
/**
* 提案附议 H5 分页列表。
* H5 菜单权限与 PC 权限独立,列表查询仍复用 PC 的同一数据口径。
*/
@At("/h5/pageData")
@SaCheckPermission("h5.proposal.seconded")
public Result h5PageData(@Valid ProposalSearchParam pageForm, boolean approval) {
return queryPageData(pageForm, approval);
}
@At
@SaCheckPermission("proposal.seconded")
@Aop(TransAop.READ_COMMITTED)
@@ -115,12 +115,7 @@ public class ProposalUnderTakeReadController {
String unitIdsStr = "''";
if (!isSysAdmin) {
Sys_role role = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER.name());
List<Sys_user_role> userRoles = proposalCommonService.dao().query(Sys_user_role.class,
Cnd.where(Sys_user_role::getUserId, "=", SecurityUtil.getUserId())
.and(Sys_user_role::getRoleId, "=", role.getId())
);
unitIds = userRoles.stream().map(Sys_user_role::getUnderTakeId).collect(Collectors.toSet());
unitIds = new HashSet<>(proposalCommonService.getUserManagedUndertakeIds(SecurityUtil.getUserId()));
unitIdsStr = unitIds.stream()
.map(id -> "'" + id + "'")
.collect(Collectors.joining(","));
@@ -13,6 +13,7 @@ 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 com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
@@ -29,7 +30,7 @@ public class ProposalMasterUnitAssignmentHandler implements AssignmentHandler {
@Aop(TransAop.READ_COMMITTED)
public List<String> assign(TaskModel model, Execution execution) {
Dao dao = ServiceContext.find(Dao.class);
SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class);
ProposalCommonService proposalCommonService = ServiceContext.find(ProposalCommonService.class);
// 提案ID
String proposalId = execution.getProcessInstance().getBusinessNo();
@@ -64,11 +65,8 @@ public class ProposalMasterUnitAssignmentHandler implements AssignmentHandler {
throw new BaseException("主办单位不存在");
}
// 获取负责人
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER);
List<Sys_user_role> sysUserRoles = dao.query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId()).and(Sys_user_role::getUnderTakeId, "=", undertake.getId()));
List<String> selectUserIds = sysUserRoles.stream().map(Sys_user_role::getUserId).toList();
// 获取系统单位负责人。
List<String> selectUserIds = proposalCommonService.getUndertakeUnitManagerUserIds(undertake.getId());
if (selectUserIds.isEmpty()) {
throw new BaseException("主办单位没有负责人");
@@ -1,25 +1,11 @@
package com.budwk.app.zhgh.democratic.proposal.handler;
import cn.hutool.core.lang.Dict;
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.flow.entity.Candidate;
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;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.web.controllers.open.commons.service.CommonService;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.lang.Lang;
import org.nutz.lang.util.NutMap;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import java.util.List;
@@ -30,19 +16,18 @@ public class ProposalSchoolLeaderAssignmentHandler implements AssignmentHandler
@Override
public List<String> assign(TaskModel model, Execution execution) {
CommonService commonService = ServiceContext.find(CommonService.class);
List<Sys_user> users = commonService.findUserInfoByRoleCode(RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER.name());
if(Lang.isEmpty(users)) {
throw new BaseException("未查询到相关审批人");
}
return users.stream().map(Sys_user::getId).toList();
ProposalCommonService proposalCommonService = ServiceContext.find(ProposalCommonService.class);
String proposalId = execution.getProcessInstance().getBusinessNo();
List<String> userIds = proposalCommonService.getMasterUndertakeSchoolLeaderUserIds(proposalId);
if (userIds.isEmpty()) {
throw new BaseException("主办单位未设置分管校领导");
}
return userIds;
}
@Override
public String getMessage() {
return "获取提案主管校领导";
return "主办单位分管校领导";
}
@Override
@@ -11,6 +11,7 @@ import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysRoleService;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalConfig;
import com.budwk.app.zhgh.democratic.proposal.models.ProposalUndertake;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
@@ -35,21 +36,19 @@ public class ProposalSlaveUnitAssignmentHandler implements AssignmentHandler {
return assignee;
}
SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class);
ProposalCommonService proposalCommonService = ServiceContext.find(ProposalCommonService.class);
// 协办单位id
List<String> helpUnitIds = (List<String>) execution.getArgs().get("tf_slaveUnitIds");
if (helpUnitIds != null && !helpUnitIds.isEmpty()) {
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER);
for (String helpUnitId : helpUnitIds) {
ProposalUndertake undertake = dao.fetch(ProposalUndertake.class, helpUnitId);
List<Sys_user_role> sysUserRoles = dao.query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", sysRole.getId()).and(Sys_user_role::getUnderTakeId, "=", undertake.getId()));
if (sysUserRoles.isEmpty()) {
List<String> unitManagerUserIds = proposalCommonService.getUndertakeUnitManagerUserIds(undertake.getId());
if (unitManagerUserIds.isEmpty()) {
throw new BaseException(undertake.getName() + "单位没有设置负责人!");
}
assignee.addAll(sysUserRoles.stream().map(Sys_user_role::getUserId).toList());
assignee.addAll(unitManagerUserIds);
}
return assignee;
}
@@ -16,6 +16,7 @@ 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 com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
@@ -34,7 +35,7 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
@Aop(TransAop.READ_COMMITTED)
public List<String> assign(TaskModel model, Execution execution) {
Dao dao = ServiceContext.find(Dao.class);
SysRoleService sysRoleService = ServiceContext.find(SysRoleService.class);
ProposalCommonService proposalCommonService = ServiceContext.find(ProposalCommonService.class);
String proposalId = execution.getProcessInstance().getBusinessNo();
List<String> unitIds = resolveUnitIds(model, execution, dao, proposalId);
@@ -43,11 +44,6 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
throw new BaseException("提案没有配置承办单位");
}
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER);
if (sysRole == null) {
throw new BaseException("提案承办单位领导角色不存在");
}
List<String> assignee = new ArrayList<>();
for (String unitId : unitIds) {
ProposalUndertake undertake = dao.fetch(ProposalUndertake.class, unitId);
@@ -56,10 +52,8 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
}
// 只要存在单位 ID,就必须找到该单位负责人,否则流程无法继续往下分派。
List<Sys_user_role> 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)) {
List<String> unitManagerUserIds = proposalCommonService.getUndertakeUnitManagerUserIds(undertake.getId());
if (ObjectUtil.isEmpty(unitManagerUserIds)) {
throw new BaseException(undertake.getName() + "单位没有设置负责人!");
}
/*
@@ -67,7 +61,7 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
* 同一个用户若同时负责主办、协办多个承办单位,不能在这里跨单位去重;
* 否则流程会把多个承办单位合并成一个会签实例,导致“全部承办单位答复完”判断失真。
*/
assignee.addAll(sysUserRoles.stream().map(Sys_user_role::getUserId).toList());
assignee.addAll(unitManagerUserIds);
}
return assignee;
}
@@ -177,7 +171,7 @@ public class ProposalUnitAssignmentHandler implements AssignmentHandler {
@Override
public String getMessage() {
return "提案单位处理人";
return "承办单位负责人";
}
@Override
@@ -0,0 +1,30 @@
package com.budwk.app.zhgh.democratic.proposal.service;
import org.nutz.lang.util.NutMap;
import java.util.List;
/**
* 提案流程控制服务。
* 状态调整必须按流程实例整体处理,避免并行承办单位待办被拆分为多个互相独立的跳转操作。
*/
public interface ProposalControlService {
/**
* 查询当前流程实例可回退的历史节点。
* 节点顺序依据教代会提案流程定义,不依赖迁移任务的插入 ID。
*
* @param instanceId 流程实例 ID
* @return 可跳转节点列表
*/
List<NutMap> listJumpNodes(Long instanceId);
/**
* 将整个提案流程实例跳转到指定历史节点。
* 会先废弃全部当前待办,再创建一个目标节点待办;已完成审核历史保留不动。
*
* @param instanceId 流程实例 ID
* @param targetTaskName 目标任务节点编码
*/
void jump(Long instanceId, String targetTaskName);
}
@@ -57,6 +57,38 @@ public interface ProposalCommonService extends BaseService<ProposalInfo> {
*/
List<String> getProposalUnionRoleUserIds(ProposalInfo proposalInfo, String... roleCodes);
/**
* 按承办单位编码关联系统单位,查询该系统单位已设置的单位负责人。
*
* @param undertakeId 提案承办单位 ID
* @return 可参与提案流程的单位负责人用户 ID
*/
List<String> getUndertakeUnitManagerUserIds(String undertakeId);
/**
* 获取承办单位的一个单位负责人登录名,供旧 BPM 指派接口使用。
*
* @param undertakeId 提案承办单位 ID
* @return 单位负责人登录名;未设置时返回空字符串
*/
String getUndertakeUnitManagerLoginName(String undertakeId);
/**
* 查询用户作为系统单位负责人的对应承办单位范围。
*
* @param userId 用户 ID
* @return 可管理的承办单位 ID
*/
List<String> getUserManagedUndertakeIds(String userId);
/**
* 查询提案主办单位已设置的分管校领导。
*
* @param proposalId 提案 ID
* @return 主办单位分管校领导用户 ID;多个主办单位或多人时合并去重
*/
List<String> getMasterUndertakeSchoolLeaderUserIds(String proposalId);
/**
* 按白名单字段对已汇总的统计结果进行后端排序。
*
@@ -30,6 +30,7 @@ import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.vo.ProcessTaskVO;
import com.budwk.app.sys.models.Sys_dict;
import com.budwk.app.sys.models.Sys_role;
import com.budwk.app.sys.models.Sys_unit;
import com.budwk.app.sys.models.Sys_user;
import com.budwk.app.sys.models.Sys_user_role;
import com.budwk.app.sys.services.SysDictService;
@@ -123,8 +124,14 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
return false;
}
// H5 列表首次加载不会传排序字段;Map.of 实现不接受空键,需在查询白名单前返回。
String orderName = pageForm.getPageOrderName();
if (StrUtil.isBlank(orderName)) {
return false;
}
// 仅允许白名单中的字段参与排序,防止构造任意SQL排序字段。
String orderColumn = allowedOrderColumns.get(pageForm.getPageOrderName());
String orderColumn = allowedOrderColumns.get(orderName);
String orderBy = PageUtil.getOrder(pageForm.getPageOrderBy());
if (StrUtil.isBlank(orderColumn) || StrUtil.isBlank(orderBy)) {
return false;
@@ -251,6 +258,107 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
.toList();
}
/**
* 提案承办单位和系统单位通过单位编码一一关联;单位负责人以系统单位角色为唯一来源。
* 历史 PROPOSAL_UNIT_LEADER 数据保留用于审计,但不再参与新流程取人。
*/
@Override
public List<String> getUndertakeUnitManagerUserIds(String undertakeId) {
ProposalUndertake undertake = dao().fetch(ProposalUndertake.class, undertakeId);
if (undertake == null || StrUtil.isBlank(undertake.getCode())) {
return List.of();
}
Sys_unit unit = dao().fetch(Sys_unit.class, Cnd.where(Sys_unit::getUnitcode, "=", undertake.getCode()));
Sys_role role = sysRoleService.getByCode("UNIT_MANAGER");
if (unit == null || role == null) {
return List.of();
}
return dao().query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", role.getId())
.and(Sys_user_role::getUnitId, "=", unit.getId()))
.stream()
.map(Sys_user_role::getUserId)
.filter(StrUtil::isNotBlank)
.distinct()
.toList();
}
@Override
public String getUndertakeUnitManagerLoginName(String undertakeId) {
for (String userId : getUndertakeUnitManagerUserIds(undertakeId)) {
Sys_user user = dao().fetch(Sys_user.class, userId);
if (user != null && StrUtil.isNotBlank(user.getLoginname())) {
return user.getLoginname();
}
}
return "";
}
@Override
public List<String> getUserManagedUndertakeIds(String userId) {
Sys_role role = sysRoleService.getByCode("UNIT_MANAGER");
if (role == null || StrUtil.isBlank(userId)) {
return List.of();
}
List<String> unitIds = dao().query(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", role.getId())
.and(Sys_user_role::getUserId, "=", userId))
.stream()
.map(Sys_user_role::getUnitId)
.filter(StrUtil::isNotBlank)
.distinct()
.toList();
if (unitIds.isEmpty()) {
return List.of();
}
List<String> unitCodes = dao().query(Sys_unit.class, Cnd.where(Sys_unit::getId, "in", unitIds))
.stream()
.map(Sys_unit::getUnitcode)
.filter(StrUtil::isNotBlank)
.distinct()
.toList();
if (unitCodes.isEmpty()) {
return List.of();
}
return dao().query(ProposalUndertake.class, Cnd.where(ProposalUndertake::getCode, "in", unitCodes))
.stream()
.map(ProposalUndertake::getId)
.toList();
}
/**
* 校领导审批仅处理主办单位的分管校领导。角色关联以承办单位 ID 为范围,
* 避免把其他承办单位配置的校领导加入当前提案的待办。
*/
@Override
public List<String> getMasterUndertakeSchoolLeaderUserIds(String proposalId) {
if (StrUtil.isBlank(proposalId)) {
return List.of();
}
Sys_role role = sysRoleService.getByCode(RoleConstant.PROPOSAL_BRANCH_SCHOOL_LEADER);
if (role == null) {
return List.of();
}
List<String> masterUndertakeIds = dao().query(ProposalReplyUnit.class,
Cnd.where(ProposalReplyUnit::getProposalId, "=", proposalId)
.and(ProposalReplyUnit::getIsMaster, "=", true))
.stream()
.map(ProposalReplyUnit::getUnitId)
.filter(StrUtil::isNotBlank)
.distinct()
.toList();
if (masterUndertakeIds.isEmpty()) {
return List.of();
}
return dao().query(Sys_user_role.class,
Cnd.where(Sys_user_role::getRoleId, "=", role.getId())
.and(Sys_user_role::getUnderTakeId, "in", masterUndertakeIds))
.stream()
.map(Sys_user_role::getUserId)
.filter(StrUtil::isNotBlank)
.distinct()
.filter(this::isEnabledUser)
.toList();
}
/** 校验流程参与人仍是可用系统用户。 */
private boolean isEnabledUser(String userId) {
Sys_user user = dao().fetch(Sys_user.class, userId);
@@ -1039,13 +1147,11 @@ public class ProposalCommonServiceImpl extends BaseServiceImpl<ProposalInfo> imp
@Override
public ProposalUndertake getSelfManageUndertake() {
Sys_role role = sysRoleService.getByCode(RoleConstant.PROPOSAL_UNIT_LEADER.name());
Sys_user_role userRole = dao().fetch(Sys_user_role.class, Cnd.where(Sys_user_role::getRoleId, "=", role.getId())
.and(Sys_user_role::getUserId, "=", SecurityUtil.getUserId()));
if (Lang.isEmpty(userRole)) {
throw new RuntimeException("当前登录用户不是" + role.getName());
List<String> undertakeIds = getUserManagedUndertakeIds(SecurityUtil.getUserId());
if (undertakeIds.isEmpty()) {
throw new RuntimeException("当前登录用户不是单位负责人");
}
return dao().fetch(ProposalUndertake.class, Cnd.where(ProposalUndertake::getId, "=", userRole.getUnderTakeId()));
return dao().fetch(ProposalUndertake.class, undertakeIds.get(0));
}
@Override
@@ -42,6 +42,7 @@ import org.nutz.ioc.loader.annotation.IocBean;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -82,6 +83,13 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
proposalIds = List.of(proposalId);
}
/*
* 必须先同步当前主协办单位,再推进至承办单位答复节点。
* 答复节点在协办列表为空时会回退读取 proposal_reply_unit;若流程先推进,
* 就会误用上一次确认遗留的协办单位,生成错误的并行答复任务。
*/
syncCommitteeFilingUnitData(proposalIds, args);
/*
* 并案场景下,同一个节点会在每条提案实例上各有一条待办。
* 这里统一按 taskName 找出整组正在办理中的任务,逐条执行,确保并案提案的流程状态保持一致。
@@ -95,14 +103,13 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
cloneArgs.put(FlowConst.PROCESS_TASK_ID_KEY, executeTask.getId());
flowCommonService.executeTask(cloneArgs);
}
syncCommitteeFilingUnitData(proposalIds, args);
}
/**
* old-flow 页面提交后,需要把立案结果以及主协办单位同步落业务表。
* 此节点不再维护立案编号,已有提案的历史立案编号保持不变。
* 若这次改成“不予立案”等无需承办单位的结果,也要先清掉旧的承办单位记录,避免脏数据残留。
* 重新选择承办单位时,仍被选中的单位保留原记录,未再选择的单位才会被清理。
*/
private void syncCommitteeFilingUnitData(List<String> proposalIds, Dict args) {
String caseFilingResult = args.getStr(FlowConst.TASK_FORM_DATA_PREFIX + "caseFilingResult");
@@ -113,8 +120,10 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
.add("caseFilingType", caseFilingType),
Cnd.where(ProposalInfo::getId, "in", proposalIds));
dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds));
// 原实现会先清空全部单位,导致跳回后重新确认承办单位时丢失仍被选中单位的答复关联。
// dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds));
if (!List.of("CONFIRM_FILING", "SUGGESTION").contains(caseFilingResult)) {
dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds));
return;
}
@@ -127,24 +136,58 @@ public class ProposalCommitteeFilingUnitServiceImpl extends BaseServiceImpl<Prop
}
List<String> slaveUnitIds = getUnitIdList(args, FlowConst.TASK_FORM_DATA_PREFIX + "slaveUnitIds");
List<String> allUnitIds = new ArrayList<>();
allUnitIds.addAll(masterUnitIds);
allUnitIds.addAll(slaveUnitIds);
if (allUnitIds.isEmpty()) {
Map<String, Boolean> selectedUnitRoleMap = new LinkedHashMap<>();
masterUnitIds.forEach(unitId -> selectedUnitRoleMap.put(unitId, true));
// 同一单位同时被选为主协办时,以主办身份为准,避免重复创建承办单位记录。
slaveUnitIds.forEach(unitId -> selectedUnitRoleMap.putIfAbsent(unitId, false));
if (selectedUnitRoleMap.isEmpty()) {
dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds));
return;
}
Map<String, ProposalUndertake> undertakeMap = dao().query(ProposalUndertake.class, Cnd.where(ProposalUndertake::getId, "in", allUnitIds))
List<String> selectedUnitIds = new ArrayList<>(selectedUnitRoleMap.keySet());
Map<String, ProposalUndertake> undertakeMap = dao().query(ProposalUndertake.class, Cnd.where(ProposalUndertake::getId, "in", selectedUnitIds))
.stream()
.collect(Collectors.toMap(ProposalUndertake::getId, v -> v));
List<ProposalReplyUnit> replyUnits = new ArrayList<>();
for (String proposalId : proposalIds) {
replyUnits.addAll(buildReplyUnits(proposalId, masterUnitIds, undertakeMap, true));
replyUnits.addAll(buildReplyUnits(proposalId, slaveUnitIds, undertakeMap, false));
List<ProposalReplyUnit> existingReplyUnits = dao().query(ProposalReplyUnit.class,
Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds));
Map<String, ProposalReplyUnit> existingUnitMap = new LinkedHashMap<>();
for (ProposalReplyUnit replyUnit : existingReplyUnits) {
existingUnitMap.putIfAbsent(replyUnit.getProposalId() + "_" + replyUnit.getUnitId(), replyUnit);
}
if (!replyUnits.isEmpty()) {
dao().insert(replyUnits);
List<ProposalReplyUnit> newReplyUnits = new ArrayList<>();
for (String proposalId : proposalIds) {
for (Map.Entry<String, Boolean> selectedUnit : selectedUnitRoleMap.entrySet()) {
ProposalUndertake undertake = undertakeMap.get(selectedUnit.getKey());
if (undertake == null) {
continue;
}
ProposalReplyUnit existingReplyUnit = existingUnitMap.get(proposalId + "_" + selectedUnit.getKey());
if (existingReplyUnit == null) {
ProposalReplyUnit replyUnit = new ProposalReplyUnit();
replyUnit.setProposalId(proposalId);
replyUnit.setUnitId(undertake.getId());
replyUnit.setUnitCode(undertake.getCode());
replyUnit.setUnitName(undertake.getName());
replyUnit.setIsMaster(selectedUnit.getValue());
newReplyUnits.add(replyUnit);
continue;
}
// 保留既有记录的 ID、是否答复和审计字段,只同步本次重新确认的单位信息与主协办身份。
existingReplyUnit.setUnitCode(undertake.getCode());
existingReplyUnit.setUnitName(undertake.getName());
existingReplyUnit.setIsMaster(selectedUnit.getValue());
dao().update(existingReplyUnit, "unitCode|unitName|isMaster");
}
}
// 本次未再选中的单位不再属于当前承办范围,删除其配置;其已完成流程任务历史仍保留可查看。
dao().clear(ProposalReplyUnit.class, Cnd.where(ProposalReplyUnit::getProposalId, "in", proposalIds)
.and(ProposalReplyUnit::getUnitId, "not in", selectedUnitIds));
if (!newReplyUnits.isEmpty()) {
dao().insert(newReplyUnits);
}
}
@@ -0,0 +1,154 @@
package com.budwk.app.zhgh.democratic.proposal.service.impl;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.exception.BaseException;
import com.budwk.app.base.service.impl.BaseServiceImpl;
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.enums.ProcessTaskPerformTypeEnum;
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
import com.budwk.app.flow.enums.ProcessSubmitTypeEnum;
import com.budwk.app.flow.service.FlowCommonService;
import com.budwk.app.flow.service.ProcessTaskService;
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalControlService;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
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 java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 教代会提案流程状态调整实现。
* 迁移任务的数据库 ID 不代表真实流程顺序,统一按 JDHTA_NC 的节点顺序处理。
*/
@IocBean(args = {"refer:dao"})
public class ProposalControlServiceImpl extends BaseServiceImpl<ProcessInstance> implements ProposalControlService {
/** JDHTA_NC 流程节点的稳定业务顺序。 */
private static final Map<String, Integer> TASK_ORDERS = Map.ofEntries(
Map.entry("startTask", 10),
Map.entry("invite", 20),
Map.entry("second", 30),
Map.entry("secondaryUnionAudit", 40),
Map.entry("partyOrganizationAudit", 50),
Map.entry("committee", 60),
Map.entry("committeeFiling", 60),
Map.entry("committeeFilingUnit", 70),
Map.entry("unit_reply", 80),
Map.entry("unitReply", 80),
Map.entry("schoolLeader", 90),
Map.entry("feedback", 100),
Map.entry("feedbackEvaluation", 100),
Map.entry("caseCheck", 110)
);
@Inject
private ProcessTaskService processTaskService;
@Inject
private FlowCommonService flowCommonService;
public ProposalControlServiceImpl(Dao dao) {
super(dao);
}
@Override
public List<NutMap> listJumpNodes(Long instanceId) {
List<ProcessTask> doingTasks = getDoingTasks(instanceId);
int currentOrder = getCurrentOrder(doingTasks);
List<ProcessTask> doneTasks = dao().query(ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "=", instanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.FINISHED.getCode())
.asc(ProcessTask::getFinishTime)
.asc(ProcessTask::getId));
Map<String, NutMap> nodes = new LinkedHashMap<>();
for (ProcessTask task : doneTasks) {
Integer taskOrder = TASK_ORDERS.get(task.getTaskName());
if (taskOrder == null || taskOrder >= currentOrder
|| ProcessTaskPerformTypeEnum.COUNTERSIGN.getCode().equals(task.getPerformType())) {
continue;
}
nodes.putIfAbsent(task.getTaskName(), NutMap.NEW()
.setv("value", task.getTaskName())
.setv("label", task.getDisplayName())
.setv("seq", taskOrder));
}
return nodes.values().stream()
.sorted(Comparator.comparingInt(node -> node.getInt("seq")))
.toList();
}
@Override
@Aop(TransAop.READ_COMMITTED)
public void jump(Long instanceId, String targetTaskName) {
if (StrUtil.isBlank(targetTaskName)) {
throw new BaseException("请选择目标节点");
}
List<ProcessTask> doingTasks = getDoingTasks(instanceId);
int currentOrder = getCurrentOrder(doingTasks);
Integer targetOrder = TASK_ORDERS.get(targetTaskName);
if (targetOrder == null || targetOrder >= currentOrder) {
throw new BaseException("只能跳转到当前节点之前的流程节点");
}
if (listJumpNodes(instanceId).stream().noneMatch(node -> targetTaskName.equals(node.getString("value")))) {
throw new BaseException("目标节点没有可用的历史审核记录");
}
ProcessTask sourceTask = doingTasks.get(0);
String operator = SecurityUtil.getUserId();
if (!processTaskService.isAllowed(sourceTask, operator)) {
throw new BaseException("当前用户不能调整该流程");
}
Dict args = Dict.create();
args.set(FlowConst.SUBMIT_TYPE, ProcessSubmitTypeEnum.JUMP.getCode());
args.set(FlowConst.TASK_NAME, targetTaskName);
args.set("tf_sourceTaskKey", sourceTask.getTaskName());
args.set("tf_audit", true);
// 除执行入口外的并行待办必须先废弃,避免跳转后仍遗留承办单位答复等旧待办。
for (int index = 1; index < doingTasks.size(); index++) {
processTaskService.abandonProcessTask(doingTasks.get(index).getId(), operator, args);
}
flowCommonService.executeTask(args.set(FlowConst.PROCESS_TASK_ID_KEY, sourceTask.getId()));
}
/** 查询并校验流程实例的当前待办任务。 */
private List<ProcessTask> getDoingTasks(Long instanceId) {
if (instanceId == null) {
throw new BaseException("流程实例不能为空");
}
List<ProcessTask> doingTasks = dao().query(ProcessTask.class,
Cnd.where(ProcessTask::getProcessInstanceId, "=", instanceId)
.and(ProcessTask::getTaskState, "=", ProcessTaskStateEnum.DOING.getCode())
.asc(ProcessTask::getId));
if (doingTasks.isEmpty()) {
throw new BaseException("流程没有进行中的待办");
}
return doingTasks;
}
/** 同一实例只允许存在同一业务节点的并行待办,防止异常混合状态被错误跳转。 */
private int getCurrentOrder(List<ProcessTask> doingTasks) {
Set<String> taskNames = doingTasks.stream().map(ProcessTask::getTaskName).collect(java.util.stream.Collectors.toSet());
if (taskNames.size() != 1) {
throw new BaseException("流程存在多个不同节点的待办,请先处理异常待办");
}
Integer taskOrder = TASK_ORDERS.get(doingTasks.get(0).getTaskName());
if (taskOrder == null) {
throw new BaseException("当前节点不支持流程跳转");
}
return taskOrder;
}
}
@@ -18,6 +18,7 @@ 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.ProposalFeedbackEvaluationParam;
import com.budwk.app.zhgh.democratic.proposal.service.ProposalFeedbackEvaluationService;
import com.budwk.app.zhgh.democratic.proposal.service.common.ProposalCommonService;
import org.nutz.dao.Cnd;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
@@ -34,6 +35,8 @@ public class ProposalFeedbackEvaluationServiceImpl extends BaseServiceImpl<Propo
private BpmService bpmService;
@Inject
private CommonService commonService;
@Inject
private ProposalCommonService proposalCommonService;
public ProposalFeedbackEvaluationServiceImpl(Dao dao) {
super(dao);
@@ -60,7 +63,7 @@ public class ProposalFeedbackEvaluationServiceImpl extends BaseServiceImpl<Propo
ProposalUndertake undertake = dao().fetch(ProposalUndertake.class, hostUnitId);
String approvalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.PROPOSAL_UNIT_LEADER, Cnd.where(Sys_user_role::getUnderTakeId, "=", hostUnitId));
String approvalLoginName = proposalCommonService.getUndertakeUnitManagerLoginName(hostUnitId);
if (StrUtil.isBlank(approvalLoginName)) {
throw new BaseException("{}单位负责人没有配置,请联系校工会!", undertake.getName());
}
@@ -84,7 +84,7 @@ public class ProposalSchoolLeaderApprovalServiceImpl extends BaseServiceImpl<Pro
String underTakeId = createVariable.getStr("underTakeId");
ProposalUndertake undertake = dao().fetch(ProposalUndertake.class, underTakeId);
String approvalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.PROPOSAL_UNIT_LEADER, Cnd.where(Sys_user_role::getUnderTakeId, "=", underTakeId));
String approvalLoginName = proposalCommonService.getUndertakeUnitManagerLoginName(underTakeId);
if (StrUtil.isBlank(approvalLoginName)) {
throw new BaseException("{}单位负责人没有配置,请联系校工会!", undertake.getName());
}
@@ -106,7 +106,7 @@ public class ProposalSchoolLeaderApprovalServiceImpl extends BaseServiceImpl<Pro
String underTakeId = createVariable.getStr("underTakeId");
ProposalUndertake undertake = dao().fetch(ProposalUndertake.class, underTakeId);
String approvalLoginName = commonService.findUserLoginNameByRoleCode(RoleConstant.PROPOSAL_UNIT_LEADER, Cnd.where(Sys_user_role::getUnderTakeId, "=", underTakeId));
String approvalLoginName = proposalCommonService.getUndertakeUnitManagerLoginName(underTakeId);
if (StrUtil.isBlank(approvalLoginName)) {
throw new BaseException("{}单位负责人没有配置,请联系校工会!", undertake.getName());
}
@@ -27,7 +27,7 @@ const UNIT_LEADER_MANAGE_TEMPLATE = {
<el-table-column label="角色" prop="roleName"></el-table-column>
<el-table-column label="操作" width="100px">
<template v-slot="scope">
<el-button size="mini" type="danger" @click="doDelete(scope.row)">删除</el-button>
<el-button v-if="scope.row.canManage" size="mini" type="danger" @click="doDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -36,7 +36,10 @@ const UNIT_LEADER_MANAGE_TEMPLATE = {
<el-dialog title="添加" :visible.sync="dialogFormVisible" width="600px" :close-on-click-modal="false">
<el-form :model="formData" ref="form" size="small" label-width="60px">
<el-form-item prop="roleCode" label="角色">
<dict-select placeholder="请选择角色" v-model="formData.roleCode" code="UNIT_ROLES" @change="onRoleChange"></dict-select>
<el-select placeholder="请选择角色" style="width: 100%" v-model="formData.roleCode" @change="onRoleChange">
<el-option label="单位负责人" value="UNIT_MANAGER"></el-option>
<el-option label="单位分管校领导" value="UNIT_SCHOOL_LEADER"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="userId" label="人员">
<user-select
@@ -27,12 +27,12 @@ layout("/layouts/platform.html"){
<el-table-column label="序号" type="index" :index="indexMethod" width="60"></el-table-column>
<el-table-column prop="name" label="单位名称" sortable="custom" show-overflow-tooltip></el-table-column>
<el-table-column prop="code" label="单位编码" sortable="custom" width="150px"></el-table-column>
<!-- <el-table-column prop="branchSchoolLeader" label="分管校领导及联系方式" width="180px"></el-table-column>-->
<el-table-column prop="unitLeader" label="单位领导及联系方式" sortable="custom" width="180px" show-overflow-tooltip></el-table-column>
<el-table-column prop="branchSchoolLeader" label="单位分管校领导" sortable="custom" width="180px" show-overflow-tooltip></el-table-column>
<el-table-column prop="unitLeader" label="单位负责人" sortable="custom" width="180px" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="350px">
<template slot-scope="{row}">
<!-- <el-button size="mini" type="primary" @click="$refs.userRef.onOpen(row.id,true)">设置分管校领导</el-button>-->
<el-button size="mini" type="primary" @click="$refs.userRef.onOpen(row.id,false)">设置单位领导</el-button>
<el-button size="mini" type="primary" @click="$refs.userRef.onOpen(row.id,true)">设置领导</el-button>
<el-button size="mini" type="primary" @click="$refs.userRef.onOpen(row.id,false)">设置负责人</el-button>
<el-button size="mini" type="danger" @click="onDelete(row)">删除</el-button>
</template>
</el-table-column>
@@ -1,10 +1,15 @@
const USER_COMPONENT = {
template: `
<el-dialog title="设置领导" :visible.sync="visible" width="1000px" :close-on-click-modal="false">
<el-dialog :title="isSchoolLeader ? '设置单位分管校领导' : '设置单位负责人'" :visible.sync="visible" width="1000px" :close-on-click-modal="false">
<el-row v-if="isSchoolLeader" type="flex" align="middle" style="margin-bottom: 15px">
<el-input v-model="leaderUserKeyword" clearable placeholder="输入姓名或工号搜索" style="width: 300px" @keyup.enter.native="searchLeaderUsers"></el-input>
<el-button type="primary" size="small" icon="el-icon-search" style="margin-left: 10px" @click="searchLeaderUsers">搜索</el-button>
<span style="margin-left: 15px;color: #606266">共{{leaderUserTotalCount}}人,默认展示前50人</span>
</el-row>
<el-transfer
ref="transferRef"
:titles="['可选人员', '已选人员']"
filterable
:filterable="!isSchoolLeader"
:props="{
key: 'id',
label: 'label'
@@ -26,22 +31,58 @@ const USER_COMPONENT = {
selectUserIds: [],
allUsers: [],
unitId: null,
isSchoolLeader: ""
isSchoolLeader: "",
leaderUserKeyword: "",
leaderUserTotalCount: 0
}
},
methods: {
onOpen(id, isSchoolLeader) {
this.isSchoolLeader = isSchoolLeader
this.unitId = id
this.visible = true
this.$set(this, "isSchoolLeader", isSchoolLeader)
this.$set(this, "unitId", id)
this.$set(this, "leaderUserKeyword", "")
this.$set(this, "visible", true)
if (isSchoolLeader) {
this.searchLeaderUsers()
} else {
this.loadUnitManagerUsers()
}
},
searchLeaderUsers() {
this.$axios
.post("/platform/proposal/config/unit/" + (isSchoolLeader ? "branchSchoolLeaderTransferData" : "unitUsersTransferData"), { id })
.then((res) => {
this.selectUserIds = res.data.selectUserIds
this.allUsers = res.data.allUsers
.post("/platform/proposal/config/unit/branchSchoolLeaderUserPageData", {
id: this.unitId,
keyword: this.leaderUserKeyword
})
this.$refs.transferRef.clearQuery("left")
this.$refs.transferRef.clearQuery("right")
.then((res) => {
this.$set(this, "leaderUserTotalCount", res.code === 0 ? res.data.totalCount : 0)
this.applyTransferData(res)
})
},
loadUnitManagerUsers() {
this.$axios
.post("/platform/proposal/config/unit/unitUsersTransferData", { id: this.unitId })
.then((res) => {
this.$set(this, "leaderUserTotalCount", 0)
this.applyTransferData(res)
})
},
applyTransferData(res) {
if (res.code !== 0) {
return
}
// 穿梭框必须先拿到候选人员,再设置已选人员,已选项才能稳定回显。
this.$set(this, "allUsers", res.data.allUsers || [])
this.$set(this, "selectUserIds", res.data.selectUserIds || [])
this.$nextTick(() => {
if (this.$refs.transferRef) {
this.$refs.transferRef.clearQuery("left")
this.$refs.transferRef.clearQuery("right")
}
})
},
doSubmit() {
@@ -206,7 +206,8 @@ layout("/layouts/platform.html"){
},
tableColumns: [
{label: "提案编号", prop: "code"},
{label: "立案编号", prop: "caseFilingCode"},
// 按当前业务要求屏蔽立案编号,避免列表和自定义导出带出该字段。
// {label: "立案编号", prop: "caseFilingCode"},
{label: "提案名称", prop: "name"},
{label: "提案人", prop: "createUserName"},
{label: "提案类别", prop: "typeName"},
@@ -114,18 +114,15 @@ layout("/layouts/platform.html"){
},
openJumpDialog(row) {
this.jumpCurrentRow = row
this.jumpForm = {
this.$set(this, "jumpCurrentRow", row)
this.$set(this, "jumpForm", {
targetTaskName: "",
tf_audit: true
}
this.jumpDialogVisible = true
})
this.$set(this, "jumpDialogVisible", true)
this.$axios.post('/platform/proposal/control/jumpNodes', {instanceId: row.instanceId}).then(res => {
if (res.code === 0) {
const nodeList = res.data || []
const currentNode = nodeList.find(item => item.value === row.taskKey)
const currentSeq = currentNode ? Number(currentNode.seq) : Number.MAX_SAFE_INTEGER
this.jumpNodeOptions = nodeList.filter(item => item.value !== row.taskKey && Number(item.seq) < currentSeq)
this.$set(this, "jumpNodeOptions", res.data || [])
}
})
},
@@ -139,15 +136,10 @@ layout("/layouts/platform.html"){
type: "warning"
}).then(() => {
const params = {
processTaskId: this.jumpCurrentRow.taskId,
submitType: 4,
taskName: this.jumpForm.targetTaskName,
tf_sourceTaskKey: this.jumpCurrentRow.taskKey,
tf_audit: true
instanceId: this.jumpCurrentRow.instanceId,
targetTaskName: this.jumpForm.targetTaskName
}
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify(params)
}).then((res) => {
this.$axios.post("/platform/proposal/control/jump", params).then((res) => {
if (res.code === 0) {
this.jumpDialogVisible = false
this.$message.success(res.msg)
@@ -23,9 +23,11 @@ layout("/layouts/platform.html"){
</el-card>
<el-card shadow="never">
<table-tool :columns.sync="tableColumns">
<el-button class="ml5" icon="el-icon-upload2" type="primary" size="small"
@click="openImport">导入提案
</el-button>
<!-- 导入提案入口按当前业务要求屏蔽,保留导入逻辑以便后续恢复。
<el-button class="ml5" icon="el-icon-upload2" type="primary" size="small"
@click="openImport">导入提案
</el-button>
-->
</table-tool>
<el-table :data="tableData" @sort-change="pageOrder" header-align="center" style="width: 100%" row-key="id">
<el-table-column label="序号" width="50" type="index" :index="indexMethod" fixed="left"></el-table-column>
@@ -76,8 +78,8 @@ layout("/layouts/platform.html"){
<el-button v-if="row.canRevoke" size="mini" type="danger" @click="openRevoke(row)">
撤回
</el-button>
<!-- v-if="row.taskKey === 'startTask' || !row.instanceId"-->
<el-button size="mini" type="danger" @click="del(row.id)">
<el-button v-if="row.taskKey === 'startTask' || !row.instanceId" size="mini" type="danger"
@click="del(row.id)">
删除
</el-button>
</template>
@@ -82,7 +82,7 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-form>
<el-row type="flex" justify="end" v-if="row.curTaskCode ==='second'">
<el-button @click="$refs.guava.index()" size="small">返回返回</el-button>
<el-button @click="$refs.guava.index()" size="small">返回</el-button>
<el-button @click="handleTaskAction(20)" size="small" type="danger">不同意</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意</el-button>
</el-row>
@@ -241,7 +241,36 @@ const PROPOSAL_INFO = {
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", {bizId: this.row.id}).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data
const tasks = res.data || []
// 按提案业务流程展示历史记录,避免迁移或并行任务受数据库写入顺序影响。
const taskOrders = {
startTask: 10,
invite: 20,
second: 30,
secondaryUnionAudit: 40,
partyOrganizationAudit: 50,
committee: 60,
committeeFiling: 60,
committeeFilingUnit: 70,
unit_reply: 80,
unitReply: 80,
schoolLeader: 90,
feedback: 100,
feedbackEvaluation: 100
}
tasks.sort((first, second) => {
const orderDifference = (taskOrders[first.taskName] || 999) - (taskOrders[second.taskName] || 999)
if (orderDifference !== 0) {
return orderDifference
}
const firstTime = first.finishTime || ''
const secondTime = second.finishTime || ''
if (firstTime !== secondTime) {
return firstTime.localeCompare(secondTime)
}
return first.id - second.id
})
this.$set(this, "doneTasks", tasks)
}
})
},
@@ -32,6 +32,9 @@ layout("/layouts/platform_h5.html"){
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-edit"></i><span>立案</span>
</div>
<div class="action-btn" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-undo"></i><span>撤回</span>
</div>
</template>
</table-list>
@@ -50,10 +53,11 @@ layout("/layouts/platform_h5.html"){
:placeholder="masterUnitLabel === '办理单位' ? '请选择办理单位' : '请选择主办单位'"
@click="openMasterUnitPicker"
:rules="[{ required: showUndertakeUnit, message: '请选择承办单位' }]" required></van-field>
<van-popup v-model="showMasterUnitPicker" position="bottom" round>
<div style="padding: 16px">
<van-popup v-model="showMasterUnitPicker" position="bottom" round :style="{height: '70vh'}">
<div style="box-sizing: border-box; display: flex; flex-direction: column; height: 100%; padding: 16px">
<div class="process-title">{{masterUnitLabel}}</div>
<van-checkbox-group v-model="formData.tf_masterUnitIds">
<van-search :value="masterUnitKeyword" placeholder="请输入单位名称搜索" @input="changeMasterUnitKeyword"></van-search>
<van-checkbox-group v-model="formData.tf_masterUnitIds" style="flex: 1; overflow-y: auto">
<van-cell-group>
<van-cell v-for="item in availableMasterUnits" :key="item.id" clickable :title="item.name"
@click="toggleMasterUnit(item.id)">
@@ -66,10 +70,11 @@ layout("/layouts/platform_h5.html"){
</van-popup>
<van-field v-if="formData.tf_caseFilingResult === 'CONFIRM_FILING'" :value="slaveUnitNames" label="协办单位"
readonly is-link placeholder="请选择协办单位" @click="openSlaveUnitPicker"></van-field>
<van-popup v-model="showSlaveUnitPicker" position="bottom" round>
<div style="padding: 16px">
<van-popup v-model="showSlaveUnitPicker" position="bottom" round :style="{height: '70vh'}">
<div style="box-sizing: border-box; display: flex; flex-direction: column; height: 100%; padding: 16px">
<div class="process-title">协办单位</div>
<van-checkbox-group v-model="formData.tf_slaveUnitIds">
<van-search :value="slaveUnitKeyword" placeholder="请输入单位名称搜索" @input="changeSlaveUnitKeyword"></van-search>
<van-checkbox-group v-model="formData.tf_slaveUnitIds" style="flex: 1; overflow-y: auto">
<van-cell-group>
<van-cell v-for="item in availableSlaveUnits" :key="item.id" clickable :title="item.name"
@click="toggleSlaveUnit(item.id)">
@@ -109,7 +114,9 @@ layout("/layouts/platform_h5.html"){
showApprovalForm: false,
showResultPicker: false,
showMasterUnitPicker: false,
showSlaveUnitPicker: false
showSlaveUnitPicker: false,
masterUnitKeyword: "",
slaveUnitKeyword: ""
}
},
computed: {
@@ -131,10 +138,14 @@ layout("/layouts/platform_h5.html"){
return this.getUnitNames(this.formData.tf_slaveUnitIds)
},
availableMasterUnits() {
return this.underTakeOptions.filter((item) => !this.formData.tf_slaveUnitIds.includes(item.id))
return this.underTakeOptions.filter((item) => {
return !this.formData.tf_slaveUnitIds.includes(item.id) && this.matchUnitKeyword(item.name, this.masterUnitKeyword)
})
},
availableSlaveUnits() {
return this.underTakeOptions.filter((item) => !this.formData.tf_masterUnitIds.includes(item.id))
return this.underTakeOptions.filter((item) => {
return !this.formData.tf_masterUnitIds.includes(item.id) && this.matchUnitKeyword(item.name, this.slaveUnitKeyword)
})
}
},
methods: {
@@ -149,17 +160,31 @@ layout("/layouts/platform_h5.html"){
this.$set(this, "showResultPicker", false)
},
openMasterUnitPicker() {
this.$set(this, "masterUnitKeyword", "")
this.$set(this, "showMasterUnitPicker", true)
},
closeMasterUnitPicker() {
this.$set(this, "masterUnitKeyword", "")
this.$set(this, "showMasterUnitPicker", false)
},
openSlaveUnitPicker() {
this.$set(this, "slaveUnitKeyword", "")
this.$set(this, "showSlaveUnitPicker", true)
},
closeSlaveUnitPicker() {
this.$set(this, "slaveUnitKeyword", "")
this.$set(this, "showSlaveUnitPicker", false)
},
// 选择弹层按单位名称实时筛选,不影响已选单位的值。
matchUnitKeyword(unitName, keyword) {
return !keyword || (unitName || "").toLowerCase().includes(keyword.toLowerCase())
},
changeMasterUnitKeyword(value) {
this.$set(this, "masterUnitKeyword", value)
},
changeSlaveUnitKeyword(value) {
this.$set(this, "slaveUnitKeyword", value)
},
listSession() {
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
if (res.code === 0) {
@@ -262,6 +287,19 @@ layout("/layouts/platform_h5.html"){
}).catch(function () {})
} catch (error) {}
},
onRevoke(row) {
this.$dialog.confirm({title: "提示", message: "您确定要撤回吗?"}).then(() => {
this.$axios.post("/platform/proposal/committeeFiling/h5/revokeTask", {
taskId: row.taskId,
proposalId: row.id
}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
}).catch(function () {})
},
doSearch() {
this.$nextTick(() => {
this.$set(this.pageForm, "pageNumber", 1)
@@ -32,6 +32,9 @@ layout("/layouts/platform_h5.html"){
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-edit"></i><span>确认</span>
</div>
<div class="action-btn" v-if="row.canRevoke" @click="onRevoke(row)">
<i class="fa fa-undo"></i><span>撤回</span>
</div>
</template>
</table-list>
@@ -50,10 +53,11 @@ layout("/layouts/platform_h5.html"){
:placeholder="masterUnitLabel === '办理单位' ? '请选择办理单位' : '请选择主办单位'"
@click="openMasterUnitPicker"
:rules="[{ required: showUndertakeUnit, message: '请选择承办单位' }]" required></van-field>
<van-popup v-model="showMasterUnitPicker" position="bottom" round>
<div style="padding: 16px">
<van-popup v-model="showMasterUnitPicker" position="bottom" round :style="{height: '70vh'}">
<div style="box-sizing: border-box; display: flex; flex-direction: column; height: 100%; padding: 16px">
<div class="process-title">{{masterUnitLabel}}</div>
<van-checkbox-group v-model="formData.tf_masterUnitIds">
<van-search :value="masterUnitKeyword" placeholder="请输入单位名称搜索" @input="changeMasterUnitKeyword"></van-search>
<van-checkbox-group v-model="formData.tf_masterUnitIds" style="flex: 1; overflow-y: auto">
<van-cell-group>
<van-cell v-for="item in availableMasterUnits" :key="item.id" clickable :title="item.name"
@click="toggleMasterUnit(item.id)">
@@ -66,10 +70,11 @@ layout("/layouts/platform_h5.html"){
</van-popup>
<van-field v-if="formData.tf_caseFilingResult === 'CONFIRM_FILING'" :value="slaveUnitNames" label="协办单位"
readonly is-link placeholder="请选择协办单位" @click="openSlaveUnitPicker"></van-field>
<van-popup v-model="showSlaveUnitPicker" position="bottom" round>
<div style="padding: 16px">
<van-popup v-model="showSlaveUnitPicker" position="bottom" round :style="{height: '70vh'}">
<div style="box-sizing: border-box; display: flex; flex-direction: column; height: 100%; padding: 16px">
<div class="process-title">协办单位</div>
<van-checkbox-group v-model="formData.tf_slaveUnitIds">
<van-search :value="slaveUnitKeyword" placeholder="请输入单位名称搜索" @input="changeSlaveUnitKeyword"></van-search>
<van-checkbox-group v-model="formData.tf_slaveUnitIds" style="flex: 1; overflow-y: auto">
<van-cell-group>
<van-cell v-for="item in availableSlaveUnits" :key="item.id" clickable :title="item.name"
@click="toggleSlaveUnit(item.id)">
@@ -109,7 +114,9 @@ layout("/layouts/platform_h5.html"){
showApprovalForm: false,
showResultPicker: false,
showMasterUnitPicker: false,
showSlaveUnitPicker: false
showSlaveUnitPicker: false,
masterUnitKeyword: "",
slaveUnitKeyword: ""
}
},
computed: {
@@ -131,10 +138,14 @@ layout("/layouts/platform_h5.html"){
return this.getUnitNames(this.formData.tf_slaveUnitIds)
},
availableMasterUnits() {
return this.underTakeOptions.filter((item) => !this.formData.tf_slaveUnitIds.includes(item.id))
return this.underTakeOptions.filter((item) => {
return !this.formData.tf_slaveUnitIds.includes(item.id) && this.matchUnitKeyword(item.name, this.masterUnitKeyword)
})
},
availableSlaveUnits() {
return this.underTakeOptions.filter((item) => !this.formData.tf_masterUnitIds.includes(item.id))
return this.underTakeOptions.filter((item) => {
return !this.formData.tf_masterUnitIds.includes(item.id) && this.matchUnitKeyword(item.name, this.slaveUnitKeyword)
})
}
},
methods: {
@@ -149,17 +160,31 @@ layout("/layouts/platform_h5.html"){
this.$set(this, "showResultPicker", false)
},
openMasterUnitPicker() {
this.$set(this, "masterUnitKeyword", "")
this.$set(this, "showMasterUnitPicker", true)
},
closeMasterUnitPicker() {
this.$set(this, "masterUnitKeyword", "")
this.$set(this, "showMasterUnitPicker", false)
},
openSlaveUnitPicker() {
this.$set(this, "slaveUnitKeyword", "")
this.$set(this, "showSlaveUnitPicker", true)
},
closeSlaveUnitPicker() {
this.$set(this, "slaveUnitKeyword", "")
this.$set(this, "showSlaveUnitPicker", false)
},
// 选择弹层按单位名称实时筛选,不影响已选单位的值。
matchUnitKeyword(unitName, keyword) {
return !keyword || (unitName || "").toLowerCase().includes(keyword.toLowerCase())
},
changeMasterUnitKeyword(value) {
this.$set(this, "masterUnitKeyword", value)
},
changeSlaveUnitKeyword(value) {
this.$set(this, "slaveUnitKeyword", value)
},
listSession() {
this.$axios.post("/platform/proposal/common/listSession").then((res) => {
if (res.code === 0) {
@@ -298,6 +323,19 @@ layout("/layouts/platform_h5.html"){
}).catch(function () {})
} catch (error) {}
},
onRevoke(row) {
this.$dialog.confirm({title: "提示", message: "您确定要撤回吗?"}).then(() => {
this.$axios.post("/platform/proposal/committeeFilingUnit/h5/revokeTask", {
taskId: row.taskId,
proposalId: row.id
}).then((res) => {
if (res.code === 0) {
this.$toast.success(res.msg)
this.doSearch()
}
})
}).catch(function () {})
},
closeApproval() {
this.$refs.proposalInfoRef.onClose()
},
@@ -18,7 +18,7 @@ layout("/layouts/platform_h5.html"){
</van-dropdown-menu>
</van-sticky>
<table-list api="/platform/proposal/mine/pageData" :page_form.sync="pageForm" @ready="onReady" ref="tableListRef"
<table-list api="/platform/proposal/mine/h5/pageData" :page_form.sync="pageForm" @ready="onReady" ref="tableListRef"
title="name">
<template v-slot="{index,row}">
<table-column label="提案编号">{{row.code}}</table-column>
@@ -23,7 +23,7 @@ layout("/layouts/platform_h5.html"){
</van-tabs>
</van-sticky>
<table-list api="/platform/proposal/seconded/pageData" :page_form.sync="pageForm" @ready="onReady"
<table-list api="/platform/proposal/seconded/h5/pageData" :page_form.sync="pageForm" @ready="onReady"
ref="tableListRef"
title="name">
<template v-slot="{index,row}">
@@ -189,19 +189,30 @@ layout("/layouts/platform_h5.html"){
this.$refs.proposalInfoRef.onOpen(row)
},
onApproval(row) {
async onApproval(row) {
// 与 PC 端保持一致,确认已沟通后才允许进入答复表单。
try {
await this.$dialog.confirm({
title: "提示",
message: "是否已与提案代表进行充分沟通?",
confirmButtonText: "是",
cancelButtonText: "否"
})
} catch (error) {
return
}
this.loadCandidates(row.taskId)
this.showApprovalForm = true
this.$set(this, "showApprovalForm", true)
this.$refs.proposalInfoRef.onOpen(row)
this.formData = {
this.$set(this, "formData", {
processTaskId: row.taskId,
taskKey: row.taskKey,
taskName: row.taskName,
proposalId: row.id,
underTakeName: row.underTakeName,
underTakeIsMaster: row.underTakeIsMaster
}
})
},
// 加载候选人列表