2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

kintone 日付 to 日付の重複チェック

Last updated at Posted at 2021-09-11

#2つの日付の期間でレコード重複を確認する。
期間内に重なる日付がある時にエラーを検知して警告したい。
この記事はコメントを頂き、有効な内容は記事最下部と致しました。
image.png

記事内容

例)
図書館の本の借用を例として考える。

Aさんが9/1~7にとある本を既に借りていて、
同じ本を借りようとした場合にエラーを起こしたい。

レコード1が既に登録されていて、レコード2を登録されるとき。

レコード1
借用者:A
借用本:kintone認定 アソシエイト試験対策テキスト
本番号:2203456903
開始日:9/1
終了日:9/7

新規レコード2
借用者:A
借用本:kintone認定 アソシエイト試験対策テキスト
本番号:2203456903
開始日:9/3
終了日:9/9

レコード1
image.png

レコード2を追加しようとするとエラー
image.png

解決策

結論、JavaScriptでの日付計算はほとんど利用しない
テーブルを用意し、2フィールドの日付間の日数分、日付データを格納する。
重複チェックでレコードを参照する際にテーブルの日付データをクエリに指定する。
尚、js初心者向けに全記載のコードで説明していきます。

フィールド

フィールドコード フィールドタイプ
開始日 日付フィールド
終了日 日付フィールド
本番号 文字列一行(数値でもok)
借用者 文字列一行
テーブル テーブル
日付 テーブル --> 日付フィールド
借用本 文字列一行(今回は不使用)

テーブルへ日付格納

日付フィールドのみのテーブルを作成し、
保存イベントで日付間の全ての日付を格納。

ソースコード Ver0.1

(() => {
    'use strict';

    //与えられた日付型から文字列の日付を返す関数(yyyy-MM-dd)0埋め日付
    const getDateString = (date) => {
        return `${date.getFullYear()}-${("0" + (date.getMonth() + 1)).slice(-2)}-${("0" + date.getDate()).slice(-2)}`;
    }

    //二つの日付フィールドの差を計算して日数で返す関数
    //1を足す理由はその日を含める計算のため9/1~9/7は合計7日間
    //ミリ秒単位の差分計算の結果 / 1000ms * 60sec * 60 min * 24hour = 差分日数
    const getDifference = (dateFrom, dateTo) => {
        return 1 + Number((new Date(dateTo) - new Date(dateFrom)) / (1000 * 60 * 60 * 24));
    }

    kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], (event) => {
        const record = event.record;

        //入力必須フィールド空チェック
        const mandatoryFields = [
            '開始日',
            '終了日',
            '本番号',
            '借用者'
        ];

        //必須チェック
        const result = mandatoryFields.reduce((acc, field) => {
            record[field].error = null;
            if (!record[field].value) {
                record[field].error = '必須です';
                acc.push(field);
            }
            return acc;
        }, []);

        //一つでも空であればエラー文と共にreturn
        if (result.length > 0) return event;

        const totalDays = getDifference(record['開始日'].value, record['終了日'].value);

        //テーブル初期化
        record['テーブル'].value = [];

        let date = new Date(record['開始日'].value);
        let i = 0;

        //合計日数分繰り返し
        do {

            //テーブルに日付を格納
            record['テーブル'].value.push({
                value: {
                    "日付": {
                        type: "DATE",
                        value: getDateString(date)
                    }
                }
            });

            //日付インクリメント(繰り上げ)
            date.setDate(date.getDate() + 1);
            //繰り返し用のインクリメント(繰り上げ)
            i++;

        } while (i < totalDays);

        return event;

    });
})();

重複チェック

kintone.apiで条件付きでレコードを取得する。
条件:
借用者がAで2203456903の本番号の本を9/1、9/2、9/3. . . 9/7に借りているレコードが
編集中のレコードを除き、存在するか。

kintone.api、GETの際クエリ指定が今回の肝になる。

上記の条件をクエリ記法でどのように表すか

クエリをどうするか

andor の使い分け。
最初の例の場合で考えるとすると、

借用者 = "A" and 本番号 = "2203456903" and 日付 = "2021-09-01"  or 
借用者 = "A" and 本番号 = "2203456903" and 日付 = "2021-09-02"  or
.........
借用者 = "A" and 本番号 = "2203456903" and 日付 = "2021-09-07"

と日付単位で絞り込んでいく。

ダメな例

借用者 = "A" and 本番号 = "2203456903" and 日付 = "2021-09-01"  or 日付 = "2021-09-02 or ...."

こうなると、最初の9/1日だけ、借用者と本番号の指定があり、
9/2以降は日付が9/2のすべてのレコードを参照することになる。

ソースコード Ver1.0

(() => {
    'use strict';

    //与えられた日付型から文字列の日付を返す関数(yyyy-MM-dd)0埋め日付
    const getDateString = (date) => {
        return `${date.getFullYear()}-${("0" + (date.getMonth() + 1)).slice(-2)}-${("0" + date.getDate()).slice(-2)}`;
    }

    //二つの日付フィールドの差を計算して日数で返す
    //1を足す理由はその日を含める計算のため9/1~9/7は合計7日間
    //ミリ秒単位の差分計算の結果 / 1000ms * 60sec * 60 min * 24hour = 差分日数
    const getDifference = (dateFrom, dateTo) => {
        return 1 + Number((new Date(dateTo) - new Date(dateFrom)) / (1000 * 60 * 60 * 24));
    }

    kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], (event) => {
        const record = event.record;

        //入力必須フィールド空チェック
        const mandatoryFields = [
            '開始日',
            '終了日',
            '本番号',
            '借用者'
        ];

        //必須チェック
        const result = mandatoryFields.reduce((acc, field) => {
            record[field].error = null;
            if (!record[field].value) {
                record[field].error = '必須です';
                acc.push(field);
            }
            return acc;
        }, []);

        //一つでも空であればエラー文と共にreturn
        if (result.length > 0) return event;

        const totalDays = getDifference(record['開始日'].value, record['終了日'].value);

        //テーブル初期化
        record['テーブル'].value = [];

        let date = new Date(record['開始日'].value);
        //****************************以下追記******************************//
        let query = ``;
        //****************************以上追記******************************//
        let i = 0;

        //合計日数分繰り返し
        do {

            //テーブルに日付を格納
            record['テーブル'].value.push({
                value: {
                    "日付": {
                        type: "DATE",
                        value: getDateString(date)
                    }
                }
            });

            //****************************以下追記******************************//
            //基本のクエリ
            let basicQuery = `借用者 = "${record['借用者'].value}" and 本番号 = "${record['本番号'].value}" and 日付 in ("${getDateString(date)}")`;

            //クエリ記法繰り返し二回目以降は or を追記(三項演算子を使用)
            query += i === 0 ? basicQuery : ` or ` + basicQuery;

            //新規レコードの場合と編集レコードの場合で自レコードを含むか含まないかクエリ分岐
            query += event.recordId ? ` and $id != "${event.recordId}"` : '';
            //****************************以上追記******************************//
            

            //日付インクリメント(繰り上げ)
            date.setDate(date.getDate() + 1);
            //繰り返し用のインクリメント(繰り上げ)
            i++;

        } while (i < totalDays);

        //****************************以下追記******************************//
        //最後にソート文
        query += ' order by レコード番号 desc limit 100 offset 0';

        const param = {
            app: event.appId,
            query: query
        }

        return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', param).then((resp) => {
            if (resp.records.length > 0) {
                event.error = '日付の範囲内に、既に記録された日付が含まれています。';
                record['開始日'].error = '確認';
                record['終了日'].error = '確認';
            }
            return event;
        }).catch((error) => {
            console.log(error);
            event.error = '既存レコードの取得に失敗しました。';
            return event;
        });
        //****************************以上追記******************************//
        //return event; 削除
    });
})();

今回の肝

テーブルに指定期間の日付をすべて格納

絞り込み条件に一致させるため、9/1、9/7の間の日付を準備すると、
標準機能の絞り込みでも使用できるようになるため、便利。

テーブル内の日付を含めた条件で重複チェック

標準機能の絞り込みも使用できるということは、
apiで該当レコードを取得することも容易ですね。
jsで日付計算は複雑になりがちなので避けてみました
逆にテーブルの設置無し、jsのみで対応が難しかったから、が本音なので、
方法があれば教えてほしいです。。

フィールドコード書き換えれば使えますというやつ

Ver1.0のコメントアウトを取っただけです。

JS
(() => {
    'use strict';

    const getDateString = (date) => {
        return `${date.getFullYear()}-${("0" + (date.getMonth() + 1)).slice(-2)}-${("0" + date.getDate()).slice(-2)}`;
    }

    const getDifference = (dateFrom, dateTo) => {
        return 1 + Number((new Date(dateTo) - new Date(dateFrom)) / (1000 * 60 * 60 * 24));
    }

    kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], (event) => {
        const record = event.record;

        const mandatoryFields = [
            '開始日',
            '終了日',
            '本番号',
            '借用者'
        ];

        const result = mandatoryFields.reduce((acc, field) => {
            record[field].error = null;
            if (!record[field].value) {
                record[field].error = '必須です';
                acc.push(field);
            }
            return acc;
        }, []);

        if (result.length > 0) return event;

        const totalDays = getDifference(record['開始日'].value, record['終了日'].value);
        record['テーブル'].value = [];

        let date = new Date(record['開始日'].value);
        let query = ``;
        let i = 0;

        do {

            record['テーブル'].value.push({
                value: {
                    "日付": {
                        type: "DATE",
                        value: getDateString(date)
                    }
                }
            });

            let basicQuery = `借用者 = "${record['借用者'].value}" and 本番号 = "${record['本番号'].value}" and 日付 in ("${getDateString(date)}")`;
            query += i === 0 ? basicQuery : ` or ` + basicQuery;
            query += event.recordId ? ` and $id != "${event.recordId}"` : '';

            date.setDate(date.getDate() + 1);
            i++;

        } while (i < totalDays);

        query += ' order by レコード番号 desc limit 100 offset 0';

        const param = {
            app: event.appId,
            query: query
        }

        return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', param).then((resp) => {
            if (resp.records.length > 0) {
                event.error = '日付の範囲内に、既に記録された日付が含まれています。';
                record['開始日'].error = '確認';
                record['終了日'].error = '確認';
            }
            return event;
        }).catch((error) => {
            console.log(error);
            event.error = '既存レコードの取得に失敗しました。';
            return event;
        });

    });
})();

コメントで一蹴していただきました。(こちらがシンプルです。)

@rgn212さん
こんな感じでは駄目でしょうか?

// 既存貸し出し期間
const existing = {
  start: 20210901,
  end  : 20210907,
};
// 申請期間
const request = {
  start: 20210903,
  end  : 20210909,
};
if(request.end >= existing.start && existing.end >= request.start) {
  console.log('重複しています');
}
else {
  console.log('重複していません');
}

応用して、
開始日が新規終了日より小さく
終了日が新規開始日より大きいものを取得。以上。

let query = ``;
const includeOwn = event.recordId ? ` and $id != "${event.recordId}"` : '';
query = `借用者 = "${record['借用者'].value}" and 本番号 = "${record['本番号'].value}" and 終了日 >= "${record['開始日'].value}" and 開始日 <= "${record['終了日'].value}"${includeOwn}`
query += ' order by レコード番号 desc limit 100 offset 0';
(() => {
    'use strict';
    kintone.events.on(['app.record.create.submit', 'app.record.edit.submit'], (event) => {
        const record = event.record;

        const mandatoryFields = [
            '開始日',
            '終了日',
            '本番号',
            '借用者'
        ];

        const result = mandatoryFields.reduce((acc, field) => {
            record[field].error = null;
            if (!record[field].value) {
                record[field].error = '必須です';
                acc.push(field);
            }
            return acc;
        }, []);

        if (result.length > 0) return event;

        let query = ``;
        const includeOwn = event.recordId ? ` and $id != "${event.recordId}"` : '';
        query = `借用者 = "${record['借用者'].value}" and 本番号 = "${record['本番号'].value}" and 終了日 >= "${record['開始日'].value}" and 開始日 <= "${record['終了日'].value}"${includeOwn}`
        query += ' order by レコード番号 desc limit 100 offset 0';

        const param = {
            app: event.appId,
            query: query
        };

        return kintone.api(kintone.api.url('/k/v1/records', true), 'GET', param).then((resp) => {
            console.log(resp);
            if (resp.records.length > 0) {
                event.error = '日付の範囲内に、既に記録された日付が含まれています。';
                record['開始日'].error = '確認';
                record['終了日'].error = '確認';
            }
            return event;
        }).catch((error) => {
            console.log(error);
            event.error = '既存レコードの取得に失敗しました。';
            return event;
        });

    });
})();
2
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?