@happychef/algorithm 1.4.0 → 1.4.1

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.
@@ -150,6 +150,18 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
150
150
  }
151
151
  }
152
152
 
153
+ // For bowling venues, cap dinner time slots at 24:00 max
154
+ const isBowling = data?.account_type === 'bowling' || data?.accountType === 'bowling' || data?.['general-settings']?.account_type === 'bowling';
155
+ if (isBowling) {
156
+ for (const time in availableTimeblocks) {
157
+ const [h, m] = time.split(':').map(Number);
158
+ const timeMinutes = h * 60 + m;
159
+ if (timeMinutes > 1440) { // > 24:00
160
+ delete availableTimeblocks[time];
161
+ }
162
+ }
163
+ }
164
+
153
165
  return availableTimeblocks;
154
166
  }
155
167
 
package/index.js CHANGED
@@ -15,10 +15,6 @@ const restaurant_data = {
15
15
  ...require("./restaurant_data/openinghours")
16
16
  };
17
17
 
18
- const dateHelpers = {
19
- ...require("./dateHelpers")
20
- };
21
-
22
18
  const test = {
23
19
  ...require("./assignTables"),
24
20
  ...require("./getAvailableTimeblocks"),
@@ -28,8 +24,7 @@ const test = {
28
24
  ...require("./simulateTableAssignment"),
29
25
  ...require("./isDateAvailableWithTableCheck"),
30
26
  ...require("./tableHelpers"),
31
- ...require("./test"),
32
- ...require("./getDateClosingReasons")
27
+ ...require("./test")
33
28
  };
34
29
 
35
30
  const filters = {
@@ -42,7 +37,6 @@ module.exports = {
42
37
  ...processing,
43
38
  ...reservation_data,
44
39
  ...restaurant_data,
45
- ...dateHelpers,
46
40
  ...test,
47
41
  ...filters
48
42
  };
package/jest.config.js CHANGED
@@ -1,23 +1,23 @@
1
- module.exports = {
2
- testEnvironment: 'node',
3
- coverageDirectory: 'coverage',
4
- collectCoverageFrom: [
5
- '**/*.js',
6
- '!**/node_modules/**',
7
- '!**/coverage/**',
8
- '!jest.config.js',
9
- '!test.js',
10
- '!test-*.js',
11
- '!**/__tests__/**'
12
- ],
13
- testMatch: [
14
- '**/__tests__/**/*.test.js'
15
- ],
16
- testPathIgnorePatterns: [
17
- '/node_modules/',
18
- '/test\\.js$',
19
- '/test-.*\\.js$'
20
- ],
21
- verbose: true,
22
- testTimeout: 10000
23
- };
1
+ module.exports = {
2
+ testEnvironment: 'node',
3
+ coverageDirectory: 'coverage',
4
+ collectCoverageFrom: [
5
+ '**/*.js',
6
+ '!**/node_modules/**',
7
+ '!**/coverage/**',
8
+ '!jest.config.js',
9
+ '!test.js',
10
+ '!test-*.js',
11
+ '!**/__tests__/**'
12
+ ],
13
+ testMatch: [
14
+ '**/__tests__/**/*.test.js'
15
+ ],
16
+ testPathIgnorePatterns: [
17
+ '/node_modules/',
18
+ '/test\\.js$',
19
+ '/test-.*\\.js$'
20
+ ],
21
+ verbose: true,
22
+ testTimeout: 10000
23
+ };
@@ -0,0 +1,179 @@
1
+ // Minimal moment-timezone shim for Europe/Brussels only
2
+ // Replaces the full ~500KB moment-timezone library
3
+ // Implements only the subset used by @happychef/algorithm
4
+
5
+ const TIMEZONE = 'Europe/Brussels';
6
+
7
+ // EU DST rules: CET (UTC+1) / CEST (UTC+2)
8
+ // CEST starts last Sunday of March at 02:00 UTC
9
+ // CET starts last Sunday of October at 03:00 UTC
10
+ function getBrusselsOffset(date) {
11
+ const year = date.getUTCFullYear();
12
+ const month = date.getUTCMonth(); // 0-indexed
13
+
14
+ // Find last Sunday of March
15
+ const marchLast = new Date(Date.UTC(year, 2, 31));
16
+ const marchSunday = 31 - marchLast.getUTCDay();
17
+ const dstStart = Date.UTC(year, 2, marchSunday, 1, 0, 0); // 02:00 CET = 01:00 UTC
18
+
19
+ // Find last Sunday of October
20
+ const octLast = new Date(Date.UTC(year, 9, 31));
21
+ const octSunday = 31 - octLast.getUTCDay();
22
+ const dstEnd = Date.UTC(year, 9, octSunday, 1, 0, 0); // 03:00 CEST = 01:00 UTC
23
+
24
+ const ts = date.getTime();
25
+ if (ts >= dstStart && ts < dstEnd) {
26
+ return 2; // CEST (UTC+2)
27
+ }
28
+ return 1; // CET (UTC+1)
29
+ }
30
+
31
+ function toBrusselsDate(date) {
32
+ const offset = getBrusselsOffset(date);
33
+ return new Date(date.getTime() + offset * 60 * 60 * 1000);
34
+ }
35
+
36
+ function createMomentObject(date, offset) {
37
+ const brusselsDate = new Date(date.getTime() + offset * 60 * 60 * 1000);
38
+
39
+ const obj = {
40
+ _date: date,
41
+ _brusselsDate: brusselsDate,
42
+ _offset: offset,
43
+ _valid: true,
44
+
45
+ isValid() {
46
+ return this._valid;
47
+ },
48
+
49
+ day() {
50
+ return this._brusselsDate.getUTCDay();
51
+ },
52
+
53
+ hours() {
54
+ return this._brusselsDate.getUTCHours();
55
+ },
56
+
57
+ minutes() {
58
+ return this._brusselsDate.getUTCMinutes();
59
+ },
60
+
61
+ format(fmt) {
62
+ if (fmt === 'YYYY-MM-DD') {
63
+ const y = this._brusselsDate.getUTCFullYear();
64
+ const m = String(this._brusselsDate.getUTCMonth() + 1).padStart(2, '0');
65
+ const d = String(this._brusselsDate.getUTCDate()).padStart(2, '0');
66
+ return `${y}-${m}-${d}`;
67
+ }
68
+ if (fmt === 'HH:mm') {
69
+ const h = String(this._brusselsDate.getUTCHours()).padStart(2, '0');
70
+ const min = String(this._brusselsDate.getUTCMinutes()).padStart(2, '0');
71
+ return `${h}:${min}`;
72
+ }
73
+ return this._brusselsDate.toISOString();
74
+ },
75
+
76
+ startOf(unit) {
77
+ if (unit === 'day') {
78
+ const d = new Date(Date.UTC(
79
+ this._brusselsDate.getUTCFullYear(),
80
+ this._brusselsDate.getUTCMonth(),
81
+ this._brusselsDate.getUTCDate(),
82
+ 0, 0, 0, 0
83
+ ));
84
+ // Adjust back from Brussels to UTC
85
+ const utcDate = new Date(d.getTime() - this._offset * 60 * 60 * 1000);
86
+ return createMomentObject(utcDate, this._offset);
87
+ }
88
+ return this;
89
+ },
90
+
91
+ endOf(unit) {
92
+ if (unit === 'day') {
93
+ const d = new Date(Date.UTC(
94
+ this._brusselsDate.getUTCFullYear(),
95
+ this._brusselsDate.getUTCMonth(),
96
+ this._brusselsDate.getUTCDate(),
97
+ 23, 59, 59, 999
98
+ ));
99
+ const utcDate = new Date(d.getTime() - this._offset * 60 * 60 * 1000);
100
+ return createMomentObject(utcDate, this._offset);
101
+ }
102
+ return this;
103
+ },
104
+
105
+ isBefore(other) {
106
+ return this._date.getTime() < other._date.getTime();
107
+ },
108
+
109
+ isAfter(other) {
110
+ return this._date.getTime() > other._date.getTime();
111
+ },
112
+
113
+ tz() {
114
+ // Already in Brussels timezone, return self
115
+ return this;
116
+ },
117
+ };
118
+
119
+ return obj;
120
+ }
121
+
122
+ function parseDateStr(dateStr, format) {
123
+ if (format === 'YYYY-MM-DD') {
124
+ const parts = dateStr.split('-');
125
+ if (parts.length !== 3) return null;
126
+ const year = parseInt(parts[0], 10);
127
+ const month = parseInt(parts[1], 10) - 1;
128
+ const day = parseInt(parts[2], 10);
129
+ if (isNaN(year) || isNaN(month) || isNaN(day)) return null;
130
+ if (month < 0 || month > 11 || day < 1 || day > 31) return null;
131
+ // Create date at midnight Brussels time -> convert to UTC
132
+ const utcDate = new Date(Date.UTC(year, month, day, 0, 0, 0));
133
+ return utcDate;
134
+ }
135
+ return null;
136
+ }
137
+
138
+ // Main moment function
139
+ function moment(dateStr, format, timezone) {
140
+ if (dateStr === undefined || dateStr === null) {
141
+ // moment() - current time
142
+ const now = new Date();
143
+ const offset = getBrusselsOffset(now);
144
+ const obj = createMomentObject(now, offset);
145
+ obj.tz = function() { return obj; };
146
+ return obj;
147
+ }
148
+
149
+ if (typeof dateStr === 'string') {
150
+ const date = parseDateStr(dateStr, format || 'YYYY-MM-DD');
151
+ if (!date) {
152
+ return { _valid: false, isValid() { return false; }, day() { return 0; }, tz() { return this; } };
153
+ }
154
+ const offset = getBrusselsOffset(date);
155
+ return createMomentObject(date, offset);
156
+ }
157
+
158
+ // Fallback
159
+ const now = new Date();
160
+ const offset = getBrusselsOffset(now);
161
+ return createMomentObject(now, offset);
162
+ }
163
+
164
+ // moment.tz(dateStr, format, timezone) or moment().tz(timezone)
165
+ moment.tz = function(dateStr, format, timezone) {
166
+ if (typeof dateStr === 'string' && typeof format === 'string' && typeof timezone === 'string') {
167
+ // moment.tz(dateStr, format, timezone)
168
+ return moment(dateStr, format, timezone);
169
+ }
170
+ if (typeof dateStr === 'string' && typeof format === 'string' && timezone === undefined) {
171
+ // moment.tz(dateStr, timezone) - dateStr is actual date, format is timezone
172
+ // This pattern: moment().tz('Europe/Brussels')
173
+ return moment(dateStr, 'YYYY-MM-DD');
174
+ }
175
+ // Fallback: current time in Brussels
176
+ return moment();
177
+ };
178
+
179
+ module.exports = moment;
package/nul ADDED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -1,9 +1,7 @@
1
1
  const { getDailyGuestCounts } = require('./dailyGuestCounts');
2
2
  const { getMealTypesWithShifts } = require('./mealTypeCount');
3
3
  const { getDataByDateAndMealWithExceptions } = require('../restaurant_data/exceptions');
4
- const { getMealTypeByTime, daysOfWeekEnglish } = require('../restaurant_data/openinghours');
5
- const { getGuestCountAtHour } = require('../reservation_data/counter');
6
- const { getNextDateStr } = require('../dateHelpers');
4
+ const { getMealTypeByTime, parseTime: parseTimeOH, daysOfWeekEnglish } = require('../restaurant_data/openinghours');
7
5
  const moment = require('moment-timezone');
8
6
 
9
7
  /**
@@ -41,38 +39,6 @@ function getDuurReservatie(data) {
41
39
  return duurReservatie;
42
40
  }
43
41
 
44
- function getHoursOpenedPastMidnight(data, dateStr) {
45
- // Per-day value from dinner opening hours
46
- const m = moment.tz(dateStr, 'YYYY-MM-DD', 'Europe/Brussels');
47
- if (m.isValid()) {
48
- const day = daysOfWeekEnglish[m.day()];
49
- const dinnerSettings = data['openinghours-dinner']?.schemeSettings?.[day];
50
- if (dinnerSettings?.hoursOpenedPastMidnight !== undefined) {
51
- const parsed = parseInt(dinnerSettings.hoursOpenedPastMidnight, 10);
52
- return (!isNaN(parsed) && parsed > 0) ? parsed : 0;
53
- }
54
- }
55
- // Fallback: global setting from general-settings (backward compat)
56
- const val = data['general-settings']?.hoursOpenedPastMidnight;
57
- const parsed = parseInt(val, 10);
58
- return (!isNaN(parsed) && parsed > 0) ? parsed : 0;
59
- }
60
-
61
- /**
62
- * Checks if hoursOpenedPastMidnight is explicitly defined in settings.
63
- * Used to distinguish bowling restaurants (who set it) from regular ones (who don't).
64
- */
65
- function hasMidnightSetting(data, dateStr) {
66
- const m = moment.tz(dateStr, 'YYYY-MM-DD', 'Europe/Brussels');
67
- if (m.isValid()) {
68
- const day = daysOfWeekEnglish[m.day()];
69
- const dinnerSettings = data['openinghours-dinner']?.schemeSettings?.[day];
70
- if (dinnerSettings?.hoursOpenedPastMidnight !== undefined) return true;
71
- }
72
- const val = data['general-settings']?.hoursOpenedPastMidnight;
73
- return val !== undefined && val !== null;
74
- }
75
-
76
42
  /**
77
43
  * Checks if a time slot belongs to a meal that has the selected giftcard enabled.
78
44
  */
@@ -104,73 +70,32 @@ function timeHasGiftcard(data, dateStr, timeStr, giftcard) {
104
70
  }
105
71
 
106
72
  /**
107
- * Determines if a given start time fits within any meal's timeframe.
108
- * For bowling restaurants (hasMidnightSetting), the extended endTime is used so that
109
- * slots like 23:15, 24:00 are valid start times.
110
- * For regular restaurants, uses the original production logic:
111
- * startTime + duurReservatie <= mealEndTime
112
- * which naturally caps the last bookable slot (e.g. 22:45 for 23:00 close + 15min interval).
73
+ * Determines if a given start time plus duurReservatie fits within the meal timeframe.
113
74
  */
114
75
  function fitsWithinMeal(data, dateStr, startTimeStr, duurReservatie) {
115
- const startTime = parseTime(startTimeStr);
116
-
117
- // Original production logic for breakfast/lunch (always, regardless of bowling)
76
+ // Determine the meal type based on the start time
118
77
  const mealType = getMealTypeByTime(startTimeStr);
119
- if (mealType === 'breakfast' || mealType === 'lunch') {
120
- const mealData = getDataByDateAndMealWithExceptions(data, dateStr, mealType);
121
- if (!mealData) return false;
122
- const mealStartTime = parseTime(mealData.startTime);
123
- const mealEndTime = parseTime(mealData.endTime);
124
- return startTime >= mealStartTime && startTime + duurReservatie <= mealEndTime;
125
- }
126
-
127
- if (hasMidnightSetting(data, dateStr)) {
128
- // Bowling dinner: allow start times up to extended endTime (e.g. 24:00)
129
- const dinnerData = getDataByDateAndMealWithExceptions(data, dateStr, 'dinner');
130
- if (!dinnerData) return false;
131
- const dinnerStart = parseTime(dinnerData.startTime);
132
- const dinnerEnd = parseTime(dinnerData.endTime);
133
- return startTime >= dinnerStart && startTime <= dinnerEnd;
134
- }
135
-
136
- // Regular restaurant dinner: original production logic
137
78
  if (!mealType) return false;
79
+
80
+ // Get the meal data (with exceptions applied)
138
81
  const mealData = getDataByDateAndMealWithExceptions(data, dateStr, mealType);
139
82
  if (!mealData) return false;
83
+
140
84
  const mealStartTime = parseTime(mealData.startTime);
141
- const mealEndTime = parseTime(mealData.endTime);
142
- return startTime >= mealStartTime && startTime + duurReservatie <= mealEndTime;
143
- }
85
+ const mealEndTime = parseTime(mealData.endTime); // Already includes restaurant's duurReservatie
86
+ const startTime = parseTime(startTimeStr);
144
87
 
145
- /**
146
- * Converts minutes since midnight to "HH:MM" string.
147
- */
148
- function minutesToTimeStr(minutes) {
149
- const h = Math.floor(minutes / 60);
150
- const m = minutes % 60;
151
- return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
152
- }
88
+ // mealEndTime includes the restaurant's base duurReservatie (added by addDuurReservatieToEndTime).
89
+ // Recover the actual closing time so bookings can't START after the venue closes,
90
+ // even when the user picks a shorter duration than the restaurant's default.
91
+ const restaurantDuur = getDuurReservatie(data);
92
+ const actualMealEnd = mealEndTime - restaurantDuur;
153
93
 
154
- /**
155
- * Gets the max capacity for a time on a given date by checking all meals.
156
- * Returns 0 if no meal covers the time (restaurant is closed at that time).
157
- */
158
- function getMaxCapacityForTime(data, dateStr, timeMinutes) {
159
- for (const mealType of ['breakfast', 'lunch', 'dinner']) {
160
- const mealData = getDataByDateAndMealWithExceptions(data, dateStr, mealType);
161
- if (!mealData) continue;
162
- const mealStart = parseTime(mealData.startTime);
163
- const mealEnd = parseTime(mealData.endTime);
164
- if (timeMinutes >= mealStart && timeMinutes <= mealEnd) {
165
- return parseInt(mealData.maxCapacity, 10) || 0;
166
- }
167
- }
168
- return 0;
94
+ return startTime >= mealStartTime && startTime <= actualMealEnd && startTime + duurReservatie <= mealEndTime;
169
95
  }
170
96
 
171
97
  function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false, duration = null) {
172
- const defaultDuurReservatie = getDuurReservatie(data);
173
- const duurReservatie = duration && duration > 0 ? duration : defaultDuurReservatie;
98
+ const duurReservatie = duration && duration > 0 ? duration : getDuurReservatie(data);
174
99
  const intervalReservatie = getInterval(data);
175
100
 
176
101
  // Slots needed
@@ -179,42 +104,6 @@ function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots =
179
104
  // Get guest counts and shifts info
180
105
  const { guestCounts, shiftsInfo } = getDailyGuestCounts(data, dateStr, reservations);
181
106
 
182
- // If custom duration exceeds default, extend guestCounts with additional slots
183
- // so that late-evening reservations have enough consecutive slots to check against.
184
- const hoursOpenedPastMidnight = getHoursOpenedPastMidnight(data, dateStr);
185
- if (duurReservatie > defaultDuurReservatie && guestCounts && Object.keys(guestCounts).length > 0) {
186
- const existingSlots = Object.keys(guestCounts).map(parseTime);
187
- const maxExistingSlot = Math.max(...existingSlots);
188
- const extraMinutes = duurReservatie - defaultDuurReservatie;
189
- const extendedEnd = maxExistingSlot + extraMinutes;
190
-
191
- // Find the last meal's maxCapacity for use with spillover slots
192
- const lastMealMaxCap = getMaxCapacityForTime(data, dateStr, maxExistingSlot);
193
-
194
- for (let t = maxExistingSlot + intervalReservatie; t <= extendedEnd; t += intervalReservatie) {
195
- const timeStr = minutesToTimeStr(t);
196
- if (!(timeStr in guestCounts)) {
197
- if (t >= 1440) {
198
- // Cross-midnight slot: use hoursOpenedPastMidnight limit and last meal's capacity
199
- const spilloverMinutes = t - 1440;
200
- if (hoursOpenedPastMidnight > 0 && spilloverMinutes < hoursOpenedPastMidnight * 60) {
201
- const gc = getGuestCountAtHour(data, reservations, minutesToTimeStr(spilloverMinutes), getNextDateStr(dateStr));
202
- if (lastMealMaxCap > 0) {
203
- guestCounts[timeStr] = lastMealMaxCap - gc;
204
- }
205
- }
206
- } else {
207
- // Same-day extended slot
208
- const gc = getGuestCountAtHour(data, reservations, timeStr, dateStr);
209
- const maxCap = getMaxCapacityForTime(data, dateStr, t);
210
- if (maxCap > 0) {
211
- guestCounts[timeStr] = maxCap - gc;
212
- }
213
- }
214
- }
215
- }
216
- }
217
-
218
107
  const availableTimeblocks = {};
219
108
 
220
109
  // Handle shifts first
@@ -235,73 +124,27 @@ function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots =
235
124
  if (guestCounts && Object.keys(guestCounts).length > 0) {
236
125
  const timeSlots = Object.keys(guestCounts).sort((a, b) => parseTime(a) - parseTime(b));
237
126
 
238
- for (let i = 0; i < timeSlots.length; i++) {
239
- // Check capacity for the first slot
127
+ for (let i = 0; i <= timeSlots.length - slotsNeeded; i++) {
128
+ // Check capacity for all needed slots
129
+ let consecutiveSlotsAvailable = true;
240
130
  if (guestCounts[timeSlots[i]] < guests) {
241
131
  continue;
242
132
  }
243
133
 
244
- const startMinutes = parseTime(timeSlots[i]);
245
- let consecutiveSlotsAvailable = true;
246
-
134
+ let previousTime = parseTime(timeSlots[i]);
247
135
  for (let j = 1; j < slotsNeeded; j++) {
248
- const neededMinutes = startMinutes + j * intervalReservatie;
249
-
250
- // First try same-day check from guestCounts.
251
- // This handles both regular times AND extended meal times (e.g. "24:00", "24:30")
252
- // produced by addDuurReservatieToEndTime. Without this, regular restaurants would
253
- // break near midnight because the cross-midnight branch rejects when hoursOpenedPastMidnight=0.
254
- const slotIndex = i + j;
255
- if (slotIndex < timeSlots.length) {
256
- const currentTimeSlot = timeSlots[slotIndex];
257
- const currentTime = parseTime(currentTimeSlot);
258
- if (currentTime === neededMinutes) {
259
- if (guestCounts[currentTimeSlot] < guests) {
260
- consecutiveSlotsAvailable = false;
261
- break;
262
- }
263
- continue; // Slot available via guestCounts
264
- }
265
- }
266
-
267
- // Slot not found in guestCounts — check cross-midnight if applicable
268
- if (neededMinutes >= 1440) {
269
- if (hoursOpenedPastMidnight <= 0) {
270
- consecutiveSlotsAvailable = false;
271
- break;
272
- }
273
- const spilloverMinutes = neededMinutes - 1440;
274
- if (spilloverMinutes >= hoursOpenedPastMidnight * 60) {
275
- consecutiveSlotsAvailable = false;
276
- break;
277
- }
278
- // Use starting meal's capacity as limit for spillover period
279
- const nextDateStr = getNextDateStr(dateStr);
280
- const nextTimeStr = minutesToTimeStr(spilloverMinutes);
281
- const nextDayGuestCount = getGuestCountAtHour(data, reservations, nextTimeStr, nextDateStr);
282
- const startMealCap = getMaxCapacityForTime(data, dateStr, startMinutes);
283
- if (startMealCap - nextDayGuestCount < guests) {
284
- consecutiveSlotsAvailable = false;
285
- break;
286
- }
287
- continue; // Cross-midnight slot OK
288
- }
289
-
290
- // Not in guestCounts and not cross-midnight — gap in availability
291
- consecutiveSlotsAvailable = false;
292
- break;
293
- }
136
+ const currentTimeSlot = timeSlots[i + j];
137
+ const currentTime = parseTime(currentTimeSlot);
294
138
 
295
- // If the reservation would end past midnight, enforce hoursOpenedPastMidnight limit
296
- // Only applies when the setting is explicitly defined (bowling restaurants).
297
- if (consecutiveSlotsAvailable && hasMidnightSetting(data, dateStr)) {
298
- const endMinutes = startMinutes + duurReservatie;
299
- if (endMinutes > 1440 + hoursOpenedPastMidnight * 60) {
139
+ // Check interval and capacity
140
+ if ((currentTime - previousTime) !== intervalReservatie || guestCounts[currentTimeSlot] < guests) {
300
141
  consecutiveSlotsAvailable = false;
142
+ break;
301
143
  }
144
+ previousTime = currentTime;
302
145
  }
303
146
 
304
- // If all consecutive slots are available, check if the start time fits within a meal
147
+ // If all consecutive slots are available, check if the full duration fits
305
148
  if (consecutiveSlotsAvailable && fitsWithinMeal(data, dateStr, timeSlots[i], duurReservatie)) {
306
149
  // Check if time matches giftcard requirement
307
150
  if (timeHasGiftcard(data, dateStr, timeSlots[i], giftcard)) {