見出し画像

kintoneの編集画面とFormBridge&kViewerで、どちらもルックアップを使いたい

 トヨクモ_kintone連携サービス Advent Calendar 2024に参加させていただきました。

 今回は、職場でFormBrideでkViewerルックアップ使ってて、ちょっと困ったことの自分流の解決策を共有します。

前提条件

  1.  電子申請をしてもらう際に、施設をルックアップで選択したい。

  2.  選択した施設の、施設名やカナ、電話番号、その他の情報なんかもkintoneに一括で登録したい。

  3.  電子申請できない人も居るので、FAXや電話申請も受け付ける。これらはkintoneで職員が入力するので、ルックアップ機能を使いたい。

さらっと考えて気づくこと

  •  (1)(2)は、まさに、FormBridgeとkViewerルックアップがうってつけ!施設マスタ作ってればきっと余裕!

  • (3)は、kintoneにルックアップがあるから、これも余裕!

 どうやら、kintoneとFormBridgeとkViewerで簡単に解決できそうです。

でも、実はここに大きな落とし穴が……
https://guide.kintoneapp.com/formbridge/specification/

FormBridgeからkintoneへの登録の対応状況

 そう、実は、FormBridgeでは、kintoneの「ルックアップ」フィールドや「ほかのフィールドをコピー」に指定したフィールドにはデータを格納できないんです。
 つまり、「電子申請でもkintoneでも同じ部分にルックアップを設定する」ことは、普通にはできないんですね。

 今回は外部からの電子申請ですが、例えば、

こんな風に、kintoneのライセンスを抑えるためにFormBridgeを使う場合も、FormBridgeとkintoneで同じようにルックアップを使うことは課題になっちゃいます。

 なんとかできないかってことで、自分なりに松竹梅で3コース考えてみました。(ちなみに、自分は環境上、竹コースを採用しました。)

解決策

【梅コース】kintoneのCSVエクスポートとインポートを使う。

 FormBridgeからkintoneの「ルックアップ」フィールドにデータは格納できない。じゃあ、FormBridgeからの格納用に「文字列(1行)」のフィールドを作って、そこにルックアップのデータを格納し、kintoneのCSVエクスポートを使って該当データを吐き出した後、フィールド名を変更してインポートしちゃおうっていう方法です。

「FormBridgeからのルックアップ」という文字列フィールドを作る
CSVエクスポートでレコード番号とFormBridgeからのルックアップを指定
こんな感じでルックアップが適用されていないレコードを抽出
フィールド名……
ルックアップに書き換えて保存
データをインポートすると……
ばっちりルックアップに反映できた!
「ほかのフィールドをコピー」に指定したフィールドにも反映されてる!

 kintoneって、「ルックアップ」フィールドにデータをインポートすると「ほかのフィールドをコピー」も自動的に反映してくれるんですね。とっても便利です(ルックアップのマスタ側の文字列に「値の重複を禁止する」のチェックが必要です)。
 これで目的は達成できますが、担当者が定期的にエクスポート・インポートを行う必要があるので、少し面倒です。

【竹コース】カスタマイズでボタン一発で処理

 基本的な考え方は梅コースと同じです。ただ、アプリをカスタマイズして、ボタン一発で内部的に同じような処理をしてみました。
 なお、今回のカスタマイズには、以下の2つのライブラリを利用しています。利用しなくてもカスタマイズは可能ですが、特にkintone REST API Clientは、使うとコードがとってもシンプルになって見やすくなります(少しカスタマイズを経験した人だと分かる、100件、500件、10,000件の壁なんかも、よろしく内部処理してくれます)。

 サンプルでアプリのテンプレートも置いておきます。
 実際にテストするには、FormBridgeルックアップ共存.js内の

  const MASTER_APP_ID = 0; // マスタアプリの appId
  const DATA_APP_ID = 0; // データアプリの appId

ここを環境に合わせて書き換える(設定画面からダウンロードして、修正後再アップロード)必要がありますので、ご注意ください。
 コードはこんな感じ。

(() => {
  'use strict';
  // kintone REST API Client と kintone UI Component が読み込まれている前提

  const MASTER_APP_ID = 0; // マスタアプリの appId
  const DATA_APP_ID = 0; // データアプリの appId

  kintone.events.on('app.record.index.show', (event) => {
    const buttonId = 'lookup-button';
    const buttonText = 'FormBridge申請データルックアップ処理';

    // ボタンを設置する
    const setupButton = () => {
      if (document.getElementById(buttonId)) return;

      const button = new Kuc.Button({
        text: buttonText,
        type: 'submit',
        id: buttonId,
        visible: true,
        disabled: false
      });

      kintone.app.getHeaderMenuSpaceElement().appendChild(button);

      button.addEventListener('click', handleButtonClick);
    };

    // ボタンが押されたときの処理
    const handleButtonClick = async () => {
      const spinner = new Kuc.Spinner({ text: '処理中...' });
      spinner.open();

      try {
        const client = new KintoneRestAPIClient();

        // データアプリの対象レコードを取得
        const targetRecords = await fetchTargetRecords(client);

        // マスタアプリの検索文字列を取得
        const masterRecords = await fetchMasterRecords(client);

        // 更新対象レコードをフィルタリング
        const recordsToUpdate = buildUpdateRecords(targetRecords, masterRecords);

        // レコードを一括更新
        if (recordsToUpdate.length > 0) {
          await client.record.updateAllRecords({
            app: DATA_APP_ID,
            records: recordsToUpdate
          });
        }

        window.alert('作業を終了しました。');
        spinner.close();
        location.reload();
      } catch (error) {
        console.error('エラーが発生しました:', error);
        window.alert(`エラーが発生しました:${error}`);
        spinner.close();
      }
    };

    // 対象レコードを取得する関数
    const fetchTargetRecords = async (client) => {
      return await client.record.getAllRecordsWithCursor({
        app: DATA_APP_ID,
        query: 'ルックアップ = "" and FormBridgeからのルックアップ != ""',
        fields: ['$id', 'FormBridgeからのルックアップ']
      });
    };

    // マスタの検索文字列を取得する関数
    const fetchMasterRecords = async (client) => {
      return await client.record.getAllRecordsWithCursor({
        app: MASTER_APP_ID,
        fields: ['検索文字列']
      });
    };

    // 更新対象レコードを生成する関数
    const buildUpdateRecords = (targetRecords, masterRecords) => {
      const masterLookup = new Set(masterRecords.map((record) => record.検索文字列.value));

      return targetRecords
        .map((record) => {
          if (masterLookup.has(record['FormBridgeからのルックアップ'].value)) {
            return {
              id: record.$id.value,
              record: {
                ルックアップ: { value: record['FormBridgeからのルックアップ'].value }
              }
            };
          }
          return null;
        })
        .filter((record) => record !== null);
    };

    setupButton();
  });
})();
スクリプトはこの順番で読み込みましょう
一覧表に一括処理を行うボタンが!
ボタン一発で処理完了♪

 一覧表でボタンを押すだけで、梅コースと同じような作業をやってくれます。ボタン一発でできることから、梅コースより担当者が楽そうです。
 ただ、カスタマイズなので、今後のアプリの設計変更等の際には気を付ける必要があります。それでも、「ルックアップ」フィールドにデータを適用すると「ほかのフィールドをコピー」も自動的に反映してくれるっていう仕組みは健在なので、カスタマイズで取り扱うフィールドの数は必要最低限で済みますから、かなりシンプルです。

【松コース】gusuku CustomineのJobRunnner機能を使う

 これが使えるなら最も手っ取り早くて確実です。

 FormBridgeからデータをkintoneに登録したときに、JobRunnnerを使ってkintoneのルックアップを更新するやり方です。
 残念ながら、自分の環境ではJobRunnerが使えないんですが、このやり方なら、CSVをエクスポートする手間もボタン押す手間もないので、「作業忘れ」がなくなります。
 他のwebhookをトリガーにイベント実行できるサービスを使ってもいいんですが、gusuku Customineだと知見も集まってるし楽ちんかな?
 そういちろうさんが、実際にnoteでアウトプットしてくださってます。

さいごに

 こんな感じで、kintoneやToyokumo kintoneAppで課題が生じても、いろんな方法で解決していくことが可能です。考える過程が「楽しい♪」って思えるようになれば、きっと、これからの業務改善がモリモリはかどります!
 今回のアドベントカレンダーもそうですが、kintone界隈の人って、色んな経験や知見を惜しみなくアウトプットしてくれてます。ぜひ、色んな知識を手に入れて、業務改善を楽しんでください♪

いいなと思ったら応援しよう!