开发者指南:使用 Apex 编程方式管理 Salesforce 审批流程
背景与应用场景
作为一名 Salesforce 开发人员,我们经常与平台强大的声明式工具打交道。Approval Process (审批流程) 就是其中最经典的功能之一。它允许管理员通过图形化界面配置多步骤的记录审批工作流,例如费用报销、合同审批、折扣申请等。这套机制在标准用户界面下运行得非常出色,能够满足绝大多数业务需求。
然而,在复杂的企业应用场景中,仅依赖标准的用户界面和自动化工具(如 Flow)来触发和管理审批流程,往往会遇到瓶颈。这时,就需要我们开发人员介入,使用 Apex 来实现更高级、更灵活的控制。以下是一些典型的应用场景:
1. 自定义用户界面触发审批
企业可能不希望使用标准的“提交以供审批”按钮,而是希望通过自定义的 Lightning Web Component (LWC) 或 Aura 组件来启动审批流程。例如,在一个复杂的订单录入页面,用户在填写完所有信息并进行一系列校验后,点击一个自定义的“提交审核”按钮,系统需要在后端通过 Apex 提交该订单记录进入审批流。
2. 复杂的业务逻辑判断
审批的入口条件 (Entry Criteria) 可能非常复杂,无法通过简单的公式字段或标准条件来定义。例如,一个折扣申请是否需要提交给高级经理审批,可能取决于客户的历史交易数据、当前库存水平以及外部系统的信用评级——这些逻辑需要通过 Apex 查询和计算后才能确定。
3. 批量处理与集成
当需要批量提交记录进行审批时,例如,在月底将所有“待处理”的合同批量提交给法务部门,手动操作是不现实的。通过编写 Batch Apex (批处理 Apex),我们可以轻松实现对成千上万条记录的自动化批量提交。同样,当外部系统(如 ERP)通过 API 更新 Salesforce 中的记录状态时,可能需要自动触发相应的审批流程,这也必须通过 Apex 来完成。
4. 自动化审批响应
在某些场景下,审批或拒绝的操作也可以自动化。例如,如果一个审批请求在 48 小时内未被处理,系统可以自动将其升级或批准。或者,当某个关联记录的状态发生变化时,系统需要自动批准或拒绝一个待处理的审批请求。这些都需要通过 Apex 编程来实现对审批工作项 (Work Item) 的直接操作。
正是由于这些需求的存在,掌握如何使用 Apex 与 Approval Process 交互,成为了 Salesforce 开发人员的一项核心技能。
原理说明
要通过 Apex 与审批流程进行交互,我们首先需要理解其背后的对象模型和核心 Apex 类。Salesforce 提供了专门的 `Approval` 命名空间,其中包含了处理审批流程所需的所有方法和类。
核心对象模型
- ProcessInstance: 当一条记录进入一个审批流程时,系统会创建一个 `ProcessInstance` 记录。它代表了该记录审批过程的整个生命周期实例。
- ProcessInstanceStep: 代表 `ProcessInstance` 中的一个具体步骤。例如,“经理审批”、“总监审批”都是独立的步骤。
- ProcessInstanceWorkitem: 这是最关键的对象之一。它代表一个分配给特定用户或队列的待处理审批请求,也就是用户在“需要我批准的项目”列表中看到的具体项目。我们对审批请求的“批准”、“拒绝”或“重新分配”操作,实际上就是对这个 `ProcessInstanceWorkitem` 记录进行处理。
核心 Apex 类与方法
在 `Approval` 命名空间中,有两个主要的类用于编程方式地控制审批流程:
1. `Approval.ProcessSubmitRequest`
这个类用于构建一个“提交审批”的请求。当你需要将一条记录提交到其关联的审批流程时,你需要实例化这个对象并设置其属性。主要方法包括:
- `setObjectId(Id objectId)`: 必需。 设置要提交审批的记录 ID。
- `setComments(String comments)`: 设置提交时的备注。
- `setSubmitterId(Id submitterId)`: 指定提交者。如果留空,则默认为当前上下文用户。
- `setNextApproverIds(Id[] nextApproverIds)`: 如果审批流程的第一个步骤允许提交者选择批准人,可以通过这个方法来指定。
- `setProcessDefinitionNameOrId(String processDefinitionNameOrId)`: 当一个对象有多个审批流程时,用此方法指定要进入哪一个。如果不指定,Salesforce 会自动选择第一个符合入口条件的活动审批流程。
2. `Approval.ProcessWorkitemRequest`
这个类用于处理一个已经存在的待审批项 (Work Item)。当你需要批准、拒绝或撤回一个审批请求时,你需要使用这个类。
- `setWorkitemId(Id workitemId)`: 必需。 设置要处理的 `ProcessInstanceWorkitem` 的记录 ID。
- `setAction(String action)`: 必需。 指定要执行的操作。标准值包括 'Approve', 'Reject', 'Removed' (用于撤回)。
- `setComments(String comments)`: 设置批准或拒绝时的备注。
- `setNextApproverIds(Id[] nextApproverIds)`: 在批准后,如果需要手动指定下一步的批准人,可以使用此方法。
无论是提交还是处理,最后都需要调用统一的方法:`Approval.process()`。这个方法接受单个请求对象或一个请求对象列表,并返回一个 `Approval.ProcessResult` 对象(或列表),通过检查结果对象的 `isSuccess()` 和 `getErrors()` 方法,我们可以判断操作是否成功。
示例代码
以下示例代码均来自 Salesforce 官方文档,并添加了详细的中文注释以帮助理解。
示例 1: 提交记录以供审批
此示例演示了如何在一个 Apex 方法中提交一个特定的客户 (Account) 记录进入审批流程。假设我们已经为 Account 对象创建了一个名为 `Account_Approval_Process` 的审批流程。
public class SubmitAccountForApproval {
public static void submitAccount(Id accountId) {
// 1. 创建一个审批提交请求对象
Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
// 2. 设置要提交审批的记录ID
// 这是必需的步骤,告诉系统我们要对哪条记录进行操作
req.setObjectId(accountId);
// 3. (可选) 设置提交备注
req.setComments('将此客户提交以供审批。');
// 4. (可选) 如果审批流程的第一个步骤允许提交者选择批准人,
// 我们可以通过以下方式指定一个或多个批准人的ID
// List<Id> nextApproverIds = new List<Id>{'005xxxxxxxxxxxxxxx'};
// req.setNextApproverIds(nextApproverIds);
// 5. (可选) 如果Account对象有多个审批流程,需要明确指定使用哪一个
// 可以使用审批流程的唯一名称 (Unique Name) 或其 ID
req.setProcessDefinitionNameOrId('Account_Approval_Process');
// 6. (可选) 如果设置为true,即使记录不满足入口条件,也会尝试提交。
// 这通常会导致错误,除非入口条件是在Apex中判断的。默认为false。
req.setSkipEntryCriteria(false);
try {
// 7. 调用 process 方法提交请求
Approval.ProcessResult result = Approval.process(req);
// 8. 检查处理结果
if (result.isSuccess()) {
System.debug('记录成功提交审批。ProcessInstance ID: ' + result.getInstanceId());
} else {
// 如果失败,遍历错误信息并打印
for (Approval.ProcessResult.Error err : result.getErrors()) {
System.debug('提交失败。错误信息: ' + err.getMessage());
System.debug('错误代码: ' + err.getErrorCode());
}
}
} catch (System.DmlException e) {
// 捕获可能发生的DML异常
System.debug('提交审批时发生异常: ' + e.getMessage());
}
}
}
示例 2: 批准或拒绝一个待审批请求
此示例展示了如何查询一个待审批项 (`ProcessInstanceWorkitem`) 并对其进行批准操作。这通常用于自动化处理或者在自定义界面中响应审批。
public class ApproveOrRejectRequest {
public static void approveRequest(Id recordId) {
// 1. 首先,需要找到与特定记录关联的、待处理的审批工作项
// ProcessInstanceWorkitem 是代表待办审批项的对象
ProcessInstanceWorkitem workitem;
try {
workitem = [SELECT Id FROM ProcessInstanceWorkitem WHERE ProcessInstance.TargetObjectId = :recordId AND ProcessInstance.Status = 'Pending' ORDER BY CreatedDate DESC LIMIT 1];
} catch (System.QueryException e) {
System.debug('未找到与记录 ' + recordId + ' 关联的待审批项。');
return;
}
// 2. 创建一个审批工作项处理请求对象
Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
// 3. 设置要处理的工作项ID
// 这是必需的步骤
req.setWorkitemId(workitem.Id);
// 4. 指定要执行的操作
// 'Approve' - 批准
// 'Reject' - 拒绝
// 'Removed' - 撤回 (仅当提交者或管理员操作时)
req.setAction('Approve');
// 5. (可选) 设置批准或拒绝时的备注
req.setComments('自动批准此请求,因为它符合所有预定义规则。');
// 6. (可选) 如果当前步骤后需要手动选择下一位批准人
// List<Id> nextApproverIds = new List<Id>{'005xxxxxxxxxxxxxxx'};
// req.setNextApproverIds(nextApproverIds);
try {
// 7. 调用 process 方法处理请求
Approval.ProcessResult result = Approval.process(req);
// 8. 检查结果
if (result.isSuccess()) {
System.debug('请求已成功批准。');
} else {
for (Approval.ProcessResult.Error err : result.getErrors()) {
System.debug('批准失败。错误信息: ' + err.getMessage());
}
}
} catch (System.DmlException e) {
System.debug('处理审批请求时发生异常: ' + e.getMessage());
}
}
}
注意事项
权限 (Permissions)
执行审批操作的用户权限至关重要。
- 提交审批: 运行 Apex 代码的用户必须对该记录拥有读取权限,并且其简档 (Profile) 或权限集 (Permission Set) 必须允许其“提交记录以供审批”。此外,该用户必须是审批流程初始提交者中允许的用户之一。
- 处理审批项: 运行代码的用户必须是当前分配到的批准人,或者是拥有“修改所有数据”权限的管理员,或者是该批准人在代理设置中指定的代理人。否则,`Approval.process()` 调用将会失败。
API 限制 (API Limits)
`Approval.process()` 方法的调用会消耗 DML (Data Manipulation Language) 语句的 Governor Limits (执行调控器和限制)。每次调用 `Approval.process()`,无论你传递的是单个请求还是一个列表,其消耗的 DML 语句数量等于列表中请求对象的数量。一个事务中最多可以执行 150 条 DML 语句。因此,在进行批量处理时,务必将请求对象收集到一个列表中,然后进行单次调用,而不是在循环中单独调用。
// 正确的批量处理方式
List<Approval.ProcessSubmitRequest> requests = new List<Approval.ProcessSubmitRequest>();
for (Account acc : accountsToSubmit) {
Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
req.setObjectId(acc.Id);
requests.add(req);
}
if (!requests.isEmpty()) {
Approval.process(requests);
}
错误处理 (Error Handling)
`Approval.process()` 方法不会在失败时直接抛出异常,除非发生了系统级的错误(如 DML 异常)。它会返回一个 `ProcessResult` 对象。必须检查 `result.isSuccess()` 的返回值。如果返回 `false`,你需要遍历 `result.getErrors()` 来获取具体的错误信息,例如“记录不满足审批流程的入口条件”、“找不到活动的审批流程”或“用户无权执行此操作”等。优雅地处理这些错误并向用户提供有意义的反馈是高质量代码的标志。
事务与记录锁定 (Transaction and Record Locking)
当一条记录被提交到审批流程后,Salesforce 会自动将其锁定。这意味着除了当前分配的批准人和拥有“修改所有数据”权限的管理员外,其他任何用户(包括记录的所有者)都无法编辑该记录。如果在 Apex 中尝试对锁定的记录执行 DML 更新操作,将会收到 `UNABLE_TO_LOCK_ROW` 错误。在设计解决方案时,必须考虑到这一点。如果需要在审批过程中更新记录,通常需要先通过 Apex 将其从审批流程中撤回,更新后再重新提交。
总结与最佳实践
通过 Apex 对 Salesforce Approval Process 进行编程控制,为我们开发者打开了一扇通往高级自动化的大门。它弥合了声明式工具的局限性,使得我们能够构建完全符合复杂业务需求的、高度定制化的审批解决方案。
最佳实践
- 声明式优先原则: 在着手编写 Apex 之前,始终优先考虑能否通过标准的审批流程配置、Flow 或其他声明式工具来满足需求。只有在声明式方法无法实现时,才选择 Apex 作为解决方案。
- 代码的批量化: 永远不要在循环中调用 `Approval.process()`。始终将你的请求对象(`ProcessSubmitRequest` 或 `ProcessWorkitemRequest`)添加到一个列表中,然后对整个列表进行一次调用。这是避免触及 Governor Limits 的关键。
- 使用异步 Apex: 对于从触发器 (Trigger) 中发起的审批提交,或者涉及复杂计算和外部调用的场景,强烈建议使用异步 Apex(如 `@future` 方法、Queueable Apex 或 Batch Apex)。这有助于避免“混合 DML”错误(即在同一事务中同时操作设置对象和非设置对象),并能防止事务超时。
- 健壮的错误处理与日志记录: 详细检查 `ProcessResult` 并记录所有错误。在自定义界面中,将清晰的错误信息反馈给最终用户,指导他们如何解决问题。
- 全面的单元测试: 编写 Apex 测试类时,要覆盖所有业务逻辑,包括成功提交、提交失败(如不满足入口条件)、成功批准/拒绝以及用户权限不足等场景。使用 `Test.startTest()` 和 `Test.stopTest()` 来包裹你的审批调用,以便在测试执行后查询 `ProcessInstance` 和 `ProcessInstanceWorkitem` 的状态来断言结果的正确性。
总之,将 Apex 的灵活性与 Approval Process 的结构化相结合,可以创造出功能强大且高度可靠的自动化工作流,从而极大地提升企业的运营效率。
评论
发表评论