@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 CHANGED
@@ -299,10 +299,19 @@ async function assignTablesIfPossible({
299
299
  }
300
300
  }
301
301
 
302
- // 5) Get duration and interval settings
303
- const duurReservatie = parseInt(
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
- const rDuration = duurReservatie; // assuming same duration
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) {
@@ -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
- if (isTimeAvailableSync(data, dateStr, time, guests, reservations)) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.2.13",
3
+ "version": "1.2.14",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
- const endTime = startTime + duurReservatie; // Use 'duurReservatie' from general settings
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 duurReservatie = 120; // Default: 2 hours in minutes
453
+ let defaultDuurReservatie = 120; // Default: 2 hours in minutes
449
454
  let intervalReservatie = 15; // Default: 15 minute intervals
450
- duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
455
+ defaultDuurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
451
456
  intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
452
457
 
453
- console.log(`[isTimeAvailableSync] Using duration: ${duurReservatie}min, interval: ${intervalReservatie}min`);
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, duurReservatie, intervalReservatie);
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
- // Fall back to simulation for backwards compatibility
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
- const rSlots = computeRequiredSlots(r.time, duurReservatie, intervalReservatie);
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, duurReservatie, intervalReservatie);
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]) {