#2つの日付の期間でレコード重複を確認する。
期間内に重なる日付がある時にエラーを検知して警告したい。
この記事はコメントを頂き、有効な内容は記事最下部と致しました。
記事内容
例)
図書館の本の借用を例として考える。
Aさんが9/1~7にとある本を既に借りていて、
同じ本を借りようとした場合にエラーを起こしたい。
レコード1が既に登録されていて、レコード2を登録されるとき。
レコード1
借用者:A
借用本:kintone認定 アソシエイト試験対策テキスト
本番号:2203456903
開始日:9/1
終了日:9/7
新規レコード2
借用者:A
借用本:kintone認定 アソシエイト試験対策テキスト
本番号:2203456903
開始日:9/3
終了日:9/9
解決策
結論、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の際クエリ指定が今回の肝になる。
上記の条件をクエリ記法でどのように表すか
クエリをどうするか
and
と or
の使い分け。
最初の例の場合で考えるとすると、
借用者 = "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;
});
});
})();