見出し画像

会議室予約アプリで予約済リストを表示する

やりたいこと

Kintoneで会議室予約アプリを作りたいのですが、予約の重複を防止する機能はどうやって実現したらいいでしょうか?という相談です。
社内の会議室や社用車などの予約管理アプリは、ニーズが高そうなのに、Kintoneの標準機能でアプリを作成しようとすると、予約の重複登録が出来てしまうという問題にぶつかります。
Kintoneの標準機能だけで、予約管理アプリを運用するのは難しいですね。
アプリの一覧表で予約したいリソースの予約状況を検索してから予約登録してもらうという性善説に立った運用が一番低コストなのですが・・・
うっかりミスで重複予約してしまったということはあるかもしれません。

予約登録(保存)する時に条件が重複するレコードを検知してエラーにする方法は、エラー後の復帰処理も実務上の運用も複雑になるので、後で紹介する有料プラグインを用いた方法をお勧めします。
有料プラグインを使うほどの業務量では無いので、無料で出来る範囲でカスタマイズしたいという方は以下の記事も参考にしてください。

今回は、予約登録する前に予約しようとしている会議室の予約済リストを表示するボタンをフォームに設置して、予約済の日時リストをフォーム上で確認して予約の重複登録を回避してもらう、という方法と運用を考えました。

デモ画面

予約済リストの表示

予約リソース名(会議室名など)をドロップダウリストで選択し、予約表示ボタンをクリックすると、同アプリ内の予約済データを予約開始日時の降順ソートで表示します。
ドロップリストで予約リソース名を変えて「予約表示」ボタンを押すたびにクエリ検索を実行して再表示します。

予約アプリのフォーム設定

予約アプリのカスタマイズの準備として、アプリの設計>フォーム画面で、Javascriptで操作する2つのスペースフィールドを追加しておきます。
1つ目は、「予約表示」ボタンの設置スペース
2つ目は、「検索結果」の表示スペースです。
2つのスペースフィールドの設定で「要素ID」を入力しておきます。
スペースフィールドの「要素ID」は半角英数字のみ設定可能です。

予約アプリのフォーム設定

予約済レコードを表示するJavascriptコード

/* 予約アプリで予約済レコードを表示する */
(function () {
    'use strict';

    // 初期設定
    const RESOURCE_FIELD_CODE = '予約リソース';
    const START_DATE_TIME_FIELD_CODE = '予約開始日時';
    const END_DATE_TIME_FIELD_CODE = '予約終了日時';
    const BUTTON_SPACE_FIELD_ID = 'Button_space';
    const RESULT_SPACE_FIELD_ID = 'Result_space';

    // 日時データの書式設定
    function formatDateTime(dateTimeStr) {
        const date = new Date(dateTimeStr);
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        const day = String(date.getDate()).padStart(2, '0');
        const hour = String(date.getHours()).padStart(2, '0');
        const minute = String(date.getMinutes()).padStart(2, '0');
        return `${year}-${month}-${day} ${hour}:${minute}`;
    }

    // 予約リソースが同一のレコードを検索して予約開始日の降順ソートで表示する
    async function fetchAndDisplayRecords(resource) {
        const query = `${RESOURCE_FIELD_CODE} in ("${resource}") order by ${START_DATE_TIME_FIELD_CODE} desc`;
        const appID = kintone.app.getId();

        try {
            const resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', {
                app: appID,
                query: query
            });

            const resultSpaceField = kintone.app.record.getSpaceElement(RESULT_SPACE_FIELD_ID);
            if (!resultSpaceField) return;

            if (resp.records.length === 0) {
                resultSpaceField.innerHTML = '<b style="color: blue;">予約データはありません</b>';
                return;
            }

            let tableHTML = `<table border="1"><tr><th>${RESOURCE_FIELD_CODE}</th><th>${START_DATE_TIME_FIELD_CODE}</th><th>${END_DATE_TIME_FIELD_CODE}</th></tr>`;
            resp.records.forEach(record => {
                const formattedStartDateTime = formatDateTime(record[START_DATE_TIME_FIELD_CODE].value);
                const formattedEndDateTime = formatDateTime(record[END_DATE_TIME_FIELD_CODE].value);
                const recordLink = `/k/${appID}/show#record=${record.$id.value}`;
                tableHTML += `<tr><td><a href="${recordLink}" target="_blank">${record[RESOURCE_FIELD_CODE].value}</a></td><td>${formattedStartDateTime}</td><td>${formattedEndDateTime}</td></tr>`;
            });
            tableHTML += '</table>';

            resultSpaceField.innerHTML = tableHTML;
        } catch (error) {
            console.error(error);
        }
    }

    // 新規登録画面と編集画面でイベント発火させる
    kintone.events.on(['app.record.create.show', 'app.record.edit.show'], function (event) {
        const buttonSpaceField = kintone.app.record.getSpaceElement(BUTTON_SPACE_FIELD_ID);
        if (!buttonSpaceField) return event;

        const button = document.createElement('button');
        button.id = 'show-reservations-button';
        button.textContent = '予約表示';
        buttonSpaceField.appendChild(button);

        button.addEventListener('click', function () {
            const currentRecord = kintone.app.record.get();  // Get the current record data
            const currentResource = currentRecord.record[RESOURCE_FIELD_CODE] ? currentRecord.record[RESOURCE_FIELD_CODE].value : null;
            if (currentResource) {
                fetchAndDisplayRecords(currentResource);
            } else {
                const resultSpaceField = kintone.app.record.getSpaceElement(RESULT_SPACE_FIELD_ID);
                if (resultSpaceField) {
                    resultSpaceField.innerHTML = '<b style="color: blue;">予約リソースが選択されていません</b>';
                }
            }
        });

        return event;
    });
})();

初期設定のフィールドコード名とスペースフィールドの要素IDを正しく設定すれば、正しく動作することを確認済です。
予約リソース、予約開始日時、予約終了日時を、予約アプリのフィールドコード名と合わせて指定します。
BUTTON_SPACE_FIELD_IDは、ボタン設置スペースの「要素ID」を指定します。
RESULT_SPACE_FIELD_IDは、検索結果表示スペースの「要素ID」を指定します。

Kintoneアプリで少しだけ会議室予約や備品貸出等の予約管理をしたいというケースでご活用ください。
もっと高機能な予約管理が欲しい場合は、以下に紹介する有料プラグインや予約管理専用のアプリを利用した方が良いと思います。

会議室予約管理の出来る有料プラグイン

Kintoneで会議室予約を実現できる有料プラグインも紹介しておきます。
予約管理だけではなく、スケジュール管理もカレンダー形式で出来ます。
カレンダーPlus
KOYOMI

予約管理専用アプリの活用

予約の重複エラーを検知する機能が欲しい場合は、Kintoeアプリで作成するよりも、無料で使える予約管理アプリを利用した方が低コストで実現できるかもしれません。

よろしければサポートお願いします! いただいたサポートは、note記事制作の活動費に使わせていただきます!