@happychef/algorithm 1.3.2 → 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.
- package/.claude/settings.local.json +16 -0
- package/.github/workflows/ci-cd.yml +80 -80
- package/BRANCH_PROTECTION_SETUP.md +167 -167
- package/CHANGELOG.md +8 -8
- package/README.md +144 -144
- package/RESERVERINGEN_GIDS.md +986 -986
- package/__tests__/crossMidnight.test.js +63 -0
- package/__tests__/crossMidnightTimeblocks.test.js +312 -0
- package/__tests__/edgeCases.test.js +271 -0
- package/__tests__/filters.test.js +276 -276
- package/__tests__/isDateAvailable.test.js +179 -175
- package/__tests__/isTimeAvailable.test.js +174 -168
- package/__tests__/restaurantData.test.js +422 -422
- package/__tests__/tableHelpers.test.js +247 -247
- package/assignTables.js +506 -444
- package/changes/2025/December/PR2___change.md +14 -14
- package/changes/2025/December/PR3_add__change.md +20 -20
- package/changes/2025/December/PR4___.md +15 -15
- package/changes/2025/December/PR5___.md +15 -15
- package/changes/2025/December/PR6__del_.md +17 -17
- package/changes/2025/December/PR7_add__change.md +21 -21
- package/changes/2026/February/PR15_add__change.md +21 -21
- package/changes/2026/February/PR16_add__.md +20 -0
- package/changes/2026/February/PR16_add_getDateClosingReasons.md +31 -31
- package/changes/2026/January/PR10_add__change.md +21 -21
- package/changes/2026/January/PR11_add__change.md +19 -19
- package/changes/2026/January/PR12_add__.md +21 -21
- package/changes/2026/January/PR13_add__change.md +20 -20
- package/changes/2026/January/PR14_add__change.md +19 -19
- package/changes/2026/January/PR8_add__change.md +38 -38
- package/changes/2026/January/PR9_add__change.md +19 -19
- package/dateHelpers.js +31 -0
- package/filters/maxArrivalsFilter.js +114 -114
- package/filters/maxGroupsFilter.js +221 -221
- package/filters/timeFilter.js +89 -89
- package/getAvailableTimeblocks.js +158 -158
- package/getDateClosingReasons.js +193 -193
- package/grouping.js +162 -162
- package/index.js +48 -43
- package/isDateAvailable.js +80 -80
- package/isDateAvailableWithTableCheck.js +172 -172
- package/isTimeAvailable.js +26 -26
- package/jest.config.js +23 -23
- package/package.json +27 -27
- package/processing/dailyGuestCounts.js +73 -73
- package/processing/mealTypeCount.js +133 -133
- package/processing/timeblocksAvailable.js +344 -182
- package/reservation_data/counter.js +82 -75
- package/restaurant_data/exceptions.js +150 -150
- package/restaurant_data/openinghours.js +142 -142
- package/simulateTableAssignment.js +833 -726
- package/tableHelpers.js +209 -209
- package/tables/time/parseTime.js +19 -19
- package/tables/time/shifts.js +7 -7
- package/tables/utils/calculateDistance.js +13 -13
- package/tables/utils/isTableFreeForAllSlots.js +14 -14
- package/tables/utils/isTemporaryTableValid.js +39 -39
- package/test/test_counter.js +194 -194
- package/test/test_dailyCount.js +81 -81
- package/test/test_datesAvailable.js +106 -106
- package/test/test_exceptions.js +172 -172
- package/test/test_isDateAvailable.js +330 -330
- package/test/test_mealTypeCount.js +54 -54
- package/test/test_timesAvailable.js +88 -88
- package/test-detailed-filter.js +100 -100
- package/test-lunch-debug.js +110 -110
- package/test-max-arrivals-filter.js +79 -79
- package/test-meal-stop-fix.js +147 -147
- package/test-meal-stop-simple.js +93 -93
- package/test-timezone-debug.js +47 -47
- 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
|
};
|