ApexによるSalesforce承認プロセスの完全活用:開発者向けガイド

背景と応用シナリオ

Salesforce の Salesforce Developer (Salesforce 開発者) として、私たちはビジネスロジックの自動化において中心的な役割を担っています。その中でも、Approval Process (承認プロセス) は、組織内の意思決定フローを体系化し、自動化するための非常に強力な宣言的ツールです。経費報告書の承認、商談の割引率の承認、休暇申請など、多段階の承認が必要な業務は数多く存在します。

通常、承認プロセスの設定は Salesforce 管理者が行いますが、より複雑な要件や、他のシステムとの連携、カスタムユーザーインターフェースの実装などが必要な場合、開発者の介入が不可欠となります。例えば、以下のようなシナリオが考えられます。

  • 特定の条件を満たしたレコードを Apex トリガーによって自動的に承認申請する。
  • 外部システムからの API コールをトリガーとして、レコードを承認または却下する。
  • カスタムの Visualforce ページや Lightning Web Component (LWC) から承認プロセスを操作する。
  • 一括で大量のレコードを承認申請する必要があるバッチ処理を実装する。

この記事では、Salesforce 開発者の視点から、承認プロセスの基本的な仕組みを解説し、Apex を用いてプログラム的に承認プロセスを操作する方法について、具体的なコード例を交えながら詳しく掘り下げていきます。


原理説明

Apex から承認プロセスを操作する前に、その背後で動作しているデータモデルを理解することが重要です。承認プロセスが開始されると、Salesforce はいくつかの標準 sObject (エスオブジェクト) を使用してその状態を管理します。

主要な sObject

ProcessInstance: これは、特定のレコードに対する承認プロセス全体のインスタンスを表します。レコードが承認申請されると、このオブジェクトのレコードが1つ作成されます。承認プロセスが完了(承認、却下、または取り消し)すると、このレコードの Status 項目が更新されます。

ProcessInstanceStep: 承認プロセス内の個々のステップ(承認待ち、承認済み、却下済みなど)を表します。1つの ProcessInstance には、複数の ProcessInstanceStep が関連付けられます。誰がいつ承認したか、などの履歴を追跡するために使用されます。

ProcessInstanceWorkitem: 特定のユーザーまたはキューに割り当てられた、承認待ちの作業項目を表します。これが「承認待ち」の状態にある実際のタスクです。開発者がプログラムでレコードを承認または却下する場合、操作対象となるのはこのオブジェクトのレコードです。このレコードの ID を特定し、それに対してアクションを実行します。

プログラムによる操作のフロー

開発者が Apex を使用して承認プロセスを操作する際の一般的なフローは以下の通りです。

1. 承認申請 (Submission):
Apex の Approval クラスと、その内部クラスである ProcessSubmitRequest を使用します。対象レコードの ID、申請者の ID、申請時のコメントなどを設定し、Approval.process() メソッドを呼び出すことで、レコードを承認プロセスに投入します。

2. 承認/却下 (Approval/Rejection):
まず、SOQL を使用して、対象レコードに関連する `ProcessInstanceWorkitem` を特定します。このレコードの ID を取得した後、Approval.ProcessWorkitemRequest を使用して、実行するアクション('Approve' または 'Reject')、次の承認者の ID(該当する場合)、コメントなどを設定します。最後に、同じく Approval.process() メソッドを呼び出して、承認または却下を実行します。

この仕組みを理解することで、Apex コードから承認プロセスを柔軟に制御することが可能になります。


示例代码 (サンプルコード)

ここでは、商談 (Opportunity) オブジェクトを例に、具体的な Apex コードを見ていきましょう。シナリオとして、「商談の金額が $1,000,000 を超えた場合、Apex トリガーによって自動的に承認申請する」というケースと、「特定の商談をプログラムで承認する」というケースを想定します。

1. レコードの自動申請

以下のコードは、商談が作成または更新された際に、条件を満たしていれば自動的に承認申請する Apex トリガーの例です。

// Opportunity の after insert または after update で発火するトリガー
trigger OpportunityApprovalTrigger on Opportunity (after insert, after update) {
    for (Opportunity opp : Trigger.new) {
        // 以前の金額と比較し、金額が更新され、かつ特定のしきい値を超えていることを確認
        // isNew の場合は oldOpp は null なので、それも考慮
        Opportunity oldOpp = Trigger.isUpdate ? Trigger.oldMap.get(opp.Id) : null;
        if (opp.Amount > 1000000 && (Trigger.isInsert || opp.Amount != oldOpp.Amount)) {
            
            // 承認申請オブジェクトを作成します
            Approval.ProcessSubmitRequest req = new Approval.ProcessSubmitRequest();
            
            // 申請時のコメントを設定します
            req.setComments('金額が $1,000,000 を超えたため、自動的に承認申請されました。');
            
            // 承認申請の対象となるレコードの ID を設定します
            req.setObjectId(opp.Id);

            // 現在のユーザーIDを申請者として設定します
            // 必要に応じて特定のユーザーIDをハードコードまたはカスタム設定から取得することも可能です
            req.setSubmitterId(UserInfo.getUserId());

            // 承認プロセス名またはIDを指定します(省略するとSalesforceが適切なプロセスを自動選択します)
            // 特定の承認プロセスを起動したい場合は、その名前またはIDを指定することがベストプラクティスです
            // req.setProcessDefinitionNameOrId('Discount_Approval_Process');

            try {
                // 承認プロセスを開始します
                Approval.ProcessResult result = Approval.process(req);

                // 結果をチェックします
                if (result.isSuccess()) {
                    System.debug('商談 ' + opp.Name + ' が正常に承認申請されました。');
                    // 成功した場合の追加処理をここに記述
                } else {
                    for(Approval.ProcessResult.Error err : result.getErrors()) {
                        // エラーメッセージをデバッグログに出力します
                        System.debug('承認申請に失敗しました。エラー: ' + err.getMessage());
                    }
                }
            } catch (System.DmlException e) {
                // 予期せぬ DML エラーをキャッチします
                System.debug('承認申請中に DML 例外が発生しました: ' + e.getMessage());
            }
        }
    }
}

2. 承認待ちアイテムのプログラムによる承認

次に、特定のレコードに対する承認待ちアイテムを検索し、それをプログラムで承認する例です。これは、例えばバッチ処理や、カスタム UI のコントローラーメソッド内で使用されます。

public class OpportunityApprovalManager {
    public static void approveOpportunity(Id opportunityId) {
        
        // 承認待ちの作業項目 (Workitem) を見つける
        ProcessInstanceWorkitem workitem = [SELECT Id FROM ProcessInstanceWorkitem WHERE ProcessInstance.TargetObjectId = :opportunityId AND ProcessInstance.Status = 'Pending' LIMIT 1];

        if (workitem != null) {
            // 承認リクエストオブジェクトを作成します
            Approval.ProcessWorkitemRequest req = new Approval.ProcessWorkitemRequest();
            
            // 実行するアクションを設定します ('Approve', 'Reject', 'Removed')
            req.setAction('Approve');
            
            // 承認/却下を実行するユーザーのコンテキストを設定します
            // このユーザーは、割り当てられた承認者であるか、「すべてのデータの編集」権限を持つ必要があります
            // 通常は、このコードを実行している現在のユーザーです
            req.setActorId(UserInfo.getUserId());

            // 承認コメントを設定します
            req.setComments('システムにより自動承認されました。');

            // 対象となる承認待ちアイテムのIDを設定します
            req.setWorkitemId(workitem.Id);

            try {
                // 承認処理を実行します
                Approval.ProcessResult result = Approval.process(req);

                if (result.isSuccess()) {
                    System.debug('承認が正常に処理されました。');
                } else {
                    for(Approval.ProcessResult.Error err : result.getErrors()) {
                        System.debug('承認処理に失敗しました。エラー: ' + err.getMessage());
                    }
                }
            } catch (System.DmlException e) {
                System.debug('承認処理中に DML 例外が発生しました: ' + e.getMessage());
            }

        } else {
            System.debug('対象の商談に承認待ちのアイテムが見つかりませんでした。');
        }
    }
}

注意事項 (注意事項)

Apex を使用して承認プロセスを操作する際には、いくつかの重要な点に注意する必要があります。

権限 (Permissions)

・申請時: Approval.process(submitRequest) を実行するユーザーは、対象レコードの「承認申請」権限を持っている必要があります。通常、これはプロファイルや権限セットで制御されます。

・承認/却下時: Approval.process(workitemRequest) を実行するユーザーは、その作業項目に割り当てられた承認者であるか、あるいは「すべてのデータの編集」権限を持つ Salesforce 管理者である必要があります。そうでない場合、INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY のようなエラーが発生します。

API 制限 (API Limits)

・ガバナ制限: Approval.process() メソッドの呼び出しは、DML ステートメントとしてカウントされます。トリガーやバッチ処理内でこのメソッドを呼び出す場合、1トランザクションあたりの DML ステートメントの上限(150回)に達しないように、コードを一括処理 (Bulkification) する必要があります。ループ内で直接呼び出すのではなく、リクエストのリストを作成し、ループの外で一度に処理することが推奨されます。

・レコードのロック: レコードが承認プロセスに入ると、そのレコードはロックされ、承認者以外のユーザー(および一部の管理者を除く)は編集できなくなります。Apex でレコードを更新しようとすると、UNABLE_TO_LOCK_ROW エラーが発生する可能性があります。プログラムで更新を行う場合は、一度承認プロセスからレコードを取り消すか、承認/却下してプロセスを完了させる必要があります。

エラー処理 (Error Handling)

Approval.process() メソッドは、Approval.ProcessResult オブジェクトを返します。このオブジェクトの isSuccess() メソッドを必ず確認し、失敗した場合は getErrors() メソッドでエラーの詳細を取得してログに記録したり、ユーザーに通知したりするべきです。一般的なエラーには、「レコードが申請基準を満たしていない」「有効な承認プロセスが見つからない」などがあります。必ず try-catch ブロックを使用して、予期せぬ例外も捕捉するようにしてください。


まとめとベストプラクティス (まとめとベストプラクティス)

Salesforce の承認プロセスは、ビジネスフローを自動化するための強力な機能ですが、Apex を組み合わせることで、その可能性はさらに広がります。開発者として、私たちは標準機能の限界を超えるソリューションを提供することができます。

ベストプラクティス

  1. 宣言的なアプローチを優先 (Declarative First): 可能な限り、標準の承認プロセス設定(エントリ条件、承認ステップ、項目自動更新など)で要件を満たすことを目指してください。Apex は、宣言的な機能では実現不可能な、複雑なロジックや外部連携が必要な場合にのみ使用する「最後の手段」と考えるのが良いでしょう。
  2. 一括処理を徹底 (Bulkify Your Code): トリガーやバッチ処理で承認プロセスを操作する際は、必ず一括処理を念頭に置いて設計してください。リクエストオブジェクトのリストを作成し、一度の Approval.process() 呼び出しで複数のレコードを処理することで、ガバナ制限を回避し、パフォーマンスを向上させることができます。
  3. 堅牢なテストカバレッジ (Robust Test Coverage): 承認プロセスを操作する Apex コードには、十分なテストカバレッジが必要です。テストクラスでは、テストデータを作成し、Test.startTest()Test.stopTest() の間で承認申請/処理メソッドを呼び出します。その後、ProcessInstanceProcessInstanceWorkitem の状態を SOQL でクエリし、意図した通りにプロセスが進行したことをアサーションで検証します。レコードがロックされる挙動もテストすることが重要です。
  4. ハードコードを避ける (Avoid Hardcoding): 承認プロセスの名前 (ProcessDefinitionNameOrId) やユーザーIDなどをコードにハードコードするのは避けるべきです。カスタムメタデータ型やカスタム設定を使用してこれらの値を管理することで、サンドボックスと本番環境での差異や、将来の変更に柔軟に対応できます。

結論として、Apex と承認プロセスを賢く組み合わせることで、Salesforce プラットフォーム上で、より動的で、スケーラブルかつ効率的なビジネスオートメーションソリューションを構築することが可能です。開発者としてこれらのツールを深く理解し、適切に活用することが、プロジェクトの成功に繋がります。

コメント