kintoneにTODAY関数がないのでGASで夜間バッチつくりました

Google Apps Script

kintoneにはTODAY関数がありません。(2020/05時点)

契約日からの経過日数とか、期限までの残日数など、TODAY関数があったら簡単に計算できるものが、実現できない。

特にSalesForceを使ったことのある人から、「kintoneってTODAY関数ないんですね(うゎぁ、kintoneやば…)」的なことを言われると悔しいですよね。

ということで、困ったときのGoogle先生。kintoneと相性のよいGoogle Apps Scriptを利用して無料でTODAY関数もどきをつくる方法を(少々雑ですが)まとめました。

kintoneに日付フィールドをつくる

フィールド名もフィールドコードも「TODAY」にしておきましょう。

「レコード登録時の日付を初期値にする」もチェックしておきます。

GASを作成する

Googleドライブの左上にある「新規」ボタンからその他→Google Apps Scriptをクリックして作成します。」

コードはこちら。

まるごとコピペして、上部の初期設定欄にkintoneの以下の情報を入力して保存してください。

  • サブドメイン
  • アプリ番号
  • アプリ名
  • アプリのAPIトークン
/*---- 初期設定ここから ----*/
// kintoneサブドメイン(https://xxxxxx.cybozu.com/k/99/だったら、xxxxxx)
const SUB_DOMAIN = "xxxxxxx";
// kintoneアプリ番号(https://xxxxxx.cybozu.com/k/99/だったら、99)
const APP_ID = 99;
// kintoneアプリ名
const APP_NAME = "商談アプリ";
// kintone アプリのAPIトークン(レコード閲覧とレコード編集権限があればOK)
const APP_TOKEN = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
/*---- 初期設定ここまで ----*/

function myFunction() {
  // 今日の日付をつくる(yyyy-mm-dd)
  const date = new Date();
  const today = date.getFullYear() + '-' + ('0' + (date.getMonth() + 1)).slice(-2) + '-' + ('0' + date.getDate()).slice(-2);
  // レコードを一括取得
  let resp = getRecords();
  // パラメータ初期化
  var param = [];
  // 更新用のレコードを作成
  for (let i = 0; i < resp.length; i++) {
    const record = resp[i];
    param[i] = {
      "id": record['$id']['value'],
      "record": {
        "TODAY": {
          "value": today
        }
      }
    };
  }
  // レコードを一括更新
  putRecords(param);
}

// kintoneからレコードを一括取得
function getRecords(offset){
  // 取得済みの最後のレコード番号を代入。なければ0
  let lastRecordId = offset || 0;
  let limit = 500; //上限500;

  // kintoneからレコード番号順にレコード取得の準備
  var apps = {
    SHODAN_APP: { appid: APP_ID, name: APP_NAME, token: APP_TOKEN },
  };  
  var kintone_manager = new KintoneManager(SUB_DOMAIN, apps);
  var query = '$id > '+ lastRecordId;
  query += ' order by $id asc';
  query += ' limit ' + limit;
  // レコード取得
  var response = kintone_manager.search("SHODAN_APP", query);
  // 成功すれば200
  var code = response.getResponseCode();
  // 失敗したら終了
  if( code != 200 ) return false;
  var content = JSON.parse(response.getContentText());
  // レコード最大
  let records = content.records;
  lastRecordId = records[records.length - 1].$id.value;
  // 取得したレコード数が取得上限と一致なら、次のデータを取得しに行って結合(繰り返し)
  if( records.length === limit ) records = records.concat(getRecords(lastRecordId));
  return records;
}

// kintoneのレコードを一括更新
function putRecords(records) {
  const limit = 100; //上限100;
  const apps = {
    SHODAN_APP: { appid: APP_ID, name: APP_NAME, token: APP_TOKEN },
  };
  // 更新するレコードを抜き出す
  const records_upd = records.slice(0, limit);
  // 更新してレスポンス取得
  var kintone_manager = new KintoneManager(SUB_DOMAIN, apps);
  var response = kintone_manager.update("SHODAN_APP", records_upd);
  // ステータスコード取得(成功すれば200)
  var code = response.getResponseCode();
  // 200でなければ終了
  if( code != 200 ){ Logger.log(code); return false; }
  // 更新済みのレコードを削除
  records.splice(0, limit);
  // 未更新レコードが残っていれば次の更新処理へ(繰り返し)
  if(records.length > 0) putRecords(records);
}

// kintone
var KintoneManager = (function() {
    "use strict";
    // user, passが指定されれば、パスワード認証
    // 指定されなければ、APIトークン認証
    function KintoneManager(subdomain, apps, user, pass){
        this.subdomain = subdomain;
        this.authorization = null;
        this.apps = apps;

        if (arguments.length > 3) {
            this.authorization = Utilities.base64Encode(user + ":" + pass);
        } else if (arguments.length > 2) {
            // 引数が3つの場合はエンコード済みの認証情報として処理
            this.authorization = user;
        }        
    }
    // レコードの検索
    KintoneManager.prototype.search = function(app_name, query){
       var q = encodeURIComponent(query);
       var app = this.apps[app_name];
       var response = UrlFetchApp.fetch(
         "@1/records.json?app=@2&query=@3"
            .replace(/@1/g, this._getEndpoint(app.guestid))
            .replace(/@2/g, app.appid)
            .replace(/@3/g, q),
         this._getOption(app)
       );
       return response;
    };
    // レコードの更新
    KintoneManager.prototype.update = function(app_name, records) {
        var app = this.apps[app_name];
        var payload = {
          app: app.appid,
          records: records
        };
        var response = UrlFetchApp.fetch(
            "@1/records.json".replace(/@1/g, this._getEndpoint(app.guestid)),
           this._putOption(app, payload)
        );
        return response;
    };
    // GETメソッドの時のオプション情報
    KintoneManager.prototype._getOption = function(app) {
       var option = {
          method: "get",
          headers: this._authorizationHeader(app),
          muteHttpExceptions: true
       };
       return option;
    };
    // PUTメソッドの時のオプション情報
    KintoneManager.prototype._putOption = function(app,payload) {
       var option = {
               method: "put",
               contentType: "application/json",
               headers: this._authorizationHeader(app),
               muteHttpExceptions: true,
               payload: JSON.stringify(payload)
       };
       return option;
    };
    // エンドポイントの取得
    KintoneManager.prototype._getEndpoint = function(guest_id) {
      var endpoint = "https://@1.cybozu.com".replace(/@1/g,this.subdomain);
      if (guest_id == null) {
        return endpoint + "/k/v1";
      } else {
        return endpoint + "/k/guest/@1/v1".replace(/@1/g, guest_id);
      }
    };
    // ヘッダーの認証情報
    KintoneManager.prototype._authorizationHeader = function(app) {
      if (this.authorization) {
         // パスワード認証
         return { "X-Cybozu-Authorization": this.authorization };
      } else if (app.token) {
         // APIトークン認証
         return { "X-Cybozu-API-Token": app.token };
      } else {
        throw new Error("kintone APIを呼ぶための認証情報がありません。");
      }
    };
    return KintoneManager;
})();

コード上部の初期設定部分を修正して保存したら、一度左上のボタンを押して実行(またはデバッグ)してみましょう。諸々の確認画面が出たら許可してください。

成功すれば、アプリ内の全レコードの(手順1でつくった)TODAYフィールドが今日の日付に更新されています。

トリガーをセットする

実行(デバッグ)して正常に動作しているのを確認できたら、毎日自動で更新されるようトリガーをセットします。

左上の時計ボタンを押してトリガー設定画面に行き、

  • 実行する関数→myFunction
  • イベントのソース→時間主導型
  • トリガーのタイプ→日付ベースのタイマー
  • 時刻の選択→午前0時〜1時

をセットして保存します。

kintoneアプリに計算フィールドを作る

例えば、「利用開始日」という日付フィールドがあるとして、その日付から今日までの日数を計算するフィールドをつくります。

計算式はこんな感じです。

IF(利用開始日>0,(TODAY-利用開始日)/24/60/60,””)

簡単に解説すると、「利用開始日」が空欄だったら計算結果も空欄にするために、IF文で囲っています。

「TODAY-利用開始日」で日付フィールド同士の差分を計算していますが、結果が秒単位で出てしまうため、「24(時間)と60(分)と60(秒)」で割って日数にしています。

で、利用開始日が入っていれば経過日数を表示する、だけでなく、この日数が毎日更新されていくフィールドのできあがりです。

※5月22日時点の表示

この「TODAY」フィールドがあれば計算フィールドを組み合わせていろいろつくれます。

終わりに

今後、kintone側でTODAY関数が追加されればまったく無意味な記事になりそうですが、それでもしばらくはニーズがあるかと思って書きました。
Google Apps Script は kintone と相性が良くて、今回のようなkintoneだけでは実現できないバッチ処理も比較的簡単に実現することができます。ぜひご活用ください。

いろいろ端折った部分もあるのでご不明点はコメントください。

弊社ワークスタイルコンサルティングでは、ゆるーくkintoneの代理店をやっています。
kintoneやGASの提案・サポートもしつつ、年間のkintone更新費をお安くできますのでぜひお声がけください^^

コメント

  1. 西巻 より:

    こんにちは!
    GASを使って経過日数を表示しようと思っています。
    上記のGASを「試すためだけのアプリ」では正常に作動するのですが、いざ「実装したいアプリ」で同じことをすると正常に実行されません。実行ログには実行開始→400.0→実行完了とでますが、アプリを見に行くとTODAYが更新されません。
    もし原因がお分かりでしたらご教授頂けないでしょうか?

  2. 林 靖則 より:

    こんにちは。

    本件、拝見させて頂きました。

    KintoneでTODAY関数がないため、とても参考になりました。
    ありがとうございます。

    手順に従い、GASを作成し、実行ボタンを押したところ、
    実行開始
    実行完了
    と表示されましたので、無事成功したと思いました。
    しかし、Kintoneのレコードを確認するとTODAYフィールドが更新されておらず
    困っております。

    何か考えられる原因はございますでしょうか。
    レコードのフィールドは
    「日付」でレコード名およびフィールドコードは TODAY とし、「レコード登録時の日付を初期値にする」もチェックを入れております。

    お手数をお掛けし大変恐縮ですが、原因をご教示頂ければ幸いでございます。
    何卒、よろしくお願い申し上げます。

  3. 宮城 より:

    はじめまして。
    TODAY関数が無く苦戦しているときに記事を見つけて参考にさせて頂きました。
    レコード件数が300件あるアプリで試したのですが、
    100件までは更新されますが101件からは更新がされません。
    プログラミング経験が浅いので申し訳ありませんが、
    500件以上でも対応可能なのでしょうか??

  4. ながすん より:

    みなさまコメント返信できておらずすみません。
    それぞれ検証して1件ずつ返信させていただきますのでもう少々お待ちください。

タイトルとURLをコピーしました