Skip to content

工作流、任务与签核人

工作流定制同时涉及配置、Identity、运行中的 Workflow Process 和业务 Item。先用 Workflow Map、Activity、Assignment、Lifecycle 与 Notification 配置表达流程;代码只补足动态决策。

适用版本: 工作流数据模型以 R31 配置手册为主;内置 Method、启动 API 与 System Event helper 必须按目标 Release 核验。

执行层: 服务端 Method / Workflow Event;客户端只能发起请求和展示结果。

主要依据: R31 原厂手册 Unit 14、Unit 15、Unit 18,11.0 SP9 旧手册 Unit 8、Unit 9、Appendix E,以及本项目提供的签核、任务和流程代码素材。

不要直接改工作流表

WORKFLOWACTIVITYACTIVITY_ASSIGNMENT 的 SQL 表不是业务 API。直接更新创建人、状态或签核人会绕过权限、缓存、事件和审计,也可能破坏正在运行的流程。

先区分四个对象

对象含义常见用途
Workflow Map流程模板设计 Activity、Path、Assignment 和变量
Workflow Process一次运行实例查询当前流程、状态和受控 Item
Activity运行中的节点完成、委派、动态分配
Activity AssignmentActivity 与签核 Identity 的关系指定谁可以处理当前节点

User 与 Identity 也不能混用。Assignment 的 related_id 通常指 Identity,而不是 User。

获取 Controlled Item

标注:Classic client compatibility

旧版本经常通过内置 Method Get My Controlled Item,从 Activity 或 Workflow Process 找到业务 Item。不要复制 2008 年的 VB.NET 实现;先确认目标数据库仍有该 Method,并从当前工作流上下文调用它。

csharp
Innovator inn = this.getInnovator();

Item controlled = this.apply("Get My Controlled Item");
if (controlled.isError())
{
    return inn.newError("无法取得工作流受控 Item,请联系管理员并提供操作时间。");
}

if (controlled.getItemCount() != 1)
{
    return inn.newError("工作流没有返回唯一的受控 Item。");
}

return controlled;

调用前要确认当前 Method 的 this 确实是 Activity 或 Workflow Process。若目标 Release 已提供更明确的服务或 API,应替换上面的兼容调用。

动态增加签核人

标注:Stable(IOM 数据操作骨架) + 版本化工作流规则

动态 Assignment 必须满足五个条件:

  1. Activity 与 Identity 来自当前流程或受信服务端规则,而不是硬编码。
  2. 当前用户有权改变该 Activity 的签核人。
  3. 同一 (activity, identity) 不会重复加入。
  4. 只修改目标 Activity,不用宽泛 where 批量删除。
  5. voting_weightis_required 以及组成员展开等路由属性由当前 Workflow Map 的业务规则显式决定并验证,不能依赖未知默认值。

下面只演示“定位 Activity/Identity、授权、查重、创建关系”的骨架,不是一份可直接投产的完整签核算法:

csharp
Innovator inn = this.getInnovator();

string activityId = this.getProperty("activity_id", "");
string identityId = this.getProperty("identity_id", "");

bool activityIdIsValid = System.Text.RegularExpressions.Regex.IsMatch(
    activityId ?? "",
    "^[0-9A-Fa-f]{32}$");
bool identityIdIsValid = System.Text.RegularExpressions.Regex.IsMatch(
    identityId ?? "",
    "^[0-9A-Fa-f]{32}$");

if (!activityIdIsValid || !identityIdIsValid)
{
    return inn.newError("Activity 或 Identity ID 格式无效。");
}

// 这里应调用项目自己的服务端授权规则。
if (!CanManageAssignments(activityId))
{
    return inn.newError("当前用户不能修改此 Activity 的签核人。");
}

Item existing = inn.newItem("Activity Assignment", "get");
existing.setAttribute("select", "id");
existing.setProperty("source_id", activityId);
existing.setProperty("related_id", identityId);
existing = existing.apply();

if (existing.isError())
{
    return inn.newError("签核人查重失败,请联系管理员并提供操作时间。");
}

if (existing.getItemCount() > 0)
{
    return inn.newResult("该签核人已存在,无需重复增加。");
}

Item assignment = inn.newItem("Activity Assignment", "add");
assignment.setProperty("source_id", activityId);
assignment.setProperty("related_id", identityId);
// 由项目规则显式设置并验证,例如:
// assignment.setProperty("voting_weight", calculatedVotingWeight);
// assignment.setProperty("is_required", isRequired ? "1" : "0");
assignment = assignment.apply();

if (assignment.isError())
{
    return inn.newError("增加签核人失败,请联系管理员并提供操作时间。");
}

return assignment;

CanManageAssignments 是项目必须实现的服务端函数。它至少应确认 Activity 属于预期流程、仍处于可修改状态,并检查调用者 Identity。示例中的“先查再加”也不是原子幂等:并发请求仍可能同时通过查询。生产实现必须使用目标 Release 支持的唯一性/串行机制,或在创建时捕获并规范化重复冲突,再用并发测试证明不会产生两条 Assignment。

删除签核人

删除必须先唯一查询 (activity_id, identity_id),再删除查询得到的那个 Assignment ID。不要发布或执行“只按 source_id 删除全部 Assignment”的代码。批量清空签核人可能让流程失去合法处理者。

按条件选择流程

标注:Stable(决策原则) + Classic client compatibility(启动 API)

如果不同业务类型只是流程路径不同,优先在一个 Workflow Map 中使用 Path、变量、自动 Activity 或 Subflow。只有确实需要不同模板时,才由服务端白名单选择 Map。

csharp
string ResolveWorkflowMap(string requestKind)
{
    switch (requestKind)
    {
        case "standard":
            return "Example Standard Workflow";
        case "expedited":
            return "Example Expedited Workflow";
        default:
            throw new InvalidOperationException("不支持的流程类型。");
    }
}

启动前可以先用 IOM 做一次非原子前置检查,但查询条件必须包含目标 Map 与“运行中”语义。下面用占位符强调条件结构;具体关系名、状态字段和值由目标 Release 的 Workflow 模型确认:

csharp
Item existing = inn.newItem("Workflow", "get");
existing.setAttribute("select", "id");
existing.setProperty("source_id", this.getID());
existing.setProperty("workflow_map_id", resolvedMapId);
existing.setProperty("status", activeStatusValue); // 目标版本验证后的运行中值
existing = existing.apply();

if (existing.isError())
{
    return inn.newError("工作流查重失败,请联系管理员并提供操作时间。");
}

if (existing.getItemCount() > 0)
{
    return inn.newResult("该 Item 已有同一 Map 的运行中工作流。");
}

常见判断错误

getItemCount() < 0 不是“没有查到数据”。正常的空集合是 0;错误应先用 isError() 判断。这个错误会让启动流程的分支永远不执行。

历史或已完成 Process 不应阻止一个合法的新实例,除非业务契约明确规定“一生只能启动一次”。上面的查询也不能阻止两个并发请求同时通过。实际实例化和启动 API 在不同 Release 中可能变化,应封装为 workflowAdapter.start(item, mapId, idempotencyKey),使用目标版本支持的原子唯一性/串行机制,并做并发双提交测试。不要用 SQL 改 created_by_id;如果业务需要记录发起人,应使用受支持的属性或审计 Item。

查询当前用户任务

标注:Stable(按登录用户权限查询)

查询“我的任务”时不应临时授予高权限 Identity,否则 my_assignment=1 的语义会变得模糊,并可能返回用户本不该看到的任务。

csharp
Innovator inn = this.getInnovator();

Item query = inn.newItem("InBasket Task", "get");
query.setAttribute("select", "id,item,status");
query.setProperty("status", "Active");
query.setProperty("my_assignment", "1");

Item tasks = query.apply();
if (tasks.isError())
{
    return inn.newError("任务查询失败,请联系管理员并提供操作时间。");
}

return tasks;

如果还要筛选业务 Item 类型或日期:

  • 用 ItemType 查询得到目标 ID,不把数据库 GUID 写死在代码里。
  • 使用 Aras 约定的中性时间和项目时区策略。
  • .NET 格式化 24 小时制应使用 HH,不是 hh
  • 给查询加 select、分页或最大记录数。

登录、失败与退出事件

标注:Classic client compatibility / Private API

Successful Login、Failed Login 和 Logout 是三个不同的 System Event handler。每个 Method 只处理一种事件,不能把三段带 return 的代码拼在一起。

csharp
// 伪代码:由目标 Release 的 System Event API adapter 实现。
return systemEventLogAdapter.Write(new SystemEventRecord
{
    EventType = "SuccessfulLogin",
    Actor = eventData.LoginName,
    Message = "Login succeeded"
});

记录登录名属于个人数据处理。文档必须同时定义访问权限、保留期限、失败重试、脱敏和审计用途。旧资料里的 CCO.SystemEventLogger 应先在目标 Release 验证,再由 adapter 包装。

OOTB Method 名称不是公共契约

标注:Private API

某些应用包会提供内部 Method 来完成“加入变更”“选择对象”等动作。只记录一个 Method 名称不足以构成教程,因为它可能随 Product Engineering 或其他应用版本改变。

使用前应记录:

  • 所属应用包及版本;
  • 输入 AML 契约与返回结构;
  • 调用权限;
  • 是否允许客户 Method 直接调用;
  • 升级时的替代方案。

为什么不要这样做

原始模式标注风险
硬编码 Activity 与 Identity GUIDReject环境不可迁移,容易改错运行实例
只按 Activity 删除 AssignmentReject可能清空所有签核人
Grant 高权限后查询“我的任务”Reject扩大数据范围,破坏当前用户语义
SQL 更新 Workflow 创建人或状态Reject绕过工作流引擎与审计
复制旧版 Get My Controlled Item 实现Reject依赖旧 VB.NET、DOM 和数据模型细节
多个 System Event handler 拼在一个 MethodReject第一处 return 后其他分支不可达

验证清单

  • [ ] 动态签核人操作是幂等的,并且仅作用于一个 Activity。
  • [ ] User 与 Identity 转换经过唯一性校验。
  • [ ] 不提升权限也能正确返回“我的任务”。
  • [ ] 工作流查重先判断 isError(),再判断 getItemCount()
  • [ ] 用户重复提交或并发提交不会启动两个流程。
  • [ ] System Event 日志有隐私、权限和保留策略。
  • [ ] 目标 Release 的内置 Method 和 workflow adapter 有集成测试。

官方延伸阅读

本站内容仅供学习与参考