@happychef/algorithm 1.2.11 → 1.2.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci-cd.yml +234 -234
- package/BRANCH_PROTECTION_SETUP.md +167 -167
- package/CHANGELOG.md +8 -8
- package/README.md +144 -144
- package/RESERVERINGEN_GIDS.md +986 -986
- package/__tests__/filters.test.js +276 -276
- package/__tests__/isDateAvailable.test.js +175 -175
- package/__tests__/isTimeAvailable.test.js +168 -168
- package/__tests__/restaurantData.test.js +422 -422
- package/__tests__/tableHelpers.test.js +247 -247
- package/assignTables.js +424 -424
- package/changes/2025/December/PR2___change.md +14 -14
- package/changes/2025/December/PR3_add__change.md +20 -20
- package/changes/2025/December/PR4___.md +15 -15
- package/changes/2025/December/PR5___.md +15 -15
- package/changes/2025/December/PR6__del_.md +17 -17
- package/changes/2025/December/PR7_add__change.md +21 -21
- package/changes/2026/January/PR8_add__change.md +39 -0
- package/changes/2026/January/PR9_add__change.md +20 -0
- package/filters/maxArrivalsFilter.js +114 -114
- package/filters/maxGroupsFilter.js +221 -221
- package/filters/timeFilter.js +89 -89
- package/getAvailableTimeblocks.js +158 -158
- package/grouping.js +162 -162
- package/index.js +42 -42
- package/isDateAvailable.js +80 -80
- package/isDateAvailableWithTableCheck.js +171 -171
- package/isTimeAvailable.js +25 -25
- package/jest.config.js +23 -23
- package/package.json +27 -27
- package/processing/dailyGuestCounts.js +73 -73
- package/processing/mealTypeCount.js +133 -133
- package/processing/timeblocksAvailable.js +167 -167
- package/reservation_data/counter.js +64 -64
- package/restaurant_data/exceptions.js +149 -149
- package/restaurant_data/openinghours.js +123 -123
- package/simulateTableAssignment.js +709 -699
- package/tableHelpers.js +178 -178
- package/tables/time/parseTime.js +19 -19
- package/tables/time/shifts.js +7 -7
- package/tables/utils/calculateDistance.js +13 -13
- package/tables/utils/isTableFreeForAllSlots.js +14 -14
- package/tables/utils/isTemporaryTableValid.js +39 -39
- package/test/test_counter.js +194 -194
- package/test/test_dailyCount.js +81 -81
- package/test/test_datesAvailable.js +106 -106
- package/test/test_exceptions.js +172 -172
- package/test/test_isDateAvailable.js +330 -330
- package/test/test_mealTypeCount.js +54 -54
- package/test/test_timesAvailable.js +88 -88
- package/test-detailed-filter.js +100 -100
- package/test-lunch-debug.js +110 -110
- package/test-max-arrivals-filter.js +79 -79
- package/test-meal-stop-fix.js +147 -147
- package/test-meal-stop-simple.js +93 -93
- package/test-timezone-debug.js +47 -47
- package/test.js +336 -336
package/index.js
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
// algorithm/index.js
|
|
2
|
-
|
|
3
|
-
const processing = {
|
|
4
|
-
...require("./processing/dailyGuestCounts"),
|
|
5
|
-
...require("./processing/mealTypeCount"),
|
|
6
|
-
...require("./processing/timeblocksAvailable")
|
|
7
|
-
};
|
|
8
|
-
|
|
9
|
-
const reservation_data = {
|
|
10
|
-
...require("./reservation_data/counter")
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
const restaurant_data = {
|
|
14
|
-
...require("./restaurant_data/exceptions"),
|
|
15
|
-
...require("./restaurant_data/openinghours")
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const test = {
|
|
19
|
-
...require("./assignTables"),
|
|
20
|
-
...require("./getAvailableTimeblocks"),
|
|
21
|
-
...require("./grouping"),
|
|
22
|
-
...require("./isDateAvailable"),
|
|
23
|
-
...require("./isTimeAvailable"),
|
|
24
|
-
...require("./simulateTableAssignment"),
|
|
25
|
-
...require("./isDateAvailableWithTableCheck"),
|
|
26
|
-
...require("./tableHelpers"),
|
|
27
|
-
...require("./test")
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const filters = {
|
|
31
|
-
...require("./filters/timeFilter"),
|
|
32
|
-
...require("./filters/maxArrivalsFilter"),
|
|
33
|
-
...require("./filters/maxGroupsFilter")
|
|
34
|
-
};
|
|
35
|
-
// Merge all exports into one
|
|
36
|
-
module.exports = {
|
|
37
|
-
...processing,
|
|
38
|
-
...reservation_data,
|
|
39
|
-
...restaurant_data,
|
|
40
|
-
...test,
|
|
41
|
-
...filters
|
|
42
|
-
};
|
|
1
|
+
// algorithm/index.js
|
|
2
|
+
|
|
3
|
+
const processing = {
|
|
4
|
+
...require("./processing/dailyGuestCounts"),
|
|
5
|
+
...require("./processing/mealTypeCount"),
|
|
6
|
+
...require("./processing/timeblocksAvailable")
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const reservation_data = {
|
|
10
|
+
...require("./reservation_data/counter")
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const restaurant_data = {
|
|
14
|
+
...require("./restaurant_data/exceptions"),
|
|
15
|
+
...require("./restaurant_data/openinghours")
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const test = {
|
|
19
|
+
...require("./assignTables"),
|
|
20
|
+
...require("./getAvailableTimeblocks"),
|
|
21
|
+
...require("./grouping"),
|
|
22
|
+
...require("./isDateAvailable"),
|
|
23
|
+
...require("./isTimeAvailable"),
|
|
24
|
+
...require("./simulateTableAssignment"),
|
|
25
|
+
...require("./isDateAvailableWithTableCheck"),
|
|
26
|
+
...require("./tableHelpers"),
|
|
27
|
+
...require("./test")
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const filters = {
|
|
31
|
+
...require("./filters/timeFilter"),
|
|
32
|
+
...require("./filters/maxArrivalsFilter"),
|
|
33
|
+
...require("./filters/maxGroupsFilter")
|
|
34
|
+
};
|
|
35
|
+
// Merge all exports into one
|
|
36
|
+
module.exports = {
|
|
37
|
+
...processing,
|
|
38
|
+
...reservation_data,
|
|
39
|
+
...restaurant_data,
|
|
40
|
+
...test,
|
|
41
|
+
...filters
|
|
42
|
+
};
|
package/isDateAvailable.js
CHANGED
|
@@ -1,80 +1,80 @@
|
|
|
1
|
-
// isDateAvailable.js
|
|
2
|
-
|
|
3
|
-
const { getAvailableTimeblocks } = require('./getAvailableTimeblocks');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Parses a time string in "HH:MM" format into minutes since midnight.
|
|
7
|
-
* @param {string} timeStr - Time string in "HH:MM" format.
|
|
8
|
-
* @returns {number} Minutes since midnight.
|
|
9
|
-
*/
|
|
10
|
-
function parseTime(timeStr) {
|
|
11
|
-
const [hours, minutes] = timeStr.split(':').map(Number);
|
|
12
|
-
return hours * 60 + minutes;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Checks if a date is within the allowed future range defined by dagenInToekomst.
|
|
17
|
-
* @param {Object} data - The main data object (to access general settings).
|
|
18
|
-
* @param {string} dateStr - The date string (YYYY-MM-DD).
|
|
19
|
-
* @returns {boolean} true if within range, false otherwise.
|
|
20
|
-
*/
|
|
21
|
-
function isDateWithinAllowedRange(data, dateStr) {
|
|
22
|
-
// Get dagenInToekomst
|
|
23
|
-
let dagenInToekomst = 90;
|
|
24
|
-
if (
|
|
25
|
-
data['general-settings'] &&
|
|
26
|
-
data['general-settings'].dagenInToekomst &&
|
|
27
|
-
parseInt(data['general-settings'].dagenInToekomst, 10) > 0
|
|
28
|
-
) {
|
|
29
|
-
dagenInToekomst = parseInt(data['general-settings'].dagenInToekomst, 10);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const timeZone = 'Europe/Amsterdam';
|
|
33
|
-
|
|
34
|
-
const now = new Date();
|
|
35
|
-
const currentTimeInTimeZone = new Date(
|
|
36
|
-
now.toLocaleString('en-US', { timeZone: timeZone })
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
const maxAllowedDate = new Date(currentTimeInTimeZone.getTime());
|
|
40
|
-
maxAllowedDate.setDate(maxAllowedDate.getDate() + dagenInToekomst);
|
|
41
|
-
maxAllowedDate.setHours(23, 59, 59, 999);
|
|
42
|
-
|
|
43
|
-
const [year, month, day] = dateStr.split('-').map(Number);
|
|
44
|
-
const targetDate = new Date(Date.UTC(year, month - 1, day));
|
|
45
|
-
const targetDateInTimeZone = new Date(
|
|
46
|
-
targetDate.toLocaleString('en-US', { timeZone: timeZone })
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
return targetDateInTimeZone <= maxAllowedDate;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Checks if a date is available for a reservation of a specified number of guests.
|
|
54
|
-
* This updated version uses `getAvailableTimeblocks` to ensure that it never returns
|
|
55
|
-
* true if no actual time slots are available, including for today's date.
|
|
56
|
-
* @param {Object} data - The main data object containing settings and meal information.
|
|
57
|
-
* @param {string} dateStr - The date string in "YYYY-MM-DD" format.
|
|
58
|
-
* @param {Array} reservations - An array of reservation objects.
|
|
59
|
-
* @param {number} guests - The number of guests for the reservation.
|
|
60
|
-
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
61
|
-
* @param {string|null} giftcard - Optional giftcard to filter times by meal.
|
|
62
|
-
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
63
|
-
* @returns {boolean} - Returns true if the date has at least one available timeblock, false otherwise.
|
|
64
|
-
*/
|
|
65
|
-
function isDateAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
66
|
-
// Check if date is within allowed range (skip for admin)
|
|
67
|
-
if (!isAdmin && !isDateWithinAllowedRange(data, dateStr)) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Get available timeblocks using the existing logic
|
|
72
|
-
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
73
|
-
|
|
74
|
-
// Return true only if we have at least one available timeblock
|
|
75
|
-
return Object.keys(availableTimeblocks).length > 0;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
module.exports = {
|
|
79
|
-
isDateAvailable,
|
|
80
|
-
};
|
|
1
|
+
// isDateAvailable.js
|
|
2
|
+
|
|
3
|
+
const { getAvailableTimeblocks } = require('./getAvailableTimeblocks');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Parses a time string in "HH:MM" format into minutes since midnight.
|
|
7
|
+
* @param {string} timeStr - Time string in "HH:MM" format.
|
|
8
|
+
* @returns {number} Minutes since midnight.
|
|
9
|
+
*/
|
|
10
|
+
function parseTime(timeStr) {
|
|
11
|
+
const [hours, minutes] = timeStr.split(':').map(Number);
|
|
12
|
+
return hours * 60 + minutes;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a date is within the allowed future range defined by dagenInToekomst.
|
|
17
|
+
* @param {Object} data - The main data object (to access general settings).
|
|
18
|
+
* @param {string} dateStr - The date string (YYYY-MM-DD).
|
|
19
|
+
* @returns {boolean} true if within range, false otherwise.
|
|
20
|
+
*/
|
|
21
|
+
function isDateWithinAllowedRange(data, dateStr) {
|
|
22
|
+
// Get dagenInToekomst
|
|
23
|
+
let dagenInToekomst = 90;
|
|
24
|
+
if (
|
|
25
|
+
data['general-settings'] &&
|
|
26
|
+
data['general-settings'].dagenInToekomst &&
|
|
27
|
+
parseInt(data['general-settings'].dagenInToekomst, 10) > 0
|
|
28
|
+
) {
|
|
29
|
+
dagenInToekomst = parseInt(data['general-settings'].dagenInToekomst, 10);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const timeZone = 'Europe/Amsterdam';
|
|
33
|
+
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const currentTimeInTimeZone = new Date(
|
|
36
|
+
now.toLocaleString('en-US', { timeZone: timeZone })
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const maxAllowedDate = new Date(currentTimeInTimeZone.getTime());
|
|
40
|
+
maxAllowedDate.setDate(maxAllowedDate.getDate() + dagenInToekomst);
|
|
41
|
+
maxAllowedDate.setHours(23, 59, 59, 999);
|
|
42
|
+
|
|
43
|
+
const [year, month, day] = dateStr.split('-').map(Number);
|
|
44
|
+
const targetDate = new Date(Date.UTC(year, month - 1, day));
|
|
45
|
+
const targetDateInTimeZone = new Date(
|
|
46
|
+
targetDate.toLocaleString('en-US', { timeZone: timeZone })
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
return targetDateInTimeZone <= maxAllowedDate;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Checks if a date is available for a reservation of a specified number of guests.
|
|
54
|
+
* This updated version uses `getAvailableTimeblocks` to ensure that it never returns
|
|
55
|
+
* true if no actual time slots are available, including for today's date.
|
|
56
|
+
* @param {Object} data - The main data object containing settings and meal information.
|
|
57
|
+
* @param {string} dateStr - The date string in "YYYY-MM-DD" format.
|
|
58
|
+
* @param {Array} reservations - An array of reservation objects.
|
|
59
|
+
* @param {number} guests - The number of guests for the reservation.
|
|
60
|
+
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
61
|
+
* @param {string|null} giftcard - Optional giftcard to filter times by meal.
|
|
62
|
+
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
63
|
+
* @returns {boolean} - Returns true if the date has at least one available timeblock, false otherwise.
|
|
64
|
+
*/
|
|
65
|
+
function isDateAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
66
|
+
// Check if date is within allowed range (skip for admin)
|
|
67
|
+
if (!isAdmin && !isDateWithinAllowedRange(data, dateStr)) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Get available timeblocks using the existing logic
|
|
72
|
+
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
73
|
+
|
|
74
|
+
// Return true only if we have at least one available timeblock
|
|
75
|
+
return Object.keys(availableTimeblocks).length > 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = {
|
|
79
|
+
isDateAvailable,
|
|
80
|
+
};
|
|
@@ -1,171 +1,171 @@
|
|
|
1
|
-
// file: /src/Pages/NewReservation/StepOne/algorithm/isDateAvailableWithTableCheck.js
|
|
2
|
-
const moment = require('moment-timezone');
|
|
3
|
-
const { isDateAvailable } = require('./isDateAvailable');
|
|
4
|
-
const { getAvailableTimeblocks } = require('./getAvailableTimeblocks');
|
|
5
|
-
const { isTimeAvailableSync } = require('./simulateTableAssignment');
|
|
6
|
-
const { daysOfWeekEnglish, getMealTypeByTime } = require('./restaurant_data/openinghours');
|
|
7
|
-
|
|
8
|
-
const TIMEZONE = 'Europe/Brussels';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Checks if the specific meal/time also contains the giftcard.
|
|
12
|
-
* For instance, if Wednesday-lunch includes "Woensdag bon,"
|
|
13
|
-
* but the user picks 19:00 (dinner), we should exclude that time.
|
|
14
|
-
*
|
|
15
|
-
* @param {Object} data - The restaurant data object.
|
|
16
|
-
* @param {string} dateStr - "YYYY-MM-DD".
|
|
17
|
-
* @param {string} timeStr - "HH:MM".
|
|
18
|
-
* @param {string} giftcard - The selected giftcard.
|
|
19
|
-
* @returns {boolean} true if the meal block for timeStr on dateStr includes the giftcard, else false.
|
|
20
|
-
*/
|
|
21
|
-
function timeHasGiftcard(data, dateStr, timeStr, giftcard) {
|
|
22
|
-
if (!giftcard || !giftcard.trim()) return true; // No giftcard => no restriction
|
|
23
|
-
|
|
24
|
-
// Figure out the meal type from timeStr
|
|
25
|
-
const mealType = getMealTypeByTime(timeStr); // returns "breakfast", "lunch", or "dinner"
|
|
26
|
-
if (!mealType) {
|
|
27
|
-
return false; // Not in any recognized meal
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Day-of-week in Brussels TZ
|
|
31
|
-
const m = moment.tz(dateStr, 'YYYY-MM-DD', TIMEZONE);
|
|
32
|
-
if (!m.isValid()) return false;
|
|
33
|
-
const englishDay = daysOfWeekEnglish[m.day()];
|
|
34
|
-
|
|
35
|
-
const ohKey = `openinghours-${mealType}`;
|
|
36
|
-
const daySettings =
|
|
37
|
-
data[ohKey] &&
|
|
38
|
-
data[ohKey].schemeSettings &&
|
|
39
|
-
data[ohKey].schemeSettings[englishDay]
|
|
40
|
-
? data[ohKey].schemeSettings[englishDay]
|
|
41
|
-
: null;
|
|
42
|
-
|
|
43
|
-
if (!daySettings) {
|
|
44
|
-
console.error(`No day settings found for ${englishDay} ${mealType}`);
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (daySettings.giftcardsEnabled !== true) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (!Array.isArray(daySettings.giftcards) || daySettings.giftcards.length === 0) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Check if the selected giftcard is in the allowed list
|
|
57
|
-
return daySettings.giftcards.includes(giftcard);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Checks if a date is available for a reservation with a table assignment check.
|
|
62
|
-
* This extends isDateAvailable by also verifying that at least one time slot
|
|
63
|
-
* has an available table AND (if giftcard is selected) that the time belongs
|
|
64
|
-
* to a meal that includes that giftcard. It also filters dates based on the
|
|
65
|
-
* selected menu item's filterStartDate and filterEndDate from
|
|
66
|
-
* window.currentReservationTicketItem, but only when normalOpeningTimes is false.
|
|
67
|
-
*
|
|
68
|
-
* @param {Object} data - The restaurant data object containing settings and table information.
|
|
69
|
-
* @param {string} dateStr - The date string in "YYYY-MM-DD" format.
|
|
70
|
-
* @param {Array} reservations - An array of reservation objects.
|
|
71
|
-
* @param {number} guests - The number of guests for the reservation.
|
|
72
|
-
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
73
|
-
* @param {string|null} selectedGiftcard - The selected giftcard (if any).
|
|
74
|
-
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
75
|
-
* @returns {boolean} - Returns true if the date passes all checks to be available.
|
|
76
|
-
*/
|
|
77
|
-
function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null, isAdmin = false) {
|
|
78
|
-
// 0) Optionally filter by selected menu item's date range (only when normalOpeningTimes = false)
|
|
79
|
-
const currentItem = typeof window !== 'undefined' ? window.currentReservationTicketItem : null;
|
|
80
|
-
if (
|
|
81
|
-
currentItem &&
|
|
82
|
-
!currentItem.normalOpeningTimes
|
|
83
|
-
) {
|
|
84
|
-
const dateM = moment.tz(dateStr, 'YYYY-MM-DD', TIMEZONE).startOf('day');
|
|
85
|
-
const startM = currentItem.filterStartDate
|
|
86
|
-
? moment.tz(currentItem.filterStartDate, 'YYYY-MM-DD', TIMEZONE).startOf('day')
|
|
87
|
-
: null;
|
|
88
|
-
const endM = currentItem.filterEndDate
|
|
89
|
-
? moment.tz(currentItem.filterEndDate, 'YYYY-MM-DD', TIMEZONE).endOf('day')
|
|
90
|
-
: null;
|
|
91
|
-
|
|
92
|
-
if (startM && dateM.isBefore(startM)) {
|
|
93
|
-
console.log(`Date ${dateStr} is before the allowed start ${currentItem.filterStartDate}`);
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
if (endM && dateM.isAfter(endM)) {
|
|
97
|
-
console.log(`Date ${dateStr} is after the allowed end ${currentItem.filterEndDate}`);
|
|
98
|
-
return false;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
console.log(`\n===== Checking date availability for ${guests} guests on ${dateStr} =====`);
|
|
103
|
-
console.log('Restaurant data floors:', data.floors ? 'present' : 'missing');
|
|
104
|
-
console.log('Table settings:', JSON.stringify(data['table-settings'] || {}, null, 2));
|
|
105
|
-
|
|
106
|
-
// Calculate table assignment flag once to reduce redundant checks
|
|
107
|
-
const tableSettings = data?.['table-settings'] || {};
|
|
108
|
-
const isTableAssignmentEnabled =
|
|
109
|
-
tableSettings.isInstalled === true &&
|
|
110
|
-
tableSettings.assignmentMode === 'automatic' &&
|
|
111
|
-
data.floors &&
|
|
112
|
-
Array.isArray(data.floors);
|
|
113
|
-
|
|
114
|
-
console.log(`Table assignment is ${isTableAssignmentEnabled ? 'ENABLED' : 'DISABLED'} for date ${dateStr}`);
|
|
115
|
-
|
|
116
|
-
// 1) First, do the standard day-level checks (including simple giftcard check).
|
|
117
|
-
const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
|
|
118
|
-
if (!basicDateAvailable) {
|
|
119
|
-
console.log(`Date ${dateStr} fails basic availability check`);
|
|
120
|
-
return false;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// If table assignment is not enabled, stop here - the date is available
|
|
124
|
-
if (!isTableAssignmentEnabled) {
|
|
125
|
-
console.log(`Table assignment disabled, date ${dateStr} is available`);
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// 2) Get all available timeblocks for this date
|
|
130
|
-
console.log(`Getting available timeblocks for ${dateStr}`);
|
|
131
|
-
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
|
|
132
|
-
console.log(`Found ${Object.keys(availableTimeblocks).length} available timeblocks before table check`);
|
|
133
|
-
|
|
134
|
-
// If no timeblocks are available at all, exit early
|
|
135
|
-
if (Object.keys(availableTimeblocks).length === 0) {
|
|
136
|
-
console.log(`No available timeblocks on ${dateStr}, date is unavailable`);
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// 3) Check if any timeblock has an available table and satisfies giftcard requirements
|
|
141
|
-
console.log(`Checking table availability for each timeblock on ${dateStr}`);
|
|
142
|
-
|
|
143
|
-
let atLeastOneAvailable = false;
|
|
144
|
-
let checkedCount = 0;
|
|
145
|
-
|
|
146
|
-
for (const time of Object.keys(availableTimeblocks)) {
|
|
147
|
-
checkedCount++;
|
|
148
|
-
|
|
149
|
-
// If there's a giftcard, confirm this particular time belongs to a meal that has that giftcard
|
|
150
|
-
if (selectedGiftcard && !timeHasGiftcard(data, dateStr, time, selectedGiftcard)) {
|
|
151
|
-
console.log(`Time ${time} doesn't match giftcard meal requirements`);
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
console.log(`Checking table availability for ${time} on ${dateStr} for ${guests} guests`);
|
|
156
|
-
if (isTimeAvailableSync(data, dateStr, time, guests, reservations)) {
|
|
157
|
-
console.log(`Found available time ${time} with table assignment!`);
|
|
158
|
-
atLeastOneAvailable = true;
|
|
159
|
-
break;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
console.log(`Checked ${checkedCount}/${Object.keys(availableTimeblocks).length} timeblocks`);
|
|
164
|
-
console.log(`Date ${dateStr} with table check: ${atLeastOneAvailable ? 'Available' : 'Unavailable'}`);
|
|
165
|
-
|
|
166
|
-
return atLeastOneAvailable;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
module.exports = {
|
|
170
|
-
isDateAvailableWithTableCheck,
|
|
171
|
-
};
|
|
1
|
+
// file: /src/Pages/NewReservation/StepOne/algorithm/isDateAvailableWithTableCheck.js
|
|
2
|
+
const moment = require('moment-timezone');
|
|
3
|
+
const { isDateAvailable } = require('./isDateAvailable');
|
|
4
|
+
const { getAvailableTimeblocks } = require('./getAvailableTimeblocks');
|
|
5
|
+
const { isTimeAvailableSync } = require('./simulateTableAssignment');
|
|
6
|
+
const { daysOfWeekEnglish, getMealTypeByTime } = require('./restaurant_data/openinghours');
|
|
7
|
+
|
|
8
|
+
const TIMEZONE = 'Europe/Brussels';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Checks if the specific meal/time also contains the giftcard.
|
|
12
|
+
* For instance, if Wednesday-lunch includes "Woensdag bon,"
|
|
13
|
+
* but the user picks 19:00 (dinner), we should exclude that time.
|
|
14
|
+
*
|
|
15
|
+
* @param {Object} data - The restaurant data object.
|
|
16
|
+
* @param {string} dateStr - "YYYY-MM-DD".
|
|
17
|
+
* @param {string} timeStr - "HH:MM".
|
|
18
|
+
* @param {string} giftcard - The selected giftcard.
|
|
19
|
+
* @returns {boolean} true if the meal block for timeStr on dateStr includes the giftcard, else false.
|
|
20
|
+
*/
|
|
21
|
+
function timeHasGiftcard(data, dateStr, timeStr, giftcard) {
|
|
22
|
+
if (!giftcard || !giftcard.trim()) return true; // No giftcard => no restriction
|
|
23
|
+
|
|
24
|
+
// Figure out the meal type from timeStr
|
|
25
|
+
const mealType = getMealTypeByTime(timeStr); // returns "breakfast", "lunch", or "dinner"
|
|
26
|
+
if (!mealType) {
|
|
27
|
+
return false; // Not in any recognized meal
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Day-of-week in Brussels TZ
|
|
31
|
+
const m = moment.tz(dateStr, 'YYYY-MM-DD', TIMEZONE);
|
|
32
|
+
if (!m.isValid()) return false;
|
|
33
|
+
const englishDay = daysOfWeekEnglish[m.day()];
|
|
34
|
+
|
|
35
|
+
const ohKey = `openinghours-${mealType}`;
|
|
36
|
+
const daySettings =
|
|
37
|
+
data[ohKey] &&
|
|
38
|
+
data[ohKey].schemeSettings &&
|
|
39
|
+
data[ohKey].schemeSettings[englishDay]
|
|
40
|
+
? data[ohKey].schemeSettings[englishDay]
|
|
41
|
+
: null;
|
|
42
|
+
|
|
43
|
+
if (!daySettings) {
|
|
44
|
+
console.error(`No day settings found for ${englishDay} ${mealType}`);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (daySettings.giftcardsEnabled !== true) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!Array.isArray(daySettings.giftcards) || daySettings.giftcards.length === 0) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Check if the selected giftcard is in the allowed list
|
|
57
|
+
return daySettings.giftcards.includes(giftcard);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Checks if a date is available for a reservation with a table assignment check.
|
|
62
|
+
* This extends isDateAvailable by also verifying that at least one time slot
|
|
63
|
+
* has an available table AND (if giftcard is selected) that the time belongs
|
|
64
|
+
* to a meal that includes that giftcard. It also filters dates based on the
|
|
65
|
+
* selected menu item's filterStartDate and filterEndDate from
|
|
66
|
+
* window.currentReservationTicketItem, but only when normalOpeningTimes is false.
|
|
67
|
+
*
|
|
68
|
+
* @param {Object} data - The restaurant data object containing settings and table information.
|
|
69
|
+
* @param {string} dateStr - The date string in "YYYY-MM-DD" format.
|
|
70
|
+
* @param {Array} reservations - An array of reservation objects.
|
|
71
|
+
* @param {number} guests - The number of guests for the reservation.
|
|
72
|
+
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
73
|
+
* @param {string|null} selectedGiftcard - The selected giftcard (if any).
|
|
74
|
+
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
75
|
+
* @returns {boolean} - Returns true if the date passes all checks to be available.
|
|
76
|
+
*/
|
|
77
|
+
function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null, isAdmin = false) {
|
|
78
|
+
// 0) Optionally filter by selected menu item's date range (only when normalOpeningTimes = false)
|
|
79
|
+
const currentItem = typeof window !== 'undefined' ? window.currentReservationTicketItem : null;
|
|
80
|
+
if (
|
|
81
|
+
currentItem &&
|
|
82
|
+
!currentItem.normalOpeningTimes
|
|
83
|
+
) {
|
|
84
|
+
const dateM = moment.tz(dateStr, 'YYYY-MM-DD', TIMEZONE).startOf('day');
|
|
85
|
+
const startM = currentItem.filterStartDate
|
|
86
|
+
? moment.tz(currentItem.filterStartDate, 'YYYY-MM-DD', TIMEZONE).startOf('day')
|
|
87
|
+
: null;
|
|
88
|
+
const endM = currentItem.filterEndDate
|
|
89
|
+
? moment.tz(currentItem.filterEndDate, 'YYYY-MM-DD', TIMEZONE).endOf('day')
|
|
90
|
+
: null;
|
|
91
|
+
|
|
92
|
+
if (startM && dateM.isBefore(startM)) {
|
|
93
|
+
console.log(`Date ${dateStr} is before the allowed start ${currentItem.filterStartDate}`);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (endM && dateM.isAfter(endM)) {
|
|
97
|
+
console.log(`Date ${dateStr} is after the allowed end ${currentItem.filterEndDate}`);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
console.log(`\n===== Checking date availability for ${guests} guests on ${dateStr} =====`);
|
|
103
|
+
console.log('Restaurant data floors:', data.floors ? 'present' : 'missing');
|
|
104
|
+
console.log('Table settings:', JSON.stringify(data['table-settings'] || {}, null, 2));
|
|
105
|
+
|
|
106
|
+
// Calculate table assignment flag once to reduce redundant checks
|
|
107
|
+
const tableSettings = data?.['table-settings'] || {};
|
|
108
|
+
const isTableAssignmentEnabled =
|
|
109
|
+
tableSettings.isInstalled === true &&
|
|
110
|
+
tableSettings.assignmentMode === 'automatic' &&
|
|
111
|
+
data.floors &&
|
|
112
|
+
Array.isArray(data.floors);
|
|
113
|
+
|
|
114
|
+
console.log(`Table assignment is ${isTableAssignmentEnabled ? 'ENABLED' : 'DISABLED'} for date ${dateStr}`);
|
|
115
|
+
|
|
116
|
+
// 1) First, do the standard day-level checks (including simple giftcard check).
|
|
117
|
+
const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
|
|
118
|
+
if (!basicDateAvailable) {
|
|
119
|
+
console.log(`Date ${dateStr} fails basic availability check`);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// If table assignment is not enabled, stop here - the date is available
|
|
124
|
+
if (!isTableAssignmentEnabled) {
|
|
125
|
+
console.log(`Table assignment disabled, date ${dateStr} is available`);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 2) Get all available timeblocks for this date
|
|
130
|
+
console.log(`Getting available timeblocks for ${dateStr}`);
|
|
131
|
+
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
|
|
132
|
+
console.log(`Found ${Object.keys(availableTimeblocks).length} available timeblocks before table check`);
|
|
133
|
+
|
|
134
|
+
// If no timeblocks are available at all, exit early
|
|
135
|
+
if (Object.keys(availableTimeblocks).length === 0) {
|
|
136
|
+
console.log(`No available timeblocks on ${dateStr}, date is unavailable`);
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 3) Check if any timeblock has an available table and satisfies giftcard requirements
|
|
141
|
+
console.log(`Checking table availability for each timeblock on ${dateStr}`);
|
|
142
|
+
|
|
143
|
+
let atLeastOneAvailable = false;
|
|
144
|
+
let checkedCount = 0;
|
|
145
|
+
|
|
146
|
+
for (const time of Object.keys(availableTimeblocks)) {
|
|
147
|
+
checkedCount++;
|
|
148
|
+
|
|
149
|
+
// If there's a giftcard, confirm this particular time belongs to a meal that has that giftcard
|
|
150
|
+
if (selectedGiftcard && !timeHasGiftcard(data, dateStr, time, selectedGiftcard)) {
|
|
151
|
+
console.log(`Time ${time} doesn't match giftcard meal requirements`);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log(`Checking table availability for ${time} on ${dateStr} for ${guests} guests`);
|
|
156
|
+
if (isTimeAvailableSync(data, dateStr, time, guests, reservations)) {
|
|
157
|
+
console.log(`Found available time ${time} with table assignment!`);
|
|
158
|
+
atLeastOneAvailable = true;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(`Checked ${checkedCount}/${Object.keys(availableTimeblocks).length} timeblocks`);
|
|
164
|
+
console.log(`Date ${dateStr} with table check: ${atLeastOneAvailable ? 'Available' : 'Unavailable'}`);
|
|
165
|
+
|
|
166
|
+
return atLeastOneAvailable;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
module.exports = {
|
|
170
|
+
isDateAvailableWithTableCheck,
|
|
171
|
+
};
|
package/isTimeAvailable.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
const { timeblocksAvailable } = require('./processing/timeblocksAvailable');
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Checks if a given time (HH:MM) is available for a reservation.
|
|
5
|
-
* @param {Object} data - The main data object containing settings and opening hours.
|
|
6
|
-
* @param {string} dateStr - The date string in "YYYY-MM-DD" format.
|
|
7
|
-
* @param {string} timeStr - The time string in "HH:MM" format.
|
|
8
|
-
* @param {Array} reservations - An array of existing reservation objects.
|
|
9
|
-
* @param {number} guests - The number of guests for the new reservation request.
|
|
10
|
-
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
11
|
-
* @param {string|null} giftcard - Optional giftcard to filter times by meal.
|
|
12
|
-
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
13
|
-
* @returns {boolean} - Returns true if the time is available, false otherwise.
|
|
14
|
-
*/
|
|
15
|
-
function isTimeAvailable(data, dateStr, timeStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
16
|
-
// Get all available timeblocks for the specified date and guest count
|
|
17
|
-
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
18
|
-
|
|
19
|
-
// Check if the specific timeStr is one of the available keys
|
|
20
|
-
return Object.prototype.hasOwnProperty.call(availableTimeblocks, timeStr);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
module.exports = {
|
|
24
|
-
isTimeAvailable,
|
|
25
|
-
};
|
|
1
|
+
const { timeblocksAvailable } = require('./processing/timeblocksAvailable');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a given time (HH:MM) is available for a reservation.
|
|
5
|
+
* @param {Object} data - The main data object containing settings and opening hours.
|
|
6
|
+
* @param {string} dateStr - The date string in "YYYY-MM-DD" format.
|
|
7
|
+
* @param {string} timeStr - The time string in "HH:MM" format.
|
|
8
|
+
* @param {Array} reservations - An array of existing reservation objects.
|
|
9
|
+
* @param {number} guests - The number of guests for the new reservation request.
|
|
10
|
+
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
11
|
+
* @param {string|null} giftcard - Optional giftcard to filter times by meal.
|
|
12
|
+
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
13
|
+
* @returns {boolean} - Returns true if the time is available, false otherwise.
|
|
14
|
+
*/
|
|
15
|
+
function isTimeAvailable(data, dateStr, timeStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
16
|
+
// Get all available timeblocks for the specified date and guest count
|
|
17
|
+
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
18
|
+
|
|
19
|
+
// Check if the specific timeStr is one of the available keys
|
|
20
|
+
return Object.prototype.hasOwnProperty.call(availableTimeblocks, timeStr);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
isTimeAvailable,
|
|
25
|
+
};
|