在庫管理の機能強化

上記の在庫管理でできることを増やしました。
なお、分かりやすさ重視でフィールドコードはフィールド名と一致させています。

JavaScript

(() => {
  'use strict';

  // レコード追加画面で保存したときの処理
  kintone.events.on('app.record.create.submit', async (event) => {
    const record = event.record;
    const stockAppId = kintone.app.getLookupTargetAppId('商品コード');
    const outBoundAppId = kintone.app.getId();
    const tableRecords = record.出庫商品リスト.value;
    let hasError = false;
    let errorMessages = new Set();
    let itemCodeSet = new Set();

    try {
      const client = new KintoneRestAPIClient();
      const requests = [];
      const stockUpdates = [];

      for (const row of tableRecords) {
        const itemCode = row.value.商品コード.value;
        const outBoundNum = row.value.出庫数.value;
        row.value.商品コード.error = null;
        row.value.出庫数.error = null;

        // [商品コード]のバリデーション
        if (!itemCode) {
          row.value.商品コード.error = '[商品コード]を入力してください。';
          hasError = true;
          errorMessages.add('[商品コード]が未入力の行があります。');
        } else if (itemCodeSet.has(itemCode)) {
          row.value.商品コード.error = '[商品コード]が重複しています。';
          hasError = true;
          errorMessages.add('[商品コード]が重複している行があります。');
        } else {
          itemCodeSet.add(itemCode);
        }

        // [出庫数]のバリデーション
        if (!outBoundNum) {
          row.value.出庫数.error = '[出庫数]を入力してください。';
          hasError = true;
          errorMessages.add('[出庫数]が未入力の行があります。');
        } else if (!/^\d+$/.test(outBoundNum)) {
          row.value.出庫数.error = '[出庫数]は半角数字で入力してください。';
          hasError = true;
          errorMessages.add('[出庫数]に半角数字以外が入力されている行があります。');
        } else if (Number(outBoundNum) < 1) {
          row.value.出庫数.error = '[出庫数]は1以上にしてください。';
          hasError = true;
          errorMessages.add('[出庫数]に0が入力されている行があります。');
        }

        if (!itemCode || !outBoundNum || !/^\d+$/.test(outBoundNum) || Number(outBoundNum) < 1) {
          continue;
        }

        // 在庫アプリから[商品コード]が一致するレコードを取得
        const { records: stockRecords } = await client.record.getRecords({
          app: stockAppId,
          query: '商品コード = "' + itemCode + '"',
          fields: ['$id', '$revision', '在庫数']
        });

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

        // 現時点の[在庫数]が足りているか確認
        if (newStockNum < 0) {
          row.value.出庫数.error = `現在の在庫は ${stockNum} です。`;
          hasError = true;
          errorMessages.add(`在庫が不足している商品があります。`);
          continue;
        }

        // [在庫数]更新リクエストを作成
        stockUpdates.push({
          id: stockRecords[0].$id.value,
          record: {
            在庫数: { value: newStockNum }
          },
          revision: stockRecords[0].$revision.value
        });
      }

      // エラーがあったら画面上部にエラーメッセージを表示
      if (hasError) {
        event.error = Array.from(errorMessages).join('\n');
        return event;
      }

      // [在庫数]更新リクエストをバッチ送信
      while (stockUpdates.length > 0) {
        const batch = stockUpdates.splice(0, 99);
        requests.push({
          method: 'PUT',
          api: '/k/v1/records.json',
          payload: {
            app: stockAppId,
            records: batch
          }
        });
      }

      // 出庫アプリのレコード追加リクエストを作成
      requests.push({
        method: 'POST',
        api: '/k/v1/record.json',
        payload: {
          app: outBoundAppId,
          record: {
            出庫先: { value: record.出庫先.value },
            出庫商品リスト: { value: tableRecords }
          }
        }
      });

      try {
        // リクエスト一括送信
        const bulkResp = await client.bulkRequest({ requests });
        location.href = '/k/' + outBoundAppId + '/show#record=' + bulkResp.results[requests.length - 1].id;
        return false;
      } catch (err) {
        // リクエスト一括送信に失敗した場合のエラーハンドリング
        console.log(err);
        event.error = '出庫に失敗しました。';
        return event;
      }
    } catch (err) {
      // 在庫アプリからレコード取得に失敗した場合のエラーハンドリング
      console.log(err);
      event.error = '商品を取得できませんでした。';
      return event;
    }
  });

  // レコード追加画面で[取消]を編集不可にする
  kintone.events.on('app.record.create.show', (event) => {
    event.record.取消.disabled = true;
    return event;
  });

  // レコード詳細画面を表示したときの処理
  kintone.events.on('app.record.detail.show', async (event) => {
    const record = event.record;

    // [取消]に「済」のチェックがあるレコードでは何もしない
    if (record.取消.value.includes('済')) {
      return event;
    }

    // 取消ボタンを作成
    const cancelButton = document.createElement('button');
    cancelButton.id = 'cancel-button';
    cancelButton.innerText = '取消';
    cancelButton.style.marginTop = '18px';
    cancelButton.style.marginLeft = '18px';

    // 取消ボタンのクリックイベント
    cancelButton.onclick = async () => {
      if (confirm('本当にこの出庫処理を取消しますか?')) {
        try {
          const client = new KintoneRestAPIClient();
          const stockAppId = kintone.app.getLookupTargetAppId('商品コード');
          const recordId = kintone.app.record.getId();
          const requests = [];
          const stockUpdates = [];

          // [出庫商品リスト]をループして[在庫数]を元に戻す
          for (const row of record.出庫商品リスト.value) {
            const itemCode = row.value.商品コード.value;
            const outBoundNum = Number(row.value.出庫数.value);

            const { records: stockRecords } = await client.record.getRecords({
              app: stockAppId,
              query: '商品コード = "' + itemCode + '"',
              fields: ['$id', '$revision', '在庫数']
            });

            if (stockRecords.length === 0) {
              continue;
            }

            const stockNum = Number(stockRecords[0].在庫数.value);
            const newStockNum = stockNum + outBoundNum;

            stockUpdates.push({
              id: stockRecords[0].$id.value,
              record: {
                在庫数: { value: newStockNum }
              },
              revision: stockRecords[0].$revision.value
            });
          }

          // [在庫数]更新リクエストをバッチ送信
          while (stockUpdates.length > 0) {
            const batch = stockUpdates.splice(0, 99);
            requests.push({
              method: 'PUT',
              api: '/k/v1/records.json',
              payload: {
                app: stockAppId,
                records: batch
              }
            });
          }

          // [取消]に「済」チェックを入れるリクエスト
          requests.push({
            method: 'PUT',
            api: '/k/v1/record.json',
            payload: {
              app: kintone.app.getId(),
              id: recordId,
              record: {
                取消: { value: ['済'] }
              }
            }
          });

          // リクエスト一括送信
          await client.bulkRequest({ requests });

          // 取消処理の結果通知
          alert('取消が完了しました。');
          location.reload();
        } catch (err) {
          console.error(err);
          alert('取消に失敗しました。');
        }
      }
    };

    // パンくずリストの下に取消ボタンを設置
    kintone.app.record.getHeaderMenuSpaceElement().appendChild(cancelButton);

    return event;
  });

})();
「いいね!」 3

CSS

/* 画面上部に出すエラーメッセージを改行可能にする */
.notifier-body-cybozu li {
  white-space: pre;
  color: white;
}
  • エラーハンドリングの強化

「いいね!」 2
  • 複数商品の同時出庫を可能に

在庫アプリの状態

出庫アプリでレコード追加

在庫アプリで[在庫数]が減る

「いいね!」 1
  • 出庫処理を取り消せる

出庫処理を取り消したいレコードの詳細画面で、取消ボタンを押す。

確認ダイアログに「OK」を返すと、出庫取消が実行される。

在庫アプリの[在庫数]が、取り消したレコードの[出庫数]の分だけ増える。

[取消]チェックボックスに「済」のチェックが入り、取消ボタンが非表示になる。
(同じレコードを何度も取り消すミスの防止)

「いいね!」 1

複数商品を一気に出庫したり、取り消せたりするのはいいですね!実用的な内容だと思います。

ちょっと気になった点があったので、共有させてください。想定済みであればスルーでお願いします。

  • テーブルに20行以上商品があると出庫に失敗する
    • BulkRequestに含められるリクエストの上限が20件なので、1行ずつ処理すると、在庫レコードの更新19件 + 出庫レコードの追加1件 = 合計20件 が最大値になるかなと思います
    • ここはエラーチェックしておいた方がいい気がします
  • 出庫取り消しが複数回できる可能性がある
    • 在庫レコードのUPDATEと出庫レコードのUPDATEが別々に発行されているので、在庫数の復活だけ成功して、取消フラグを立てるのが失敗する可能性があります
    • レコード追加時と同様に、BulkRequestにまとめるといいと思います
「いいね!」 2

修正しました。

  • [在庫数]更新リクエストを複数のレコードを更新するに変えて
    これの一度にレコード100件までしか対応できない制限もリクエストのバッチ化で回避。

  • 取消するときのリクエストを bulkRequest にまとめ、リビジョン比較もするように。

「いいね!」 1

このトピックはベストアンサーに選ばれた返信から 3 日が経過したので自動的にクローズされました。新たに返信することはできません。