流程审批人动态计算¶
一句话定位:流程流转时按业务规则(角色、部门、上级、金额、申请人…)算出"谁来审批",以及在流程运行起来之后查询/替换/追加审批人。落点以人工节点的
actorListScript/assistListScript/circulatorListScript为主,配合操作前后置脚本做运行时干预。
写在前面——审批人返回值的三种接受形式
不论挂在 actorListScript、assistListScript 还是 circulatorListScript,平台对脚本的返回值都同样接受以下三种形式(任选其一):
| 形式 | 示例 | 适用 |
|---|---|---|
单个用户 ID(String) |
"__vUWxP9RwmFv" |
只有一个审批人 |
多个用户 ID 的字符串(; 分隔) |
"id1;id2;id3" |
多人并审 / 会签 |
用户对象(UserVO)或其集合(Collection<UserVO>) |
user / [user1, user2] |
已通过 getUserById 等取到对象,直接返回更省一次反查 |
提示:在
actorListScript上下文中,平台**通常还会预置一个名为userlist的ArrayList** 用于累加(源素材flow-node.md即如此使用),脚本内可直接userlist.add(user)后return userlist;如未注入,请自行createObject("java.util.ArrayList")新建一个。
本章场景清单¶
| 场景 | 落点 / Label | 主要素材 |
|---|---|---|
| 按角色 / 部门 / 上级 / 金额算审批人 | WORKFLOW:NODE_ACTOR_LIST(actorListScript) |
flow-approver-script、flow-node |
| 设置协办人 / 抄送人 | WORKFLOW:NODE_COAPPROVER / NODE_COPYTO |
flow-node |
| 运行时动态更新审批人(替换 / 追加 / 替换指定) | 操作前置 / 后置 | update-flow-approver |
| 组织架构辅助查询:角色 ⇄ 用户互查、取用户上级 | 值脚本 / 计算脚本 | get-users-by-role-or-roles-by-user、get-user-superior |
| 取当前待处理审批人信息(ID / 姓名 / 登录名) | 值脚本 / 计算脚本 | get-current-approver-info |
按角色 / 部门 / 上级 / 金额计算审批人(标杆场景)¶
业务目标¶
流程流转到某人工节点时,不写死审批人,而是按当前文档的字段值(部门、金额、申请人…)和当前用户上下文,**动态算出**这一步的审批人。这是 actorListScript 最典型的用法,几乎每个有分支的业务流程都会用到。
写在哪儿¶
流程 → 流程节点 → 审批人设置 → 通过 → 脚本(.flow 节点属性 actorListScript,actorEditMode 设为"脚本";Label WORKFLOW:NODE_ACTOR_LIST)。
触发时机与上下文¶
- 触发时机:流程提交、节点进入时计算该节点的审批人。
- 可用环境变量:
WebUser(当前登录用户,通过getWebUser()取得)、CurrentDocument(当前文档,通过getCurrentDocument()取得)。 - 上下文预置:
actorListScript上下文通常预置userlist(ArrayList),可直接累加并return。
返回值契约¶
返回 **用户 ID / User 对象 / 其集合**之一,含义是"本节点的审批人或审批人集合"。详见章首"三种接受形式"表。返回 null / 空 ArrayList 表示未取到,平台按节点上其它配置兜底。
示例代码¶
/* 按"当前文档部门 + 角色'部门负责人'"算出本节点审批人 */
(function () {
var roleid = getRoleIdByName("部门负责人"); // 取角色 ID
var dptid = getItemValueAsString("部门"); // 取当前文档"部门"字段
var users = getUsersByDptIdAndRoleId(dptid, roleid); // 取部门+角色下的用户集合(Java List)
if (users != null) {
for (var iter = users.iterator(); iter.hasNext();) {
var user = iter.next();
// actorListScript 上下文预置了 userlist;若未预置可改为自建:
// var userlist = createObject("java.util.ArrayList");
userlist.add(getUserById(user.getId()));
}
}
return userlist; // 返回 Collection<UserVO>
})()
代码说明¶
| 关键步骤 | 函数 / 字段 | 文档 |
|---|---|---|
| 取角色 ID | getRoleIdByName(name) |
iscript-guid/api → 用户中心 |
| 取部门+角色下的用户 | getUsersByDptIdAndRoleId(dptid, roleid) 返回 Collection<UserVO> |
同上 |
| 取当前文档字段值 | getItemValueAsString("部门") |
iscript-guid/functions → 文档函数 |
| 反查用户对象 | getUserById(id) 返回 UserVO |
iscript-guid/api → 用户中心 |
遍历 Java Collection |
.iterator() + .hasNext() + .next()(不是 JS 数组下标) |
见 GraalVM 差异 — Java List vs JS 数组 |
变体与扩展¶
下面四个变体只改"取用户的来源",挂载点、返回值契约、上下文都与标杆场景一致。
① 按角色(不限部门)——getUsersByRoleId(roleid) 返回 Collection<UserVO>:
(function () {
var roleid = getRoleIdByName("财务复核");
var users = getUsersByRoleId(roleid);
if (users != null) {
for (var iter = users.iterator(); iter.hasNext();) {
userlist.add(iter.next()); // UserVO 本身即可入列
}
}
return userlist;
})()
② 按部门——getUsersByDptId(dptid):
(function () {
var dptid = getItemValueAsString("部门");
var users = getUsersByDptId(dptid);
if (users != null) {
for (var iter = users.iterator(); iter.hasNext();) {
userlist.add(iter.next());
}
}
return userlist;
})()
③ 按上级(直接上级)——当前用户 getSuperior() 返回 UserVO,可能为 null:
(function () {
var superior = getWebUser().getSuperior();
if (superior != null) {
userlist.add(superior); // 单个 UserVO,直接加入集合
}
return userlist;
})()
④ 按金额阶梯选审批人——返回 ; 分隔的 ID 串:
(function () {
var amount = getItemValueAsDouble("金额");
var approverIds;
if (amount <= 1000) {
approverIds = "__部门经理ID__";
} else if (amount <= 10000) {
approverIds = "__部门经理ID__;__财务经理ID__";
} else {
approverIds = "__部门经理ID__;__财务经理ID__;__总经理ID__";
}
return approverIds; // 返回 ; 分隔的用户 ID 字符串
})()
常见坑¶
- 把 Java
Collection当 JS 数组:queryBySQL/getUsersByDptIdAndRoleId返回的是java.util.List,长度用.size()、取元素用.get(i)、遍历用.iterator()——**不要**写users.length、users[i]。详见 GraalVM 差异。 getSuperior()返回null:用户未配上级时直接add会空指针,必须先判空,必要时回退到默认审批人。- 角色/部门名拼错或大小写不一致:
getRoleIdByName是按名称精确匹配,建议在"代码说明"提到的角色管理里核对名称;查不到会返回null,导致后续getUsersByRoleId(null)空集。 - 忘记
return:actorListScript必须把审批人集合return给平台;只add不返回,平台拿不到结果。 - 多个 ID 用分号分隔:返回字符串形式时,分隔符是 英文分号
;,不是逗号或竖线。
协办人 / 抄送人(属性同构场景)¶
协办人、抄送人挂载点不同属性,但返回值契约、上下文、写法与「按角色 / 部门 / 上级 / 金额计算审批人」完全一致——都是"返回用户 ID / User / 集合之一"。差异仅在于触发时机与 UI 入口。
| 场景 | Label | 属性 | 触发时机 | 位置 |
|---|---|---|---|---|
| 协办人 | WORKFLOW:NODE_COAPPROVER |
assistListScript |
用户点"协办"按钮时计算协办候选 | 流程 → 基本信息 → 允许加签协办人 |
| 抄送人 | WORKFLOW:NODE_COPYTO |
circulatorListScript |
用户点"抄送"按钮时计算抄送候选 | 流程 → 抄送设置 |
示例(协办人;抄送人把属性换成 circulatorListScript 即可):
/* 把"部门负责人"角色下用户作为协办候选 */
(function () {
var roleid = getRoleIdByName("部门负责人");
var dptid = getItemValueAsString("部门");
var users = getUsersByDptIdAndRoleId(dptid, roleid);
if (users != null) {
for (var iter = users.iterator(); iter.hasNext();) {
var user = iter.next();
userlist.add(getUserById(user.getId()));
}
}
return userlist;
})()
协办/抄送的返回值同样接受章首列出的三种形式。常见坑同「按角色 / 部门 / 上级 / 金额计算审批人」。
运行时动态更新审批人(替换 / 追加 / 替换指定)¶
业务目标¶
流程已经流转起来、审批人已生成后,业务上有时需要**临时变更**当前节点的审批人——例如审批人离职/调岗、按字段中途换人、给某个流程加一个会签人。这种"算审批人"不是发生在节点进入时(不是 actorListScript),而是发生在表单操作(按钮前置/后置)或定时任务里。
写在哪儿¶
表单 → 表单操作 → **操作前置 / 操作后置**脚本(.form 的 activity 脚本;纯副作用,通常不必 return)。
这是直接操作流程对象的"硬干预"——风险较高,强烈建议同时落库(更新
T_ACTOR表),否则刷新/重启后内存与表数据可能不一致。
触发时机与上下文¶
- 触发时机:用户点击表单上的某个操作按钮("重派审批人""追加会签"等自定义按钮)。
- 可用环境变量:
WebUser、CurrentDocument;通过getCurrentDocument()拿到doc,再getFlowProcess().doViewByDocument(doc)拿到流程对象。
返回值契约¶
无强制返回值;脚本以**副作用**方式更新当前节点的审批人。可 return 一个状态字符串(如 "审批人更新成功")便于调试或回显。
示例代码(完全替换当前节点审批人)¶
(function () {
var doc = getCurrentDocument();
var docId = doc.getId();
var newIds = "__新审批人ID1__;__新审批人ID2__"; // ; 分隔
var dName = sys_function.DATASOURCENAME; // 当前数据源名
// 1) 取当前流程状态记录
var stateSql = "select domainid,id from t_flowstatert where docid = '" + docId + "'";
var flowState = findBySQL(stateSql);
if (flowState == null) { return "未找到流程状态记录"; }
var stateId = flowState.getId();
// 2) 取当前节点 ID(从流程状态表)
var nodeSql = "select domainid,current_node_id from t_flowstatert where id = '" + stateId + "'";
var curFlow = findBySQL(nodeSql);
if (curFlow == null) { return "未找到当前节点"; }
var nodeId = curFlow.getItemValueAsString("current_node_id");
// 3) 删除旧的待处理审批人(state=0 表示待处理)
var delSql = "delete from T_ACTOR where FLOWSTATERT_ID = '" + stateId +
"' and NODE_ID = '" + nodeId + "' and STATE = 0";
updateByDSName(dName, delSql);
// 4) 写入新的审批人
var ids = newIds.split(";");
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
if (id != null && id.trim().length > 0) { // JS 字符串 length 是属性
var user = getUserById(id);
if (user != null) {
var insSql = "insert into T_ACTOR (domainid, flowstatert_id, node_id, actor_id, actor_name, state) " +
"values ('" + getWebUser().getDomainid() + "', '" + stateId + "', '" + nodeId + "', '" +
id + "', '" + user.getName() + "', 0)";
updateByDSName(dName, insSql);
}
}
}
// 5) 同步内存中的流程对象(可选,使本次会话立即生效)
var flow = getFlowProcess().doViewByDocument(doc);
if (flow != null && flow.getCurrentNode() != null) {
flow.getCurrentNode().setActors(newIds);
}
return "审批人更新成功";
})()
代码说明¶
| 关键步骤 | 函数 / 字段 | 文档 |
|---|---|---|
| 当前文档 | getCurrentDocument()、doc.getId() |
iscript-guid/api → doc |
| 取流程对象 | getFlowProcess().doViewByDocument(doc)、flow.getCurrentNode()、node.setActors(ids) |
iscript-guid/api → flow |
| 单条查询 | findBySQL(sql) 返回首条记录或 null |
iscript-guid/api → database |
| 数据源更新 | updateByDSName(dsName, sql) |
同上 |
| 当前数据源名 | sys_function.DATASOURCENAME |
平台内置常量 |
| 反查用户姓名 | getUserById(id).getName() |
iscript-guid/api → 用户中心 |
变体与扩展¶
- 追加审批人(不删原有):去掉第 3 步的
delete,并在第 4 步insert前用countBySQL检查actor_id是否已存在,避免重复。 - 替换指定审批人(保留其它):把第 3 步的
delete改为带and ACTOR_ID = '__旧ID__'的精确删除,或直接update T_ACTOR set ACTOR_ID = ... where ACTOR_ID = '__旧ID__'。 - 更新后通知:循环里对每个新 ID 调
sendMessage(userId, "您已被设置为审批人"),链接iscript-guid/functions→ 消息函数。 - 记录日志:再
insert一条到自建的tlk_流程日志表(字段:文档 ID / 操作类型 / 原审批人 / 新审批人 / 操作人 / 时间),便于审计。
常见坑¶
- 只改内存、不落库:
node.setActors(...)只更新当前流程对象,不刷新T_ACTOR表;下次重新加载时审批人会"还原"。务必两步都做。 - SQL 拼接注入:示例为可读性直拼;生产环境若
docId/newIds来自不可信输入,应转义单引号或改用参数化方案。 split结果用.length():String.prototype.split返回 JS 数组,长度是.length属性,不是.length()方法。详见 GraalVM 差异。STATE字段语义:T_ACTOR.STATE = 0才是"待处理",1是"已处理";更新错状态会把已审的人重新拉回待办。- 原审批人的待办不会自动消失:完全替换后,被移除审批人的待办条目需平台额外清理或由用户手动关闭,必要时配
sendMessage通知。 - 权限:动态更新审批人是高权限操作,建议在脚本最前面加上当前用户角色判断(如必须为"管理员")再放行。
组织架构辅助查询:角色 ⇄ 用户、用户上级¶
这些不是直接挂 actorListScript 的脚本,而是**给「按角色 / 部门 / 上级 / 金额计算审批人」/「运行时动态更新审批人」取审批人时用到的工具查询**——例如"这个角色下有谁""这个用户的上级是谁""这个用户拥有哪些角色"。通常嵌在「按角色 / 部门 / 上级 / 金额计算审批人」的 userlist.add(...) 之前,或写在值脚本里供后续逻辑使用。
| 任务 | API / 方法 | 返回 | 备注 |
|---|---|---|---|
| 角色名 → 角色 ID | getRoleIdByName(name) |
String |
名称精确匹配,找不到返回 null |
| 角色 ID → 用户集合 | getUsersByRoleId(roleid) |
Collection<UserVO> |
用 .iterator() 遍历 |
| 部门 ID + 角色 ID → 用户集合 | getUsersByDptIdAndRoleId(dptid, roleid) |
Collection<UserVO> |
同时按部门+角色过滤 |
| 用户 → 直接上级 | user.getSuperior() |
UserVO 或 null |
用户未配上级时为 null |
| 用户 → 角色集合 | user.getRoles() |
Collection<RoleVO> |
用于"是否有 XX 角色"判断 |
片段 A:取角色下所有用户 ID(; 分隔串)
(function () {
var roleid = getRoleIdByName("复核员");
var users = getUsersByRoleId(roleid);
var ids = [];
if (users != null) {
for (var iter = users.iterator(); iter.hasNext();) {
ids.push(iter.next().getId());
}
}
return ids.join(";"); // 例如 "__idA__;__idB__"
})()
片段 B:取用户直接上级 ID
(function () {
var user = getWebUser();
var sup = user != null ? user.getSuperior() : null;
return sup != null ? sup.getId() : "";
})()
片段 C:递归取所有上级(到最高级)——循环向上,遇到 null 终止;注意防范循环引用(A 的上级是 B、B 的上级又是 A),可加一个最大层数兜底。
(function () {
var current = getWebUser();
var chain = [];
var guard = 0; // 防循环引用兜底
while (current != null && guard < 50) {
var sup = current.getSuperior();
if (sup == null) { break; }
chain.push(sup.getId());
current = sup;
guard++;
}
return chain.join(";");
})()
片段 D:判断用户是否拥有某角色
(function () {
var user = getWebUser();
var roles = user != null ? user.getRoles() : null;
if (roles != null) {
for (var iter = roles.iterator(); iter.hasNext();) {
if (iter.next().getName() === "管理员") { // === 不是 .equals
return true;
}
}
}
return false;
})()
角色与用户是多对多关系;更多组织架构查询(部门名 ⇄ ID、上下级部门、用户扩展字段等)见 组织架构查询与维护。
取当前待处理审批人信息¶
业务目标¶
把"当前文档此刻正卡在谁手里"展示出来——用于表单上显示"当前审批人:张三、李四"、视图里作为一列、或据此判断当前用户是否是经办人(决定按钮显示/隐藏)。
写在哪儿¶
表单 → 字段 → 值脚本 / 计算脚本(.form 字段属性 valueScript / calculateScript);或视图 → 列 → 列值脚本。
触发时机与上下文¶
- 触发时机:字段值计算/视图列渲染时。
- 可用环境变量:
CurrentDocument(通过getCurrentDocument()取得);表单上下文下还有WebUser。
返回值契约¶
按挂载点走值脚本通用契约(详见 getting-started → 默认包装与返回值契约通则):
- 用于字段值:返回字符串(如
"张三、李四")或 JSON 串; - 用于"是否经办人"判断:返回
Boolean。
示例代码(取当前待处理审批人姓名,顿号拼接)¶
(function () {
var doc = getCurrentDocument();
var docId = doc.getId();
// 1) 取当前文档的流程状态记录 ID
var stateSql = "select domainid,id from t_flowstatert where docid = '" + docId + "'";
var state = findBySQL(stateSql);
if (state == null) { return "无流程记录"; }
var stateId = state.getId();
// 2) 查该状态下的待处理审批人(STATE=0)
var actorSql = "select domainid,actor_id,actor_name from t_actor " +
"where flowstatert_id = '" + stateId + "' and state = 0";
var actors = queryBySQL(actorSql); // Java List
var names = [];
if (actors != null && actors.size() > 0) {
for (var iter = actors.iterator(); iter.hasNext();) {
var row = iter.next();
names.push(row.getItemValueAsString("actor_name"));
}
}
return names.length > 0 ? "当前审批人:" + names.join("、") : "无待处理审批人";
})()
代码说明¶
| 关键步骤 | 函数 / 字段 | 文档 |
|---|---|---|
| 查多条 | queryBySQL(sql) 返回 java.util.List |
iscript-guid/api → database |
| 查一条 | findBySQL(sql) 返回首条或 null |
同上 |
| 取行字段 | row.getItemValueAsString("actor_name") |
iscript-guid/functions → 文档函数 |
| 列表大小 | actors.size()(Java List),JS 数组才用 .length |
见 GraalVM 差异 |
变体与扩展¶
- 判断当前用户是否是经办人:把循环改为
countBySQL("select count(*) from t_actor where ... and actor_id = '" + userId + "'"),返回count > 0;常用于隐藏/只读脚本。详见 权限与可见性控制。 - 一次取全字段(ID / 姓名 / 登录名 / 节点):
select加node_id,循环里getUserById(actor_id).getLoginno()取登录名,组装 JSON 返回。 - 首条审批人:把
queryBySQL换成findBySQL+limit 1,直接return那条记录的姓名。
常见坑¶
STATE字段:必须state = 0(待处理)才是当前审批人;不加过滤会把已处理(state = 1)的人也算上。queryBySQL返回空时:可能返回null或空List——先用!= null再用.size() > 0双重判断,避免空指针。- 流程已结束:没有待处理审批人时
names为空,应返回友好提示(如"流程已结束")而不是空串。 - Java List vs JS 数组:
names是脚本内[]创建的 JS 数组,用.length;actors是queryBySQL返回的 Java List,用.size()——两者别混。详见 GraalVM 差异。
相关场景¶
- 组织架构查询与维护:用户对象属性、部门名 ⇄ ID 互转、上下级部门递归、批量建部门/角色——是本章取审批人的底座。
- 流程查询、审批历史与人工干预:流程完整信息、当前/上一节点状态、审批历史、强制终止/回退/干预提交等"高级流程操作"。
- 权限与可见性控制:根据"当前用户是否经办人"控制按钮只读/隐藏。
- 流程路由条件与子流程:路径条件分支、子流程选择/实例数/参数传递。