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にレコードデータを登録してみます。