kintone と Microsoft Azure を連携してみよう (Azure Functions その2)

著者名:菊地 宏司

目次

caution
警告

この Tips は以下の記事の続きとなります。
初めて Azure Functions をご利用される方は(Azure Functions その 1)から始めることを推奨しています。
kintoneとMicrosoft Azureを連携してみよう (Azure Functionsその1)

こんにちは。菊地です。

前回 に引き続き今回も kintone と Azure の連携について紹介したいと思います。
今回のテーマは Azure Functions を使った定期実行処理についてです。
これまでは、サーバーを立てそこからスケジュール実行でプログラムを動かして処理をするのが一般的でした。
これが Azure Functions を使うことでサーバーレスを実現できます。
サーバーレスにすることでバックエンドアプリケーションを動かすためのマシンの設定が不要になり管理コストも下がるというメリットがあります。

イメージ

今回の定期実行のイメージです。
kintone 側には交通費申請アプリ交通費集計アプリの 2 つのアプリがあります。
ユーザは日ごろの業務で発生する交通費を交通費申請アプリに登録します。
ここではプロセス管理をしており、「未申請(下書き)」→「上長確認中」→「承認」とフローが流れます。
承認された交通費は
翌月の1日 AM2:00に集計処理が実行
され、部署ごとに集計された月毎データが交通費集計アプリへ登録されます。

Azure Functionsの準備

Azure Functions 関数 (External link) の準備を参考に設定します。

「Azure Functions 関数の準備」では[タイマー]、[JavaScript]を選択してください。

パッケージのインストール

今回使用するパッケージです。

  • request(HttpRequest を行うためのパッケージ)
  • request-promise(Promise を利用するためのパッケージ)
  • moment(日付操作用のパッケージ)
1
npm install request request-promise moment --save-dev

インストール方法は kintoneとMicrosoft Azureを連携してみよう (Azure Functionsその1) を参照してください。

kintone アプリテンプレート

AzureFunctions連携(定期実行).zip

アプリテンプレートの読み込みについては アプリテンプレートファイルをkintoneに登録する (External link) を参照してください。

テンプレートからのアプリ作成については 登録済みのテンプレートからアプリを作成する (External link) を参照してください。

JavaScript 処理フロー

開発準備ができたところで実装していきましょう。実装する処理はこちらです。

  1. 交通費申請アプリで前月に登録された承認済みのレコードを取得
  2. 取得したレコードから交通費を部署毎に集計
  3. 集計した結果を交通費集計アプリに登録

ソースコードの記述

コードの記載箇所は kintoneとMicrosoft Azureを連携してみよう (Azure Functionsその1) を参照してください。

  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
const rq = require('request-promise');
const moment = require('moment');

const BASE_URL = 'https://{subdomain}.cybozu.com/k/v1/';
const APP_ID_AGG = 1; // 交通費集計アプリID
const APP_ID_AGG_TRANS = 2; // 交通費申請アプリID
const API_TOKEN_AGG = '{交通費集計の app token}'; // 交通費集計アプリのAPIトークン (閲覧/追加)
const API_TOKEN_TRANS = '{交通費申請の app token}'; // 交通費申請アプリのAPIトークン (閲覧)
const TARGET_DEPARTMENT = ['営業部', '総務部', '経理部', '開発部'];

module.exports = function(context, myTimer) {

  // 交通費申請アプリで前月に登録された承認済みのレコードを取得
  function aggregateTransportationCost(target_dept) {
    const query = 'ステータス = "承認" and ' +
                    'year = "' + moment().year() + '" and ' +
                    'month = "' + moment().subtract(1, 'months').format('M') + '" and ' +
                    'department in ("' + target_dept + '")';
    const body = {
      app: APP_ID_AGG_TRANS,
      query: query,
      fields: ['sum_money']
    };
    const get_option = {
      url: BASE_URL + 'records.json',
      method: 'GET',
      json: body,
      headers: {
        'X-Cybozu-API-Token': API_TOKEN_TRANS
      }
    };
    return rq(get_option);
  }

  // 集計処理
  function aggregate(departments, opt_cnt, opt_resultObj) {
    const i = opt_cnt || 0;
    const obj = opt_resultObj || {};
    const dept = departments[i];

    // すべての部署の処理が終わったらreturn
    if (!dept) {
      return obj;
    }

    // 取得したレコードから交通費を部署毎に集計
    return aggregateTransportationCost(dept).then((resp) => {
      let sum = 0;
      for (const j in resp.records) {
        if (!Object.prototype.hasOwnProperty.call(resp.records, j)) {
          continue;
        }
        sum += parseInt(resp.records[j].sum_money.value, 10);
      }
      obj[dept] = sum;
      return aggregate(departments, parseInt(i, 10) + 1, obj);
    });
  }

  // 集計した結果を交通費集計アプリに登録
  function postAggregateData(obj) {
    const records = [];
    for (const i in obj) {
      if (!Object.prototype.hasOwnProperty.call(obj, i)) {
        continue;
      }
      records.push({
        年: {value: moment().year()},
        月: {value: moment().subtract(1, 'months').format('M')},
        部署: {value: i},
        合計金額: {value: obj[i]}
      });
    }

    const post_option = {
      url: BASE_URL + 'records.json',
      method: 'POST',
      headers: {
        'X-Cybozu-API-Token': API_TOKEN_AGG,
        'Content-Type': 'application/json'
      },
      json: {
        app: APP_ID_AGG,
        records: records
      }
    };
    // レコードを登録
    return rq(post_option);
  }

  aggregate(TARGET_DEPARTMENT)
    .then(postAggregateData)
    .then((resp) => {
      context.done();
    })
    .catch((e) => {
      context.log('error');
      context.fail();
    });
};

上記の JS をコードに貼り付けて kintone のサブドメイン、アプリ ID、API トークンを変更してください。

1
2
3
4
5
const BASE_URL = 'https://{subdomain}.cybozu.com/k/v1/';
const APP_ID_AGG = 1; // 交通費集計アプリID
const APP_ID_AGG_TRANS = 2; // 交通費申請アプリID
const API_TOKEN_AGG = '{交通費集計の app token}'; // 交通費集計アプリのAPIトークン (閲覧/追加)
const API_TOKEN_TRANS = '{交通費申請の app token}'; // 交通費申請アプリのAPIトークン (閲覧)
  • API トークンについては APIトークンを生成する (External link) を参照してください。
  • 交通費集計アプリの API トークンには閲覧と追加の権限を設定してください。
  • 交通費申請アプリの API トークンには閲覧の権限を設定してください。

スケジューラーの設定

スケジュール項目に cron 式で設定します。

1
0 0 2 1 * *

フォーマット形式

項目 設定する値
format {second} {minute} {hour} {day} {month} {day of the week}
To trigger once every 5 minutes: 0 */5 * * * *
To trigger once at the top of every hour: 0 0 * * * *
To trigger once every two hours: 0 0 */2 * * *
To trigger once every hour from 9 AM to 5 PM: 0 0 9-17 * * *
To trigger At 9:30 AM every day: 0 30 9 * * *
To trigger At 9:30 AM every weekday: 0 30 9 * * 1-5

さいごに

Azure Functions を利用した定期実行処理いかがでしたでしょうか。
今回は Azure Functions を使い kintone レコードの取得+追加をしました。
アイデアによってさまざまな利用パターンが考えられるのでぜひ試してみてください。

次回は kintone レコード追加時、 Azure Storage にレコードデータを登録してみます。