@happychef/algorithm 1.0.4 → 1.1.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.
@@ -30,9 +30,10 @@ function parseDateTimeInTimeZone(dateStr, timeStr, timeZone) {
30
30
  * @param {number} guests - The number of guests for the reservation.
31
31
  * @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
32
32
  * @param {string|null} giftcard - Optional giftcard to filter times by meal.
33
+ * @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
33
34
  * @returns {Object} - Returns a pruned object of available time blocks or shifts, or an empty object if out of range.
34
35
  */
35
- function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null) {
36
+ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
36
37
  // Get 'uurOpVoorhand' from general settings
37
38
  let uurOpVoorhand = 4;
38
39
  if (
@@ -74,8 +75,8 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
74
75
  targetDate.toLocaleString('en-US', { timeZone: timeZone })
75
76
  );
76
77
 
77
- // Check if targetDateInTimeZone is within dagenInToekomst
78
- if (targetDateInTimeZone > maxAllowedDate) {
78
+ // Check if targetDateInTimeZone is within dagenInToekomst (skip for admin)
79
+ if (!isAdmin && targetDateInTimeZone > maxAllowedDate) {
79
80
  // Out of allowed range, return empty object
80
81
  return {};
81
82
  }
@@ -85,10 +86,10 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
85
86
  currentTimeInTimeZone.toDateString() === targetDateInTimeZone.toDateString();
86
87
 
87
88
  // Get available time blocks or shifts
88
- const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard);
89
+ const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
89
90
 
90
- // If the date is today and uurOpVoorhand is greater than zero, prune time blocks
91
- if (isToday && uurOpVoorhand >= 0) {
91
+ // If the date is today and uurOpVoorhand is greater than zero, prune time blocks (skip for admin)
92
+ if (!isAdmin && isToday && uurOpVoorhand >= 0) {
92
93
  const cutoffTime = new Date(currentTimeInTimeZone.getTime());
93
94
  cutoffTime.setHours(cutoffTime.getHours() + uurOpVoorhand);
94
95
 
package/index.js CHANGED
@@ -3,16 +3,16 @@
3
3
  const processing = {
4
4
  ...require("./processing/dailyGuestCounts"),
5
5
  ...require("./processing/mealTypeCount"),
6
- ...require("./processing/timeblocksAvailable"),
6
+ ...require("./processing/timeblocksAvailable")
7
7
  };
8
8
 
9
9
  const reservation_data = {
10
- ...require("./reservation_data/counter"),
10
+ ...require("./reservation_data/counter")
11
11
  };
12
12
 
13
13
  const restaurant_data = {
14
14
  ...require("./restaurant_data/exceptions"),
15
- ...require("./restaurant_data/openinghours"),
15
+ ...require("./restaurant_data/openinghours")
16
16
  };
17
17
 
18
18
  const test = {
@@ -24,39 +24,19 @@ const test = {
24
24
  ...require("./simulateTableAssignment"),
25
25
  ...require("./isDateAvailableWithTableCheck"),
26
26
  ...require("./tableHelpers"),
27
- ...require("./test"),
27
+ ...require("./test")
28
28
  };
29
29
 
30
30
  const filters = {
31
31
  ...require("./filters/timeFilter"),
32
32
  ...require("./filters/maxArrivalsFilter"),
33
- ...require("./filters/maxGroupsFilter"),
33
+ ...require("./filters/maxGroupsFilter")
34
34
  };
35
-
36
- // ✅ Table-related logic and utilities
37
- const tables = {
38
- ...require("./tables/main.js"),
39
- ...require("./tables/availability/isTimeAvailableSync"),
40
- ...require("./tables/availability/getAvailableTablesForTime"),
41
- ...require("./tables/availability/findMultiTableCombination"),
42
- ...require("./tables/assignment/filterTimeblocksByTableAvailability"),
43
- ...require("./tables/assignment/assignTablesForGivenTime"),
44
- ...require("./tables/utils/safeParseInt"),
45
- ...require("./tables/utils/isTemporaryTableValid"),
46
- ...require("./tables/utils/isTableFreeForAllSlots"),
47
- ...require("./tables/utils/calculateDistance"),
48
- ...require("./tables/time/shifts").shifts,
49
- ...require("./tables/time/parseTime"),
50
- ...require("./tables/time/getMealTypeByTime"),
51
- ...require("./tables/time/computeRequiredSlots"),
52
- };
53
-
54
- // ✅ Export everything
35
+ // Merge all exports into one
55
36
  module.exports = {
56
37
  ...processing,
57
38
  ...reservation_data,
58
39
  ...restaurant_data,
59
40
  ...test,
60
- ...filters,
61
- ...tables,
41
+ ...filters
62
42
  };
@@ -59,16 +59,17 @@ function isDateWithinAllowedRange(data, dateStr) {
59
59
  * @param {number} guests - The number of guests for the reservation.
60
60
  * @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
61
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.
62
63
  * @returns {boolean} - Returns true if the date has at least one available timeblock, false otherwise.
63
64
  */
64
- function isDateAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null) {
65
- // Check if date is within allowed range
66
- if (!isDateWithinAllowedRange(data, dateStr)) {
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)) {
67
68
  return false;
68
69
  }
69
70
 
70
71
  // Get available timeblocks using the existing logic
71
- const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, giftcard);
72
+ const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
72
73
 
73
74
  // Return true only if we have at least one available timeblock
74
75
  return Object.keys(availableTimeblocks).length > 0;
@@ -71,9 +71,10 @@ function timeHasGiftcard(data, dateStr, timeStr, giftcard) {
71
71
  * @param {number} guests - The number of guests for the reservation.
72
72
  * @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
73
73
  * @param {string|null} selectedGiftcard - The selected giftcard (if any).
74
+ * @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
74
75
  * @returns {boolean} - Returns true if the date passes all checks to be available.
75
76
  */
76
- function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null) {
77
+ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null, isAdmin = false) {
77
78
  // 0) Optionally filter by selected menu item's date range (only when normalOpeningTimes = false)
78
79
  const currentItem = typeof window !== 'undefined' ? window.currentReservationTicketItem : null;
79
80
  if (
@@ -113,7 +114,7 @@ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, bloc
113
114
  console.log(`Table assignment is ${isTableAssignmentEnabled ? 'ENABLED' : 'DISABLED'} for date ${dateStr}`);
114
115
 
115
116
  // 1) First, do the standard day-level checks (including simple giftcard check).
116
- const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard);
117
+ const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
117
118
  if (!basicDateAvailable) {
118
119
  console.log(`Date ${dateStr} fails basic availability check`);
119
120
  return false;
@@ -127,7 +128,7 @@ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, bloc
127
128
 
128
129
  // 2) Get all available timeblocks for this date
129
130
  console.log(`Getting available timeblocks for ${dateStr}`);
130
- const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard);
131
+ const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
131
132
  console.log(`Found ${Object.keys(availableTimeblocks).length} available timeblocks before table check`);
132
133
 
133
134
  // If no timeblocks are available at all, exit early
@@ -9,11 +9,12 @@ const { timeblocksAvailable } = require('./processing/timeblocksAvailable');
9
9
  * @param {number} guests - The number of guests for the new reservation request.
10
10
  * @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
11
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.
12
13
  * @returns {boolean} - Returns true if the time is available, false otherwise.
13
14
  */
14
- function isTimeAvailable(data, dateStr, timeStr, reservations, guests, blockedSlots = [], giftcard = null) {
15
+ function isTimeAvailable(data, dateStr, timeStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
15
16
  // Get all available timeblocks for the specified date and guest count
16
- const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard);
17
+ const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
17
18
 
18
19
  // Check if the specific timeStr is one of the available keys
19
20
  return Object.prototype.hasOwnProperty.call(availableTimeblocks, timeStr);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.0.4",
3
+ "version": "1.1.1",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "author": "happy chef",
@@ -89,7 +89,7 @@ function fitsWithinMeal(data, dateStr, startTimeStr, duurReservatie) {
89
89
  return startTime >= mealStartTime && startTime + duurReservatie <= mealEndTime;
90
90
  }
91
91
 
92
- function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null) {
92
+ function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
93
93
  const duurReservatie = getDuurReservatie(data);
94
94
  const intervalReservatie = getInterval(data);
95
95
 
@@ -149,8 +149,8 @@ function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots =
149
149
  }
150
150
  }
151
151
 
152
- // Filter out blocked time slots
153
- if (blockedSlots && blockedSlots.length > 0) {
152
+ // Filter out blocked time slots (skip for admin)
153
+ if (!isAdmin && blockedSlots && blockedSlots.length > 0) {
154
154
  for (const blockedSlot of blockedSlots) {
155
155
  // Check if the blocked slot matches the current date
156
156
  if (blockedSlot.date === dateStr && blockedSlot.time) {
@@ -135,7 +135,11 @@ function calculateDistance(tableA, tableB) {
135
135
  }
136
136
 
137
137
  /**
138
- * Backtracking function to find a combination of tables (multi-table assignment).
138
+ * OPTIMIZED: Backtracking function to find a combination of tables (multi-table assignment).
139
+ * Improvements:
140
+ * - Pre-computed valid tables list to avoid redundant checks
141
+ * - Better pruning with capacity tracking
142
+ * - Early termination when exact match found
139
143
  */
140
144
  function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet, best, requiredSlots, tableOccupiedSlots, reservationDateStr, reservationTimeStr) {
141
145
  // Base case: All guests are seated
@@ -152,7 +156,7 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
152
156
  best.minDistance = distanceSum;
153
157
  best.tables = [...currentSet]; // Store a copy
154
158
  best.tableCount = currentSet.length;
155
- console.log(`Found new best table combination: ${best.tables.map(t => t.tableNumber).join(', ')} for ${guestsNeeded} guests`);
159
+ console.log(`Found new best table combination: ${best.tables.map(t => t.tableNumber).join(', ')}`);
156
160
  }
157
161
  return;
158
162
  }
@@ -161,21 +165,23 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
161
165
  return;
162
166
  }
163
167
 
168
+ // OPTIMIZATION: Prune if we already have enough tables in current set
164
169
  if (currentSet.length >= best.tableCount && best.tableCount !== Infinity) {
165
- return; // Pruning based on table count
170
+ return; // Can't improve on current best
166
171
  }
167
172
 
173
+ // OPTIMIZATION: Calculate max possible capacity from remaining tables
168
174
  let maxPossibleCapacity = 0;
169
175
  for (let i = startIndex; i < tables.length; i++) {
170
176
  const tbl = tables[i];
171
177
  // Only consider valid & free tables for potential capacity
172
178
  if (isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr) &&
173
- isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
179
+ isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
174
180
  {
175
181
  maxPossibleCapacity += tbl.maxCapacity;
176
182
  }
177
183
  }
178
-
184
+
179
185
  if (maxPossibleCapacity < guestsNeeded) {
180
186
  return; // Impossible to seat remaining guests
181
187
  }
@@ -188,24 +194,23 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
188
194
  if (!isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr)) {
189
195
  continue; // Skip invalid temporary table
190
196
  }
191
-
197
+
192
198
  // 2. Check if table is free for all required slots
193
199
  if (!isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots)) {
194
200
  continue; // Skip occupied table
195
201
  }
196
-
202
+
197
203
  // 3. Check if table contributes meaningfully
198
204
  const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
199
205
  if (canSeat < tbl.minCapacity && canSeat < guestsNeeded) {
200
206
  continue; // Don't use a table for fewer than its min capacity unless it fulfills the remainder
201
207
  }
202
-
208
+
203
209
  if (canSeat <= 0) continue; // Table doesn't help
204
210
 
205
211
  // --- Recurse ---
206
212
  currentSet.push(tbl); // Add table to current combination
207
- console.log(`Trying table ${tbl.tableNumber} (${tbl.minCapacity}-${tbl.maxCapacity}) for ${canSeat}/${guestsNeeded} guests`);
208
-
213
+
209
214
  findMultiTableCombination(
210
215
  tables,
211
216
  guestsNeeded - canSeat, // Reduce guests needed
@@ -217,35 +222,26 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
217
222
  reservationDateStr,
218
223
  reservationTimeStr
219
224
  );
220
-
225
+
221
226
  currentSet.pop(); // Backtrack: remove table to explore other combinations
227
+
228
+ // OPTIMIZATION: Early exit if we found a single-table solution (best possible)
229
+ if (best.tableCount === 1) {
230
+ return;
231
+ }
222
232
  }
223
233
  }
224
234
 
225
235
  function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccupiedSlots, selectedZitplaats = null) {
226
236
  console.log(`[assignTablesForGivenTime] Processing ${date} ${time} for ${guests} guests`);
227
-
237
+
228
238
  // FIXED: More robust parsing of settings with detailed logging
229
239
  const generalSettings = restaurantData["general-settings"] || {};
230
- console.log("[assignTablesForGivenTime] General settings:", generalSettings);
231
-
232
- // Check what format the data is in
233
- console.log("[assignTablesForGivenTime] duurReservatie format:", typeof generalSettings.duurReservatie);
234
- console.log("[assignTablesForGivenTime] intervalReservatie format:", typeof generalSettings.intervalReservatie);
235
-
236
- // Check for MongoDB format
237
- if (typeof generalSettings.duurReservatie === 'object' && generalSettings.duurReservatie?.$numberInt) {
238
- console.log("[assignTablesForGivenTime] duurReservatie is in MongoDB format:", generalSettings.duurReservatie?.$numberInt);
239
- }
240
-
241
- if (typeof generalSettings.intervalReservatie === 'object' && generalSettings.intervalReservatie?.$numberInt) {
242
- console.log("[assignTablesForGivenTime] intervalReservatie is in MongoDB format:", generalSettings.intervalReservatie?.$numberInt);
243
- }
244
-
240
+
245
241
  // DEFAULT VALUES - this approach mirrors the TypeScript implementation
246
242
  let duurReservatie = 120; // Default: 2 hours in minutes
247
243
  let intervalReservatie = 15; // Default: 15 minute intervals
248
-
244
+
249
245
  // Use safeParseInt for robust parsing
250
246
  duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
251
247
  intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
@@ -256,7 +252,7 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
256
252
  console.error("Invalid interval settings.");
257
253
  return [];
258
254
  }
259
-
255
+
260
256
  const requiredSlots = computeRequiredSlots(time, duurReservatie, intervalReservatie);
261
257
  if (!requiredSlots || requiredSlots.length === 0) {
262
258
  console.error(`Could not compute required slots for ${time}`);
@@ -267,17 +263,33 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
267
263
  const allTables = getAllTables(restaurantData, selectedZitplaats);
268
264
  console.log(`[assignTablesForGivenTime] Checking ${allTables.length} tables`);
269
265
 
266
+ // OPTIMIZATION: Pre-filter and sort tables for better performance
267
+ // Filter out invalid and occupied tables first
268
+ const validTables = allTables.filter(t =>
269
+ isTemporaryTableValid(t, date, time) &&
270
+ isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots)
271
+ );
272
+
273
+ if (validTables.length === 0) {
274
+ console.log(`[assignTablesForGivenTime] No valid tables available`);
275
+ return [];
276
+ }
277
+
278
+ // OPTIMIZATION: Sort tables by capacity (prefer exact matches first)
279
+ // This helps find optimal solutions faster
280
+ validTables.sort((a, b) => {
281
+ // Prioritize tables that can seat exactly the number of guests
282
+ const aExact = (a.minCapacity <= guests && guests <= a.maxCapacity) ? 0 : 1;
283
+ const bExact = (b.minCapacity <= guests && guests <= b.maxCapacity) ? 0 : 1;
284
+ if (aExact !== bExact) return aExact - bExact;
285
+
286
+ // Then sort by capacity (smaller tables first to minimize waste)
287
+ return a.maxCapacity - b.maxCapacity;
288
+ });
289
+
270
290
  // --- Try single-table assignment first ---
271
- for (const t of allTables) {
272
- // Use imported validation function
273
- if (!isTemporaryTableValid(t, date, time)) {
274
- continue;
275
- }
276
-
277
- if (t.minCapacity <= guests &&
278
- guests <= t.maxCapacity &&
279
- isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots))
280
- {
291
+ for (const t of validTables) {
292
+ if (t.minCapacity <= guests && guests <= t.maxCapacity) {
281
293
  console.log(`[assignTablesForGivenTime] Assigned single table ${t.tableNumber} for ${guests} guests`);
282
294
  return [t.tableNumber];
283
295
  }
@@ -288,7 +300,7 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
288
300
  // --- Try multi-table assignment ---
289
301
  const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
290
302
  findMultiTableCombination(
291
- allTables,
303
+ validTables, // Use pre-filtered and sorted tables
292
304
  guests,
293
305
  0, // Start index
294
306
  [], // Initial empty set
@@ -409,14 +421,28 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
409
421
  const allTables = getAllTables(restaurantData, selectedZitplaats); // Get all tables
410
422
  console.log(`[isTimeAvailableSync] Checking ${allTables.length} tables for new reservation`);
411
423
 
412
- // 1. Try single table assignment
413
- for (const t of allTables) {
414
- if (!isTemporaryTableValid(t, date, time)) continue; // Check temporary validity
424
+ // OPTIMIZATION: Pre-filter valid tables
425
+ const validTables = allTables.filter(t =>
426
+ isTemporaryTableValid(t, date, time) &&
427
+ isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots)
428
+ );
429
+
430
+ if (validTables.length === 0) {
431
+ console.log(`[isTimeAvailableSync] No valid tables available`);
432
+ return false;
433
+ }
434
+
435
+ // OPTIMIZATION: Sort for faster exact matches
436
+ validTables.sort((a, b) => {
437
+ const aExact = (a.minCapacity <= guests && guests <= a.maxCapacity) ? 0 : 1;
438
+ const bExact = (b.minCapacity <= guests && guests <= b.maxCapacity) ? 0 : 1;
439
+ if (aExact !== bExact) return aExact - bExact;
440
+ return a.maxCapacity - b.maxCapacity;
441
+ });
415
442
 
416
- if (t.minCapacity <= guests &&
417
- guests <= t.maxCapacity &&
418
- isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots))
419
- {
443
+ // 1. Try single table assignment
444
+ for (const t of validTables) {
445
+ if (t.minCapacity <= guests && guests <= t.maxCapacity) {
420
446
  console.log(`[isTimeAvailableSync] Found single table ${t.tableNumber} for ${guests} guests at ${time}`);
421
447
  return true; // Available
422
448
  }
@@ -427,7 +453,7 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
427
453
  // 2. Try multi-table assignment
428
454
  const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
429
455
  findMultiTableCombination(
430
- allTables,
456
+ validTables, // Use pre-filtered tables
431
457
  guests,
432
458
  0, [], best,
433
459
  requiredSlots,
@@ -441,7 +467,7 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
441
467
  } else {
442
468
  console.log(`[isTimeAvailableSync] No table combination available`);
443
469
  }
444
-
470
+
445
471
  return result; // Available if a combination was found
446
472
 
447
473
  } catch (error) {
@@ -1,79 +0,0 @@
1
- const safeParseInt = require('../utils/safeParseInt');
2
- const computeRequiredSlots = require('../time/computeRequiredSlots');
3
- const getAllTables = require('../data/getAllTables');
4
- const isTemporaryTableValid = require('../utils/isTemporaryTableValid');
5
- const findMultiTableCombination = require('../availability/findMultiTableCombination');
6
- const isTableFreeForAllSlots = require('../utils/isTableFreeForAllSlots');
7
-
8
- /**
9
- * Attempts to assign tables for a single reservation at a given time.
10
- * Returns an array of table numbers (single-table or multi-table).
11
- */
12
- function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccupiedSlots, selectedZitplaats = null) {
13
-
14
- // FIXED: More robust parsing of settings with detailed logging
15
- const generalSettings = restaurantData["general-settings"] || {};
16
-
17
- // Check what format the data is in
18
- // Check for MongoDB format
19
- if (typeof generalSettings.duurReservatie === 'object' && generalSettings.duurReservatie?.$numberInt) {
20
- }
21
- if (typeof generalSettings.intervalReservatie === 'object' && generalSettings.intervalReservatie?.$numberInt) {
22
- }
23
-
24
- // DEFAULT VALUES - this approach mirrors the TypeScript implementation
25
- let duurReservatie = 120; // Default: 2 hours in minutes
26
- let intervalReservatie = 15; // Default: 15 minute intervals
27
-
28
- // Use safeParseInt for robust parsing
29
- duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
30
- intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
31
-
32
- if (intervalReservatie <= 0) {
33
- console.error("Invalid interval settings.");
34
- return [];
35
- }
36
-
37
- const requiredSlots = computeRequiredSlots(time, duurReservatie, intervalReservatie);
38
- if (!requiredSlots || requiredSlots.length === 0) {
39
- console.error(`Could not compute required slots for ${time}`);
40
- return [];
41
- }
42
-
43
- // Fetch all tables
44
- const allTables = getAllTables(restaurantData, selectedZitplaats);
45
-
46
- // Try single-table assignment first
47
- for (const t of allTables) {
48
- if (!isTemporaryTableValid(t, date, time)) continue;
49
-
50
- if (t.minCapacity <= guests &&
51
- guests <= t.maxCapacity &&
52
- isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots))
53
- {
54
- return [t.tableNumber];
55
- }
56
- }
57
-
58
- // Try multi-table assignment
59
- const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
60
- findMultiTableCombination(
61
- allTables,
62
- guests,
63
- 0,
64
- [],
65
- best,
66
- requiredSlots,
67
- tableOccupiedSlots,
68
- date,
69
- time
70
- );
71
-
72
- if (best.tables.length > 0) {
73
- } else {
74
- }
75
-
76
- return best.tables.map(t => t.tableNumber);
77
- }
78
-
79
- module.exports = assignTablesForGivenTime;
@@ -1,48 +0,0 @@
1
- // Import directly to avoid circular dependency with tables/main.js
2
- const isTimeAvailableSync = require('../availability/isTimeAvailableSync');
3
-
4
- /**
5
- * Filters timeblocks based on availability for a given guest count and date.
6
- */
7
- function filterTimeblocksByTableAvailability(
8
- restaurantData,
9
- date,
10
- timeblocks,
11
- guests,
12
- reservations,
13
- selectedZitplaats = null
14
- ) {
15
- if (guests < 0) {
16
- guests = 0; // Use a reasonable default
17
- }
18
-
19
- const tableSettings = restaurantData?.['table-settings'] || {};
20
- const isTableAssignmentEnabled =
21
- tableSettings.isInstalled === true &&
22
- tableSettings.assignmentMode === 'automatic';
23
-
24
- if (!isTableAssignmentEnabled) {
25
- return timeblocks;
26
- }
27
-
28
- const filteredTimeblocks = {};
29
- let availableCount = 0;
30
- let unavailableCount = 0;
31
-
32
- for (const time in timeblocks) {
33
- if (isTimeAvailableSync(restaurantData, date, time, guests, reservations, selectedZitplaats)) {
34
- filteredTimeblocks[time] = timeblocks[time];
35
- availableCount++;
36
- } else {
37
- unavailableCount++;
38
- }
39
- }
40
-
41
- if (availableCount > 0) {
42
- // no-op, preserve original structure
43
- }
44
-
45
- return filteredTimeblocks;
46
- }
47
-
48
- module.exports = filterTimeblocksByTableAvailability;
@@ -1,100 +0,0 @@
1
- const isTemporaryTableValid = require('../utils/isTemporaryTableValid');
2
- const isTableFreeForAllSlots = require('../utils/isTableFreeForAllSlots');
3
- const calculateDistance = require('../utils/calculateDistance');
4
-
5
- /**
6
- * Backtracking function to find a combination of tables (multi-table assignment).
7
- */
8
- function findMultiTableCombination(
9
- tables,
10
- guestsNeeded,
11
- startIndex,
12
- currentSet,
13
- best,
14
- requiredSlots,
15
- tableOccupiedSlots,
16
- reservationDateStr,
17
- reservationTimeStr
18
- ) {
19
- // Base case: All guests are seated
20
- if (guestsNeeded <= 0) {
21
- // Calculate total distance for the current combination
22
- let distanceSum = 0;
23
- for (let i = 0; i < currentSet.length; i++) {
24
- for (let j = i + 1; j < currentSet.length; j++) {
25
- distanceSum += calculateDistance(currentSet[i], currentSet[j]);
26
- }
27
- }
28
- // Update best solution if this one is better (fewer tables, then lower distance)
29
- if (currentSet.length < best.tableCount || (currentSet.length === best.tableCount && distanceSum < best.minDistance)) {
30
- best.minDistance = distanceSum;
31
- best.tables = [...currentSet]; // Store a copy
32
- best.tableCount = currentSet.length;
33
- }
34
- return;
35
- }
36
-
37
- if (startIndex >= tables.length) {
38
- return;
39
- }
40
-
41
- if (currentSet.length >= best.tableCount && best.tableCount !== Infinity) {
42
- return; // Pruning based on table count
43
- }
44
-
45
- let maxPossibleCapacity = 0;
46
- for (let i = startIndex; i < tables.length; i++) {
47
- const tbl = tables[i];
48
- // Only consider valid & free tables for potential capacity
49
- if (isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr) &&
50
- isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
51
- {
52
- maxPossibleCapacity += tbl.maxCapacity;
53
- }
54
- }
55
-
56
- if (maxPossibleCapacity < guestsNeeded) {
57
- return; // Impossible to seat remaining guests
58
- }
59
-
60
- for (let i = startIndex; i < tables.length; i++) {
61
- const tbl = tables[i];
62
-
63
- // 1. Check if temporary table is valid for this date/time
64
- if (!isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr)) {
65
- continue;
66
- }
67
-
68
- // 2. Check if table is free for all required slots
69
- if (!isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots)) {
70
- continue;
71
- }
72
-
73
- // 3. Check if table contributes meaningfully
74
- const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
75
- if (canSeat < tbl.minCapacity && canSeat < guestsNeeded) {
76
- continue; // Don't use a table for fewer than its min capacity unless it fulfills the remainder
77
- }
78
-
79
- if (canSeat <= 0) continue; // Table doesn't help
80
-
81
- // Recurse
82
- currentSet.push(tbl);
83
-
84
- findMultiTableCombination(
85
- tables,
86
- guestsNeeded - canSeat,
87
- i + 1,
88
- currentSet,
89
- best,
90
- requiredSlots,
91
- tableOccupiedSlots,
92
- reservationDateStr,
93
- reservationTimeStr
94
- );
95
-
96
- currentSet.pop(); // Backtrack
97
- }
98
- }
99
-
100
- module.exports = findMultiTableCombination;