【Garoon JavaScript API】ワークフロー承認後にスケジュールを登録する

目次

概要

2017 年 11 月のアップデートで追加された ワークフロー申請を承認したときのイベント を使い、ワークフローの申請内容をスケジュールに登録します(予定を登録する部分は Garoon SOAP API を使用しています)。

前提条件と注意事項

  • このカスタマイズには、クラウド版 Garoon またはパッケージ版 Garoon 4.10 以降の環境が必要です。
  • ワークフロー JavaScript カスタマイズは、JavaScript を適用してから申請されたワークフローが対象になります。
    それ以前に申請されたワークフローには適用されません。
  • 代理承認時にカスタマイズが動作しない不具合を確認しています。(2019/2/21 追記)

できること

有給休暇、出張など、申請が必要なイベントについてワークフローとスケジュールを連携させることができます。
ワークフローでは、スケジュールへの自動登録機能が搭載されていますが、対象項目の指定等、より柔軟な連携が可能になります。

完成イメージ

Garoon のワークフローにて承認すると、申請内容がスケジュールへ登録される流れになります。

  • 「承認する」ボタンをクリックすると、ワークフローの内容が申請者のスケジュールに登録されます。
  • スケジュールのメモ欄には、承認されたワークフローへのリンクが設定されます。

スケジュール側には特別な設定は不要です。
ワークフローの設定にのみ、JavaScript ファイルを設定しカスタマイズしていきます。

Garoon ワークフローの設定手順

ワークフローの項目の内容は、会社によって異なります。
ここでは、サンプルということで、スケジュールの項目をおおむね網羅した申請フォームに JavaScript/CSS カスタマイズを設定する流れを説明します。

1. ワークフローの申請フォームを作成する

まずは以下の項目を配置して、ワークフローの申請フォームを作成していきます。
申請フォームの作成方法については、Garoon ヘルプ - 申請フォームの作成の流れ クラウド版 (External link) パッケージ版 (External link) を参照してください。

申請フォームは、スケジュールの項目と対応付けます。それぞれの項目は以下のとおり設定してください。
項目コードは、JavaScript コード内でそれぞれの項目を指定するための一意の文字列になります。

項目名 項目タイプ 項目コード 必須 備考
開始日時 日付(日付と時刻) From
終了日時 日付(日付と時刻) To
予定メニュー メニュー Menu 出張/休みをメニュー項目とします。
デフォルトの予定メニュー選択肢です。
タイトル 文字列(1行) Title
メモ 文字列(複数行) Memo
公開方法 ラジオボタン Scope 公開/非公開をラジオ項目とします。

上記のとおり設定が完了したら、土台となる申請フォームの作成は完了です。

2. Javascript/CSSファイルを適用する

申請フォームの作成が完了したので、ここから作成した申請フォームに JavaScript ファイルを適用していきます。

3. 適用ファイルの準備

次のサンプルコードをエディタにコピーして、ファイル名を「wf_to_sch.js」、文字コードを「UTF-8」で保存します。
ファイル名は任意ですが、ファイルの拡張子は「js」にしてください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/**
 * Garoon JavaScript、SOAP APIを使ったサンプルプログラム
 *
 * 「wf_to_sch.js」ファイル
 *
 * Copyright (c) 2018 Cybozu
 *
 * Licensed under the MIT License
 */
(function() {
  'use strict';
  const myJQuery = jQuery.noConflict(true);
  (function($) {

    /**
     * 共通SOAPコンテンツ
     * ${XXXX}の箇所は実施処理等に合わせて置換して使用
     */
    const SOAP_TEMPLATE =
        '<?xml version="1.0" encoding="UTF-8"?>' +
        '<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">' +
          '<soap:Header>' +
            '<Action>${ACTION}</Action>' +
            '<Timestamp>' +
              '<Created>${CREATED}</Created>' +
              '<Expires>2037-08-12T14:45:00Z</Expires>' +
            '</Timestamp>' +
            '<Locale>jp</Locale>' +
          '</soap:Header>' +
          '<soap:Body>' +
            '<${ACTION}>' +
              '<parameters>${PARAMETERS}</parameters>' +
            '</${ACTION}>' +
          '</soap:Body>' +
          '</soap:Envelope>';

    /**
     * スケジュール登録パラメータテンプレート
     * ${XXXX}の箇所は入力値等で置換して使用
     */
    const SCH_ADD_TEMPLATE =
        '<request_token>${REQUEST_TOKEN}</request_token>' +
        '<schedule_event xmlns="" id="dummy" event_type="normal" pubic_type="${SCOPE}" version="dummy" ' +
          'plan="${MENU}" detail="${TITLE}" description="${MEMO}" ' +
          'timezone="Asia/Tokyo" end_timezone="Asia/Tokyo" allday="false" start_only="false">' +
          '<members>' +
            '<member>' +
              '<user id="${USER_ID}"></user>' +
            '</member>' +
          '</members>' +
          '<when>' +
            '<datetime start="${START_TIME}" end="${END_TIME}"></datetime>' +
          '</when>' +
        '</schedule_event>';

    // 文字列をHTMLエスケープ
    const escapeHtml = function(str) {
      return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    };

    // リクエストトークン取得
    const getRequestToken = function() {
      const defer = $.Deferred();

      // リクエストトークンの取得
      let tokenRequest = SOAP_TEMPLATE;
      tokenRequest = tokenRequest.replace('${PARAMETERS}', '');
      tokenRequest = tokenRequest.split('${ACTION}').join('UtilGetRequestToken');
      tokenRequest = tokenRequest.replace('${CREATED}', luxon.DateTime.utc().startOf('second').toISO({suppressMilliseconds: true}));
      $.ajax({
        type: 'post',
        url: '/g/util_api/util/api.csp',
        cache: false,
        async: false,
        data: tokenRequest
      })
        .then((respForToken) => {
          defer.resolve($(respForToken).find('request_token').text());
        });
      // 本来はエラー処理を実施

      return defer.promise();
    };

    // 指定のユーザ名に対応するユーザIDを取得する
    const getApplicantId = function(userNm) {
      const defer = $.Deferred();

      // 申請者情報(申請者のID)の取得
      let applicantRequest = SOAP_TEMPLATE;
      applicantRequest = applicantRequest.replace('${PARAMETERS}', '<login_name>' + userNm + '</login_name>');
      applicantRequest = applicantRequest.split('${ACTION}').join('BaseGetUsersByLoginName');
      applicantRequest = applicantRequest.replace(
        '${CREATED}', luxon.DateTime.utc().startOf('second').toISO({suppressMilliseconds: true}));

      $.ajax({
        type: 'post',
        url: '/g/cbpapi/base/api.csp',
        cache: false,
        async: false,
        data: applicantRequest
      })
        .then((respForApplicant) => {
          defer.resolve($(respForApplicant).find('user').attr('key'));
        });
      // 本来はエラー処理を実施

      return defer.promise();
    };

    // ワークフロー承認イベントで起動する
    // 申請内容をスケジュールに登録する
    garoon.events.on('workflow.request.approve.submit.success', (event) => {
      // 申請内容を取得する
      const request = event.request;

      return getRequestToken().then((requestToken) => {
        // 申請者のユーザ名をSOAPで処理できるID形式に変換
        return getApplicantId(request.applicant.code).then((applicantId) => {
          // スケジュールSOAP API固有のパラメータを設定
          let schAddParam = SCH_ADD_TEMPLATE;

          schAddParam = schAddParam.replace('${REQUEST_TOKEN}', escapeHtml(requestToken));
          schAddParam = schAddParam.replace('${MENU}', request.items.Menu.value); // 予定メニュー
          schAddParam = schAddParam.replace('${TITLE}', request.items.Title.value); // タイトル

          // メモ欄には申請へのURLを付加
          const url = location.protocol + '//' + location.hostname +
                    '/g/workflow/view.csp?pid=' + request.id +
                    '&amp;fid=' + request.folders[request.folders.length - 1].id + '&#xA;';
          // 改行の変換
          schAddParam = schAddParam.replace('${MEMO}', url + request.items.Memo.value.split('\n').join('&#xA;'));

          const startTime = luxon.DateTime.fromISO(request.items.From.value.date + 'T' + request.items.From.value.time + ':00')
            .toUTC().startOf('second').toISO({suppressMilliseconds: true});
          schAddParam = schAddParam.replace(
            '${START_TIME}', startTime); // 開始日時 UTCに変換
          const endTime = luxon.DateTime.fromISO(request.items.To.value.date + 'T' + request.items.To.value.time + ':00')
            .toUTC().startOf('second').toISO({suppressMilliseconds: true});
          schAddParam = schAddParam.replace(
            '${END_TIME}', endTime); // 終了日時 UTCに変換

          schAddParam = schAddParam.replace('${USER_ID}', applicantId); // 申請者

          // 公開方法は、公開⇒public、非公開⇒privateに変換
          let scope;
          if (request.items.Scope.value === '公開') {
            scope = 'public';
          } else if (request.items.Scope.value === '非公開') {
            scope = 'private';
          }

          schAddParam = schAddParam.replace('${SCOPE}', scope);

          let schAddRequest = SOAP_TEMPLATE;
          // SOAPパラメータを完成させる
          schAddRequest = schAddRequest.replace('${PARAMETERS}', schAddParam);
          // 実行処理を指定
          schAddRequest = schAddRequest.split('${ACTION}').join('ScheduleAddEvents');
          schAddRequest = schAddRequest.replace(
            '${CREATED}', luxon.DateTime.utc().startOf('second').toISO({suppressMilliseconds: true}));

          // スケジュール登録の実行
          $.ajax({
            type: 'post',
            url: '/g/cbpapi/schedule/api.csp',
            cache: false,
            async: false,
            data: schAddRequest
          });
          // 本来はエラー処理を実施
        });
      });
    });
  })(myJQuery);
})();

ポイント

  • request.approve.submit.success イベントに実装することにより、承認が行われた後に起動する処理を作成できます。

4. JavaScript/CSSファイルとして使用するファイルのおよびリンクの追加

  1. 「申請フォーム情報」部分の右端にある「JavaScript / CSS によるカスタマイズ」をクリックします。


    ワークフローのカスタマイズが許可されていない場合、申請フォームの詳細画面に「JavaScript / CSS によるカスタマイズ」というリンクが表示されません。
    詳細は、 ワークフローのカスタマイズを許可する (External link) を参照してください。

  2. [カスタマイズ]項目に「適用する」を選択します。wf_to_sch.js が使用する jQuery、Luxon、および作成した「wf_to_sch.js」ファイルを追加し、「設定する」をクリックします。

本カスタマイズでは、 Cybozu CDN の以下のライブラリーを使用します。

  • jQuery
    https://js.cybozu.com/jquery/3.6.4/jquery.min.js
  • Luxon
    https://js.cybozu.com/luxon/3.3.0/luxon.min.js
caution
警告

jQuery、Luxon は wf_to_sch.js より上位に登録してください。

以上ですべての設定は完了です。最初にお見せした完成イメージのとおり、動けば成功です。

おわりに

Garoon API のカスタマイズサンプル ワークフローとスケジュールとの連携方法を紹介しました。
ワークフローの承認実行のタイミングで Garoon 内の別アプリにデータを登録することが簡単にできます。

information

この Tips は、2023 年 4 月版 Garoon で動作を確認しています。