【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 で動作を確認しています。