kintoneで安全に在庫管理を行うテクニック

目次

はじめに

今回は、以前紹介した「bulkRequestを使った複数アプリの一括処理(在庫管理や予約申請など)」を、kintoneアプリに実装する方法を紹介します。
具体的には、在庫アプリと出庫アプリの2つを使って、「出庫アプリでXX個の出庫を登録すると、在庫アプリの在庫がXX個減る」というしくみを作ります。
また、kintone JavaScript Client(@kintone/rest-api-client)を使用して、REST APIのコードを簡潔にしています。非常に便利なツールなので、これからコードを書く方はぜひ参考にしてみてください。

デモ環境

デモ環境 (External link) で実際に動作を確認できます。
ログイン情報は cybozu developer networkデモ環境で確認してください。

完成イメージ

まずは、完成イメージから見ていきましょう。
在庫アプリはこのような状態です。

出庫アプリに出庫数を登録します。

すると、出庫アプリには出庫数が登録されます。

また、在庫アプリからは在庫数が減りました!

在庫が足りない場合は、このようなエラーを表示します。

①「在庫数の確認時」と②「在庫数の変更時」のリビジョンが異なる場合(①と②の間に在庫のデータが更新された場合)はこのようなエラーを表示します。

これで安全に在庫管理ができますね。

kintoneの設定

「在庫」アプリの作成

フィールド名 フィールドタイプ フィールドコード 備考
商品コード 文字列(1行) itemCode 必須項目にする
値の重複を禁止する
商品名 文字列(1行) itemName 必須項目にする
在庫数 数値 stockNum 必須項目にする

「出庫」アプリの作成

フィールド名 フィールドタイプ フィールドコード 備考
出庫先 文字列(1行) destination
商品コード ルックアップ itemCode 必須項目にする
関連付けるアプリ:在庫アプリ
コピー元のフィールド:商品コード
ほかのフィールドのコピー:「商品名」に「在庫アプリの商品名」
商品名 文字列(1行) itemName
出庫数 数値 pickNum 必須項目にする

「出庫」アプリのJS/CSS設定

「アプリの設定 > JavaScript / CSSでカスタマイズ」の「PC用のJavaScriptファイル」に以下のURL/ファイルを設定します。

  • kintone JavaScript Client(@kintone/rest-api-client)
    以下のURLを指定します。
    https://js.cybozu.com/kintone-rest-api-client/5.5.2/KintoneRestAPIClient.min.js
  • 以下のサンプルコードをエディタにコピーして、ファイル名を「sample.js」、文字コードを「UTF-8N」で保存、アップロードします。
    ファイル名は任意ですが、ファイルの拡張子は「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
/*
 * kintoneで安全に在庫を行うサンプルプログラム
 * Copyright (c) 2024 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
*/

(() => {
  'use strict';

  kintone.events.on('app.record.create.submit', async (event) => {
    // 保存するレコードの情報を取得
    const outBoundRecord = event.record;
    // 在庫管理アプリのアプリIDを取得
    const stockAppId = kintone.app.getLookupTargetAppId('itemCode');
    // 出庫管理アプリのアプリIDを取得
    const outBoundAppId = kintone.app.getId();
    // 出庫数を取得
    const pickNum = Number(outBoundRecord.pickNum.value);
    // 出庫先を取得
    const destination = outBoundRecord.destination.value;
    // 商品コードを取得
    const itemCode = outBoundRecord.itemCode.value;

    try {
      // KintoneRestAPIClientのインスタンスを作成
      const client = new KintoneRestAPIClient();

      // 在庫管理アプリから該当商品のレコードを取得
      const {records: stockRecords} = await client.record.getRecords({
        app: stockAppId,
        query: 'itemCode = "' + itemCode + '"',
        fields: ['$id', '$revision', 'stockNum']
      });

      // 商品レコードが1件取得できなかった場合はエラーを返す
      if (stockRecords.length !== 1) {
        event.error = '商品が特定できません。';
        return event;
      }

      // 現在の在庫数を取得
      const stockNum = Number(stockRecords[0].stockNum.value);
      // 出庫後の在庫数を計算
      const newStockNum = stockNum - pickNum;

      // 在庫が足りない場合はエラーメッセージを表示して終了
      if (newStockNum < 0) {
        event.error = `在庫が足りません。今の在庫数は ${stockNum} です。`;
        return event;
      }

      // 在庫管理アプリのレコード更新と出庫管理アプリのレコード登録のリクエストを設定
      const params = {
        requests: [
          // 在庫管理アプリの在庫数更新
          {
            method: 'PUT',
            api: '/k/v1/record.json',
            payload: {
              app: stockAppId,
              id: stockRecords[0].$id.value,
              record: {
                stockNum: {value: newStockNum}
              },
              revision: stockRecords[0].$revision.value
            }
          },
          // 出庫管理アプリの出庫情報登録
          {
            method: 'POST',
            api: '/k/v1/record.json',
            payload: {
              app: outBoundAppId,
              record: {
                destination: {value: destination},
                itemCode: {value: itemCode},
                pickNum: {value: pickNum}
              }
            }
          }
        ]
      };

      try {
        // 在庫更新と出庫登録をbulkRequestで同時に実行
        const bulkResp = await client.bulkRequest(params);
        // 登録した出庫情報の詳細ページにリダイレクト
        location.href = '/k/' + outBoundAppId + '/show#record=' + bulkResp.results[1].id;
        // レコードの保存はbulkRequest内で実行済みなので、
        // 保存イベントの動作はキャンセルする
        return false;
      } catch (err) {
        // bulkRequestに失敗した場合のエラーハンドリング
        console.log(err);
        event.error = '出庫に失敗しました。';
        return event;
      }
    } catch (err) {
      // 在庫管理アプリから商品のレコード取得に失敗した場合のエラーハンドリング
      console.log(err);
      event.error = '商品を取得できませんでした。';
      return event;
    }
  });
})();

ポイントは以下の3つです!

  • bulkRequest(kintoneBulkRequest)を使って、2つのアプリ(在庫、出庫)を同時に操作します。
  • bulkRequestによる在庫アプリの更新時にはリビジョンを指定します。
  • bulkRequestが成功した場合は「return false」として、レコードの登録自体はキャンセルします。(94行目)

リビジョンについて

kintoneでは、各レコードの更新履歴をリビジョン(revision)で管理しています。
レコード作成時にはリビジョンが1に設定され、その後、レコードを保存するたびにリビジョン番号が1ずつ増加します。

sample.jsでは、出庫時にまず在庫管理アプリから在庫情報を取得します。
この際に取得したリビジョン値は、その時点での最新の状態を表す値となります。
その後、出庫後の在庫数を計算し、レコードを更新する際に、取得したリビジョン値を指定します。

更新処理では、指定されたリビジョンと現在のレコードのリビジョンが比較されます。
両方のリビジョンが一致している場合のみ、更新処理が実行されます。
一致しない場合は、取得から更新までの間に他のプロセスでレコードが変更されたことを意味し、衝突を防ぐために更新処理は中止されます。
詳細は次のAPIドキュメントを参考ください。
1件レコード更新時のパラメータ

更新時にリビジョンを指定しない場合

レコード更新時にリビジョンを指定しない場合、リビジョン番号は検証されません。
意図せず他の変更を上書きしてしまうリスクがありますが、複数のデータを一括で更新する場合や、競合リスクが低いケースでは便利に活用できることもあります。

終わりに

リビジョンの使い方わかりましたでしょうか。
サンプルコードはレコードの登録、出庫にしか対応していません。
ぜひレコードの編集や入庫にも対応させてみてください。

変更履歴

  • 2019/06/10
    • 利用ライブラリをkintone Utility Library for JavaScriptからkintone JS SDKに変更
  • 2024/07/25
    • 利用ライブラリをkintone JS SDKからkintone JavaScript Clientに変更
information

このTipsは、2024年7月版kintoneで動作を確認しています。