承認(Approval)テーブルに関する通知は標準のビジネスルールを理解した上で設定する

検討・企画

承認依頼時や承認却下時など、承認に伴うメールを送信したい場合があります。ServiceNowの承認に関する通知はちょっと癖のある作りになっており、これを理解していないと上手に設定することができません。この記事では標準の作りを説明した上で、新たな通知の設定方法を示します。

標準の承認(Approval)テーブルに関する通知の仕組み

承認(Approval)テーブルはServiceNowの多くの機能から共通で用いられるテーブルです。これを考慮してか、標準で柔軟性・拡張性のある通知の仕組みが存在します。まずはそれについて説明します。

概要(図解)

図解すると、こんなイメージで動きます。ポイントは、Insert, Updateトリガの通知(Notification)ではなく、イベントトリガの通知(Notification)になっている点です。なお、標準でイベントが作成されるのは承認依頼時(state=requested)、承認否認・却下時(state=rejected)、承認キャンセル時(state=cancelled)で、それぞれに対して通知が設定されているかはテーブルによります。

標準の承認(Approval)テーブルに関する通知の仕組み(概要)

そして、イベントを作成しているのが2つのBusiness Rule(ビジネスルール)です。これらは、承認対象のレコードがタスク(Task)テーブルを継承しているテーブルのレコードの場合と、継承していないテーブルのレコードの場合で使い分けられています。以下、これらの動作について説明します。

承認対象がタスク(Task)テーブルを継承している場合

タスク(Task)テーブルを継承しているテーブルに対する承認のステータスに変更があると、”Approval Events (Task)”というビジネスルール(Business Rule)によってイベントが作成され、通知が行われます。

イベント作成部分に注目してコードを抜き出すと次のようになっています。

if (current.state.changes() && current.state=='cancelled') {
   var event = "approval.cancelled";
   if (isRequest) // (引用者コメント) 承認対象のレコードがRequest[sc_request]テーブルのレコードかを判定
      event = "request.approval.cancelled";
   else if (isSCTask) // (引用者コメント) Catalog Task[sc_task]テーブルのレコードかを判定
      event = "sc_task.approval.cancelled";
   else if (isStdChange) // (引用者コメント) Standard Change Approval[std_change_proposal]テーブルのレコードかを判定
      event = "std_change_proposal.approval.cancelled";

   gs.eventQueue(event, current, gs.getUserID(), gs.getUserName());
}

if (current.state.changes() && current.state=='requested') {
   var event = "approval.inserted";
   if (isRequest)
      event = "request.approval.inserted";
   else if (isSCTask)
      event = "sc_task.approval.inserted";
   else if (isStdChange)
      event = "std_change_proposal.approval.inserted";

   gs.eventQueue(event, current, gs.getUserID(), gs.getUserName());
   updateTask(current, current.approver.getDisplayValue() + " requested to approve task");
}

// (引用者コメント) state=='rejected', state=='approved'の場合は省略

つまり、承認対象がRequest[sc_request]、Catalog Task[sc_task]、Standard Change Approval[std_change_proposal]テーブルのレコードであれば”<テーブル名>.approval.<inserted or rejected or cancelled>”イベントを、その他のタスク継承テーブルのレコードであれば”approval.<inserted or rejected or cancelled>”イベントを作成しています

承認対象がタスク(Task)テーブルを継承していない場合

タスク(Task)テーブルを継承していないテーブルに対する承認のステータスに変更があると、”Approval Events (Non-Task)”というビジネスルール(Business Rule)によってイベントが作成され、通知が行われます。

イベント作成部分に注目してコードを抜き出すと次のようになっています。

function getEventPrefix(sourceTable) {
	if (sourceTable!=null && sourceTable.startsWith('kb_template')) {
		return "kb_knowledge";
	}
	return sourceTable;
}

function sendEventsNonTask() {
   if (!current.state.changes()) 
      return;

   var event = getEventPrefix(current.source_table);
   switch (current.state + "") {    
      case 'cancelled':
         event += ".approval.cancelled";
         gs.eventQueue(event, current, gs.getUserID(), gs.getUserName());
         break;
      case 'requested':
         event += ".approval.inserted";
         gs.eventQueue(event, current, gs.getUserID(), gs.getUserName());
         updateRecord(current, current.approver.getDisplayValue() + " requested to approve task");
	 break;
      case 'rejected':
         event += ".approval.rejected";
         gs.eventQueue(event, current, current.state, previous.state);
         var isAuto = ((current.operation() == 'insert') && isFD)?" auto":"";
         updateRecord(current, current.approver.getDisplayValue() + isAuto + " rejected the task.", 
               current.comments.getJournalEntry(-1));
         notifyMyFriends(current);
         break;
      case 'approved':
         var isAuto = ((current.operation() == 'insert') && isFD)?" auto":"";
         updateRecord(current, current.approver.getDisplayValue() + isAuto + " approved the task.", 
               current.comments.getJournalEntry(-1));
         break;
      default: 
   }
         
 }

タスク継承テーブルの場合と違って、switchで分岐されていて途中で切りづらかったので長めに引用しましたが、結局やっているのは“<テーブル名>.approval.<inserted or rejected or cancelled>”イベントを作っているだけです。

承認に関する通知の設定方法

これまでの仕組みを踏まえ、標準を最大限に尊重した、承認に関する通知の設定ズ方法を説明します。

承認対象がタスク(Task)テーブルを継承している場合

タスクを継承している場合は、2つ選択肢があります。どちらを選択するかはポリシーによりますが、結局どちらも標準のコンポーネントをいじる必要があり、それであればより標準の意図をくみ取っていると考えられ、柔軟性・拡張性の高い方法1がよいと考えています。

方法1: 新しいイベントを作り、”Approval Events (Task)” Business Ruleを変更する

新しいイベントを作成し、それを”Approval Events (Task)” Business Ruleから発生させるようにする方法です。この方法は標準のビジネスルール(Business Rule)を変更することにはなりますが、このビジネスルール自体が拡張を前提とした書き方になっていることから、この変更は許容され得ると考えます。

次のタスク(Task)テーブルを継承したMy Task Tableに対する承認依頼が行われたときの通知を作成する場合を例に具体的な手順を示します。

タスク(Task)テーブルを継承したMy Task Table
  1. Event Registoryにイベントを登録する
  2. “Approval Events (Task)” Business Ruleを変更し、登録したイベントを発生させるようにコードを追加する
  3. 登録したイベントに対する通知を作成する
1. Event Registoryにイベントを登録する

メニューの検索欄に”sysevent_register.list”と入力しイベントの一覧に遷移し、”New”ボタンよりイベントを登録します。今回は”mytask.approval.inserted”というイベントを登録しました。

イベントの登録方法
2. “Approval Events (Task)” Business Ruleを変更し、登録したイベントを発生させるようにコードを追加する

変更を加えた部分のみ記載します。標準のコードに従い、テーブルをチェックしてイベントを作成する一連の処理を1つ加えます。

// (追加箇所) My Task Tableに対する承認かどうかを判定
function checkMyTask() {
    var task = current.sysapproval.sys_class_name || current.source_table;
    return (task == 'u_my_task_table');
}

// (追加箇所) 判定結果を変数に格納
var isMyTask = checkMyTask();

if (current.state.changes() && current.state == 'requested') {
    var event = "approval.inserted";
    if (isRequest)
        event = "request.approval.inserted";
    else if (isSCTask)
        event = "sc_task.approval.inserted";
    else if (isStdChange)
        event = "std_change_proposal.approval.inserted";
    // (追加箇所) My Task Tableに対する承認の場合に発生させるイベントを指定
    else if (isMyTask)
        event = "mytask.approval.inserted";

    gs.eventQueue(event, current, gs.getUserID(), gs.getUserName());
    updateTask(current, current.approver.getDisplayValue() + " requested to approve task");
}
3. 登録したイベントに対する通知を作成する

最後に、イベントトリガの通知を作成します。メニュー > System Notification > Email > Notificationsより通知のリストを開き、”New”ボタンより通知を作成します。イベントトリガの通知を作成するためには、Send whenを”Event is fired”、Event nameを上記で作成したイベントに設定します。

イベントトリガの通知の作成方法

こちらの方法では、ビジネスルールを変更しますが、標準の他の通知には影響を与えません。

方法2: 標準の”approval.<inserted or rejected or cancelled>”イベントに対する通知をつぶした上で新たな作成する

二つ目の方法は、”Approval Events (Task)” ビジネスルール自体は変更せず、標準で生成される”approval.<inserted or rejected or cancelled>”イベントに対する標準の通知を非アクティブにした上で、同イベントに対する通知を新たに作成し、テーブルの判定はConditionにおいてTask typeなどで行う方法です。

上述のように、”Approval Events (Task)”ビジネスルールは承認対象がRequest[sc_request]、Catalog Task[sc_task]、Standard Change Approval[std_change_proposal]以外のタスク継承テーブルであれば、常に”approval.<inserted or rejected or cancelled>”イベントを発生させます。そして、このイベントに対しては標準で通知が設定されています。そのため、標準の通知を非アクティブにした上で、”approval.<inserted or rejected or cancelled>”イベントに対する通知を作成することで、タスク継承テーブルに対する承認に関する新たな通知を設定することができます。

しかし、特に条件を設定しなければ、Request、Catalog Task、Standard Change Approvalテーブル以外の全てのタスク継承テーブルに対して同じ通知が送信されてしまうので、下図のようにConditionにおいてTask typeなどでフィルタをかける必要があります。

方法2の場合の通知の作成方法

こちらは標準の他の通知を非アクティブにする必要がありますが、一方で標準のビジネスルール(Business Rule)をいじる必要はありません。

承認対象がタスク(Task)テーブルを継承していない場合

この場合は、“Approval Events (Non-Task)”ビジネスルールが”<テーブル名>.approval.<inserted or rejected or cancelled>”イベントを作ってくれるので、やることはイベントの登録とイベントトリガの通知の作成だけです。

  1. Event Registoryに”<テーブル名>.approval.<inserted or rejected or cancelled>”という名前のを登録する
  2. 登録したイベントに対する通知を作成する

具体的な手順は上記タスクテーブルを継承する場合を参照ください。

おわりに

承認テーブルは多くのテーブルから利用されるため、承認対象テーブルごとに承認を設定するための仕組みが標準で組み込まれています。承認に対する通知を設定するときは、これを理解した上で上手にやってあげることで、通知が増えても複雑化せずに済みます。

以上です。

コメント

  1. 匿名 より:

    本投稿の内容は、
    承認(Approval)テーブルのビジネスルール(Approval Events (Task) or Approval Events (Non-Task))が発行するイベントを、通知(Notification)側の”Event name”に指定することで、通知が送信できる旨の説明をいただいていると把握しております。

    投稿で記載いただいているサンプルコードとして、
    新規で作られたイベントや通知(Notification)に設定しているテーブルは”My Task Table”なのですが、
    Approvalから発行しているイベントは”current”(Approvalテーブルのレコード)が指定されているため、対象とするテーブルに差異が生じるのではと思います。

    その場合、通知(Notification)の本文や宛先のフィールド指定(Users/Groups in fields)で”My Task Table”のフィールドを指定しても、正しく参照してくれずフィールド指定した部分が空白になるものと思っていました。

    私の知識不足で大変恐縮なのですが、
    このようなテーブルの差異が生じた場合においても、
    通知(Notification)の本文などで”My Task Table”のフィールドを指定し、
    正しく表示させる術はあるのでしょうか?

    ご確認いただけると幸いです。

    • hayapi より:

      ◆ ご質問内容の理解と整理
      >Approvalから発行しているイベントは”current”(Approvalテーブルのレコード)が指定されているため、対象とするテーブルに差異が生じるのではと思います。
      >通知(Notification)の本文などで”My Task Table”のフィールドを指定し、正しく表示させる術はあるのでしょうか?

      こちらのご指摘について、「対象とするテーブルに差異が生じる」というのは半分正しく半分正しくないと考えます。
      これはシステムのデータベース設計の話だと思っていて、承認行為を扱う際、JIRAなどチケット自体に承認の情報(承認者や承認状況)を持たせるシステムもありますが、
      ServiceNowでは承認対象のテーブルとは独立して承認行為を管理するテーブル(Approvalテーブル)が存在するため、
      承認に関する通知として、対象テーブルが承認(Approval)テーブルになっていることは、設計上自然なことであると考えます。
      例えば、承認(Approval)テーブルに関する標準の通知(Notification)である”Approval Request”という通知においても、対象テーブルは承認(Approval)テーブルであり、承認対象となるTaskテーブルなどにはなっていません。

      一方で、匿名さんが「通知(Notification)の本文などで”My Task Table”のフィールドを指定し、」とおっしゃる通り、
      承認に関して承認者等に通知を送る際には、承認対象の情報(何に対して承認するの?)がないと意思決定が難しいため、
      承認に関する通知の中に、承認対象のレコードの情報を含めたいという要件はよくある要件であって、そのため、匿名さんが「対象とするテーブルに差異が生じる」とおっしゃっているのも理解できます。

      以上を踏まえると、今回お答えすべき問いは、
      「承認(Approval)テーブルを対象とする通知において、承認対象のレコードをの情報を含めるためにはどうしたらよいか?」
      だと考えます。

      ◆ 対象テーブルがTask継承テーブルの場合の標準の考え方
      この問いに対してServiceNowの標準では、Taskテーブルを継承したテーブルに対する承認については答えを用意しています。

      上記でも引用した標準の”Approval Request”という通知の中身(中身は”change.itil.approve.role”というEmail Template)を見てみると、次のようなコードが記載されています。
      >

      Comments:

      >

      ${sysapproval.description}

      ApprovalテーブルのApproval for[sysapproval]フィールドはTaskテーブルへのReferenceフィールドになっており、dot-walkによってその値を参照し通知メールの内容に含めるように設定されています。
      このように、承認対象がTask参照テーブルであれば、Approval for[sysapproval]フィールドからdot-walkでたどることで、フィールドを参照することができます。

      ◆ 対象テーブルがTaskテーブルではない場合の対応
      個人的にはほとんど遭遇したことがないのですが、まれにTaskを継承していない自作のテーブルに対する承認をApprovalテーブルで実施したい場合があります。
      この場合は、ApprovalテーブルのApproving[document_id]フィールドに承認対象のレコードが格納されるはずです。
      このケースでは、Email Scriptを用いてApproving[document_id]フィールドの値からGlideRecordを用いて対象レコードの値を取得してくる必要があると思われます。

      ◆ 承認に対する通知で承認対象のレコードの詳細を示さないのも一つの手
      また、上述のように対応方法は存在するものの、承認に関する通知の中では詳細を示さず、
      あくまでApprovalレコードのフィールドのみを用いて、「XXさんからYYというレコードに関する承認依頼が来ています。承認はこちらのリンクを参照ください」という通知にとどめるのも一つです。
      というのも、承認対象のテーブルごとに通知を作っていると、通知の数が増えて開発・保守工数が増えますし、
      承認対象が明細を持っているような場合(例えば、会計に関する承認など)は、通知メールの中に明細情報も含めようとすると、コード量も増えますし視認性も悪くなります。
      そのため、通知はあくまでServiceNowの画面に誘導する導線として捉えるというのも一つの選択肢かなと思います。

      以上です。

      • 匿名 より:

        ご丁寧なご解説ありがとうございます。
        すごくわかりやすくとても勉強になりました。

        > ◆ 対象テーブルがTaskテーブルではない場合の対応

        やりたいのは上記でした。

        > このケースでは、Email Scriptを用いてApproving[document_id]フィールドの値からGlideRecordを用いて対象レコードの値を取得してくる必要があると思われます。

        上記も実際に試してみて実現できることを確認いたしました。
        ちなみに正しい方法かはわかりませんが、
        「Message HTML」内でApproving[document_id]からdot-walkでたどることも可能でした。
        ありがとうございます。

        • hayapi より:

          ご返信ありがとうございます。
          >「Message HTML」内でApproving[document_id]からdot-walkでたどることも可能でした。
          こんなことも可能なのですね。こちらこそ勉強になりました。

タイトルとURLをコピーしました