@happychef/algorithm 1.2.13 → 1.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assignTables.js +22 -3
- package/getAvailableTimeblocks.js +2 -2
- package/isDateAvailable.js +2 -2
- package/isDateAvailableWithTableCheck.js +5 -4
- package/package.json +1 -1
- package/processing/timeblocksAvailable.js +2 -2
- package/reservation_data/counter.js +11 -1
- package/simulateTableAssignment.js +35 -18
package/assignTables.js
CHANGED
|
@@ -299,10 +299,19 @@ async function assignTablesIfPossible({
|
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
// 5) Get duration
|
|
303
|
-
|
|
302
|
+
// 5) Get duration from reservation or settings
|
|
303
|
+
let duurReservatie = parseInt(
|
|
304
304
|
restaurantSettings["general-settings"]?.duurReservatie?.$numberInt || restaurantSettings["general-settings"]?.duurReservatie || 120
|
|
305
305
|
);
|
|
306
|
+
// Use reservation specific duration if provided
|
|
307
|
+
if (reservation.duration) {
|
|
308
|
+
const rawDur = reservation.duration.$numberInt ?? reservation.duration;
|
|
309
|
+
const parsedDur = parseInt(rawDur, 10);
|
|
310
|
+
if (!isNaN(parsedDur) && parsedDur > 0) {
|
|
311
|
+
duurReservatie = parsedDur;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
306
315
|
const intervalReservatie = parseInt(
|
|
307
316
|
restaurantSettings["general-settings"]?.intervalReservatie?.$numberInt || restaurantSettings["general-settings"]?.intervalReservatie || 30
|
|
308
317
|
);
|
|
@@ -322,7 +331,17 @@ async function assignTablesIfPossible({
|
|
|
322
331
|
// No need to skip the current reservation as it's not yet inserted
|
|
323
332
|
|
|
324
333
|
// compute that reservation's time slots
|
|
325
|
-
|
|
334
|
+
// Use reservation specific duration if available, else default
|
|
335
|
+
let rDuration = duurReservatie;
|
|
336
|
+
if (r.duration) {
|
|
337
|
+
// Handle both direct value and MongoDB $numberInt
|
|
338
|
+
// Use ?? to correctly handle 0 values
|
|
339
|
+
const rawDur = r.duration.$numberInt ?? r.duration;
|
|
340
|
+
const parsed = parseInt(rawDur, 10);
|
|
341
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
342
|
+
rDuration = parsed;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
326
345
|
const rSlots = computeRequiredSlots(r.time, rDuration, intervalReservatie);
|
|
327
346
|
|
|
328
347
|
if (r.tables) {
|
|
@@ -30,7 +30,7 @@ function parseDateTimeInTimeZone(dateStr, timeStr, timeZone) {
|
|
|
30
30
|
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
31
31
|
* @returns {Object} - Returns a pruned object of available time blocks or shifts, or an empty object if out of range.
|
|
32
32
|
*/
|
|
33
|
-
function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
33
|
+
function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false, duration = null) {
|
|
34
34
|
// Get 'uurOpVoorhand' from general settings
|
|
35
35
|
let uurOpVoorhand = 0;
|
|
36
36
|
if (
|
|
@@ -79,7 +79,7 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
|
|
|
79
79
|
currentTimeInTimeZone.toDateString() === targetDateInTimeZone.toDateString();
|
|
80
80
|
|
|
81
81
|
// Get available time blocks or shifts
|
|
82
|
-
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
82
|
+
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin, duration);
|
|
83
83
|
|
|
84
84
|
// If the date is today and uurOpVoorhand is greater than zero, prune time blocks (skip for admin)
|
|
85
85
|
if (!isAdmin && isToday && uurOpVoorhand >= 0) {
|
package/isDateAvailable.js
CHANGED
|
@@ -62,14 +62,14 @@ function isDateWithinAllowedRange(data, dateStr) {
|
|
|
62
62
|
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
63
63
|
* @returns {boolean} - Returns true if the date has at least one available timeblock, false otherwise.
|
|
64
64
|
*/
|
|
65
|
-
function isDateAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
65
|
+
function isDateAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false, duration = null) {
|
|
66
66
|
// Check if date is within allowed range (skip for admin)
|
|
67
67
|
if (!isAdmin && !isDateWithinAllowedRange(data, dateStr)) {
|
|
68
68
|
return false;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// Get available timeblocks using the existing logic
|
|
72
|
-
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
72
|
+
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin, duration);
|
|
73
73
|
|
|
74
74
|
// Return true only if we have at least one available timeblock
|
|
75
75
|
return Object.keys(availableTimeblocks).length > 0;
|
|
@@ -74,7 +74,7 @@ function timeHasGiftcard(data, dateStr, timeStr, giftcard) {
|
|
|
74
74
|
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
75
75
|
* @returns {boolean} - Returns true if the date passes all checks to be available.
|
|
76
76
|
*/
|
|
77
|
-
function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null, isAdmin = false) {
|
|
77
|
+
function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null, isAdmin = false, duration = null) {
|
|
78
78
|
// 0) Optionally filter by selected menu item's date range (only when normalOpeningTimes = false)
|
|
79
79
|
const currentItem = typeof window !== 'undefined' ? window.currentReservationTicketItem : null;
|
|
80
80
|
if (
|
|
@@ -114,7 +114,7 @@ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, bloc
|
|
|
114
114
|
console.log(`Table assignment is ${isTableAssignmentEnabled ? 'ENABLED' : 'DISABLED'} for date ${dateStr}`);
|
|
115
115
|
|
|
116
116
|
// 1) First, do the standard day-level checks (including simple giftcard check).
|
|
117
|
-
const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
|
|
117
|
+
const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin, duration);
|
|
118
118
|
if (!basicDateAvailable) {
|
|
119
119
|
console.log(`Date ${dateStr} fails basic availability check`);
|
|
120
120
|
return false;
|
|
@@ -128,7 +128,7 @@ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, bloc
|
|
|
128
128
|
|
|
129
129
|
// 2) Get all available timeblocks for this date
|
|
130
130
|
console.log(`Getting available timeblocks for ${dateStr}`);
|
|
131
|
-
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
|
|
131
|
+
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin, duration);
|
|
132
132
|
console.log(`Found ${Object.keys(availableTimeblocks).length} available timeblocks before table check`);
|
|
133
133
|
|
|
134
134
|
// If no timeblocks are available at all, exit early
|
|
@@ -153,7 +153,8 @@ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, bloc
|
|
|
153
153
|
}
|
|
154
154
|
|
|
155
155
|
console.log(`Checking table availability for ${time} on ${dateStr} for ${guests} guests`);
|
|
156
|
-
|
|
156
|
+
// Pass null for selectedZitplaats, then duration
|
|
157
|
+
if (isTimeAvailableSync(data, dateStr, time, guests, reservations, null, duration)) {
|
|
157
158
|
console.log(`Found available time ${time} with table assignment!`);
|
|
158
159
|
atLeastOneAvailable = true;
|
|
159
160
|
break;
|
package/package.json
CHANGED
|
@@ -89,8 +89,8 @@ 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, isAdmin = false) {
|
|
93
|
-
const duurReservatie = getDuurReservatie(data);
|
|
92
|
+
function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false, duration = null) {
|
|
93
|
+
const duurReservatie = duration && duration > 0 ? duration : getDuurReservatie(data);
|
|
94
94
|
const intervalReservatie = getInterval(data);
|
|
95
95
|
|
|
96
96
|
// Slots needed
|
|
@@ -48,7 +48,17 @@ function parseTime(timeStr) {
|
|
|
48
48
|
|
|
49
49
|
const startTime = parseTime(reservation.time);
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
// Use reservation specific duration if available, else default
|
|
52
|
+
let rDuration = duurReservatie;
|
|
53
|
+
if (reservation.duration) {
|
|
54
|
+
const rawDur = reservation.duration.$numberInt ?? reservation.duration;
|
|
55
|
+
const parsed = parseInt(rawDur, 10);
|
|
56
|
+
if (!isNaN(parsed) && parsed > 0) {
|
|
57
|
+
rDuration = parsed;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const endTime = startTime + rDuration;
|
|
52
62
|
// Check if the target time is within the reservation time range
|
|
53
63
|
// Start time is inclusive, end time is exclusive
|
|
54
64
|
if (targetTime >= startTime && targetTime < endTime) {
|
|
@@ -322,7 +322,7 @@ function findMultiTableCombination(tables, guestsTotal, startIndex, currentSet,
|
|
|
322
322
|
}
|
|
323
323
|
}
|
|
324
324
|
|
|
325
|
-
function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccupiedSlots, selectedZitplaats = null) {
|
|
325
|
+
function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccupiedSlots, selectedZitplaats = null, duration = null) {
|
|
326
326
|
console.log(`[assignTablesForGivenTime] Processing ${date} ${time} for ${guests} guests`);
|
|
327
327
|
|
|
328
328
|
// Clear cache if it gets too large (prevent memory leaks)
|
|
@@ -341,6 +341,11 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
|
|
|
341
341
|
duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
|
|
342
342
|
intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
|
|
343
343
|
|
|
344
|
+
// Use passed duration if available, otherwise default
|
|
345
|
+
if (duration && duration > 0) {
|
|
346
|
+
duurReservatie = duration;
|
|
347
|
+
}
|
|
348
|
+
|
|
344
349
|
console.log(`[assignTablesForGivenTime] Using duration: ${duurReservatie}min, interval: ${intervalReservatie}min`);
|
|
345
350
|
|
|
346
351
|
if (intervalReservatie <= 0) {
|
|
@@ -411,8 +416,8 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
|
|
|
411
416
|
return best.tables.map(t => t.tableNumber);
|
|
412
417
|
}
|
|
413
418
|
|
|
414
|
-
function isTimeAvailableSync(restaurantData, date, time, guests, reservations, selectedZitplaats = null) {
|
|
415
|
-
console.log(`\n[isTimeAvailableSync] Checking ${date} ${time} for ${guests} guests`);
|
|
419
|
+
function isTimeAvailableSync(restaurantData, date, time, guests, reservations, selectedZitplaats = null, duration = null) {
|
|
420
|
+
console.log(`\n[isTimeAvailableSync] Checking ${date} ${time} for ${guests} guests (duration: ${duration || 'default'})`);
|
|
416
421
|
|
|
417
422
|
if (guests < 0) {
|
|
418
423
|
console.log(`[isTimeAvailableSync] Detected negative guest count (${guests}), adjusting to default of 2`);
|
|
@@ -445,19 +450,22 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
445
450
|
}
|
|
446
451
|
|
|
447
452
|
const generalSettings = restaurantData["general-settings"] || {};
|
|
448
|
-
let
|
|
453
|
+
let defaultDuurReservatie = 120; // Default: 2 hours in minutes
|
|
449
454
|
let intervalReservatie = 15; // Default: 15 minute intervals
|
|
450
|
-
|
|
455
|
+
defaultDuurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
|
|
451
456
|
intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
|
|
452
457
|
|
|
453
|
-
|
|
458
|
+
// Use passed duration or default
|
|
459
|
+
const currentDuurReservatie = duration && duration > 0 ? duration : defaultDuurReservatie;
|
|
460
|
+
|
|
461
|
+
console.log(`[isTimeAvailableSync] Using duration: ${currentDuurReservatie}min (default: ${defaultDuurReservatie}), interval: ${intervalReservatie}min`);
|
|
454
462
|
|
|
455
463
|
if (intervalReservatie <= 0) {
|
|
456
464
|
console.error(`[isTimeAvailableSync] Invalid interval settings for ${date} ${time}`);
|
|
457
465
|
return false;
|
|
458
466
|
}
|
|
459
467
|
|
|
460
|
-
const requiredSlots = computeRequiredSlots(time,
|
|
468
|
+
const requiredSlots = computeRequiredSlots(time, currentDuurReservatie, intervalReservatie);
|
|
461
469
|
if (!requiredSlots || requiredSlots.length === 0) {
|
|
462
470
|
console.error(`[isTimeAvailableSync] Could not compute required slots for ${date} ${time}`);
|
|
463
471
|
return false; // Cannot proceed if slots are invalid
|
|
@@ -477,8 +485,15 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
477
485
|
console.log(`[isTimeAvailableSync] Processing ${reservationsForDate.length} existing reservations on ${date}`);
|
|
478
486
|
|
|
479
487
|
for (const r of reservationsForDate) {
|
|
488
|
+
// Calculate duration once per reservation
|
|
489
|
+
const rDuration = safeParseInt(r.duration, defaultDuurReservatie);
|
|
490
|
+
|
|
491
|
+
console.log(`[DEBUG] Reservation ${r.time} (${r.firstName}): Duration=${rDuration}, Tables raw=${JSON.stringify(r.tables)}`);
|
|
492
|
+
|
|
480
493
|
// NEW: Try to get actual table assignment first
|
|
481
494
|
const actualTables = getActualTableAssignment(r);
|
|
495
|
+
console.log(`[DEBUG] Reservation ${r.time}: actualTables detected = ${JSON.stringify(actualTables)}`);
|
|
496
|
+
|
|
482
497
|
let assignedTables = [];
|
|
483
498
|
|
|
484
499
|
if (actualTables.length > 0) {
|
|
@@ -486,14 +501,17 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
486
501
|
assignedTables = actualTables;
|
|
487
502
|
console.log(`[isTimeAvailableSync] Using actual table assignment for reservation: ${assignedTables.join(', ')}`);
|
|
488
503
|
} else {
|
|
489
|
-
//
|
|
504
|
+
// ... simulation log ...
|
|
490
505
|
console.log(`[isTimeAvailableSync] No actual table data, simulating assignment for reservation`);
|
|
491
|
-
assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots);
|
|
506
|
+
assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots, null, rDuration); // Simulation
|
|
492
507
|
}
|
|
493
508
|
|
|
494
509
|
// Update the occupancy map based on the actual or simulated assignment
|
|
495
510
|
if (assignedTables.length > 0) {
|
|
496
|
-
|
|
511
|
+
// Use reservation specific duration if available
|
|
512
|
+
const rSlots = computeRequiredSlots(r.time, rDuration, intervalReservatie);
|
|
513
|
+
console.log(`[DEBUG] Reservation ${r.time}: Occupying slots=${rSlots.join(',')} on tables=${assignedTables.join(',')}`);
|
|
514
|
+
|
|
497
515
|
if (!rSlots || rSlots.length === 0) continue; // Skip if slots invalid
|
|
498
516
|
|
|
499
517
|
assignedTables.forEach(tableNumber => {
|
|
@@ -502,8 +520,6 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
502
520
|
}
|
|
503
521
|
rSlots.forEach(slot => tableOccupiedSlots[tableNumber].add(slot));
|
|
504
522
|
});
|
|
505
|
-
|
|
506
|
-
console.log(`[isTimeAvailableSync] Marked tables ${assignedTables.join(', ')} as occupied for time ${r.time}`);
|
|
507
523
|
}
|
|
508
524
|
}
|
|
509
525
|
|
|
@@ -566,7 +582,7 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
566
582
|
}
|
|
567
583
|
}
|
|
568
584
|
|
|
569
|
-
function filterTimeblocksByTableAvailability(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats = null) {
|
|
585
|
+
function filterTimeblocksByTableAvailability(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats = null, duration = null) {
|
|
570
586
|
console.log(`[filterTimeblocksByTableAvailability] Filtering timeblocks for ${date} with ${guests} guests`);
|
|
571
587
|
|
|
572
588
|
if (guests < 0) {
|
|
@@ -590,7 +606,7 @@ function filterTimeblocksByTableAvailability(restaurantData, date, timeblocks, g
|
|
|
590
606
|
let unavailableCount = 0;
|
|
591
607
|
|
|
592
608
|
for (const time in timeblocks) {
|
|
593
|
-
if (isTimeAvailableSync(restaurantData, date, time, guests, reservations, selectedZitplaats)) {
|
|
609
|
+
if (isTimeAvailableSync(restaurantData, date, time, guests, reservations, selectedZitplaats, duration)) {
|
|
594
610
|
filteredTimeblocks[time] = timeblocks[time];
|
|
595
611
|
availableCount++;
|
|
596
612
|
} else {
|
|
@@ -606,8 +622,8 @@ function filterTimeblocksByTableAvailability(restaurantData, date, timeblocks, g
|
|
|
606
622
|
return filteredTimeblocks;
|
|
607
623
|
}
|
|
608
624
|
|
|
609
|
-
function getAvailableTimeblocksWithTableCheck(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats = null) {
|
|
610
|
-
return filterTimeblocksByTableAvailability(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats);
|
|
625
|
+
function getAvailableTimeblocksWithTableCheck(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats = null, duration = null) {
|
|
626
|
+
return filterTimeblocksByTableAvailability(restaurantData, date, timeblocks, guests, reservations, selectedZitplaats, duration);
|
|
611
627
|
}
|
|
612
628
|
|
|
613
629
|
function getAvailableTablesForTime(restaurantData, date, time, guests, reservations, selectedZitplaats = null) {
|
|
@@ -656,17 +672,18 @@ function getAvailableTablesForTime(restaurantData, date, time, guests, reservati
|
|
|
656
672
|
|
|
657
673
|
console.log(`[getAvailableTablesForTime] Building occupancy map (Processing ${reservationsForDate.length} existing reservations)`);
|
|
658
674
|
for (const r of reservationsForDate) {
|
|
675
|
+
const rDuration = safeParseInt(r.duration, duurReservatie);
|
|
659
676
|
const actualTables = getActualTableAssignment(r);
|
|
660
677
|
let assignedTables = [];
|
|
661
678
|
|
|
662
679
|
if (actualTables.length > 0) {
|
|
663
680
|
assignedTables = actualTables;
|
|
664
681
|
} else {
|
|
665
|
-
assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots, null);
|
|
682
|
+
assignedTables = assignTablesForGivenTime(restaurantData, r.date, r.time, r.guests, tableOccupiedSlots, null, rDuration);
|
|
666
683
|
}
|
|
667
684
|
|
|
668
685
|
if (assignedTables.length > 0) {
|
|
669
|
-
const rSlots = computeRequiredSlots(r.time,
|
|
686
|
+
const rSlots = computeRequiredSlots(r.time, rDuration, intervalReservatie);
|
|
670
687
|
if (!rSlots || rSlots.length === 0) continue;
|
|
671
688
|
assignedTables.forEach(tableNumber => {
|
|
672
689
|
if (!tableOccupiedSlots[tableNumber]) {
|