@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,20 +1,20 @@
1
- # PR 9 - Fix table minimum capacity validation for reservations
2
-
3
- **Actions:**
4
-
5
- ## Changes Summary
6
- ADDED:
7
- - New condition `sum(minCapacity) <= guests` added to the `findMultiTableCombination` function in `simulateTableAssignment.js` to validate minimum capacity constraints.
8
- - New variable `minSum` introduced within the combination validation loop to accumulate the sum of `table.minCapacity` for selected tables.
9
-
10
- NO_REMOVALS
11
-
12
- CHANGED:
13
- - Modified the `findMultiTableCombination` function in `simulateTableAssignment.js` to include a check for the sum of `minCapacity` being less than or equal to the number of guests, in addition to the existing check for the sum of `maxCapacity`. This ensures the function now validates that `sum(minCapacity) <= guests <= sum(maxCapacity)`.
14
- - Updated the logic within the combination validation loop to calculate `totalMinCapacity` alongside `totalMaxCapacity` and use both in the condition to determine a valid table combination.
15
-
16
- ---
17
-
18
- **Author:** thibaultvandesompele2
19
- **Date:** 2026-01-07
1
+ # PR 9 - Fix table minimum capacity validation for reservations
2
+
3
+ **Actions:**
4
+
5
+ ## Changes Summary
6
+ ADDED:
7
+ - New condition `sum(minCapacity) <= guests` added to the `findMultiTableCombination` function in `simulateTableAssignment.js` to validate minimum capacity constraints.
8
+ - New variable `minSum` introduced within the combination validation loop to accumulate the sum of `table.minCapacity` for selected tables.
9
+
10
+ NO_REMOVALS
11
+
12
+ CHANGED:
13
+ - Modified the `findMultiTableCombination` function in `simulateTableAssignment.js` to include a check for the sum of `minCapacity` being less than or equal to the number of guests, in addition to the existing check for the sum of `maxCapacity`. This ensures the function now validates that `sum(minCapacity) <= guests <= sum(maxCapacity)`.
14
+ - Updated the logic within the combination validation loop to calculate `totalMinCapacity` alongside `totalMaxCapacity` and use both in the condition to determine a valid table combination.
15
+
16
+ ---
17
+
18
+ **Author:** thibaultvandesompele2
19
+ **Date:** 2026-01-07
20
20
  **PR Link:** https://github.com/thibaultvandesompele2/15-happy-algorithm/pull/9
package/dateHelpers.js ADDED
@@ -0,0 +1,31 @@
1
+ // dateHelpers.js — Simple date string arithmetic for cross-midnight support
2
+
3
+ /**
4
+ * Returns the next day's date string in YYYY-MM-DD format.
5
+ * @param {string} dateStr - Date in "YYYY-MM-DD" format.
6
+ * @returns {string} Next day in "YYYY-MM-DD" format.
7
+ */
8
+ function getNextDateStr(dateStr) {
9
+ const [y, m, d] = dateStr.split('-').map(Number);
10
+ const next = new Date(y, m - 1, d + 1);
11
+ const ny = next.getFullYear();
12
+ const nm = String(next.getMonth() + 1).padStart(2, '0');
13
+ const nd = String(next.getDate()).padStart(2, '0');
14
+ return `${ny}-${nm}-${nd}`;
15
+ }
16
+
17
+ /**
18
+ * Returns the previous day's date string in YYYY-MM-DD format.
19
+ * @param {string} dateStr - Date in "YYYY-MM-DD" format.
20
+ * @returns {string} Previous day in "YYYY-MM-DD" format.
21
+ */
22
+ function getPrevDateStr(dateStr) {
23
+ const [y, m, d] = dateStr.split('-').map(Number);
24
+ const prev = new Date(y, m - 1, d - 1);
25
+ const py = prev.getFullYear();
26
+ const pm = String(prev.getMonth() + 1).padStart(2, '0');
27
+ const pd = String(prev.getDate()).padStart(2, '0');
28
+ return `${py}-${pm}-${pd}`;
29
+ }
30
+
31
+ module.exports = { getNextDateStr, getPrevDateStr };
@@ -1,115 +1,115 @@
1
- // file: /src/Pages/NewReservation/StepOne/algorithm/maxArrivalsFilter.js
2
-
3
- /**
4
- * Simple max arrivals filter for time slots
5
- *
6
- * Only considers exact arrivals at each time slot without factoring duration
7
- */
8
-
9
- /**
10
- * Get meal type based on time
11
- * @param {string} time - Time string (HH:MM)
12
- * @returns {string|null} - Meal type or null
13
- */
14
- function getMealType(time) {
15
- const hour = parseInt(time.split(':')[0], 10);
16
-
17
- if (hour >= 4 && hour < 11) return 'breakfast';
18
- if (hour >= 11 && hour < 16) return 'lunch';
19
- if (hour >= 16 && hour < 23) return 'dinner';
20
-
21
- return null;
22
- }
23
-
24
- /**
25
- * Extract number value from various data formats
26
- * @param {*} value - Value from data object
27
- * @returns {number|null} - Number or null
28
- */
29
- function extractNumber(value) {
30
- if (!value) return null;
31
-
32
- // Handle MongoDB NumberInt format
33
- if (value.$numberInt) {
34
- return parseInt(value.$numberInt, 10);
35
- }
36
-
37
- // Handle regular number
38
- if (typeof value === 'number') {
39
- return value;
40
- }
41
-
42
- // Handle string number
43
- if (typeof value === 'string') {
44
- const parsed = parseInt(value, 10);
45
- return isNaN(parsed) ? null : parsed;
46
- }
47
-
48
- return null;
49
- }
50
-
51
- /**
52
- * Count guests arriving at exact time
53
- * @param {Array} reservations - Reservation list
54
- * @param {string} date - Date (YYYY-MM-DD)
55
- * @param {string} time - Time (HH:MM)
56
- * @returns {number} - Guest count
57
- */
58
- function countArrivalsAtTime(reservations, date, time) {
59
- return reservations
60
- .filter(r => r.date === date && r.time === time)
61
- .reduce((sum, r) => sum + (parseInt(r.guests, 10) || 0), 0);
62
- }
63
-
64
- /**
65
- * Filter timeblocks based on max arrivals settings
66
- * @param {Object} restaurantData - Restaurant data
67
- * @param {string} date - Date string
68
- * @param {Object} timeblocks - Available timeblocks
69
- * @param {Array} reservations - Existing reservations
70
- * @param {number} guests - New reservation guest count
71
- * @returns {Object} - Filtered timeblocks
72
- */
73
- function filterTimeblocksByMaxArrivals(restaurantData, date, timeblocks, reservations, guests) {
74
- const filteredBlocks = {};
75
-
76
- for (const [time, timeData] of Object.entries(timeblocks)) {
77
- // Get meal type for this time
78
- const mealType = getMealType(time);
79
- if (!mealType) {
80
- // Keep the timeblock if we can't determine its meal type
81
- filteredBlocks[time] = timeData;
82
- continue;
83
- }
84
-
85
- // Get max arrivals config for this meal type
86
- const maxArrivalsConfig = restaurantData[`max-arrivals-${mealType}`];
87
- if (!maxArrivalsConfig) {
88
- // Keep the timeblock if no max arrivals config for this meal type
89
- filteredBlocks[time] = timeData;
90
- continue;
91
- }
92
-
93
- // Get max arrivals value for this specific time
94
- const maxArrivals = extractNumber(maxArrivalsConfig[time]);
95
- if (maxArrivals === null) {
96
- // Keep the timeblock if no specific max arrivals for this time
97
- filteredBlocks[time] = timeData;
98
- continue;
99
- }
100
-
101
- // Count current arrivals at this exact time
102
- const currentArrivals = countArrivalsAtTime(reservations, date, time);
103
-
104
- // Only include timeblock if adding these guests doesn't exceed max arrivals
105
- if (currentArrivals + guests <= maxArrivals) {
106
- filteredBlocks[time] = timeData;
107
- }
108
- }
109
-
110
- return filteredBlocks;
111
- }
112
-
113
- module.exports = {
114
- filterTimeblocksByMaxArrivals
1
+ // file: /src/Pages/NewReservation/StepOne/algorithm/maxArrivalsFilter.js
2
+
3
+ /**
4
+ * Simple max arrivals filter for time slots
5
+ *
6
+ * Only considers exact arrivals at each time slot without factoring duration
7
+ */
8
+
9
+ /**
10
+ * Get meal type based on time
11
+ * @param {string} time - Time string (HH:MM)
12
+ * @returns {string|null} - Meal type or null
13
+ */
14
+ function getMealType(time) {
15
+ const hour = parseInt(time.split(':')[0], 10);
16
+
17
+ if (hour >= 4 && hour < 11) return 'breakfast';
18
+ if (hour >= 11 && hour < 16) return 'lunch';
19
+ if (hour >= 16 && hour < 23) return 'dinner';
20
+
21
+ return null;
22
+ }
23
+
24
+ /**
25
+ * Extract number value from various data formats
26
+ * @param {*} value - Value from data object
27
+ * @returns {number|null} - Number or null
28
+ */
29
+ function extractNumber(value) {
30
+ if (!value) return null;
31
+
32
+ // Handle MongoDB NumberInt format
33
+ if (value.$numberInt) {
34
+ return parseInt(value.$numberInt, 10);
35
+ }
36
+
37
+ // Handle regular number
38
+ if (typeof value === 'number') {
39
+ return value;
40
+ }
41
+
42
+ // Handle string number
43
+ if (typeof value === 'string') {
44
+ const parsed = parseInt(value, 10);
45
+ return isNaN(parsed) ? null : parsed;
46
+ }
47
+
48
+ return null;
49
+ }
50
+
51
+ /**
52
+ * Count guests arriving at exact time
53
+ * @param {Array} reservations - Reservation list
54
+ * @param {string} date - Date (YYYY-MM-DD)
55
+ * @param {string} time - Time (HH:MM)
56
+ * @returns {number} - Guest count
57
+ */
58
+ function countArrivalsAtTime(reservations, date, time) {
59
+ return reservations
60
+ .filter(r => r.date === date && r.time === time)
61
+ .reduce((sum, r) => sum + (parseInt(r.guests, 10) || 0), 0);
62
+ }
63
+
64
+ /**
65
+ * Filter timeblocks based on max arrivals settings
66
+ * @param {Object} restaurantData - Restaurant data
67
+ * @param {string} date - Date string
68
+ * @param {Object} timeblocks - Available timeblocks
69
+ * @param {Array} reservations - Existing reservations
70
+ * @param {number} guests - New reservation guest count
71
+ * @returns {Object} - Filtered timeblocks
72
+ */
73
+ function filterTimeblocksByMaxArrivals(restaurantData, date, timeblocks, reservations, guests) {
74
+ const filteredBlocks = {};
75
+
76
+ for (const [time, timeData] of Object.entries(timeblocks)) {
77
+ // Get meal type for this time
78
+ const mealType = getMealType(time);
79
+ if (!mealType) {
80
+ // Keep the timeblock if we can't determine its meal type
81
+ filteredBlocks[time] = timeData;
82
+ continue;
83
+ }
84
+
85
+ // Get max arrivals config for this meal type
86
+ const maxArrivalsConfig = restaurantData[`max-arrivals-${mealType}`];
87
+ if (!maxArrivalsConfig) {
88
+ // Keep the timeblock if no max arrivals config for this meal type
89
+ filteredBlocks[time] = timeData;
90
+ continue;
91
+ }
92
+
93
+ // Get max arrivals value for this specific time
94
+ const maxArrivals = extractNumber(maxArrivalsConfig[time]);
95
+ if (maxArrivals === null) {
96
+ // Keep the timeblock if no specific max arrivals for this time
97
+ filteredBlocks[time] = timeData;
98
+ continue;
99
+ }
100
+
101
+ // Count current arrivals at this exact time
102
+ const currentArrivals = countArrivalsAtTime(reservations, date, time);
103
+
104
+ // Only include timeblock if adding these guests doesn't exceed max arrivals
105
+ if (currentArrivals + guests <= maxArrivals) {
106
+ filteredBlocks[time] = timeData;
107
+ }
108
+ }
109
+
110
+ return filteredBlocks;
111
+ }
112
+
113
+ module.exports = {
114
+ filterTimeblocksByMaxArrivals
115
115
  };