跳转至

流程审批人动态计算

一句话定位:流程流转时按业务规则(角色、部门、上级、金额、申请人…)算出"谁来审批",以及在流程运行起来之后查询/替换/追加审批人。落点以人工节点的 actorListScript / assistListScript / circulatorListScript 为主,配合操作前后置脚本做运行时干预。

写在前面——审批人返回值的三种接受形式

不论挂在 actorListScriptassistListScript 还是 circulatorListScript,平台对脚本的返回值都同样接受以下三种形式(任选其一):

形式 示例 适用
单个用户 ID(String "__vUWxP9RwmFv" 只有一个审批人
多个用户 ID 的字符串(; 分隔) "id1;id2;id3" 多人并审 / 会签
用户对象(UserVO)或其集合(Collection<UserVO> user / [user1, user2] 已通过 getUserById 等取到对象,直接返回更省一次反查

提示:在 actorListScript 上下文中,平台**通常还会预置一个名为 userlistArrayList** 用于累加(源素材 flow-node.md 即如此使用),脚本内可直接 userlist.add(user)return userlist;如未注入,请自行 createObject("java.util.ArrayList") 新建一个。


本章场景清单

场景 落点 / Label 主要素材
按角色 / 部门 / 上级 / 金额算审批人 WORKFLOW:NODE_ACTOR_LISTactorListScript 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 节点属性 actorListScriptactorEditMode 设为"脚本";Label WORKFLOW:NODE_ACTOR_LIST)。

参考 iscript-guid/basic → 流程节点

触发时机与上下文

  • 触发时机:流程提交、节点进入时计算该节点的审批人。
  • 可用环境变量WebUser(当前登录用户,通过 getWebUser() 取得)、CurrentDocument(当前文档,通过 getCurrentDocument() 取得)。
  • 上下文预置actorListScript 上下文通常预置 userlistArrayList),可直接累加并 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.lengthusers[i]。详见 GraalVM 差异
  • getSuperior() 返回 null:用户未配上级时直接 add 会空指针,必须先判空,必要时回退到默认审批人。
  • 角色/部门名拼错或大小写不一致getRoleIdByName 是按名称精确匹配,建议在"代码说明"提到的角色管理里核对名称;查不到会返回 null,导致后续 getUsersByRoleId(null) 空集。
  • 忘记 returnactorListScript 必须把审批人集合 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 表),否则刷新/重启后内存与表数据可能不一致。

触发时机与上下文

  • 触发时机:用户点击表单上的某个操作按钮("重派审批人""追加会签"等自定义按钮)。
  • 可用环境变量WebUserCurrentDocument;通过 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() UserVOnull 用户未配上级时为 null
用户 → 角色集合 user.getRoles() Collection<RoleVO> 用于"是否有 XX 角色"判断

文档:iscript-guid/api → 用户中心

片段 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 / 姓名 / 登录名 / 节点)selectnode_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 数组,用 .lengthactorsqueryBySQL 返回的 Java List,用 .size()——两者别混。详见 GraalVM 差异

相关场景