@happychef/algorithm 1.2.13 → 1.2.15

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/jest.config.js CHANGED
@@ -1,23 +1,23 @@
1
- module.exports = {
2
- testEnvironment: 'node',
3
- coverageDirectory: 'coverage',
4
- collectCoverageFrom: [
5
- '**/*.js',
6
- '!**/node_modules/**',
7
- '!**/coverage/**',
8
- '!jest.config.js',
9
- '!test.js',
10
- '!test-*.js',
11
- '!**/__tests__/**'
12
- ],
13
- testMatch: [
14
- '**/__tests__/**/*.test.js'
15
- ],
16
- testPathIgnorePatterns: [
17
- '/node_modules/',
18
- '/test\\.js$',
19
- '/test-.*\\.js$'
20
- ],
21
- verbose: true,
22
- testTimeout: 10000
23
- };
1
+ module.exports = {
2
+ testEnvironment: 'node',
3
+ coverageDirectory: 'coverage',
4
+ collectCoverageFrom: [
5
+ '**/*.js',
6
+ '!**/node_modules/**',
7
+ '!**/coverage/**',
8
+ '!jest.config.js',
9
+ '!test.js',
10
+ '!test-*.js',
11
+ '!**/__tests__/**'
12
+ ],
13
+ testMatch: [
14
+ '**/__tests__/**/*.test.js'
15
+ ],
16
+ testPathIgnorePatterns: [
17
+ '/node_modules/',
18
+ '/test\\.js$',
19
+ '/test-.*\\.js$'
20
+ ],
21
+ verbose: true,
22
+ testTimeout: 10000
23
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.2.13",
3
+ "version": "1.2.15",
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]) {
@@ -1,100 +1,100 @@
1
- // Detailed filter test - simulating getAvailableTimeblocks logic
2
- const { timeblocksAvailable } = require('./processing/timeblocksAvailable');
3
- const { getMealTypeByTime, parseTime } = require('./tableHelpers');
4
-
5
- const restaurantData = {
6
- "_id": "demo",
7
- "openinghours-lunch": {
8
- "schemeSettings": {
9
- "Monday": {
10
- "enabled": true,
11
- "startTime": "14:15",
12
- "endTime": "15:00",
13
- "maxCapacityEnabled": true,
14
- "maxCapacity": "10"
15
- }
16
- }
17
- },
18
- "general-settings": {
19
- "zitplaatsen": 13,
20
- "uurOpVoorhand": 0,
21
- "dagenInToekomst": 365,
22
- "intervalReservatie": 30,
23
- "duurReservatie": 60,
24
- "ontbijtStop": "",
25
- "lunchStop": "",
26
- "dinerStop": ""
27
- },
28
- "max-arrivals-lunch": {
29
- "14:15": 10,
30
- "14:30": 10,
31
- "14:45": 10
32
- }
33
- };
34
-
35
- const testDate = "2025-12-08";
36
- const guests = 2;
37
- const reservations = [];
38
- const uurOpVoorhand = 0;
39
- const isAdmin = false;
40
-
41
- console.log('=== DETAILED FILTER TEST ===\n');
42
-
43
- // Get timeblocks
44
- const availableTimeblocks = timeblocksAvailable(restaurantData, testDate, reservations, guests, [], null, isAdmin);
45
- console.log('Initial timeblocks:', Object.keys(availableTimeblocks));
46
-
47
- // Simulate the time filtering logic
48
- const now = new Date();
49
- const [year, month, day] = testDate.split('-').map(Number);
50
- const targetDateInTimeZone = new Date(year, month - 1, day);
51
- const isToday = now.toDateString() === targetDateInTimeZone.toDateString();
52
-
53
- console.log('\nTime filter check:');
54
- console.log('Is today:', isToday);
55
- console.log('uurOpVoorhand:', uurOpVoorhand);
56
- console.log('Current time:', now.toString());
57
-
58
- if (isToday && uurOpVoorhand >= 0) {
59
- const cutoffTime = new Date(now.getTime());
60
- cutoffTime.setHours(cutoffTime.getHours() + uurOpVoorhand);
61
- console.log('Cutoff time:', cutoffTime.toString());
62
-
63
- for (const key of Object.keys(availableTimeblocks)) {
64
- const timeStr = key;
65
- const [hours, minutes] = timeStr.split(':').map(Number);
66
- const timeBlockDateTime = new Date(year, month - 1, day, hours, minutes);
67
-
68
- console.log(`\n Checking ${timeStr}:`);
69
- console.log(` timeBlockDateTime: ${timeBlockDateTime.toString()}`);
70
- console.log(` cutoffTime: ${cutoffTime.toString()}`);
71
- console.log(` timeBlockDateTime < cutoffTime: ${timeBlockDateTime < cutoffTime}`);
72
- console.log(` Would be deleted: ${timeBlockDateTime < cutoffTime ? 'YES' : 'NO'}`);
73
- }
74
- }
75
-
76
- // Check lunchStop filter
77
- console.log('\n\nLunch Stop filter check:');
78
- const settings = restaurantData['general-settings'];
79
- const lunchStop = settings.lunchStop || null;
80
- console.log('lunchStop value:', JSON.stringify(lunchStop));
81
- console.log('lunchStop truthy:', !!lunchStop);
82
-
83
- if (lunchStop) {
84
- console.log('LunchStop is set, would apply filter');
85
- for (const time in availableTimeblocks) {
86
- const mealType = getMealTypeByTime(time);
87
- console.log(` ${time}: mealType=${mealType}`);
88
-
89
- if (mealType === 'lunch') {
90
- const timeMinutes = parseTime(time);
91
- const stopMinutes = parseTime(lunchStop);
92
- console.log(` timeMinutes=${timeMinutes}, stopMinutes=${stopMinutes}`);
93
- console.log(` Would remove: ${timeMinutes >= stopMinutes ? 'YES' : 'NO'}`);
94
- }
95
- }
96
- } else {
97
- console.log('LunchStop is NOT set, no filter applied');
98
- }
99
-
100
- console.log('\n=== END TEST ===');
1
+ // Detailed filter test - simulating getAvailableTimeblocks logic
2
+ const { timeblocksAvailable } = require('./processing/timeblocksAvailable');
3
+ const { getMealTypeByTime, parseTime } = require('./tableHelpers');
4
+
5
+ const restaurantData = {
6
+ "_id": "demo",
7
+ "openinghours-lunch": {
8
+ "schemeSettings": {
9
+ "Monday": {
10
+ "enabled": true,
11
+ "startTime": "14:15",
12
+ "endTime": "15:00",
13
+ "maxCapacityEnabled": true,
14
+ "maxCapacity": "10"
15
+ }
16
+ }
17
+ },
18
+ "general-settings": {
19
+ "zitplaatsen": 13,
20
+ "uurOpVoorhand": 0,
21
+ "dagenInToekomst": 365,
22
+ "intervalReservatie": 30,
23
+ "duurReservatie": 60,
24
+ "ontbijtStop": "",
25
+ "lunchStop": "",
26
+ "dinerStop": ""
27
+ },
28
+ "max-arrivals-lunch": {
29
+ "14:15": 10,
30
+ "14:30": 10,
31
+ "14:45": 10
32
+ }
33
+ };
34
+
35
+ const testDate = "2025-12-08";
36
+ const guests = 2;
37
+ const reservations = [];
38
+ const uurOpVoorhand = 0;
39
+ const isAdmin = false;
40
+
41
+ console.log('=== DETAILED FILTER TEST ===\n');
42
+
43
+ // Get timeblocks
44
+ const availableTimeblocks = timeblocksAvailable(restaurantData, testDate, reservations, guests, [], null, isAdmin);
45
+ console.log('Initial timeblocks:', Object.keys(availableTimeblocks));
46
+
47
+ // Simulate the time filtering logic
48
+ const now = new Date();
49
+ const [year, month, day] = testDate.split('-').map(Number);
50
+ const targetDateInTimeZone = new Date(year, month - 1, day);
51
+ const isToday = now.toDateString() === targetDateInTimeZone.toDateString();
52
+
53
+ console.log('\nTime filter check:');
54
+ console.log('Is today:', isToday);
55
+ console.log('uurOpVoorhand:', uurOpVoorhand);
56
+ console.log('Current time:', now.toString());
57
+
58
+ if (isToday && uurOpVoorhand >= 0) {
59
+ const cutoffTime = new Date(now.getTime());
60
+ cutoffTime.setHours(cutoffTime.getHours() + uurOpVoorhand);
61
+ console.log('Cutoff time:', cutoffTime.toString());
62
+
63
+ for (const key of Object.keys(availableTimeblocks)) {
64
+ const timeStr = key;
65
+ const [hours, minutes] = timeStr.split(':').map(Number);
66
+ const timeBlockDateTime = new Date(year, month - 1, day, hours, minutes);
67
+
68
+ console.log(`\n Checking ${timeStr}:`);
69
+ console.log(` timeBlockDateTime: ${timeBlockDateTime.toString()}`);
70
+ console.log(` cutoffTime: ${cutoffTime.toString()}`);
71
+ console.log(` timeBlockDateTime < cutoffTime: ${timeBlockDateTime < cutoffTime}`);
72
+ console.log(` Would be deleted: ${timeBlockDateTime < cutoffTime ? 'YES' : 'NO'}`);
73
+ }
74
+ }
75
+
76
+ // Check lunchStop filter
77
+ console.log('\n\nLunch Stop filter check:');
78
+ const settings = restaurantData['general-settings'];
79
+ const lunchStop = settings.lunchStop || null;
80
+ console.log('lunchStop value:', JSON.stringify(lunchStop));
81
+ console.log('lunchStop truthy:', !!lunchStop);
82
+
83
+ if (lunchStop) {
84
+ console.log('LunchStop is set, would apply filter');
85
+ for (const time in availableTimeblocks) {
86
+ const mealType = getMealTypeByTime(time);
87
+ console.log(` ${time}: mealType=${mealType}`);
88
+
89
+ if (mealType === 'lunch') {
90
+ const timeMinutes = parseTime(time);
91
+ const stopMinutes = parseTime(lunchStop);
92
+ console.log(` timeMinutes=${timeMinutes}, stopMinutes=${stopMinutes}`);
93
+ console.log(` Would remove: ${timeMinutes >= stopMinutes ? 'YES' : 'NO'}`);
94
+ }
95
+ }
96
+ } else {
97
+ console.log('LunchStop is NOT set, no filter applied');
98
+ }
99
+
100
+ console.log('\n=== END TEST ===');