@happychef/algorithm 1.3.0 → 1.4.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.
Files changed (71) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/.github/workflows/ci-cd.yml +80 -80
  3. package/BRANCH_PROTECTION_SETUP.md +167 -167
  4. package/CHANGELOG.md +8 -8
  5. package/README.md +144 -144
  6. package/RESERVERINGEN_GIDS.md +986 -986
  7. package/__tests__/crossMidnight.test.js +63 -0
  8. package/__tests__/crossMidnightTimeblocks.test.js +312 -0
  9. package/__tests__/edgeCases.test.js +271 -0
  10. package/__tests__/filters.test.js +276 -276
  11. package/__tests__/isDateAvailable.test.js +179 -175
  12. package/__tests__/isTimeAvailable.test.js +174 -168
  13. package/__tests__/restaurantData.test.js +422 -422
  14. package/__tests__/tableHelpers.test.js +247 -247
  15. package/assignTables.js +506 -444
  16. package/changes/2025/December/PR2___change.md +14 -14
  17. package/changes/2025/December/PR3_add__change.md +20 -20
  18. package/changes/2025/December/PR4___.md +15 -15
  19. package/changes/2025/December/PR5___.md +15 -15
  20. package/changes/2025/December/PR6__del_.md +17 -17
  21. package/changes/2025/December/PR7_add__change.md +21 -21
  22. package/changes/2026/February/PR15_add__change.md +21 -21
  23. package/changes/2026/February/PR16_add__.md +20 -0
  24. package/changes/2026/February/PR16_add_getDateClosingReasons.md +31 -31
  25. package/changes/2026/January/PR10_add__change.md +21 -21
  26. package/changes/2026/January/PR11_add__change.md +19 -19
  27. package/changes/2026/January/PR12_add__.md +21 -21
  28. package/changes/2026/January/PR13_add__change.md +20 -20
  29. package/changes/2026/January/PR14_add__change.md +19 -19
  30. package/changes/2026/January/PR8_add__change.md +38 -38
  31. package/changes/2026/January/PR9_add__change.md +19 -19
  32. package/dateHelpers.js +31 -0
  33. package/filters/maxArrivalsFilter.js +114 -114
  34. package/filters/maxGroupsFilter.js +221 -221
  35. package/filters/timeFilter.js +89 -89
  36. package/getAvailableTimeblocks.js +158 -158
  37. package/getDateClosingReasons.js +193 -193
  38. package/grouping.js +162 -162
  39. package/index.js +48 -43
  40. package/isDateAvailable.js +80 -80
  41. package/isDateAvailableWithTableCheck.js +172 -172
  42. package/isTimeAvailable.js +26 -26
  43. package/jest.config.js +23 -23
  44. package/package.json +27 -27
  45. package/processing/dailyGuestCounts.js +73 -73
  46. package/processing/mealTypeCount.js +133 -133
  47. package/processing/timeblocksAvailable.js +344 -182
  48. package/reservation_data/counter.js +82 -75
  49. package/restaurant_data/exceptions.js +150 -150
  50. package/restaurant_data/openinghours.js +142 -142
  51. package/simulateTableAssignment.js +833 -726
  52. package/tableHelpers.js +209 -209
  53. package/tables/time/parseTime.js +19 -19
  54. package/tables/time/shifts.js +7 -7
  55. package/tables/utils/calculateDistance.js +13 -13
  56. package/tables/utils/isTableFreeForAllSlots.js +14 -14
  57. package/tables/utils/isTemporaryTableValid.js +39 -39
  58. package/test/test_counter.js +194 -194
  59. package/test/test_dailyCount.js +81 -81
  60. package/test/test_datesAvailable.js +106 -106
  61. package/test/test_exceptions.js +172 -172
  62. package/test/test_isDateAvailable.js +330 -330
  63. package/test/test_mealTypeCount.js +54 -54
  64. package/test/test_timesAvailable.js +88 -88
  65. package/test-detailed-filter.js +100 -100
  66. package/test-lunch-debug.js +110 -110
  67. package/test-max-arrivals-filter.js +79 -79
  68. package/test-meal-stop-fix.js +147 -147
  69. package/test-meal-stop-simple.js +93 -93
  70. package/test-timezone-debug.js +47 -47
  71. package/test.js +336 -336
@@ -1,54 +1,54 @@
1
- // mealReservationTests.js
2
-
3
- const { getGuestCountsForMeal } = require('../processing/mealTypeCount');
4
-
5
- // Sample data including shifts
6
- const data = {
7
- "_id": "demo",
8
- "openinghours-breakfast": {
9
- "schemeSettings": {
10
- "Monday": {
11
- "enabled": true,
12
- "startTime": "07:00",
13
- "endTime": "09:30",
14
- "maxCapacityEnabled": true,
15
- "maxCapacity": "12",
16
- "shiftsEnabled": true,
17
- "shifts": [
18
- {
19
- "name": "Shift 1",
20
- "time": "08:00"
21
- },
22
- {
23
- "name": "Shift 2",
24
- "time": "09:00"
25
- }
26
- ]
27
- }
28
- },
29
- "storedNumber": {
30
- "$numberInt": "0"
31
- }
32
- },
33
- // Other meal data...
34
- };
35
-
36
- // Sample reservations
37
- const reservations = [
38
- { guests: "5", time: "08:00", date: "2024-12-02" },
39
- { guests: "3", time: "09:00", date: "2024-12-02" },
40
- { guests: "2", time: "07:00", date: "2024-12-02" },
41
- { guests: "4", time: "07:30", date: "2024-12-02" },
42
- { guests: "6", time: "08:00", date: "2024-12-02" }, // Reservation on a different date
43
- ];
44
-
45
- console.log('--- Meal Reservation Guest Counts Tests ---');
46
-
47
- // Test 1: Breakfast on January 12th (Monday) with shifts
48
- const dateStr = '2024-12-02';
49
- const mealType = 'breakfast';
50
-
51
- const guestCounts = getGuestCountsForMeal(data, dateStr, mealType, reservations);
52
-
53
- console.log('Test 1 - Guest Counts for Breakfast on January 12th with Shifts:');
54
- console.log(guestCounts);
1
+ // mealReservationTests.js
2
+
3
+ const { getGuestCountsForMeal } = require('../processing/mealTypeCount');
4
+
5
+ // Sample data including shifts
6
+ const data = {
7
+ "_id": "demo",
8
+ "openinghours-breakfast": {
9
+ "schemeSettings": {
10
+ "Monday": {
11
+ "enabled": true,
12
+ "startTime": "07:00",
13
+ "endTime": "09:30",
14
+ "maxCapacityEnabled": true,
15
+ "maxCapacity": "12",
16
+ "shiftsEnabled": true,
17
+ "shifts": [
18
+ {
19
+ "name": "Shift 1",
20
+ "time": "08:00"
21
+ },
22
+ {
23
+ "name": "Shift 2",
24
+ "time": "09:00"
25
+ }
26
+ ]
27
+ }
28
+ },
29
+ "storedNumber": {
30
+ "$numberInt": "0"
31
+ }
32
+ },
33
+ // Other meal data...
34
+ };
35
+
36
+ // Sample reservations
37
+ const reservations = [
38
+ { guests: "5", time: "08:00", date: "2024-12-02" },
39
+ { guests: "3", time: "09:00", date: "2024-12-02" },
40
+ { guests: "2", time: "07:00", date: "2024-12-02" },
41
+ { guests: "4", time: "07:30", date: "2024-12-02" },
42
+ { guests: "6", time: "08:00", date: "2024-12-02" }, // Reservation on a different date
43
+ ];
44
+
45
+ console.log('--- Meal Reservation Guest Counts Tests ---');
46
+
47
+ // Test 1: Breakfast on January 12th (Monday) with shifts
48
+ const dateStr = '2024-12-02';
49
+ const mealType = 'breakfast';
50
+
51
+ const guestCounts = getGuestCountsForMeal(data, dateStr, mealType, reservations);
52
+
53
+ console.log('Test 1 - Guest Counts for Breakfast on January 12th with Shifts:');
54
+ console.log(guestCounts);
@@ -1,88 +1,88 @@
1
- // timeblocksAvailableTests.js
2
-
3
- const { timeblocksAvailable } = require('../processing/timeblocksAvailable');
4
-
5
- // Sample data (same as previous examples)
6
- const data = {
7
- "_id": "demo",
8
- "general-settings": {
9
- "zitplaatsen": "5",
10
- "duurReservatie": "120", // Reservation duration of 120 minutes
11
- "intervalReservatie": "30" // Time increment of 30 minutes
12
- },
13
- "openinghours-breakfast": {
14
- "schemeSettings": {
15
- "Monday": {
16
- "enabled": false,
17
- "startTime": "07:00",
18
- "endTime": "11:00",
19
- "maxCapacityEnabled": false,
20
- "maxCapacity": "0",
21
- "shiftsEnabled": false,
22
- "shifts": []
23
- }
24
- },
25
- "storedNumber": {
26
- "$numberInt": "0"
27
- }
28
- },
29
- "openinghours-lunch": {
30
- "schemeSettings": {
31
- "Monday": {
32
- "enabled": false,
33
- "startTime": "13:00",
34
- "endTime": "16:00",
35
- "maxCapacityEnabled": true,
36
- "maxCapacity": "20",
37
- "shiftsEnabled": false,
38
- "shifts": []
39
- }
40
- },
41
- "storedNumber": {
42
- "$numberInt": "0"
43
- }
44
- },
45
- "openinghours-dinner": {
46
- "schemeSettings": {
47
- "Monday": {
48
- "enabled": true,
49
- "startTime": "16:00",
50
- "endTime": "23:00",
51
- "maxCapacityEnabled": true,
52
- "maxCapacity": "5",
53
- "shiftsEnabled": true,
54
- "shifts": [
55
- {
56
- "name": "Shift 1",
57
- "time": "18:00"
58
- },
59
- {
60
- "name": "Shift 2",
61
- "time": "20:00"
62
- }
63
- ]
64
- }
65
- },
66
- "storedNumber": {
67
- "$numberInt": "0"
68
- }
69
- },
70
- "exceptions": []
71
- };
72
-
73
- // Sample reservations
74
- const reservations = [
75
- // Dinner reservations
76
- { guests: "5", time: "18:00", date: "2024-12-02" }, // Shift 1 is fully booked
77
- { guests: "2", time: "20:00", date: "2024-12-02" }, // Shift 2 has available seats
78
- ];
79
-
80
- console.log('--- Time Blocks Availability Tests ---');
81
-
82
- const guests = 3;
83
- const dateStr = '2024-12-02';
84
-
85
- const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests);
86
-
87
- console.log(`Available time blocks for ${guests} guests on ${dateStr}:`);
88
- console.log(availableTimeblocks);
1
+ // timeblocksAvailableTests.js
2
+
3
+ const { timeblocksAvailable } = require('../processing/timeblocksAvailable');
4
+
5
+ // Sample data (same as previous examples)
6
+ const data = {
7
+ "_id": "demo",
8
+ "general-settings": {
9
+ "zitplaatsen": "5",
10
+ "duurReservatie": "120", // Reservation duration of 120 minutes
11
+ "intervalReservatie": "30" // Time increment of 30 minutes
12
+ },
13
+ "openinghours-breakfast": {
14
+ "schemeSettings": {
15
+ "Monday": {
16
+ "enabled": false,
17
+ "startTime": "07:00",
18
+ "endTime": "11:00",
19
+ "maxCapacityEnabled": false,
20
+ "maxCapacity": "0",
21
+ "shiftsEnabled": false,
22
+ "shifts": []
23
+ }
24
+ },
25
+ "storedNumber": {
26
+ "$numberInt": "0"
27
+ }
28
+ },
29
+ "openinghours-lunch": {
30
+ "schemeSettings": {
31
+ "Monday": {
32
+ "enabled": false,
33
+ "startTime": "13:00",
34
+ "endTime": "16:00",
35
+ "maxCapacityEnabled": true,
36
+ "maxCapacity": "20",
37
+ "shiftsEnabled": false,
38
+ "shifts": []
39
+ }
40
+ },
41
+ "storedNumber": {
42
+ "$numberInt": "0"
43
+ }
44
+ },
45
+ "openinghours-dinner": {
46
+ "schemeSettings": {
47
+ "Monday": {
48
+ "enabled": true,
49
+ "startTime": "16:00",
50
+ "endTime": "23:00",
51
+ "maxCapacityEnabled": true,
52
+ "maxCapacity": "5",
53
+ "shiftsEnabled": true,
54
+ "shifts": [
55
+ {
56
+ "name": "Shift 1",
57
+ "time": "18:00"
58
+ },
59
+ {
60
+ "name": "Shift 2",
61
+ "time": "20:00"
62
+ }
63
+ ]
64
+ }
65
+ },
66
+ "storedNumber": {
67
+ "$numberInt": "0"
68
+ }
69
+ },
70
+ "exceptions": []
71
+ };
72
+
73
+ // Sample reservations
74
+ const reservations = [
75
+ // Dinner reservations
76
+ { guests: "5", time: "18:00", date: "2024-12-02" }, // Shift 1 is fully booked
77
+ { guests: "2", time: "20:00", date: "2024-12-02" }, // Shift 2 has available seats
78
+ ];
79
+
80
+ console.log('--- Time Blocks Availability Tests ---');
81
+
82
+ const guests = 3;
83
+ const dateStr = '2024-12-02';
84
+
85
+ const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests);
86
+
87
+ console.log(`Available time blocks for ${guests} guests on ${dateStr}:`);
88
+ console.log(availableTimeblocks);
@@ -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 ===');