カテゴリー内の他の記事

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

Moment.js はメンテナンスモードになり、日付処理できる代替ライブラリへの移行が推奨されています。
代替ライブラリのひとつ Luxon については、kintone カスタマイズでの導入方法の紹介記事があります。

Index

概要

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

前提条件と注意事項

  • このカスタマイズには、クラウド版 Garoon または パッケージ版 Garoon 4.10以降の環境が必要です。
  • ワークフロー JavaScriptカスタマイズは、JavaScriptを適用した後に申請されたワークフローが対象になります。
    それ以前に申請されたワークフローには適用されません。
  • サンプルプログラムは、その動作を保証するものではありません。
  • サンプルプログラムの技術的なサポート等は行っていません。
  • 代理承認時にカスタマイズが動作しない不具合があることを確認しております。(2019/2/21追記)

できること

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

完成イメージ 

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

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

___________________.png

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

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

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

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

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

__________.png

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

項目名 項目タイプ 項目コード 必須 備考
開始日時 日付(日付と時刻) From  
終了日時 日付(日付と時刻) To  
予定メニュー メニュー Menu  

出張/休みをメニュー項目とします。

※デフォルトの予定メニュー選択肢です。
タイトル 文字列(1行) Title  
メモ 文字列(複数行) Memo    
公開方法 ラジオボタン Scope  公開/非公開をラジオ項目とします。

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

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

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

3. 適用ファイルの準備

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

ポイント

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

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

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

    ________.png

    ※ 申請フォームの詳細画面に「JavaScript / CSSによるカスタマイズ」というリンクが表示されない場合、ワークフローのカスタマイズが許可されていない場合はリンクが表示されませんので、こちらをご参照下さい。
  2. [カスタマイズ] 項目に「適用する」を選択します。wf_to_sch.jsが使用するjQuery、Moment.jsおよび作成した「wf_to_sch.js」ファイルを追加し、「設定する」をクリックします。

    JS__.png

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

  • jQuery
    https://js.cybozu.com/jquery/3.3.1/jquery.min.js
  • moment.js
    https://js.cybozu.com/momentjs/2.20.1/moment.min.js

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

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

おわりに

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

このTipsは、2018年5月版 Garoonで確認したものになります。

記事に関するフィードバック

記事のコメント欄は記事に対するフィードバックをする場となっております。
右の記事フィードバックのためのガイドを参照してコメントしてください。
記事のリンク切れなど、気になる点がある場合も、こちらのフォームからフィードバックいただけますと幸いです。

Avatar
倉さん

例えば、承認が2段階での承認の際に、2回起動して同じ内容が2件投入されてしまいます。

最終2段目の承認と1段目の承認の見分け(制御)は可能ですか?

Avatar
cybozu Development team

倉西様

cybozu developer networkをご活用いただき、ありがとうございます。cybozu developer network事務局です。

ご質問いただいている件ですが、workflow.request.approve.submit.success で、経路ごとに設定している「stepCode(経路ステップコード)」を取得することができます。

https://developer.cybozu.io/hc/ja/articles/115005298746

経路ステップの設定:https://jp.cybozu.help/ja/g/admin/application/workflow/routestep_item

 

なお記事のコメント欄では、記事に誤りがある、記事通りに動作しない、といった直接記事に関する投稿を受け付けております。

技術的なご質問がある場合は、是非コミュニティもご活用頂けますと幸いです。

今後ともどうぞよろしくお願いいたします。

Avatar
Tsutsumi Ayumi

パッケージ版を使っています。

こちらのソースを参考にコードを書いてみたのですが、404エラーになってしまいます。

おそらくURLの箇所を変更しなければならないというところはわかるのですが、

調べてみても解決策が分かりませんでした。教えて頂ければ幸いです。

 

 

Avatar
cybozu Development team

Tsutsumi Ayumi 様

お世話になっております。
cybozu developer network 運営局です。

2点ご教示頂けないでしょうか?

  • パッケージ版のバージョンは、 Garoon 4.10 以上でしょうか?
  • 404エラーが発生するのは、どんな画面操作を行ったときでしょうか?
    また404エラーが発生するときの画面のURLをご教示頂けないでしょうか
    (サーバーIPやホスト名などは xxx などの適当な文字列に置き換えて秘匿していただきたいです)

よろしくお願いいたします。

Avatar
Tsutsumi Ayumi

運営局 担当様

迅速な対応ありがとうございます。

詳細を省いてしまい、すみません。

 

・パッケージ版のバージョンは5.0.1です。

・承認をするときに次の画面に進まないので、コンソールで確認すると、下記のエラーが走りました。

POST http://xxx.xxx.xxx/g/util_api/util/api.csp 404 (Not Found)

 

以上、よろしくお願いいたします。

Avatar
cybozu Development team

Tsutsumi Ayumi 様

お世話になっております。

cybozu developer network 運営局です。

ご教示くださりありがとうございます。

バージョンは問題なさそうです。
先のコメントで頂いたとおり、お使いの Garoon の API のリクエストパスが異なっているためかと思われます。

このコードの SOAP API のパスは、クラウド版に合わせてホスト名のあとのパスが /g/ である前提( Garoon の URL は http(s)://xxx.xxx.xxx/g/)になっています。
ご利用の Garoon URL の ホスト名のあとのパスをご確認いただけないでしょうか。

もし、Garoon の URL が http(s)://xxx.xxx.xxx/grn/ でしたら、
77行目の場合は url: '/g/util_api/util/api.csp', を url: '/grn/util_api/util/api.csp', に修正する必要があります。
なお、url: '/g/util_api/util/api.csp', を url: 'http://xxx.xxx.xxx/grn/util_api/util/api.csp', のような、フルパス指定もできます。

103行目、169行目でも SOAP API のパスを指定しているので、同様に修正が必要です。

また、134行目で、スケジュールのコメント欄に記述する、申請したワークフローのURLがあるのですが、こちらも
'/g/workflow/view.csp?pid=' + request.id + を '/grn/workflow/view.csp?pid=' + request.id + に修正します。

よろしくお願いいたします。

Avatar
Tsutsumi Ayumi

担当者様

ご丁寧な回答ありがとうございます。

GaroonのURLがhttp://xxx.xxx.xxx/scripts/cbgrn/grn.exe/なので、
フルパスでhttp://xxx.xxx.xxx/scripts/cbgrn/grn.exe/util_api/util/api.csp と指定したのですが、
やはり同じエラーになってしまいます。。

Avatar
cybozu Development team

Tsutsumi Ayumi 様

お世話になっております。cybozu developer network 運営局です。

度々失礼いたします。

.csp を無くしてリクエストした場合はいかがでしょうか?
なので、url: '/g/util_api/util/api.csp' を url: '/scripts/cbgrn/grn.exe/util_api/util/api', としていただくイメージです。

手元のオンプレ環境で検証したところ、 .csp を外すと正常にリクエストが返却されたことを確認いたしました。

Avatar
Tsutsumi Ayumi

ご担当者様

ご丁寧な回答ありがとうございます。
上記のようにurlを変更したところ、404エラーは消えました。

ただ、特にエラーがないにも関わらず、スケジュールが登録されません。。

申請ボタンを押した瞬間をコンソールで確認したところ、<user id ="underfined">だったので、ここが問題だと思っているのですが、
解決方法を教えて頂けないでしょうか。度々で本当に申し訳ございません。

Avatar
cybozu Development team

Tsutsumi Ayumi 様

お世話になっております。cybozu developer network 運営局です。

URLの問題が解決したとのこと、よかったです。

スケジュールが登録されないことについてですが、手元の環境では再現しないので特定が難しいです。
URL以外にコードを変更した箇所はございますか?

秘匿情報は xxx などに置き換えた状態で、カスタマイズで適用しているコードを教えていただけないでしょうか?

Avatar
Tsutsumi Ayumi

担当者様

 

とりあえずテストで作成しているので、上のコードそのままなのですが。。

URL以外に変更している箇所はありません。

Avatar
cybozu Development team

Tsutsumi Ayumi 様

お世話になっております。cybozu developer network 運営局です。

こちらで試してみたところ、エラー出ずスケジュール登録できました。

util_api/util/api.cspの「.csp」はすべて外しましたでしょうか?

cybozu Development teamにより編集されました
Avatar
Tsutsumi Ayumi

.cspは外しました。

下記のコードを利用しているので、確認して頂ければ幸いです。。

 

/**
 * Garoon JavaScript、SOAP APIを使ったサンプルプログラム
 *
 * 「wf_to_sch.js」ファイル
 *
 * Copyright (c) 2018 Cybozu
 *
 * Licensed under the MIT License
 */
(function() {
  'use strict';
  var myJQuery = jQuery.noConflict(true);
  (function($) {

    /**
     * 共通SOAPコンテンツ
     * ${XXXX}の箇所は実施処理等に合わせて置換して使用
     */
    var 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}の箇所は入力値等で置換して使用
     */
    var 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エスケープ
    var escapeHtml = function(str) {
      return str
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;');
    };

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

      // リクエストトークンの取得
      var tokenRequest = SOAP_TEMPLATE;
      tokenRequest = tokenRequest.replace('${PARAMETERS}', '');
      tokenRequest = tokenRequest.split('${ACTION}').join('UtilGetRequestToken');
      tokenRequest = tokenRequest.replace('${CREATED}', moment().add(-9, 'hours').format('YYYY-MM-DDTHH:mm:ssZ'));
      $.ajax({
        type: 'post',
        url: '/scripts/cbgrn/grn.exe/util_api/util/api',
        cache: false,
        async: false,
        data: tokenRequest
      })
        .then(function(respForToken) {
          defer.resolve($(respForToken).find('request_token').text());
        });
      // 本来はエラー処理を実施

      return defer.promise();
    };

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

      // 申請者情報(申請者のID)の取得
      var applicantRequest = SOAP_TEMPLATE;
      applicantRequest = applicantRequest.replace('${PARAMETERS}', '<login_name>' + userNm + '</login_name>');
      applicantRequest = applicantRequest.split('${ACTION}').join('BaseGetUsersByLoginName');
      applicantRequest = applicantRequest.replace(
        '${CREATED}', moment().add(-9, 'hours').format('YYYY-MM-DDTHH:mm:ssZ'));

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

      return defer.promise();
    };

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

      return getRequestToken().then(function(requestToken) {
        // 申請者のユーザ名をSOAPで処理できるID形式に変換
        return getApplicantId(request.applicant.code).then(function(applicantId) {
          // スケジュールSOAP API固有のパラメータを設定
          var 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を付加
          var url = location.protocol + '//' + location.hostname +
                    '/scripts/cbgrn/grn.exe/workflow/view?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;'));

          var startTime = moment(request.items.From.value.date + 'T' + request.items.From.value.time + ':00');
          schAddParam = schAddParam.replace(
            '${START_TIME}', startTime.add(-9, 'hours').format('YYYY-MM-DDTHH:mm:ssZ')); // 開始日時(UTC対応)
          var endTime = moment(request.items.To.value.date + 'T' + request.items.To.value.time + ':00');
          schAddParam = schAddParam.replace(
            '${END_TIME}', endTime.add(-9, 'hours').format('YYYY-MM-DDTHH:mm:ssZ')); // 終了日時(UTC対応)

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

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


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

          var schAddRequest = SOAP_TEMPLATE;
          // SOAPパラメータを完成させる
          schAddRequest = schAddRequest.replace('${PARAMETERS}', schAddParam);
          // 実行処理を指定
          schAddRequest = schAddRequest.split('${ACTION}').join('ScheduleAddEvents');
          schAddRequest = schAddRequest.replace(
            '${CREATED}', moment().add(-9, 'hours').format('YYYY-MM-DDTHH:mm:ssZ'));

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

Tsutsumi Ayumi 様

頂いたソースコードは問題ないと思います。

>「申請ボタンを押した瞬間をコンソールで確認したところ、<user id ="underfined">だったので...」

また、過去頂いた↑の情報について1点見間違いしたことが分かりました。失礼いたしました。
承認ボタンではなく、申請ボタンを押した時点で、エラーが出ましたよね。
こちらのサンプルプログラムは「申請」ボタンを押下するタイミングで実行されない為、
エラーは他のカスタマイズによるものではないかと思います。

ご確認お願い致します。

 

 

Avatar
Tsutsumi Ayumi

ご担当者様

>「申請ボタンを押した瞬間をコンソールで確認したところ、<user id ="underfined">だったので...」

上は申請ボタンではなく承認ボタンの誤りでした。申し訳ございません。。

機能が出来るかどうかの確認なので、特に何のカスタマイズも行っておらずそのままです。上のソースコードと下記のファイルしかありません。

 

Avatar
cybozu Development team

Tsutsumi Ayumi 様

頂いたソースコード(URLをこちらの環境に合わせて変更しただけ)で確認したところ、ご指摘のエラーが出ずスケジュール登録できました。

環境依存ではないかと思います。

https://developer.cybozu.io/hc/ja/articles/207613916 を参考にデバッグすると原因を特定できるかと思います。

例:ユーザー情報を処理する行にbreakpoint入れてどこで処理止まるか判明できます。

 

cybozu Development teamにより編集されました
Avatar
Tsutsumi Ayumi

ご担当者様

ご丁寧にありがとうございます。

URLを参考に調べてみたいと思います。

サインインしてコメントを残してください。