@happychef/algorithm 1.0.0

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.
@@ -0,0 +1,77 @@
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
+ * @returns {boolean} - Returns true if the date has at least one available timeblock, false otherwise.
61
+ */
62
+ function isDateAvailable(data, dateStr, reservations, guests) {
63
+ // Check if date is within allowed range
64
+ if (!isDateWithinAllowedRange(data, dateStr)) {
65
+ return false;
66
+ }
67
+
68
+ // Get available timeblocks using the existing logic
69
+ const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests);
70
+
71
+ // Return true only if we have at least one available timeblock
72
+ return Object.keys(availableTimeblocks).length > 0;
73
+ }
74
+
75
+ module.exports = {
76
+ isDateAvailable,
77
+ };
@@ -0,0 +1,169 @@
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 {string|null} selectedGiftcard - The selected giftcard (if any).
73
+ * @returns {boolean} - Returns true if the date passes all checks to be available.
74
+ */
75
+ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, selectedGiftcard) {
76
+ // 0) Optionally filter by selected menu item's date range (only when normalOpeningTimes = false)
77
+ const currentItem = typeof window !== 'undefined' ? window.currentReservationTicketItem : null;
78
+ if (
79
+ currentItem &&
80
+ !currentItem.normalOpeningTimes
81
+ ) {
82
+ const dateM = moment.tz(dateStr, 'YYYY-MM-DD', TIMEZONE).startOf('day');
83
+ const startM = currentItem.filterStartDate
84
+ ? moment.tz(currentItem.filterStartDate, 'YYYY-MM-DD', TIMEZONE).startOf('day')
85
+ : null;
86
+ const endM = currentItem.filterEndDate
87
+ ? moment.tz(currentItem.filterEndDate, 'YYYY-MM-DD', TIMEZONE).endOf('day')
88
+ : null;
89
+
90
+ if (startM && dateM.isBefore(startM)) {
91
+ console.log(`Date ${dateStr} is before the allowed start ${currentItem.filterStartDate}`);
92
+ return false;
93
+ }
94
+ if (endM && dateM.isAfter(endM)) {
95
+ console.log(`Date ${dateStr} is after the allowed end ${currentItem.filterEndDate}`);
96
+ return false;
97
+ }
98
+ }
99
+
100
+ console.log(`\n===== Checking date availability for ${guests} guests on ${dateStr} =====`);
101
+ console.log('Restaurant data floors:', data.floors ? 'present' : 'missing');
102
+ console.log('Table settings:', JSON.stringify(data['table-settings'] || {}, null, 2));
103
+
104
+ // Calculate table assignment flag once to reduce redundant checks
105
+ const tableSettings = data?.['table-settings'] || {};
106
+ const isTableAssignmentEnabled =
107
+ tableSettings.isInstalled === true &&
108
+ tableSettings.assignmentMode === 'automatic' &&
109
+ data.floors &&
110
+ Array.isArray(data.floors);
111
+
112
+ console.log(`Table assignment is ${isTableAssignmentEnabled ? 'ENABLED' : 'DISABLED'} for date ${dateStr}`);
113
+
114
+ // 1) First, do the standard day-level checks (including simple giftcard check).
115
+ const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, selectedGiftcard);
116
+ if (!basicDateAvailable) {
117
+ console.log(`Date ${dateStr} fails basic availability check`);
118
+ return false;
119
+ }
120
+
121
+ // If table assignment is not enabled, stop here - the date is available
122
+ if (!isTableAssignmentEnabled) {
123
+ console.log(`Table assignment disabled, date ${dateStr} is available`);
124
+ return true;
125
+ }
126
+
127
+ // 2) Get all available timeblocks for this date
128
+ console.log(`Getting available timeblocks for ${dateStr}`);
129
+ const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests);
130
+ console.log(`Found ${Object.keys(availableTimeblocks).length} available timeblocks before table check`);
131
+
132
+ // If no timeblocks are available at all, exit early
133
+ if (Object.keys(availableTimeblocks).length === 0) {
134
+ console.log(`No available timeblocks on ${dateStr}, date is unavailable`);
135
+ return false;
136
+ }
137
+
138
+ // 3) Check if any timeblock has an available table and satisfies giftcard requirements
139
+ console.log(`Checking table availability for each timeblock on ${dateStr}`);
140
+
141
+ let atLeastOneAvailable = false;
142
+ let checkedCount = 0;
143
+
144
+ for (const time of Object.keys(availableTimeblocks)) {
145
+ checkedCount++;
146
+
147
+ // If there's a giftcard, confirm this particular time belongs to a meal that has that giftcard
148
+ if (selectedGiftcard && !timeHasGiftcard(data, dateStr, time, selectedGiftcard)) {
149
+ console.log(`Time ${time} doesn't match giftcard meal requirements`);
150
+ continue;
151
+ }
152
+
153
+ console.log(`Checking table availability for ${time} on ${dateStr} for ${guests} guests`);
154
+ if (isTimeAvailableSync(data, dateStr, time, guests, reservations)) {
155
+ console.log(`Found available time ${time} with table assignment!`);
156
+ atLeastOneAvailable = true;
157
+ break;
158
+ }
159
+ }
160
+
161
+ console.log(`Checked ${checkedCount}/${Object.keys(availableTimeblocks).length} timeblocks`);
162
+ console.log(`Date ${dateStr} with table check: ${atLeastOneAvailable ? 'Available' : 'Unavailable'}`);
163
+
164
+ return atLeastOneAvailable;
165
+ }
166
+
167
+ module.exports = {
168
+ isDateAvailableWithTableCheck,
169
+ };
@@ -0,0 +1,22 @@
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
+ * @returns {boolean} - Returns true if the time is available, false otherwise.
11
+ */
12
+ function isTimeAvailable(data, dateStr, timeStr, reservations, guests) {
13
+ // Get all available timeblocks for the specified date and guest count
14
+ const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests);
15
+
16
+ // Check if the specific timeStr is one of the available keys
17
+ return Object.prototype.hasOwnProperty.call(availableTimeblocks, timeStr);
18
+ }
19
+
20
+ module.exports = {
21
+ isTimeAvailable,
22
+ };
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "@happychef/algorithm",
3
+ "version": "1.0.0",
4
+ "description": "Restaurant and reservation algorithm utilities",
5
+ "main": "index.js",
6
+ "author": "happy chef",
7
+ "license": "MIT",
8
+ "private": false,
9
+ "keywords": [
10
+ "algorithm",
11
+ "restaurant",
12
+ "reservation",
13
+ "utilities"
14
+ ],
15
+ "dependencies": {
16
+ "moment-timezone": "^0.6.0"
17
+ }
18
+ }
@@ -0,0 +1,73 @@
1
+ // dailyGuestCounts.js
2
+
3
+ const { getGuestCountsForMeal } = require('./mealTypeCount');
4
+
5
+ /**
6
+ * Calculates guest counts for breakfast, lunch, and dinner, and combines the results into a flat object.
7
+ * If time slots overlap, the available seats from the latest meal (dinner > lunch > breakfast) are used.
8
+ * @param {Object} data - The main data object.
9
+ * @param {string} dateStr - The date string (YYYY-MM-DD).
10
+ * @param {Array} reservations - An array of reservation objects.
11
+ * @returns {Object} An object containing combined guest counts for all meals with time slots as keys,
12
+ * and an array of shiftsInfo containing shift details.
13
+ */
14
+ function getDailyGuestCounts(data, dateStr, reservations) {
15
+ // Define meal types in order of priority (lowest to highest)
16
+ const mealTypes = ['breakfast', 'lunch', 'dinner'];
17
+ const combinedGuestCounts = {};
18
+ const shiftsInfo = [];
19
+ const mealPriority = {
20
+ 'breakfast': 1,
21
+ 'lunch': 2,
22
+ 'dinner': 3
23
+ };
24
+
25
+ for (const mealType of mealTypes) {
26
+ const result = getGuestCountsForMeal(data, dateStr, mealType, reservations);
27
+ if (result) {
28
+ const { guestCounts, shiftsInfo: mealShiftsInfo } = result;
29
+
30
+ // Merge guestCounts into combinedGuestCounts
31
+ for (const [time, availableSeats] of Object.entries(guestCounts)) {
32
+ if (combinedGuestCounts.hasOwnProperty(time)) {
33
+ // Compare meal priorities
34
+ const existingMealPriority = combinedGuestCounts[time].mealPriority;
35
+ const currentMealPriority = mealPriority[mealType];
36
+
37
+ if (currentMealPriority >= existingMealPriority) {
38
+ // Update with the current meal's available seats and priority
39
+ combinedGuestCounts[time] = {
40
+ availableSeats,
41
+ mealPriority: currentMealPriority
42
+ };
43
+ }
44
+ // Else, keep the existing value
45
+ } else {
46
+ // Add new time slot with available seats and meal priority
47
+ combinedGuestCounts[time] = {
48
+ availableSeats,
49
+ mealPriority: mealPriority[mealType]
50
+ };
51
+ }
52
+ }
53
+
54
+ // Merge shiftsInfo
55
+ if (mealShiftsInfo && mealShiftsInfo.length > 0) {
56
+ shiftsInfo.push(...mealShiftsInfo);
57
+ }
58
+ }
59
+ // Else do nothing if the meal is not available
60
+ }
61
+
62
+ // Extract only the availableSeats for the final output
63
+ const finalGuestCounts = {};
64
+ for (const [time, data] of Object.entries(combinedGuestCounts)) {
65
+ finalGuestCounts[time] = data.availableSeats;
66
+ }
67
+
68
+ return { guestCounts: finalGuestCounts, shiftsInfo };
69
+ }
70
+
71
+ module.exports = {
72
+ getDailyGuestCounts,
73
+ };
@@ -0,0 +1,133 @@
1
+ // mealTypeCount.js
2
+
3
+ const { getDataByDateAndMealWithExceptions } = require('../restaurant_data/exceptions');
4
+ const { parseTime } = require('../restaurant_data/openinghours');
5
+ const { getGuestCountAtHour } = require('../reservation_data/counter');
6
+
7
+ function getInterval(data) {
8
+ let intervalReservatie = 15;
9
+ if (
10
+ data['general-settings'] &&
11
+ data['general-settings'].intervalReservatie &&
12
+ parseInt(data['general-settings'].intervalReservatie, 10) > 0
13
+ ) {
14
+ intervalReservatie = parseInt(data['general-settings'].intervalReservatie, 10);
15
+ }
16
+ return intervalReservatie;
17
+ }
18
+
19
+ /**
20
+ * Retrieves meal types with shifts enabled and at least one shift defined for the specified date.
21
+ * @param {Object} data - The main data object.
22
+ * @param {string} dateStr - The date string (YYYY-MM-DD).
23
+ * @returns {Array} - An array of meal types with shifts.
24
+ */
25
+ function getMealTypesWithShifts(data, dateStr) {
26
+ const mealTypes = ['breakfast', 'lunch', 'dinner'];
27
+ const mealTypesWithShifts = [];
28
+
29
+ for (const mealType of mealTypes) {
30
+ const mealData = getDataByDateAndMealWithExceptions(data, dateStr, mealType);
31
+ if (
32
+ mealData &&
33
+ mealData.shiftsEnabled &&
34
+ Array.isArray(mealData.shifts) &&
35
+ mealData.shifts.length > 0
36
+ ) {
37
+ mealTypesWithShifts.push(mealType);
38
+ }
39
+ }
40
+
41
+ return mealTypesWithShifts;
42
+ }
43
+
44
+ function shouldIncludeEndTime(mealType, endTime) {
45
+ if ((mealType === 'breakfast' && endTime === '11:00') ||
46
+ (mealType === 'lunch' && endTime === '16:00')) {
47
+ return false; // Do not include endTime for breakfast at 11:00 and lunch at 16:00
48
+ }
49
+ return true; // Include endTime for all other cases
50
+ }
51
+
52
+ /**
53
+ * Calculates guest counts for each interval during a meal period or at shift times.
54
+ * @param {Object} data - The main data object.
55
+ * @param {string} dateStr - The date string (YYYY-MM-DD).
56
+ * @param {string} mealType - The meal type ("breakfast", "lunch", "dinner").
57
+ * @param {Array} reservations - An array of reservation objects.
58
+ * @returns {Object|null} An object mapping times to guest counts, or null if meal is not available.
59
+ */
60
+ function getGuestCountsForMeal(data, dateStr, mealType, reservations) {
61
+ const mealData = getDataByDateAndMealWithExceptions(data, dateStr, mealType);
62
+ if (!mealData) {
63
+ return null;
64
+ }
65
+
66
+ const guestCounts = {};
67
+ const shiftsInfo = [];
68
+
69
+ // Get 'intervalReservatie' from general settings, default to 15 if not set or zero
70
+ let intervalReservatie = getInterval(data);
71
+
72
+ // Check if shifts are enabled and shifts array has valid content
73
+ if (
74
+ mealData.shiftsEnabled &&
75
+ Array.isArray(mealData.shifts) &&
76
+ mealData.shifts.length > 0
77
+ ) {
78
+ // If shifts are enabled and valid, calculate guest counts at shift times
79
+ const shifts = mealData.shifts; // Array of shifts
80
+
81
+ for (const shift of shifts) {
82
+ const timeStr = shift.time; // Time of the shift in "HH:MM" format
83
+
84
+ // Get guest count at this time
85
+ const guestCount = getGuestCountAtHour(data, reservations, timeStr, dateStr);
86
+
87
+ // Store in guestCounts
88
+ guestCounts[timeStr] = mealData.maxCapacity - guestCount;
89
+
90
+ // Store shift information
91
+ shiftsInfo.push({
92
+ mealType,
93
+ shiftName: shift.name,
94
+ time: timeStr,
95
+ availableSeats: mealData.maxCapacity - guestCount,
96
+ });
97
+ }
98
+ } else {
99
+ // If shifts are not enabled or shifts array is empty/invalid, calculate guest counts at intervals
100
+ const startTime = mealData.startTime;
101
+ const endTime = mealData.endTime;
102
+
103
+ // Determine if endTime should be included
104
+ const includeEndTime = shouldIncludeEndTime(mealType, endTime);
105
+
106
+ // Convert startTime and endTime to minutes since midnight
107
+ let currentTime = parseTime(startTime);
108
+ const endTimeMinutes = parseTime(endTime);
109
+
110
+ while (includeEndTime ? currentTime <= endTimeMinutes : currentTime < endTimeMinutes) {
111
+ // Convert currentTime back to "HH:MM" format
112
+ const hours = Math.floor(currentTime / 60).toString().padStart(2, '0');
113
+ const minutes = (currentTime % 60).toString().padStart(2, '0');
114
+ const timeStr = `${hours}:${minutes}`;
115
+
116
+ // Get guest count at this time
117
+ const guestCount = getGuestCountAtHour(data, reservations, timeStr, dateStr);
118
+
119
+ // Store in guestCounts
120
+ guestCounts[timeStr] = mealData.maxCapacity - guestCount;
121
+
122
+ // Increment currentTime by 'intervalReservatie' minutes
123
+ currentTime += intervalReservatie;
124
+ }
125
+ }
126
+
127
+ return { guestCounts, shiftsInfo };
128
+ }
129
+
130
+ module.exports = {
131
+ getGuestCountsForMeal,
132
+ getMealTypesWithShifts, // Exported
133
+ };
@@ -0,0 +1,120 @@
1
+ const { getDailyGuestCounts } = require('./dailyGuestCounts');
2
+ const { getMealTypesWithShifts } = require('./mealTypeCount');
3
+ const { getDataByDateAndMealWithExceptions } = require('../restaurant_data/exceptions');
4
+ const { getMealTypeByTime, parseTime: parseTimeOH } = require('../restaurant_data/openinghours');
5
+
6
+ /**
7
+ * Parses a time string in "HH:MM" format into minutes since midnight.
8
+ */
9
+ function parseTime(timeStr) {
10
+ const [hours, minutes] = timeStr.split(':').map(Number);
11
+ return hours * 60 + minutes;
12
+ }
13
+
14
+ /**
15
+ * Retrieves interval and duurReservatie from general settings
16
+ */
17
+ function getInterval(data) {
18
+ let intervalReservatie = 15;
19
+ if (
20
+ data['general-settings'] &&
21
+ data['general-settings'].intervalReservatie &&
22
+ parseInt(data['general-settings'].intervalReservatie, 10) > 0
23
+ ) {
24
+ intervalReservatie = parseInt(data['general-settings'].intervalReservatie, 10);
25
+ }
26
+ return intervalReservatie;
27
+ }
28
+
29
+ function getDuurReservatie(data) {
30
+ let duurReservatie = 120;
31
+ if (
32
+ data['general-settings'] &&
33
+ data['general-settings'].duurReservatie &&
34
+ parseInt(data['general-settings'].duurReservatie, 10) > 0
35
+ ) {
36
+ duurReservatie = parseInt(data['general-settings'].duurReservatie, 10);
37
+ }
38
+ return duurReservatie;
39
+ }
40
+
41
+ /**
42
+ * Determines if a given start time plus duurReservatie fits within the meal timeframe.
43
+ */
44
+ function fitsWithinMeal(data, dateStr, startTimeStr, duurReservatie) {
45
+ // Determine the meal type based on the start time
46
+ const mealType = getMealTypeByTime(startTimeStr);
47
+ if (!mealType) return false;
48
+
49
+ // Get the meal data (with exceptions applied)
50
+ const mealData = getDataByDateAndMealWithExceptions(data, dateStr, mealType);
51
+ if (!mealData) return false;
52
+
53
+ const mealStartTime = parseTime(mealData.startTime);
54
+ const mealEndTime = parseTime(mealData.endTime);
55
+ const startTime = parseTime(startTimeStr);
56
+
57
+ // Check if startTime is after or equal to mealStartTime AND startTime + duurReservatie is within the mealEndTime
58
+ return startTime >= mealStartTime && startTime + duurReservatie <= mealEndTime;
59
+ }
60
+
61
+ function timeblocksAvailable(data, dateStr, reservations, guests) {
62
+ const duurReservatie = getDuurReservatie(data);
63
+ const intervalReservatie = getInterval(data);
64
+
65
+ // Slots needed
66
+ const slotsNeeded = Math.ceil(duurReservatie / intervalReservatie);
67
+
68
+ // Get guest counts and shifts info
69
+ const { guestCounts, shiftsInfo } = getDailyGuestCounts(data, dateStr, reservations);
70
+
71
+ const availableTimeblocks = {};
72
+
73
+ // Handle shifts first
74
+ const mealTypesWithShifts = getMealTypesWithShifts(data, dateStr);
75
+ if (mealTypesWithShifts.length > 0 && shiftsInfo && shiftsInfo.length > 0) {
76
+ for (const shift of shiftsInfo) {
77
+ const { time, availableSeats } = shift;
78
+ if (availableSeats >= guests && fitsWithinMeal(data, dateStr, time, duurReservatie)) {
79
+ availableTimeblocks[time] = { name: time };
80
+ }
81
+ }
82
+ }
83
+
84
+ // Handle non-shift times
85
+ if (guestCounts && Object.keys(guestCounts).length > 0) {
86
+ const timeSlots = Object.keys(guestCounts).sort((a, b) => parseTime(a) - parseTime(b));
87
+
88
+ for (let i = 0; i <= timeSlots.length - slotsNeeded; i++) {
89
+ // Check capacity for all needed slots
90
+ let consecutiveSlotsAvailable = true;
91
+ if (guestCounts[timeSlots[i]] < guests) {
92
+ continue;
93
+ }
94
+
95
+ let previousTime = parseTime(timeSlots[i]);
96
+ for (let j = 1; j < slotsNeeded; j++) {
97
+ const currentTimeSlot = timeSlots[i + j];
98
+ const currentTime = parseTime(currentTimeSlot);
99
+
100
+ // Check interval and capacity
101
+ if ((currentTime - previousTime) !== intervalReservatie || guestCounts[currentTimeSlot] < guests) {
102
+ consecutiveSlotsAvailable = false;
103
+ break;
104
+ }
105
+ previousTime = currentTime;
106
+ }
107
+
108
+ // If all consecutive slots are available, check if the full duration fits
109
+ if (consecutiveSlotsAvailable && fitsWithinMeal(data, dateStr, timeSlots[i], duurReservatie)) {
110
+ availableTimeblocks[timeSlots[i]] = { name: timeSlots[i] };
111
+ }
112
+ }
113
+ }
114
+
115
+ return availableTimeblocks;
116
+ }
117
+
118
+ module.exports = {
119
+ timeblocksAvailable,
120
+ };
@@ -0,0 +1,65 @@
1
+ // counter.js
2
+
3
+ /**
4
+ * Parses a time string in "HH:MM" format into minutes since midnight.
5
+ * @param {string} timeStr - The time string in "HH:MM" format.
6
+ * @returns {number} The time in minutes since midnight.
7
+ */
8
+ function parseTime(timeStr) {
9
+ const [hours, minutes] = timeStr.split(':').map(Number);
10
+ return hours * 60 + minutes;
11
+ }
12
+
13
+ function getDuurReservatie(data) {
14
+ let duurReservatie = 120;
15
+ if (
16
+ data['general-settings'] &&
17
+ data['general-settings'].duurReservatie &&
18
+ parseInt(data['general-settings'].duurReservatie, 10) > 0
19
+ ) {
20
+ duurReservatie = parseInt(data['general-settings'].duurReservatie, 10);
21
+ }
22
+
23
+ return duurReservatie;
24
+ }
25
+
26
+ /**
27
+ * Calculates the total number of guests for reservations that cover a specific hour on a specific date.
28
+ * @param {Object} data - The main data object containing general settings.
29
+ * @param {Array} reservations - An array of reservation objects.
30
+ * @param {string} hour - The hour to check in "HH:MM" format.
31
+ * @param {string} dateStr - The date string in "YYYY-MM-DD" format.
32
+ * @returns {number} The total number of guests for that hour on the specified date.
33
+ */
34
+ function getGuestCountAtHour(data, reservations, hour, dateStr) {
35
+ // Get 'duurReservatie' from general settings, default to 120 if not set or zero
36
+ let duurReservatie = getDuurReservatie(data)
37
+
38
+ // Convert the target hour to minutes since midnight
39
+ const targetTime = parseTime(hour);
40
+
41
+ let totalGuests = 0;
42
+
43
+ for (const reservation of reservations) {
44
+ // Only consider reservations on the specified date
45
+ if (reservation.date !== dateStr) {
46
+ continue;
47
+ }
48
+
49
+ const startTime = parseTime(reservation.time);
50
+
51
+ const endTime = startTime + duurReservatie; // Use 'duurReservatie' from general settings
52
+ // Check if the target time is within the reservation time range
53
+ // Start time is inclusive, end time is exclusive
54
+ if (targetTime >= startTime && targetTime < endTime) {
55
+ totalGuests += parseInt(reservation.guests, 10);
56
+ }
57
+ }
58
+
59
+ return totalGuests;
60
+ }
61
+
62
+ module.exports = {
63
+ getGuestCountAtHour,
64
+ };
65
+