【Garoon JavaScript API】ワークフロー承認後にメールを送信する

目次

概要

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

前提条件と注意事項

  • このカスタマイズには、クラウド版Garoonまたはパッケージ版Garoon 5.0.0以降の環境が必要です。
  • ワークフローのJavaScriptカスタマイズは、JavaScriptを適用した後に申請されたワークフローが対象となります。
    それ以前に申請されたワークフローには適用されません。
  • 事前に メールサーバーの設定 (External link) ユーザーアカウントの設定 (External link) が必要です。
  • 代理承認時にカスタマイズが動作しない不具合を確認しています。(2019/2/21追記)
  • このカスタマイズを利用できるのは、cybozu.com共通管理者権限、またはワークフローのアプリケーション管理者権限をもつユーザーに限られます。(2021/8/30追記)

できること

ワークフローとメールを連携させることにより、メール送信前に上司の確認・承認をはさむことができます。
たとえば、社外向けの情報発信メールについて、送信前に上司による確認するようなケースが想定されます。

完成イメージ

Garoonのワークフローにて承認すると、申請内容がメールで送信される流れになります。
「承認する」ボタンをクリックすると、メールが送信されます。

ワークフローの設定に対して、JavaScriptファイルを設定しカスタマイズしていきます。

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

ワークフローの項目の内容は会社によって異なります。
ここでは、メール送信のために必要な項目を網羅した申請フォームを作成し、JavaScript / CSSカスタマイズを設定する流れを説明します。

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

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

申請フォームの内容は、いわゆるメーラーです。それぞれの項目は以下のとおり設定してください。
項目コードは、JavaScriptコード内でそれぞれの項目を指定するための一意の文字列になります。

項目名 項目タイプ 項目コード 必須 備考
申請理由 文字列(複数行) 承認者への通達事項を記載します。
JavaScript では扱わないため、項目コードは不要です。
To 文字列(複数行) To 必須 メールアドレスを1行1アドレスで入力します。
Cc 文字列(複数行) Cc メールアドレスを1行1アドレスで入力します。
Bcc 文字列(複数行) Bcc メールアドレスを1行1アドレスで入力します。
タイトル 文字列(1行) Subject 必須
本文 文字列(複数行) Body 必須
添付ファイル ファイル添付 Attach 5つまで登録可能とします。

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

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

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

3. 適用ファイルの準備

次のサンプルコードをエディタにコピーして、ファイル名を「wf_to_mail.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
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/**
 * Garoon JavaScript、SOAP APIを使ったサンプルプログラム
 *
 * 「wf_to_mail.js」ファイル
 *
 * Copyright (c) 2018 Cybozu
 *
 * Licensed under the MIT License
 */

(() => {
  'use strict';
  const myJQuery = jQuery.noConflict(true);
  (($) => {

    /**
     * 共通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}' +
            '</${ACTION}>' +
          '</soap:Body>' +
        '</soap:Envelope>';

    /**
     * メール送信パラメータ
     * ${XXXX}の箇所は入力値等で置換して使用
     */
    const MAIL_SEND_TEMPLATE =
        '<parameters>' +
        '<request_token>${REQUEST_TOKEN}</request_token>' +
        '<send_mail xmlns="" ' +
          'account_id="${ACCOUNT_ID}" ' +
          'to_string="${TO}" ${CC} ${BCC}>' +
          '<mail xmlns="" key="dummy" version="dummy" subject="${SUBJECT}" body="${BODY}" ' +
            'folder_key="1"' +
            '>' +
            '${FILE_DEFS}' +
          '</mail>' +
          '${FILE_CONTENTS}' +
          '</send_mail>' +
        '</parameters>';

    /**
     * ファイル定義
     * ${XXXX}の箇所は入力値等で置換して使用
     */
    const FILE_DEF_TEMPLATE =
        '<file id="${INDEX}" name="${FILE_NAME}" mime_type="${FILE_MIME_TYPE}"></file>';

    /**
     * ファイルコンテンツ
     * ${XXXX}の箇所は入力値等で置換して使用
     */
    const FILE_CONTENT_TEMPLATE =
        '<file xmlns="" id="${INDEX}">' +
          '<content xmlns="">${FILE_CONTENT}</content>' +
        '</file>';

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

    // リクエストトークン取得
    const getRequestToken = () => {
      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();
    };

    // 実行ユーザのメール設定を取得する
    const getMailProfile = () => {
      const defer = $.Deferred();

      let profileRequest = SOAP_TEMPLATE;
      profileRequest = profileRequest.replace('${PARAMETERS}',
        '<parameters include_system_profile="false"></parameters>');
      profileRequest = profileRequest.split('${ACTION}').join('MailGetProfiles');
      profileRequest = profileRequest.replace('${CREATED}', luxon.DateTime.utc().startOf('second').toISO({suppressMilliseconds: true}));
      $.ajax({
        type: 'post',
        url: '/g/cbpapi/mail/api.csp?',
        cache: false,
        async: false,
        data: profileRequest
      })
        .then((respForProfile) => {
          defer.resolve($(respForProfile).find('from_name').attr('account_id'));
        });
      // 本来はエラー処理を実施

      return defer.promise();
    };

    // 指定IDのファイルを取得
    const downloadFile = (fileId) => {
      const url = '/api/v1/workflow/admin/files/' + fileId;

      return garoon.api(url, 'GET', {});
    };

    // 申請に登録された全ファイルを取得
    const downloadFiles = (files, index) => {
      let optIndex = index || 0;

      if (files.length === 0) {
        return new Promise((resolve, reject) => {
          resolve(files);
        });
      }

      return downloadFile(files[optIndex].id).then((resp) => {
        const content = resp.data.content;
        files[optIndex].content = content;
        optIndex++;
        if (optIndex === files.length) {
          return files;
        }
        return downloadFiles(files, optIndex);
      });
      // 本来はエラー処理を記述
    };

    // ワークフロー承認イベントで起動する
    // 申請内容をメール送信する
    garoon.events.on('workflow.request.approve.submit.success', (event) => {
      const request = event.request;

      return getRequestToken().then((requestToken) => {
        // ログインユーザのメール設定取得
        return getMailProfile().then((accountId) => {
          // 添付ファイルのbase64Binary表現を取得
          return downloadFiles(request.items.Attach.value).then((files) => {
            // ファイルパラメータの構築
            const fileDefs = [];
            const fileContents = [];
            for (let i = 0; i < files.length; i++) {
              let fileDef = FILE_DEF_TEMPLATE;
              fileDef = fileDef.replace('${INDEX}', i + 1);
              fileDef = fileDef.replace('${FILE_NAME}', files[i].name);
              fileDef = fileDef.replace('${FILE_MIME_TYPE}', files[i].contentType);
              fileDefs.push(fileDef);

              let fileContent = FILE_CONTENT_TEMPLATE;
              fileContent = fileContent.replace('${INDEX}', i + 1);
              fileContent = fileContent.replace('${FILE_CONTENT}', files[i].content);
              fileContents.push(fileContent);
            }

            // メール送信パラメータの構築
            let mailSendParam = MAIL_SEND_TEMPLATE;
            mailSendParam = mailSendParam.replace('${REQUEST_TOKEN}', escapeHtml(requestToken));
            mailSendParam = mailSendParam.replace('${ACCOUNT_ID}', accountId);
            mailSendParam = mailSendParam.replace('${SUBJECT}',
              request.items.Subject.value + '(申請者:' + request.applicant.name + ')');
            mailSendParam = mailSendParam.replace('${BODY}',
              request.items.Body.value.split('\n').join('&#xA;'));

            mailSendParam = mailSendParam.replace('${TO}', request.items.To.value.split('\n').join(','));

            let ccString = '';
            if (request.items.Cc.value.length > 0) {
              ccString = request.items.Cc.value.split('\n').join(',');
            }
            // eslint-disable-next-line no-template-curly-in-string
            mailSendParam = mailSendParam.replace('${CC}', 'cc_string="' + ccString + '"');

            let bccString = '';
            if (request.items.Bcc.value.length > 0) {
              bccString = request.items.Bcc.value.split('\n').join(',');
            }
            mailSendParam = mailSendParam.replace('${BCC}', 'bcc_string="' + bccString + '"');

            mailSendParam = mailSendParam.replace('${FILE_DEFS}', fileDefs.join(''));
            mailSendParam = mailSendParam.replace('${FILE_CONTENTS}', fileContents.join(''));

            // SOAPパラメータを構築
            let mailSendRequest = SOAP_TEMPLATE;
            mailSendRequest = mailSendRequest.replace('${PARAMETERS}', mailSendParam);
            mailSendRequest = mailSendRequest.split('${ACTION}').join('MailSendMails');
            mailSendRequest = mailSendRequest.replace(
              '${CREATED}', luxon.DateTime.utc().startOf('second').toISO({suppressMilliseconds: true}));

            // メール送信実行
            $.ajax({
              type: 'post',
              url: '/g/cbpapi/mail/api.csp?',
              cache: false,
              async: false,
              data: mailSendRequest
            });
            // 本来はエラー処理を実施
          });
        });
      });
    });
  })(myJQuery);
})();

ポイント

  • workflow.request.approve.submit.successイベントに実装することにより、承認が行われた後に起動する処理を作成できます。
  • ワークフローに登録した添付ファイルを、5つまで添付ファイルとして送信できます。
  • 送信者は申請者ではなく、承認者になります。申請者名義での送信はできないため、タイトルに申請者名を明記しています。

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

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

    ワークフローのカスタマイズが許可されていない場合は、「JavaScript / CSSによるカスタマイズ」のリンクが表示されません。
    Garoonヘルプ( クラウド版 (External link) パッケージ版 (External link) )を参照し、ワークフローのカスタマイズを許可してください。

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

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

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

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

おわりに

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

更新履歴

  • 2021年8月30日
    添付ファイルのIDを取得する処理を、workflow.request.approve.submit.successイベントのワークフローオブジェクトを使う方法から、 申請データを取得するを使う方法に変更
information

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