@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,594 @@
1
+ // =============================================================================
2
+ // FILE: simulateTableAssignment.js (UPDATED)
3
+ // =============================================================================
4
+ // Archief/Fields/algorithm/simulateTableAssignment.js
5
+
6
+ // --- Import Helpers ---
7
+ const {
8
+ getAllTables,
9
+ isTemporaryTableValid,
10
+ } = require('./tableHelpers');
11
+
12
+
13
+ /**
14
+ * Robust helper for parsing numeric values from various formats.
15
+ * Handles MongoDB $numberInt format and regular values.
16
+ */
17
+ function safeParseInt(val, defaultValue) {
18
+ console.log(`[safeParseInt] Parsing value:`, val, `with type:`, typeof val);
19
+
20
+ if (val === undefined || val === null) return defaultValue;
21
+
22
+ if (typeof val === 'object' && val !== null && '$numberInt' in val) {
23
+ try {
24
+ const parsed = parseInt(val.$numberInt, 10);
25
+ console.log(`[safeParseInt] Parsed $numberInt format:`, parsed);
26
+ if (!isNaN(parsed)) return parsed;
27
+ } catch (e) {
28
+ console.log(`[safeParseInt] Error parsing $numberInt:`, e);
29
+ }
30
+ return defaultValue;
31
+ }
32
+
33
+ if (typeof val === 'number' && !isNaN(val)) {
34
+ console.log(`[safeParseInt] Using numeric value directly:`, val);
35
+ return val;
36
+ }
37
+
38
+ if (typeof val === 'string') {
39
+ try {
40
+ const parsed = parseInt(val, 10);
41
+ console.log(`[safeParseInt] Parsed string:`, parsed);
42
+ if (!isNaN(parsed)) return parsed;
43
+ } catch (e) {
44
+ console.log(`[safeParseInt] Error parsing string:`, e);
45
+ }
46
+ }
47
+ console.log(`[safeParseInt] Could not parse, using default:`, defaultValue);
48
+ return defaultValue;
49
+ }
50
+
51
+ /**
52
+ * Extracts actual table numbers from a reservation's tables array.
53
+ * Handles MongoDB $numberInt format.
54
+ */
55
+ function extractTableNumbers(tablesArray) {
56
+ if (!Array.isArray(tablesArray)) {
57
+ return [];
58
+ }
59
+
60
+ return tablesArray.map(table => {
61
+ return safeParseInt(table, null);
62
+ }).filter(tableNum => tableNum !== null);
63
+ }
64
+
65
+ /**
66
+ * Gets actual table assignments from reservation data if available.
67
+ * @param {Object} reservation - The reservation object
68
+ * @returns {Array} Array of table numbers that are actually assigned, or empty array if no data
69
+ */
70
+ function getActualTableAssignment(reservation) {
71
+ // Check if reservation has actual table assignments
72
+ if (reservation.tables && Array.isArray(reservation.tables)) {
73
+ const tableNumbers = extractTableNumbers(reservation.tables);
74
+ console.log(`[getActualTableAssignment] Found actual table assignment for reservation:`, tableNumbers);
75
+ return tableNumbers;
76
+ }
77
+
78
+ console.log(`[getActualTableAssignment] No actual table assignment found for reservation`);
79
+ return [];
80
+ }
81
+
82
+ /**
83
+ * Converts a reservation's start time and duration into discrete time slots (in minutes from midnight).
84
+ */
85
+ function computeRequiredSlots(timeString, durationMinutes, intervalMinutes) {
86
+ const timePattern = /^([01]\d|2[0-3]):([0-5]\d)$/;
87
+ if (!timeString || !timePattern.test(timeString)) {
88
+ console.error(`Invalid time format for computeRequiredSlots: ${timeString}. Expected 'HH:MM'.`);
89
+ return [];
90
+ }
91
+
92
+ const [hour, minute] = timeString.split(":").map(Number);
93
+ const startMinutes = hour * 60 + minute;
94
+
95
+ if (intervalMinutes <= 0) {
96
+ console.error("Interval must be positive in computeRequiredSlots.");
97
+ return [startMinutes];
98
+ }
99
+
100
+ const slotCount = Math.ceil(durationMinutes / intervalMinutes);
101
+ console.log(`Computing ${slotCount} slots for time ${timeString} (duration: ${durationMinutes}min, interval: ${intervalMinutes}min)`);
102
+
103
+ const slots = [];
104
+ for (let i = 0; i < slotCount; i++) {
105
+ slots.push(startMinutes + i * intervalMinutes);
106
+ }
107
+
108
+ return slots;
109
+ }
110
+
111
+ /**
112
+ * Checks if the given tableNumber is free for all requiredSlots based on the occupied map.
113
+ */
114
+ function isTableFreeForAllSlots(tableNumber, requiredSlots, tableOccupiedSlots) {
115
+ const occupiedSlots = tableOccupiedSlots[tableNumber] || new Set();
116
+
117
+ for (const slot of requiredSlots) {
118
+ if (occupiedSlots.has(slot)) {
119
+ return false;
120
+ }
121
+ }
122
+ return true;
123
+ }
124
+
125
+ /**
126
+ * Calculates Euclidean distance between two tables (assuming x, y properties).
127
+ */
128
+ function calculateDistance(tableA, tableB) {
129
+ if (tableA?.x === undefined || tableA?.y === undefined || tableB?.x === undefined || tableB?.y === undefined) {
130
+ return Infinity; // Cannot calculate distance if coordinates are missing
131
+ }
132
+ const dx = tableA.x - tableB.x;
133
+ const dy = tableA.y - tableB.y;
134
+ return Math.sqrt(dx * dx + dy * dy);
135
+ }
136
+
137
+ /**
138
+ * Backtracking function to find a combination of tables (multi-table assignment).
139
+ */
140
+ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet, best, requiredSlots, tableOccupiedSlots, reservationDateStr, reservationTimeStr) {
141
+ // Base case: All guests are seated
142
+ if (guestsNeeded <= 0) {
143
+ // Calculate total distance for the current combination
144
+ let distanceSum = 0;
145
+ for (let i = 0; i < currentSet.length; i++) {
146
+ for (let j = i + 1; j < currentSet.length; j++) {
147
+ distanceSum += calculateDistance(currentSet[i], currentSet[j]);
148
+ }
149
+ }
150
+ // Update best solution if this one is better (fewer tables, then lower distance)
151
+ if (currentSet.length < best.tableCount || (currentSet.length === best.tableCount && distanceSum < best.minDistance)) {
152
+ best.minDistance = distanceSum;
153
+ best.tables = [...currentSet]; // Store a copy
154
+ best.tableCount = currentSet.length;
155
+ console.log(`Found new best table combination: ${best.tables.map(t => t.tableNumber).join(', ')} for ${guestsNeeded} guests`);
156
+ }
157
+ return;
158
+ }
159
+
160
+ if (startIndex >= tables.length) {
161
+ return;
162
+ }
163
+
164
+ if (currentSet.length >= best.tableCount && best.tableCount !== Infinity) {
165
+ return; // Pruning based on table count
166
+ }
167
+
168
+ let maxPossibleCapacity = 0;
169
+ for (let i = startIndex; i < tables.length; i++) {
170
+ const tbl = tables[i];
171
+ // Only consider valid & free tables for potential capacity
172
+ if (isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr) &&
173
+ isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
174
+ {
175
+ maxPossibleCapacity += tbl.maxCapacity;
176
+ }
177
+ }
178
+
179
+ if (maxPossibleCapacity < guestsNeeded) {
180
+ return; // Impossible to seat remaining guests
181
+ }
182
+
183
+ for (let i = startIndex; i < tables.length; i++) {
184
+ const tbl = tables[i];
185
+
186
+ // --- Core Checks ---
187
+ // 1. Check if temporary table is valid for this date/time
188
+ if (!isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr)) {
189
+ continue; // Skip invalid temporary table
190
+ }
191
+
192
+ // 2. Check if table is free for all required slots
193
+ if (!isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots)) {
194
+ continue; // Skip occupied table
195
+ }
196
+
197
+ // 3. Check if table contributes meaningfully
198
+ const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
199
+ if (canSeat < tbl.minCapacity && canSeat < guestsNeeded) {
200
+ continue; // Don't use a table for fewer than its min capacity unless it fulfills the remainder
201
+ }
202
+
203
+ if (canSeat <= 0) continue; // Table doesn't help
204
+
205
+ // --- Recurse ---
206
+ currentSet.push(tbl); // Add table to current combination
207
+ console.log(`Trying table ${tbl.tableNumber} (${tbl.minCapacity}-${tbl.maxCapacity}) for ${canSeat}/${guestsNeeded} guests`);
208
+
209
+ findMultiTableCombination(
210
+ tables,
211
+ guestsNeeded - canSeat, // Reduce guests needed
212
+ i + 1, // Explore next tables
213
+ currentSet,
214
+ best,
215
+ requiredSlots,
216
+ tableOccupiedSlots,
217
+ reservationDateStr,
218
+ reservationTimeStr
219
+ );
220
+
221
+ currentSet.pop(); // Backtrack: remove table to explore other combinations
222
+ }
223
+ }
224
+
225
+ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccupiedSlots, selectedZitplaats = null) {
226
+ console.log(`[assignTablesForGivenTime] Processing ${date} ${time} for ${guests} guests`);
227
+
228
+ // FIXED: More robust parsing of settings with detailed logging
229
+ 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
+
245
+ // DEFAULT VALUES - this approach mirrors the TypeScript implementation
246
+ let duurReservatie = 120; // Default: 2 hours in minutes
247
+ let intervalReservatie = 15; // Default: 15 minute intervals
248
+
249
+ // Use safeParseInt for robust parsing
250
+ duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
251
+ intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
252
+
253
+ console.log(`[assignTablesForGivenTime] Using duration: ${duurReservatie}min, interval: ${intervalReservatie}min`);
254
+
255
+ if (intervalReservatie <= 0) {
256
+ console.error("Invalid interval settings.");
257
+ return [];
258
+ }
259
+
260
+ const requiredSlots = computeRequiredSlots(time, duurReservatie, intervalReservatie);
261
+ if (!requiredSlots || requiredSlots.length === 0) {
262
+ console.error(`Could not compute required slots for ${time}`);
263
+ return [];
264
+ }
265
+
266
+ // Fetch all tables using the imported helper
267
+ const allTables = getAllTables(restaurantData, selectedZitplaats);
268
+ console.log(`[assignTablesForGivenTime] Checking ${allTables.length} tables`);
269
+
270
+ // --- 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
+ {
281
+ console.log(`[assignTablesForGivenTime] Assigned single table ${t.tableNumber} for ${guests} guests`);
282
+ return [t.tableNumber];
283
+ }
284
+ }
285
+
286
+ console.log(`[assignTablesForGivenTime] No single table found, trying combinations...`);
287
+
288
+ // --- Try multi-table assignment ---
289
+ const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
290
+ findMultiTableCombination(
291
+ allTables,
292
+ guests,
293
+ 0, // Start index
294
+ [], // Initial empty set
295
+ best, // Best solution object
296
+ requiredSlots,
297
+ tableOccupiedSlots,
298
+ date, // Pass context
299
+ time // Pass context
300
+ );
301
+
302
+ if (best.tables.length > 0) {
303
+ console.log(`[assignTablesForGivenTime] Found multi-table solution: ${best.tables.map(t => t.tableNumber).join(', ')}`);
304
+ } else {
305
+ console.log(`[assignTablesForGivenTime] No table combination found for ${guests} guests`);
306
+ }
307
+
308
+ return best.tables.map(t => t.tableNumber);
309
+ }
310
+
311
+ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, selectedZitplaats = null) {
312
+ console.log(`\n[isTimeAvailableSync] Checking ${date} ${time} for ${guests} guests`);
313
+
314
+ if (guests < 0) {
315
+ console.log(`[isTimeAvailableSync] Detected negative guest count (${guests}), adjusting to default of 2`);
316
+ guests = 2; // Use a reasonable default
317
+ }
318
+
319
+ const tableSettings = restaurantData?.['table-settings'] || {};
320
+ const isTableAssignmentEnabled = tableSettings.isInstalled === true &&
321
+ tableSettings.assignmentMode === "automatic";
322
+
323
+ console.log(`[isTimeAvailableSync] Table assignment enabled? ${isTableAssignmentEnabled}`);
324
+ console.log(`- Table settings installed: ${tableSettings.isInstalled === true}`);
325
+ console.log(`- Assignment mode: ${tableSettings.assignmentMode}`);
326
+
327
+ if (!isTableAssignmentEnabled) {
328
+ console.log(`[isTimeAvailableSync] No table assignment needed, returning true`);
329
+ return true;
330
+ }
331
+
332
+ try {
333
+ // Basic data check
334
+ if (!restaurantData?.floors || !Array.isArray(restaurantData.floors)) {
335
+ console.error(`[isTimeAvailableSync] Missing floors data for ${date} ${time}`);
336
+ return false;
337
+ }
338
+
339
+ if (guests <= 0) {
340
+ console.error(`[isTimeAvailableSync] Invalid guest count: ${guests}`);
341
+ return false; // Cannot reserve for 0 or fewer guests
342
+ }
343
+
344
+ const generalSettings = restaurantData["general-settings"] || {};
345
+ let duurReservatie = 120; // Default: 2 hours in minutes
346
+ let intervalReservatie = 15; // Default: 15 minute intervals
347
+ duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
348
+ intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
349
+
350
+ console.log(`[isTimeAvailableSync] Using duration: ${duurReservatie}min, interval: ${intervalReservatie}min`);
351
+
352
+ if (intervalReservatie <= 0) {
353
+ console.error(`[isTimeAvailableSync] Invalid interval settings for ${date} ${time}`);
354
+ return false;
355
+ }
356
+
357
+ const requiredSlots = computeRequiredSlots(time, duurReservatie, intervalReservatie);
358
+ if (!requiredSlots || requiredSlots.length === 0) {
359
+ console.error(`[isTimeAvailableSync] Could not compute required slots for ${date} ${time}`);
360
+ return false; // Cannot proceed if slots are invalid
361
+ }
362
+
363
+ // --- Build Occupancy Map using ACTUAL table assignments when available ---
364
+ const tableOccupiedSlots = {}; // { tableNumber: Set<slotMinutes> }
365
+ const reservationsForDate = reservations
366
+ .filter(r => r.date === date && r.time && r.guests > 0)
367
+ .sort((a, b) => {
368
+ // Simple time string comparison
369
+ const timeA = a.time.split(':').map(Number);
370
+ const timeB = b.time.split(':').map(Number);
371
+ return (timeA[0] * 60 + timeA[1]) - (timeB[0] * 60 + timeB[1]);
372
+ });
373
+
374
+ console.log(`[isTimeAvailableSync] Processing ${reservationsForDate.length} existing reservations on ${date}`);
375
+
376
+ for (const r of reservationsForDate) {
377
+ // NEW: Try to get actual table assignment first
378
+ const actualTables = getActualTableAssignment(r);
379
+ let assignedTables = [];
380
+
381
+ if (actualTables.length > 0) {
382
+ // Use actual table assignment from reservation data
383
+ assignedTables = actualTables;
384
+ console.log(`[isTimeAvailableSync] Using actual table assignment for reservation: ${assignedTables.join(', ')}`);
385
+ } else {
386
+ // Fall back to simulation for backwards compatibility
387
+ console.log(`[isTimeAvailableSync] No actual table data, simulating assignment for reservation`);
388
+ assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots);
389
+ }
390
+
391
+ // Update the occupancy map based on the actual or simulated assignment
392
+ if (assignedTables.length > 0) {
393
+ const rSlots = computeRequiredSlots(r.time, duurReservatie, intervalReservatie);
394
+ if (!rSlots || rSlots.length === 0) continue; // Skip if slots invalid
395
+
396
+ assignedTables.forEach(tableNumber => {
397
+ if (!tableOccupiedSlots[tableNumber]) {
398
+ tableOccupiedSlots[tableNumber] = new Set();
399
+ }
400
+ rSlots.forEach(slot => tableOccupiedSlots[tableNumber].add(slot));
401
+ });
402
+
403
+ console.log(`[isTimeAvailableSync] Marked tables ${assignedTables.join(', ')} as occupied for time ${r.time}`);
404
+ }
405
+ }
406
+
407
+ console.log(`[isTimeAvailableSync] Occupancy map built, checking availability for new reservation`);
408
+
409
+ const allTables = getAllTables(restaurantData, selectedZitplaats); // Get all tables
410
+ console.log(`[isTimeAvailableSync] Checking ${allTables.length} tables for new reservation`);
411
+
412
+ // 1. Try single table assignment
413
+ for (const t of allTables) {
414
+ if (!isTemporaryTableValid(t, date, time)) continue; // Check temporary validity
415
+
416
+ if (t.minCapacity <= guests &&
417
+ guests <= t.maxCapacity &&
418
+ isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots))
419
+ {
420
+ console.log(`[isTimeAvailableSync] Found single table ${t.tableNumber} for ${guests} guests at ${time}`);
421
+ return true; // Available
422
+ }
423
+ }
424
+
425
+ console.log(`[isTimeAvailableSync] No single table found, trying combinations...`);
426
+
427
+ // 2. Try multi-table assignment
428
+ const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
429
+ findMultiTableCombination(
430
+ allTables,
431
+ guests,
432
+ 0, [], best,
433
+ requiredSlots,
434
+ tableOccupiedSlots, // Use the final occupancy map
435
+ date, time
436
+ );
437
+
438
+ const result = best.tables.length > 0;
439
+ if (result) {
440
+ console.log(`[isTimeAvailableSync] Multi-table assignment possible: ${best.tables.map(t => t.tableNumber).join(', ')}`);
441
+ } else {
442
+ console.log(`[isTimeAvailableSync] No table combination available`);
443
+ }
444
+
445
+ return result; // Available if a combination was found
446
+
447
+ } catch (error) {
448
+ console.error(`[isTimeAvailableSync] Error during check for ${date} ${time}:`, error);
449
+ return false; // Assume unavailable on error
450
+ }
451
+ }
452
+
453
+ function filterTimeblocksByTableAvailability(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats = null) {
454
+ console.log(`[filterTimeblocksByTableAvailability] Filtering timeblocks for ${date} with ${guests} guests`);
455
+
456
+ if (guests < 0) {
457
+ console.log(`[filterTimeblocksByTableAvailability] Detected negative guest count (${guests}), adjusting to default of 2`);
458
+ guests = 0; // Use a reasonable default
459
+ }
460
+
461
+ const tableSettings = restaurantData?.['table-settings'] || {};
462
+ const isTableAssignmentEnabled = tableSettings.isInstalled === true &&
463
+ tableSettings.assignmentMode === "automatic";
464
+
465
+ if (!isTableAssignmentEnabled) {
466
+ console.log(`[filterTimeblocksByTableAvailability] No table assignment enabled, returning all timeblocks`);
467
+ return timeblocks;
468
+ }
469
+
470
+ console.log(`[filterTimeblocksByTableAvailability] Starting with ${Object.keys(timeblocks).length} timeblocks`);
471
+
472
+ const filteredTimeblocks = {};
473
+ let availableCount = 0;
474
+ let unavailableCount = 0;
475
+
476
+ for (const time in timeblocks) {
477
+ if (isTimeAvailableSync(restaurantData, date, time, guests, reservations, selectedZitplaats)) {
478
+ filteredTimeblocks[time] = timeblocks[time];
479
+ availableCount++;
480
+ } else {
481
+ unavailableCount++;
482
+ }
483
+ }
484
+
485
+ console.log(`[filterTimeblocksByTableAvailability] Result: ${availableCount} available, ${unavailableCount} unavailable times`);
486
+ if (availableCount > 0) {
487
+ console.log(`[filterTimeblocksByTableAvailability] Available times: ${Object.keys(filteredTimeblocks).join(', ')}`);
488
+ }
489
+
490
+ return filteredTimeblocks;
491
+ }
492
+
493
+ function getAvailableTimeblocksWithTableCheck(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats = null) {
494
+ return filterTimeblocksByTableAvailability(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats);
495
+ }
496
+
497
+ function getAvailableTablesForTime(restaurantData, date, time, guests, reservations, selectedZitplaats = null) {
498
+ try {
499
+ if (guests < 0) {
500
+ console.log(`[getAvailableTablesForTime] Detected negative guest count (${guests}), adjusting to default of 2`);
501
+ guests = 0;
502
+ }
503
+
504
+ const tableSettings = restaurantData?.['table-settings'] || {};
505
+ const isAutomaticAssignment = tableSettings.isInstalled === true &&
506
+ tableSettings.assignmentMode === "automatic";
507
+
508
+ if (!isAutomaticAssignment) {
509
+ return [];
510
+ }
511
+
512
+ if (!restaurantData?.floors || !Array.isArray(restaurantData.floors)) {
513
+ console.error(`[getAvailableTablesForTime] Missing floors data for ${date} ${time}`);
514
+ return [];
515
+ }
516
+ if (guests <= 0) return [];
517
+ const generalSettings = restaurantData["general-settings"] || {};
518
+ const duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
519
+ const intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
520
+
521
+ if (intervalReservatie <= 0) {
522
+ console.error(`[getAvailableTablesForTime] Invalid interval settings for ${date} ${time}`);
523
+ return [];
524
+ }
525
+
526
+ console.log(`\n[getAvailableTablesForTime] Finding available tables for ${guests} guests on ${date} at ${time}`);
527
+ const requiredSlots = computeRequiredSlots(time, duurReservatie, intervalReservatie);
528
+ if (!requiredSlots || requiredSlots.length === 0) {
529
+ console.error(`[getAvailableTablesForTime] Could not compute required slots for ${date} ${time}`);
530
+ return [];
531
+ }
532
+ const tableOccupiedSlots = {};
533
+ const reservationsForDate = reservations
534
+ .filter(r => r.date === date && r.time && r.guests > 0)
535
+ .sort((a, b) => {
536
+ const timeA = a.time.split(':').map(Number);
537
+ const timeB = b.time.split(':').map(Number);
538
+ return (timeA[0] * 60 + timeA[1]) - (timeB[0] * 60 + timeB[1]);
539
+ });
540
+
541
+ console.log(`[getAvailableTablesForTime] Building occupancy map (Processing ${reservationsForDate.length} existing reservations)`);
542
+ for (const r of reservationsForDate) {
543
+ const actualTables = getActualTableAssignment(r);
544
+ let assignedTables = [];
545
+
546
+ if (actualTables.length > 0) {
547
+ assignedTables = actualTables;
548
+ } else {
549
+ assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots, null);
550
+ }
551
+
552
+ if (assignedTables.length > 0) {
553
+ const rSlots = computeRequiredSlots(r.time, duurReservatie, intervalReservatie);
554
+ if (!rSlots || rSlots.length === 0) continue;
555
+ assignedTables.forEach(tableNumber => {
556
+ if (!tableOccupiedSlots[tableNumber]) {
557
+ tableOccupiedSlots[tableNumber] = new Set();
558
+ }
559
+ rSlots.forEach(slot => tableOccupiedSlots[tableNumber].add(slot));
560
+ });
561
+ }
562
+ }
563
+ console.log("[getAvailableTablesForTime] Occupancy map built");
564
+ const allTables = getAllTables(restaurantData, selectedZitplaats); // Get all tables with zitplaats filter applied
565
+ const availableIndividualTables = [];
566
+
567
+ console.log(`[getAvailableTablesForTime] Checking ${allTables.length} tables for individual availability`);
568
+ for (const t of allTables) {
569
+ if (!isTemporaryTableValid(t, date, time)) continue;
570
+ if (t.minCapacity <= guests && guests <= t.maxCapacity) {
571
+ if (isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots)) {
572
+ console.log(`[getAvailableTablesForTime] Table ${t.tableNumber} is available`);
573
+ availableIndividualTables.push(t); // Storing the whole object might be useful
574
+ }
575
+ }
576
+ }
577
+
578
+ console.log(`[getAvailableTablesForTime] Found ${availableIndividualTables.length} individually available tables: ${availableIndividualTables.map(t => t.tableNumber).join(', ')}`);
579
+ availableIndividualTables.sort((a, b) => a.tableNumber - b.tableNumber);
580
+
581
+ return availableIndividualTables; // Return array of table objects
582
+
583
+ } catch (error) {
584
+ console.error(`[getAvailableTablesForTime] Error for ${date} ${time}:`, error);
585
+ return []; // Return empty on error
586
+ }
587
+ }
588
+
589
+ module.exports = {
590
+ isTimeAvailableSync,
591
+ filterTimeblocksByTableAvailability,
592
+ getAvailableTimeblocksWithTableCheck,
593
+ getAvailableTablesForTime,
594
+ };