409 Conflictエラーの回避方法(bulkRequestを利用した登録/更新処理の繰り返し)について

Tips「安全に在庫管理を行うテクニック」で紹介されている様な別アプリへの登録/更新処理を真似て、
サブテーブルのデータ件数分、登録/更新を繰り返す処理を作成していますが、以下の点で困っています。
皆様のお知恵を拝借できれば幸いです。よろしくお願いします。

・アプリの構成

入力アプリ
 ・サブテーブル(”明細”)に”商品名”と”数量”を持つ。
 ・プロセス管理のあるステータス実行時に更新処理を実行。
 ・更新処理にて、サブテーブルの”商品名”、”数量”を使い、
  商品マスタの”在庫数”を更新、在庫アプリにレコードを追加する。

商品マスタ
 ・”商品名”と”在庫数”を持つ

在庫アプリ
 ・”変更種別”、”商品名”、”数量”を持つ

・課題

サブテーブルに同一商品があった場合に、409 Conflictエラーを回避したい。

またはエラーが発生した際に、更新処理を再実行させたい。

・現状

待ち処理(sleep関数)を作り、更新処理(zaikoUpdate)を順番に一定間隔で実行しようとしていますが、

更新処理(zaikoUpdate)は一定間隔で実行されるものの、データ更新処理(bulkRequest)はほぼ同じタイミングで実行されてしまう。

 

・作成中のソース

(function() {
  'use strict';

  // 在庫アプリのアプリID
  var zaikoAppId = 92;  //在庫AP
  // 商品マスタアプリのアプリID
  var itemAppId = kintone.app.getLookupTargetAppId('商品名');  //商品マスタAP

  function zaikoUpdate(tableRecord){
    // 商品マスタから在庫数を取得
    var itemCode = tableRecord.value['商品名'].value;
    var getParams = {
      app: itemAppId,
      query: '商品名 = "' + itemCode + '"'
    };
    console.log(itemCode + '登録処理開始');
    kintone.api(
      kintone.api.url('/k/v1/records', true),
      'GET',
      getParams
    ).then(function(getRes) {
      // データがあれば更新
      var recordId = getRes.records[0].$id.value;
      var revision = getRes.records[0].$revision.value;

      var itemCode = tableRecord.value['商品名'].value;
      var pickingNum = Number(tableRecord.value['数量'].value);

      // 在庫数
      var stockNum = Number(getRes.records[0].在庫数.value);
      // 出庫後の在庫数
      var newStockNum = stockNum - pickingNum;

      // 商品マスタの在庫数を更新し、在庫アプリに出庫データを追加する。
      var requestParams = {
        'requests':[
          {
            // 商品マスタアプリの在庫数を更新
            'method': 'PUT',
            'api': '/k/v1/record.json',
            'payload': {
              'app': itemAppId,
              'id': recordId,
              // 商品マスタのデータ取得時と更新時でrevisionが異なる場合はエラーにする
              'revision': revision,
              'record': {
                '在庫数': {
                  'value': newStockNum
                }
              }
            }
          },
          {
            // 在庫アプリに出庫情報を追加登録
            'method': 'POST',
            'api': '/k/v1/record.json',
            'payload': {
              'app': zaikoAppId,
              'record': {
                '変更種別': {
                  'value': '出庫'
                },
                '商品名': {
                  'value': itemCode
                },
                '数量': {
                  'value': pickingNum * -1
                }
              }
            }
          }
        ]
      };
      kintone.api(
        kintone.api.url('/k/v1/bulkRequest', true),
        'POST',
        requestParams
      ).then(function(postRes) {
        console.log(itemCode + 'を登録しました');
        return;
      }).catch(function(error) {
        console.log(error);
        console.log(itemCode + 'の登録に失敗しました');
        event.error= '出庫に失敗しました。';
        return event;
      });
    }).catch(function(error) {
      console.log(error);
      event.error = 'データ未登録';
      return event;
    });
  }

  function sleep(msec){
    var d1 = new Date();
    while (true) {
      var d2 = new Date();
      if (d2 - d1 > msec) {
        break;
      }
    }
  }

  function zaikoUpdateWait(val){
    zaikoUpdate(val);
    sleep(3000);
  }

  // 配列を順番に処理する
  function sequential(array) {
    // .reduce で順番に処理。初期値は第二引数の Promise.resolve()
    return array.reduce((promise, val) => {
      // promise が常に前回の戻り値であることを利用して
      // Promise を連鎖させるのがポイント
      return promise.then(res => zaikoUpdateWait(val));
    }, Promise.resolve());
  }

  kintone.events.on(['app.record.detail.process.proceed'], function(event) {
    if(event.action.value == '出庫する'){
      var record = event.record;

      // 明細
      var tableRecords = record.明細.value;

      sequential(tableRecords);
    }
    return event;
  });
})();

Sachiho Fujimori さん

このようなケースでは、サブテーブルのデータ件数分のAPI を発行するのではなく、
レコードの一括登録、レコードの一括更新をまとめてbulkRequest API で一回で処理するようにします。

同一商品の在庫更新は、あらかじめ集計して一つのレコードにしておきます。
集計した結果により、在庫数が足らない場合は、API前に、エラーにします。

あと、気になったのは、sequential(tableRecords) 処理で
sequential の完了を待たずに、return event;されてしまうと思います。

Sachiho Fujimori さん

ソースをあまり細かく読めていませんので、私の勘違いがあったらすみません。

出庫処理の明細をループして、1行ずつ、在庫アプリ追加と商品マスタ更新を、bulkRequestで処理するイメージであっていますか?

この方法の場合、仮に、409 Conflictが発生した時の再処理に成功したとして、将来的に”在庫数が0の場合に出庫しない”という付与条件が出てきた場合、サブテーブルの途中で409 Conflictが発生して、且つ、在庫が0になっていた場合に、該当出庫伝票の途中まではコミットしてしまっている。という状態になりそうな気がします。

”もし私が作るなら”ですが、

入力情報が

A 100個
B 200個
A 100個

であった場合、

在庫アプリ追加用に A:100,B:200,A:100 の変数を準備

商品マスタ更新用に A:-200,B-200 の変数を準備

そのうえで、レコード追加3件と在庫更新2件を、一度にbulkRequestで実行します。

前段の処理を組むのが面倒くさいですが、409 Conflict が発生した時は、処理を中止したうえで、もう一回ユーザーさんに保存を押してもらえばよいので、後段は楽になるかなと思います。

所感まで。

rex0220さん。

すみません。1分差で回答内容まで被りました(笑)

松村さん、こんにちは。

松村さんの説明のほうが具体的で、わかりやすいですね。

rex0220さん、松村さん

早速のご回答ありがとうございます。

ロジックの組み方が誤っていたのですね。

bulkRequest API 一回で処理すれば良いことに気がつけませんでした。

助かりました、ありがとうございましたm(__)m