@happychef/algorithm 1.2.11 → 1.2.12

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 (57) hide show
  1. package/.github/workflows/ci-cd.yml +234 -234
  2. package/BRANCH_PROTECTION_SETUP.md +167 -167
  3. package/CHANGELOG.md +8 -8
  4. package/README.md +144 -144
  5. package/RESERVERINGEN_GIDS.md +986 -986
  6. package/__tests__/filters.test.js +276 -276
  7. package/__tests__/isDateAvailable.test.js +175 -175
  8. package/__tests__/isTimeAvailable.test.js +168 -168
  9. package/__tests__/restaurantData.test.js +422 -422
  10. package/__tests__/tableHelpers.test.js +247 -247
  11. package/assignTables.js +424 -424
  12. package/changes/2025/December/PR2___change.md +14 -14
  13. package/changes/2025/December/PR3_add__change.md +20 -20
  14. package/changes/2025/December/PR4___.md +15 -15
  15. package/changes/2025/December/PR5___.md +15 -15
  16. package/changes/2025/December/PR6__del_.md +17 -17
  17. package/changes/2025/December/PR7_add__change.md +21 -21
  18. package/changes/2026/January/PR8_add__change.md +39 -0
  19. package/changes/2026/January/PR9_add__change.md +20 -0
  20. package/filters/maxArrivalsFilter.js +114 -114
  21. package/filters/maxGroupsFilter.js +221 -221
  22. package/filters/timeFilter.js +89 -89
  23. package/getAvailableTimeblocks.js +158 -158
  24. package/grouping.js +162 -162
  25. package/index.js +42 -42
  26. package/isDateAvailable.js +80 -80
  27. package/isDateAvailableWithTableCheck.js +171 -171
  28. package/isTimeAvailable.js +25 -25
  29. package/jest.config.js +23 -23
  30. package/package.json +27 -27
  31. package/processing/dailyGuestCounts.js +73 -73
  32. package/processing/mealTypeCount.js +133 -133
  33. package/processing/timeblocksAvailable.js +167 -167
  34. package/reservation_data/counter.js +64 -64
  35. package/restaurant_data/exceptions.js +149 -149
  36. package/restaurant_data/openinghours.js +123 -123
  37. package/simulateTableAssignment.js +709 -699
  38. package/tableHelpers.js +178 -178
  39. package/tables/time/parseTime.js +19 -19
  40. package/tables/time/shifts.js +7 -7
  41. package/tables/utils/calculateDistance.js +13 -13
  42. package/tables/utils/isTableFreeForAllSlots.js +14 -14
  43. package/tables/utils/isTemporaryTableValid.js +39 -39
  44. package/test/test_counter.js +194 -194
  45. package/test/test_dailyCount.js +81 -81
  46. package/test/test_datesAvailable.js +106 -106
  47. package/test/test_exceptions.js +172 -172
  48. package/test/test_isDateAvailable.js +330 -330
  49. package/test/test_mealTypeCount.js +54 -54
  50. package/test/test_timesAvailable.js +88 -88
  51. package/test-detailed-filter.js +100 -100
  52. package/test-lunch-debug.js +110 -110
  53. package/test-max-arrivals-filter.js +79 -79
  54. package/test-meal-stop-fix.js +147 -147
  55. package/test-meal-stop-simple.js +93 -93
  56. package/test-timezone-debug.js +47 -47
  57. package/test.js +336 -336
@@ -1,222 +1,222 @@
1
- // src/Pages/NewReservation/StepOne/algorithm/filters/maxGroupsFilter.js
2
- // Import the dedicated getMealType function instead of relying on the openinghours version
3
- // This ensures consistency in meal type determination
4
-
5
- /**
6
- * Determines the meal type (breakfast, lunch, dinner) based on the time.
7
- * @param {string} time - Time in HH:MM format
8
- * @returns {string|null} - Meal type or null if outside meal times
9
- */
10
- function getMealType(time) {
11
- const hour = parseInt(time.split(':')[0], 10);
12
-
13
- if (hour >= 4 && hour < 11) return 'breakfast';
14
- if (hour >= 11 && hour < 16) return 'lunch';
15
- if (hour >= 16 && hour < 23) return 'dinner';
16
-
17
- return null;
18
- }
19
-
20
- // --- Add console logs for debugging ---
21
- // Set DEBUG_MAX_GROUPS to true in your development environment to enable detailed logs
22
- const DEBUG_MAX_GROUPS = true; // Set to true to enable detailed logs for this filter
23
-
24
- /**
25
- * Helper function for conditional logging.
26
- * @param {string} message - The log message.
27
- * @param {...any} args - Additional arguments to log.
28
- */
29
- function logDebug(message, ...args) {
30
- if (DEBUG_MAX_GROUPS) {
31
- // Using console.debug might allow filtering in browser dev tools,
32
- // but console.log ensures visibility.
33
- console.log(`[MaxGroupsFilter] ${message}`, ...args);
34
- }
35
- }
36
- // --- End log setup ---
37
-
38
- /**
39
- * Extracts a number value from various data formats (e.g., MongoDB NumberInt).
40
- * Handles null, undefined, object with $numberInt, number, and string representations.
41
- * Returns null if the value is not a valid integer representation.
42
- * @param {*} value - Value from data object.
43
- * @returns {number|null} - Parsed integer or null if invalid.
44
- */
45
- function extractNumber(value) {
46
- if (value === null || value === undefined) {
47
- return null;
48
- }
49
-
50
- // Handle MongoDB NumberInt format: { "$numberInt": "1" }
51
- if (typeof value === 'object' && value !== null && '$numberInt' in value) {
52
- const parsed = parseInt(value.$numberInt, 10);
53
- return isNaN(parsed) ? null : parsed;
54
- }
55
-
56
- // Handle regular number type
57
- if (typeof value === 'number') {
58
- // Ensure it's an integer, not float/Infinity/NaN
59
- return Number.isInteger(value) ? value : null;
60
- }
61
-
62
- // Handle string number: "1"
63
- if (typeof value === 'string') {
64
- const parsed = parseInt(value, 10);
65
- // Ensure the string was fully parsed as a number and is finite
66
- return isNaN(parsed) ? null : parsed;
67
- }
68
-
69
- // Not a recognized number format
70
- return null;
71
- }
72
-
73
- /**
74
- * Filters available timeblocks based on maximum group size limits for each meal period.
75
- * It checks if adding a new reservation of 'guests' count would exceed the configured
76
- * maximum number of groups allowed for various size thresholds within the specific meal period.
77
- *
78
- * @param {Object} restaurantData - The main restaurant data object, expected to contain keys like 'max-groups-breakfast', 'max-groups-lunch', etc.
79
- * @param {string} dateStr - The date string in "YYYY-MM-DD" format.
80
- * @param {Object} timeblocks - An object of available timeblocks { "HH:MM": timeData } that have passed previous filters.
81
- * @param {Array} reservations - An array of existing reservation objects for potentially multiple dates.
82
- * @param {number|string} guests - The number of guests for the new reservation being considered.
83
- * @returns {Object} - A new object containing only the timeblocks that satisfy the max group limits.
84
- */
85
- function filterTimeblocksByMaxGroups(restaurantData, dateStr, timeblocks, reservations, guests) {
86
- logDebug(`--- Running MaxGroupsFilter for Date: ${dateStr}, Guests: ${guests} ---`);
87
- // Log the top-level keys related to max-groups to see what's actually present
88
- const groupKeys = Object.keys(restaurantData).filter(k => k.startsWith('max-groups-'));
89
- logDebug("Received restaurantData max-group keys:", groupKeys.length > 0 ? groupKeys : 'None found');
90
-
91
- const filteredBlocks = {}; // Initialize the object to store timeblocks that pass the filter
92
- const guestsNumber = parseInt(guests, 10); // Ensure guest count is a number
93
-
94
- // Validate guest input
95
- if (isNaN(guestsNumber) || guestsNumber <= 0) {
96
- logDebug(`Invalid guest count (${guests}). Returning all ${Object.keys(timeblocks).length} input blocks unfiltered.`);
97
- return timeblocks; // Return original blocks if guest count is invalid
98
- }
99
-
100
- // Pre-filter reservations to only those on the specific date for efficiency
101
- const reservationsOnDate = reservations.filter(r => r.date === dateStr);
102
- logDebug(`Relevant reservations on ${dateStr}: ${reservationsOnDate.length}`);
103
- logDebug(`Input timeblocks count: ${Object.keys(timeblocks).length}`);
104
-
105
- // Group reservations by meal type using our consistent getMealType function
106
- const reservationsByMealType = {};
107
- reservationsOnDate.forEach(r => {
108
- const mealType = getMealType(r.time);
109
- if (!mealType) return; // Skip if no meal type determined
110
-
111
- if (!reservationsByMealType[mealType]) {
112
- reservationsByMealType[mealType] = [];
113
- }
114
- reservationsByMealType[mealType].push(r);
115
- });
116
-
117
- logDebug('Reservations grouped by meal type:', reservationsByMealType);
118
-
119
- // Iterate through each timeblock that needs checking
120
- for (const [time, timeData] of Object.entries(timeblocks)) {
121
- const mealType = getMealType(time); // Determine meal period (e.g., 'breakfast') using our consistent function
122
- logDebug(`\nChecking Time: ${time} -> Determined Meal: ${mealType}`);
123
-
124
- // If the time doesn't fall into a recognized meal period, the filter doesn't apply
125
- if (!mealType) {
126
- logDebug(` Time ${time} has no associated meal type. Keeping block.`);
127
- filteredBlocks[time] = timeData;
128
- continue;
129
- }
130
-
131
- // Construct the key for the settings specific to this meal (e.g., 'max-groups-breakfast')
132
- const maxGroupSettingsKey = `max-groups-${mealType}`;
133
- // Retrieve the settings object for this meal from the restaurant data
134
- const maxGroupSettings = restaurantData[maxGroupSettingsKey];
135
-
136
- logDebug(` Looking for settings using key: "${maxGroupSettingsKey}"`);
137
- // If no settings object exists for this meal type, the filter doesn't apply to this timeblock
138
- if (!maxGroupSettings || typeof maxGroupSettings !== 'object') {
139
- logDebug(` No settings found for key "${maxGroupSettingsKey}". Filter doesn't apply. Keeping block.`);
140
- filteredBlocks[time] = timeData;
141
- continue; // Move to the next timeblock
142
- }
143
- // Log the actual settings found for this meal to verify correctness
144
- logDebug(` Found settings for ${mealType}:`, JSON.stringify(maxGroupSettings));
145
-
146
- // --- Settings exist, now apply the filter logic ---
147
- let isTimeAllowed = true; // Assume the timeblock is allowed until a limit is violated
148
-
149
- // Extract and sort the numeric group size limits defined in the settings (e.g., "6", "7" -> [6, 7])
150
- const limitSizes = Object.keys(maxGroupSettings)
151
- .map(key => parseInt(key, 10)) // Convert keys to numbers
152
- .filter(num => !isNaN(num) && num > 0) // Keep only positive integers
153
- .sort((a, b) => a - b); // Sort numerically (e.g., check 6 before 7)
154
- logDebug(` Defined numeric limit sizes to check for ${mealType}: [${limitSizes.join(', ')}]`);
155
-
156
- // Check against each defined limit size threshold
157
- for (const limitSize of limitSizes) {
158
- const limitSizeStr = String(limitSize); // Original string key (e.g., "6")
159
- logDebug(` Checking Limit Condition: Groups of size >= ${limitSize}`);
160
-
161
- // Does the new booking we're considering (guestsNumber) meet or exceed this limit threshold?
162
- // Example: If limitSize is 6, and guestsNumber is 7, this limit applies. If guestsNumber is 5, it doesn't.
163
- if (guestsNumber >= limitSize) {
164
- logDebug(` Applicable: Booking guests (${guestsNumber}) >= Limit threshold (${limitSize})`);
165
-
166
- // Extract the maximum number of groups allowed for this threshold
167
- const maxAllowedCount = extractNumber(maxGroupSettings[limitSizeStr]);
168
- logDebug(` Max groups allowed for >=${limitSize} is: ${maxAllowedCount} (raw setting: ${JSON.stringify(maxGroupSettings[limitSizeStr])})`);
169
-
170
- // If the setting for this limit size is invalid (null or negative), treat it as 'no limit'
171
- if (maxAllowedCount === null || maxAllowedCount < 0) {
172
- logDebug(` Skipping check: Invalid or non-positive max count defined for size ${limitSize}.`);
173
- continue; // Skip to the next limitSize
174
- }
175
-
176
- // Get reservations for this meal type - using only reservations from the same meal type
177
- const reservationsForMealType = reservationsByMealType[mealType] || [];
178
-
179
- // Count how many existing reservations for this meal type meet or exceed this limit size
180
- const existingMatchingGroupsCount = reservationsForMealType.filter(reservation => {
181
- const reservationGuests = parseInt(reservation.guests, 10);
182
- return !isNaN(reservationGuests) && reservationGuests >= limitSize;
183
- }).length;
184
-
185
- logDebug(` Count of existing groups >=${limitSize} found in ${mealType}: ${existingMatchingGroupsCount}`);
186
-
187
- // THE CORE LOGIC: Check if adding the new group would exceed the max allowed count
188
- if (existingMatchingGroupsCount + 1 > maxAllowedCount) {
189
- logDebug(` *** VIOLATION DETECTED *** for limit >=${limitSize}.`);
190
- logDebug(` (Existing Groups: ${existingMatchingGroupsCount} + New Group: 1 = ${existingMatchingGroupsCount + 1}, which is > Max Allowed: ${maxAllowedCount})`);
191
- logDebug(` Blocking time ${time} due to this violation.`);
192
- isTimeAllowed = false; // Mark the timeblock as blocked
193
- break; // No need to check further limits for this timeblock; it has already failed.
194
- } else {
195
- // This specific limit check passed
196
- logDebug(` OK: Limit for >=${limitSize} not exceeded. (Existing: ${existingMatchingGroupsCount} + New: 1 <= Max: ${maxAllowedCount})`);
197
- }
198
- } else {
199
- // The new booking's guest count is smaller than the current limit threshold, so this rule doesn't restrict it.
200
- logDebug(` Not Applicable: Booking guests (${guestsNumber}) < Limit threshold (${limitSize}). Skipping this check.`);
201
- }
202
- } // End of loop through different limitSizes (e.g., checking 6, then 7)
203
-
204
- // After checking all applicable limits for the timeblock:
205
- if (isTimeAllowed) {
206
- logDebug(` ✅ Result for time ${time}: ALLOWED (passed all applicable checks)`);
207
- filteredBlocks[time] = timeData; // Add it to the results
208
- } else {
209
- logDebug(` ❌ Result for time ${time}: BLOCKED (failed at least one limit check)`);
210
- // Do not add it to filteredBlocks
211
- }
212
- } // End of loop through all input timeblocks
213
-
214
- logDebug(`--- MaxGroupsFilter Finished. Output timeblocks count: ${Object.keys(filteredBlocks).length} ---`);
215
- return filteredBlocks; // Return the timeblocks that passed all checks
216
- }
217
-
218
- // Export the function for use in other parts of the application
219
- module.exports = {
220
- filterTimeblocksByMaxGroups,
221
- getMealType, // Export getMealType for potential use by other modules
1
+ // src/Pages/NewReservation/StepOne/algorithm/filters/maxGroupsFilter.js
2
+ // Import the dedicated getMealType function instead of relying on the openinghours version
3
+ // This ensures consistency in meal type determination
4
+
5
+ /**
6
+ * Determines the meal type (breakfast, lunch, dinner) based on the time.
7
+ * @param {string} time - Time in HH:MM format
8
+ * @returns {string|null} - Meal type or null if outside meal times
9
+ */
10
+ function getMealType(time) {
11
+ const hour = parseInt(time.split(':')[0], 10);
12
+
13
+ if (hour >= 4 && hour < 11) return 'breakfast';
14
+ if (hour >= 11 && hour < 16) return 'lunch';
15
+ if (hour >= 16 && hour < 23) return 'dinner';
16
+
17
+ return null;
18
+ }
19
+
20
+ // --- Add console logs for debugging ---
21
+ // Set DEBUG_MAX_GROUPS to true in your development environment to enable detailed logs
22
+ const DEBUG_MAX_GROUPS = true; // Set to true to enable detailed logs for this filter
23
+
24
+ /**
25
+ * Helper function for conditional logging.
26
+ * @param {string} message - The log message.
27
+ * @param {...any} args - Additional arguments to log.
28
+ */
29
+ function logDebug(message, ...args) {
30
+ if (DEBUG_MAX_GROUPS) {
31
+ // Using console.debug might allow filtering in browser dev tools,
32
+ // but console.log ensures visibility.
33
+ console.log(`[MaxGroupsFilter] ${message}`, ...args);
34
+ }
35
+ }
36
+ // --- End log setup ---
37
+
38
+ /**
39
+ * Extracts a number value from various data formats (e.g., MongoDB NumberInt).
40
+ * Handles null, undefined, object with $numberInt, number, and string representations.
41
+ * Returns null if the value is not a valid integer representation.
42
+ * @param {*} value - Value from data object.
43
+ * @returns {number|null} - Parsed integer or null if invalid.
44
+ */
45
+ function extractNumber(value) {
46
+ if (value === null || value === undefined) {
47
+ return null;
48
+ }
49
+
50
+ // Handle MongoDB NumberInt format: { "$numberInt": "1" }
51
+ if (typeof value === 'object' && value !== null && '$numberInt' in value) {
52
+ const parsed = parseInt(value.$numberInt, 10);
53
+ return isNaN(parsed) ? null : parsed;
54
+ }
55
+
56
+ // Handle regular number type
57
+ if (typeof value === 'number') {
58
+ // Ensure it's an integer, not float/Infinity/NaN
59
+ return Number.isInteger(value) ? value : null;
60
+ }
61
+
62
+ // Handle string number: "1"
63
+ if (typeof value === 'string') {
64
+ const parsed = parseInt(value, 10);
65
+ // Ensure the string was fully parsed as a number and is finite
66
+ return isNaN(parsed) ? null : parsed;
67
+ }
68
+
69
+ // Not a recognized number format
70
+ return null;
71
+ }
72
+
73
+ /**
74
+ * Filters available timeblocks based on maximum group size limits for each meal period.
75
+ * It checks if adding a new reservation of 'guests' count would exceed the configured
76
+ * maximum number of groups allowed for various size thresholds within the specific meal period.
77
+ *
78
+ * @param {Object} restaurantData - The main restaurant data object, expected to contain keys like 'max-groups-breakfast', 'max-groups-lunch', etc.
79
+ * @param {string} dateStr - The date string in "YYYY-MM-DD" format.
80
+ * @param {Object} timeblocks - An object of available timeblocks { "HH:MM": timeData } that have passed previous filters.
81
+ * @param {Array} reservations - An array of existing reservation objects for potentially multiple dates.
82
+ * @param {number|string} guests - The number of guests for the new reservation being considered.
83
+ * @returns {Object} - A new object containing only the timeblocks that satisfy the max group limits.
84
+ */
85
+ function filterTimeblocksByMaxGroups(restaurantData, dateStr, timeblocks, reservations, guests) {
86
+ logDebug(`--- Running MaxGroupsFilter for Date: ${dateStr}, Guests: ${guests} ---`);
87
+ // Log the top-level keys related to max-groups to see what's actually present
88
+ const groupKeys = Object.keys(restaurantData).filter(k => k.startsWith('max-groups-'));
89
+ logDebug("Received restaurantData max-group keys:", groupKeys.length > 0 ? groupKeys : 'None found');
90
+
91
+ const filteredBlocks = {}; // Initialize the object to store timeblocks that pass the filter
92
+ const guestsNumber = parseInt(guests, 10); // Ensure guest count is a number
93
+
94
+ // Validate guest input
95
+ if (isNaN(guestsNumber) || guestsNumber <= 0) {
96
+ logDebug(`Invalid guest count (${guests}). Returning all ${Object.keys(timeblocks).length} input blocks unfiltered.`);
97
+ return timeblocks; // Return original blocks if guest count is invalid
98
+ }
99
+
100
+ // Pre-filter reservations to only those on the specific date for efficiency
101
+ const reservationsOnDate = reservations.filter(r => r.date === dateStr);
102
+ logDebug(`Relevant reservations on ${dateStr}: ${reservationsOnDate.length}`);
103
+ logDebug(`Input timeblocks count: ${Object.keys(timeblocks).length}`);
104
+
105
+ // Group reservations by meal type using our consistent getMealType function
106
+ const reservationsByMealType = {};
107
+ reservationsOnDate.forEach(r => {
108
+ const mealType = getMealType(r.time);
109
+ if (!mealType) return; // Skip if no meal type determined
110
+
111
+ if (!reservationsByMealType[mealType]) {
112
+ reservationsByMealType[mealType] = [];
113
+ }
114
+ reservationsByMealType[mealType].push(r);
115
+ });
116
+
117
+ logDebug('Reservations grouped by meal type:', reservationsByMealType);
118
+
119
+ // Iterate through each timeblock that needs checking
120
+ for (const [time, timeData] of Object.entries(timeblocks)) {
121
+ const mealType = getMealType(time); // Determine meal period (e.g., 'breakfast') using our consistent function
122
+ logDebug(`\nChecking Time: ${time} -> Determined Meal: ${mealType}`);
123
+
124
+ // If the time doesn't fall into a recognized meal period, the filter doesn't apply
125
+ if (!mealType) {
126
+ logDebug(` Time ${time} has no associated meal type. Keeping block.`);
127
+ filteredBlocks[time] = timeData;
128
+ continue;
129
+ }
130
+
131
+ // Construct the key for the settings specific to this meal (e.g., 'max-groups-breakfast')
132
+ const maxGroupSettingsKey = `max-groups-${mealType}`;
133
+ // Retrieve the settings object for this meal from the restaurant data
134
+ const maxGroupSettings = restaurantData[maxGroupSettingsKey];
135
+
136
+ logDebug(` Looking for settings using key: "${maxGroupSettingsKey}"`);
137
+ // If no settings object exists for this meal type, the filter doesn't apply to this timeblock
138
+ if (!maxGroupSettings || typeof maxGroupSettings !== 'object') {
139
+ logDebug(` No settings found for key "${maxGroupSettingsKey}". Filter doesn't apply. Keeping block.`);
140
+ filteredBlocks[time] = timeData;
141
+ continue; // Move to the next timeblock
142
+ }
143
+ // Log the actual settings found for this meal to verify correctness
144
+ logDebug(` Found settings for ${mealType}:`, JSON.stringify(maxGroupSettings));
145
+
146
+ // --- Settings exist, now apply the filter logic ---
147
+ let isTimeAllowed = true; // Assume the timeblock is allowed until a limit is violated
148
+
149
+ // Extract and sort the numeric group size limits defined in the settings (e.g., "6", "7" -> [6, 7])
150
+ const limitSizes = Object.keys(maxGroupSettings)
151
+ .map(key => parseInt(key, 10)) // Convert keys to numbers
152
+ .filter(num => !isNaN(num) && num > 0) // Keep only positive integers
153
+ .sort((a, b) => a - b); // Sort numerically (e.g., check 6 before 7)
154
+ logDebug(` Defined numeric limit sizes to check for ${mealType}: [${limitSizes.join(', ')}]`);
155
+
156
+ // Check against each defined limit size threshold
157
+ for (const limitSize of limitSizes) {
158
+ const limitSizeStr = String(limitSize); // Original string key (e.g., "6")
159
+ logDebug(` Checking Limit Condition: Groups of size >= ${limitSize}`);
160
+
161
+ // Does the new booking we're considering (guestsNumber) meet or exceed this limit threshold?
162
+ // Example: If limitSize is 6, and guestsNumber is 7, this limit applies. If guestsNumber is 5, it doesn't.
163
+ if (guestsNumber >= limitSize) {
164
+ logDebug(` Applicable: Booking guests (${guestsNumber}) >= Limit threshold (${limitSize})`);
165
+
166
+ // Extract the maximum number of groups allowed for this threshold
167
+ const maxAllowedCount = extractNumber(maxGroupSettings[limitSizeStr]);
168
+ logDebug(` Max groups allowed for >=${limitSize} is: ${maxAllowedCount} (raw setting: ${JSON.stringify(maxGroupSettings[limitSizeStr])})`);
169
+
170
+ // If the setting for this limit size is invalid (null or negative), treat it as 'no limit'
171
+ if (maxAllowedCount === null || maxAllowedCount < 0) {
172
+ logDebug(` Skipping check: Invalid or non-positive max count defined for size ${limitSize}.`);
173
+ continue; // Skip to the next limitSize
174
+ }
175
+
176
+ // Get reservations for this meal type - using only reservations from the same meal type
177
+ const reservationsForMealType = reservationsByMealType[mealType] || [];
178
+
179
+ // Count how many existing reservations for this meal type meet or exceed this limit size
180
+ const existingMatchingGroupsCount = reservationsForMealType.filter(reservation => {
181
+ const reservationGuests = parseInt(reservation.guests, 10);
182
+ return !isNaN(reservationGuests) && reservationGuests >= limitSize;
183
+ }).length;
184
+
185
+ logDebug(` Count of existing groups >=${limitSize} found in ${mealType}: ${existingMatchingGroupsCount}`);
186
+
187
+ // THE CORE LOGIC: Check if adding the new group would exceed the max allowed count
188
+ if (existingMatchingGroupsCount + 1 > maxAllowedCount) {
189
+ logDebug(` *** VIOLATION DETECTED *** for limit >=${limitSize}.`);
190
+ logDebug(` (Existing Groups: ${existingMatchingGroupsCount} + New Group: 1 = ${existingMatchingGroupsCount + 1}, which is > Max Allowed: ${maxAllowedCount})`);
191
+ logDebug(` Blocking time ${time} due to this violation.`);
192
+ isTimeAllowed = false; // Mark the timeblock as blocked
193
+ break; // No need to check further limits for this timeblock; it has already failed.
194
+ } else {
195
+ // This specific limit check passed
196
+ logDebug(` OK: Limit for >=${limitSize} not exceeded. (Existing: ${existingMatchingGroupsCount} + New: 1 <= Max: ${maxAllowedCount})`);
197
+ }
198
+ } else {
199
+ // The new booking's guest count is smaller than the current limit threshold, so this rule doesn't restrict it.
200
+ logDebug(` Not Applicable: Booking guests (${guestsNumber}) < Limit threshold (${limitSize}). Skipping this check.`);
201
+ }
202
+ } // End of loop through different limitSizes (e.g., checking 6, then 7)
203
+
204
+ // After checking all applicable limits for the timeblock:
205
+ if (isTimeAllowed) {
206
+ logDebug(` ✅ Result for time ${time}: ALLOWED (passed all applicable checks)`);
207
+ filteredBlocks[time] = timeData; // Add it to the results
208
+ } else {
209
+ logDebug(` ❌ Result for time ${time}: BLOCKED (failed at least one limit check)`);
210
+ // Do not add it to filteredBlocks
211
+ }
212
+ } // End of loop through all input timeblocks
213
+
214
+ logDebug(`--- MaxGroupsFilter Finished. Output timeblocks count: ${Object.keys(filteredBlocks).length} ---`);
215
+ return filteredBlocks; // Return the timeblocks that passed all checks
216
+ }
217
+
218
+ // Export the function for use in other parts of the application
219
+ module.exports = {
220
+ filterTimeblocksByMaxGroups,
221
+ getMealType, // Export getMealType for potential use by other modules
222
222
  };
@@ -1,90 +1,90 @@
1
- // Archief/Fields/algorithm/stopTimeFilter.js
2
-
3
- // Assuming moment-timezone is available in the project, as used elsewhere
4
- const moment = require('moment-timezone');
5
-
6
- // Import necessary helpers (adjust path if tableHelpers.js is elsewhere)
7
- // Or redefine shifts, parseTime, getMealTypeByTime here if preferred
8
- const { shifts, parseTime, getMealTypeByTime } = require('../tableHelpers');
9
-
10
- /**
11
- * Gets the current time in minutes since midnight in the Brussels timezone.
12
- * @returns {number} Current time in minutes.
13
- */
14
- function getCurrentTimeInBrusselsMinutes() {
15
- const now = moment().tz('Europe/Brussels');
16
- return now.hours() * 60 + now.minutes();
17
- }
18
-
19
- /**
20
- * Filters a list of timeblocks based on stop times defined in general settings.
21
- * This filter only applies if the provided dateStr is the current date.
22
- *
23
- * @param {Object} timeblocks - An object where keys are time strings ("HH:MM") and values are timeblock data.
24
- * @param {Object} restaurantData - The main restaurant data object containing general-settings.
25
- * @param {string} dateStr - The date string ("YYYY-MM-DD") to check against today.
26
- * @returns {Object} - The filtered timeblocks object.
27
- */
28
- function filterTimeblocksByStopTimes(timeblocks, restaurantData, dateStr) {
29
- // Determine today's date string in the correct timezone
30
- const todayStr = moment().tz('Europe/Brussels').format('YYYY-MM-DD');
31
-
32
- // Only apply the filter if the date being checked is today
33
- if (dateStr !== todayStr) {
34
- return timeblocks; // No filtering needed for future dates
35
- }
36
-
37
- console.log(`Applying stop time filter for today (${dateStr})`);
38
-
39
- const settings = restaurantData?.['general-settings'] || {};
40
- const currentTimeMinutes = getCurrentTimeInBrusselsMinutes();
41
-
42
- // Get stop times from settings and parse them (handle missing/null values)
43
- const breakfastStopTimeStr = settings.ontbijtStop || null; // e.g., "10:00"
44
- const lunchStopTimeStr = settings.lunchStop || null; // e.g., "14:00"
45
- const dinnerStopTimeStr = settings.dinerStop || null; // e.g., "20:00"
46
-
47
- const breakfastStopTime = breakfastStopTimeStr ? parseTime(breakfastStopTimeStr) : null;
48
- const lunchStopTime = lunchStopTimeStr ? parseTime(lunchStopTimeStr) : null;
49
- const dinnerStopTime = dinnerStopTimeStr ? parseTime(dinnerStopTimeStr) : null;
50
-
51
- // console.log(`Current time (minutes): ${currentTimeMinutes}`); // Verbose
52
- // console.log(`Stop times (minutes) - Breakfast: ${breakfastStopTime}, Lunch: ${lunchStopTime}, Dinner: ${dinnerStopTime}`); // Verbose
53
-
54
- const filteredTimeblocks = {};
55
- let skippedBreakfast = 0;
56
- let skippedLunch = 0;
57
- let skippedDinner = 0;
58
-
59
- for (const time in timeblocks) {
60
- if (!timeblocks.hasOwnProperty(time)) continue;
61
-
62
- const mealType = getMealTypeByTime(time);
63
- let skip = false;
64
-
65
- if (mealType === 'breakfast' && breakfastStopTime !== null && !isNaN(breakfastStopTime) && currentTimeMinutes >= breakfastStopTime) {
66
- skip = true;
67
- skippedBreakfast++;
68
- } else if (mealType === 'lunch' && lunchStopTime !== null && !isNaN(lunchStopTime) && currentTimeMinutes >= lunchStopTime) {
69
- skip = true;
70
- skippedLunch++;
71
- } else if (mealType === 'dinner' && dinnerStopTime !== null && !isNaN(dinnerStopTime) && currentTimeMinutes >= dinnerStopTime) {
72
- skip = true;
73
- skippedDinner++;
74
- }
75
-
76
- if (!skip) {
77
- filteredTimeblocks[time] = timeblocks[time];
78
- }
79
- }
80
-
81
- if (skippedBreakfast > 0) console.log(`Stop time filter removed ${skippedBreakfast} breakfast slots.`);
82
- if (skippedLunch > 0) console.log(`Stop time filter removed ${skippedLunch} lunch slots.`);
83
- if (skippedDinner > 0) console.log(`Stop time filter removed ${skippedDinner} dinner slots.`);
84
-
85
- return filteredTimeblocks;
86
- }
87
-
88
- module.exports = {
89
- filterTimeblocksByStopTimes
1
+ // Archief/Fields/algorithm/stopTimeFilter.js
2
+
3
+ // Assuming moment-timezone is available in the project, as used elsewhere
4
+ const moment = require('moment-timezone');
5
+
6
+ // Import necessary helpers (adjust path if tableHelpers.js is elsewhere)
7
+ // Or redefine shifts, parseTime, getMealTypeByTime here if preferred
8
+ const { shifts, parseTime, getMealTypeByTime } = require('../tableHelpers');
9
+
10
+ /**
11
+ * Gets the current time in minutes since midnight in the Brussels timezone.
12
+ * @returns {number} Current time in minutes.
13
+ */
14
+ function getCurrentTimeInBrusselsMinutes() {
15
+ const now = moment().tz('Europe/Brussels');
16
+ return now.hours() * 60 + now.minutes();
17
+ }
18
+
19
+ /**
20
+ * Filters a list of timeblocks based on stop times defined in general settings.
21
+ * This filter only applies if the provided dateStr is the current date.
22
+ *
23
+ * @param {Object} timeblocks - An object where keys are time strings ("HH:MM") and values are timeblock data.
24
+ * @param {Object} restaurantData - The main restaurant data object containing general-settings.
25
+ * @param {string} dateStr - The date string ("YYYY-MM-DD") to check against today.
26
+ * @returns {Object} - The filtered timeblocks object.
27
+ */
28
+ function filterTimeblocksByStopTimes(timeblocks, restaurantData, dateStr) {
29
+ // Determine today's date string in the correct timezone
30
+ const todayStr = moment().tz('Europe/Brussels').format('YYYY-MM-DD');
31
+
32
+ // Only apply the filter if the date being checked is today
33
+ if (dateStr !== todayStr) {
34
+ return timeblocks; // No filtering needed for future dates
35
+ }
36
+
37
+ console.log(`Applying stop time filter for today (${dateStr})`);
38
+
39
+ const settings = restaurantData?.['general-settings'] || {};
40
+ const currentTimeMinutes = getCurrentTimeInBrusselsMinutes();
41
+
42
+ // Get stop times from settings and parse them (handle missing/null values)
43
+ const breakfastStopTimeStr = settings.ontbijtStop || null; // e.g., "10:00"
44
+ const lunchStopTimeStr = settings.lunchStop || null; // e.g., "14:00"
45
+ const dinnerStopTimeStr = settings.dinerStop || null; // e.g., "20:00"
46
+
47
+ const breakfastStopTime = breakfastStopTimeStr ? parseTime(breakfastStopTimeStr) : null;
48
+ const lunchStopTime = lunchStopTimeStr ? parseTime(lunchStopTimeStr) : null;
49
+ const dinnerStopTime = dinnerStopTimeStr ? parseTime(dinnerStopTimeStr) : null;
50
+
51
+ // console.log(`Current time (minutes): ${currentTimeMinutes}`); // Verbose
52
+ // console.log(`Stop times (minutes) - Breakfast: ${breakfastStopTime}, Lunch: ${lunchStopTime}, Dinner: ${dinnerStopTime}`); // Verbose
53
+
54
+ const filteredTimeblocks = {};
55
+ let skippedBreakfast = 0;
56
+ let skippedLunch = 0;
57
+ let skippedDinner = 0;
58
+
59
+ for (const time in timeblocks) {
60
+ if (!timeblocks.hasOwnProperty(time)) continue;
61
+
62
+ const mealType = getMealTypeByTime(time);
63
+ let skip = false;
64
+
65
+ if (mealType === 'breakfast' && breakfastStopTime !== null && !isNaN(breakfastStopTime) && currentTimeMinutes >= breakfastStopTime) {
66
+ skip = true;
67
+ skippedBreakfast++;
68
+ } else if (mealType === 'lunch' && lunchStopTime !== null && !isNaN(lunchStopTime) && currentTimeMinutes >= lunchStopTime) {
69
+ skip = true;
70
+ skippedLunch++;
71
+ } else if (mealType === 'dinner' && dinnerStopTime !== null && !isNaN(dinnerStopTime) && currentTimeMinutes >= dinnerStopTime) {
72
+ skip = true;
73
+ skippedDinner++;
74
+ }
75
+
76
+ if (!skip) {
77
+ filteredTimeblocks[time] = timeblocks[time];
78
+ }
79
+ }
80
+
81
+ if (skippedBreakfast > 0) console.log(`Stop time filter removed ${skippedBreakfast} breakfast slots.`);
82
+ if (skippedLunch > 0) console.log(`Stop time filter removed ${skippedLunch} lunch slots.`);
83
+ if (skippedDinner > 0) console.log(`Stop time filter removed ${skippedDinner} dinner slots.`);
84
+
85
+ return filteredTimeblocks;
86
+ }
87
+
88
+ module.exports = {
89
+ filterTimeblocksByStopTimes
90
90
  };