@happychef/algorithm 1.0.4 → 1.1.1
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/RESERVERINGEN_GIDS.md +986 -0
- package/getAvailableTimeblocks.js +7 -6
- package/index.js +7 -27
- package/isDateAvailable.js +5 -4
- package/isDateAvailableWithTableCheck.js +4 -3
- package/isTimeAvailable.js +3 -2
- package/package.json +1 -1
- package/processing/timeblocksAvailable.js +3 -3
- package/simulateTableAssignment.js +75 -49
- package/tables/assignment/assignTablesForGivenTime.js +0 -79
- package/tables/assignment/filterTimeblocksByTableAvailability.js +0 -48
- package/tables/availability/findMultiTableCombination.js +0 -100
- package/tables/availability/getAvailableTablesForTime.js +0 -106
- package/tables/availability/isTimeAvailableSync.js +0 -128
- package/tables/data/extractTableNumbers.js +0 -17
- package/tables/data/getActualTableAssignment.js +0 -16
- package/tables/data/getAllTables.js +0 -84
- package/tables/main.js +0 -33
- package/tables/time/computeRequiredSlots.js +0 -27
- package/tables/time/getMealTypeByTime.js +0 -26
- package/tables/utils/safeParseInt.js +0 -29
|
@@ -30,9 +30,10 @@ function parseDateTimeInTimeZone(dateStr, timeStr, timeZone) {
|
|
|
30
30
|
* @param {number} guests - The number of guests for the reservation.
|
|
31
31
|
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
32
32
|
* @param {string|null} giftcard - Optional giftcard to filter times by meal.
|
|
33
|
+
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
33
34
|
* @returns {Object} - Returns a pruned object of available time blocks or shifts, or an empty object if out of range.
|
|
34
35
|
*/
|
|
35
|
-
function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null) {
|
|
36
|
+
function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
36
37
|
// Get 'uurOpVoorhand' from general settings
|
|
37
38
|
let uurOpVoorhand = 4;
|
|
38
39
|
if (
|
|
@@ -74,8 +75,8 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
|
|
|
74
75
|
targetDate.toLocaleString('en-US', { timeZone: timeZone })
|
|
75
76
|
);
|
|
76
77
|
|
|
77
|
-
// Check if targetDateInTimeZone is within dagenInToekomst
|
|
78
|
-
if (targetDateInTimeZone > maxAllowedDate) {
|
|
78
|
+
// Check if targetDateInTimeZone is within dagenInToekomst (skip for admin)
|
|
79
|
+
if (!isAdmin && targetDateInTimeZone > maxAllowedDate) {
|
|
79
80
|
// Out of allowed range, return empty object
|
|
80
81
|
return {};
|
|
81
82
|
}
|
|
@@ -85,10 +86,10 @@ function getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlot
|
|
|
85
86
|
currentTimeInTimeZone.toDateString() === targetDateInTimeZone.toDateString();
|
|
86
87
|
|
|
87
88
|
// Get available time blocks or shifts
|
|
88
|
-
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard);
|
|
89
|
+
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
89
90
|
|
|
90
|
-
// If the date is today and uurOpVoorhand is greater than zero, prune time blocks
|
|
91
|
-
if (isToday && uurOpVoorhand >= 0) {
|
|
91
|
+
// If the date is today and uurOpVoorhand is greater than zero, prune time blocks (skip for admin)
|
|
92
|
+
if (!isAdmin && isToday && uurOpVoorhand >= 0) {
|
|
92
93
|
const cutoffTime = new Date(currentTimeInTimeZone.getTime());
|
|
93
94
|
cutoffTime.setHours(cutoffTime.getHours() + uurOpVoorhand);
|
|
94
95
|
|
package/index.js
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
const processing = {
|
|
4
4
|
...require("./processing/dailyGuestCounts"),
|
|
5
5
|
...require("./processing/mealTypeCount"),
|
|
6
|
-
...require("./processing/timeblocksAvailable")
|
|
6
|
+
...require("./processing/timeblocksAvailable")
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
const reservation_data = {
|
|
10
|
-
...require("./reservation_data/counter")
|
|
10
|
+
...require("./reservation_data/counter")
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const restaurant_data = {
|
|
14
14
|
...require("./restaurant_data/exceptions"),
|
|
15
|
-
...require("./restaurant_data/openinghours")
|
|
15
|
+
...require("./restaurant_data/openinghours")
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const test = {
|
|
@@ -24,39 +24,19 @@ const test = {
|
|
|
24
24
|
...require("./simulateTableAssignment"),
|
|
25
25
|
...require("./isDateAvailableWithTableCheck"),
|
|
26
26
|
...require("./tableHelpers"),
|
|
27
|
-
...require("./test")
|
|
27
|
+
...require("./test")
|
|
28
28
|
};
|
|
29
29
|
|
|
30
30
|
const filters = {
|
|
31
31
|
...require("./filters/timeFilter"),
|
|
32
32
|
...require("./filters/maxArrivalsFilter"),
|
|
33
|
-
...require("./filters/maxGroupsFilter")
|
|
33
|
+
...require("./filters/maxGroupsFilter")
|
|
34
34
|
};
|
|
35
|
-
|
|
36
|
-
// ✅ Table-related logic and utilities
|
|
37
|
-
const tables = {
|
|
38
|
-
...require("./tables/main.js"),
|
|
39
|
-
...require("./tables/availability/isTimeAvailableSync"),
|
|
40
|
-
...require("./tables/availability/getAvailableTablesForTime"),
|
|
41
|
-
...require("./tables/availability/findMultiTableCombination"),
|
|
42
|
-
...require("./tables/assignment/filterTimeblocksByTableAvailability"),
|
|
43
|
-
...require("./tables/assignment/assignTablesForGivenTime"),
|
|
44
|
-
...require("./tables/utils/safeParseInt"),
|
|
45
|
-
...require("./tables/utils/isTemporaryTableValid"),
|
|
46
|
-
...require("./tables/utils/isTableFreeForAllSlots"),
|
|
47
|
-
...require("./tables/utils/calculateDistance"),
|
|
48
|
-
...require("./tables/time/shifts").shifts,
|
|
49
|
-
...require("./tables/time/parseTime"),
|
|
50
|
-
...require("./tables/time/getMealTypeByTime"),
|
|
51
|
-
...require("./tables/time/computeRequiredSlots"),
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
// ✅ Export everything
|
|
35
|
+
// Merge all exports into one
|
|
55
36
|
module.exports = {
|
|
56
37
|
...processing,
|
|
57
38
|
...reservation_data,
|
|
58
39
|
...restaurant_data,
|
|
59
40
|
...test,
|
|
60
|
-
...filters
|
|
61
|
-
...tables,
|
|
41
|
+
...filters
|
|
62
42
|
};
|
package/isDateAvailable.js
CHANGED
|
@@ -59,16 +59,17 @@ function isDateWithinAllowedRange(data, dateStr) {
|
|
|
59
59
|
* @param {number} guests - The number of guests for the reservation.
|
|
60
60
|
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
61
61
|
* @param {string|null} giftcard - Optional giftcard to filter times by meal.
|
|
62
|
+
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
62
63
|
* @returns {boolean} - Returns true if the date has at least one available timeblock, false otherwise.
|
|
63
64
|
*/
|
|
64
|
-
function isDateAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null) {
|
|
65
|
-
// Check if date is within allowed range
|
|
66
|
-
if (!isDateWithinAllowedRange(data, dateStr)) {
|
|
65
|
+
function isDateAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
66
|
+
// Check if date is within allowed range (skip for admin)
|
|
67
|
+
if (!isAdmin && !isDateWithinAllowedRange(data, dateStr)) {
|
|
67
68
|
return false;
|
|
68
69
|
}
|
|
69
70
|
|
|
70
71
|
// Get available timeblocks using the existing logic
|
|
71
|
-
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, giftcard);
|
|
72
|
+
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
72
73
|
|
|
73
74
|
// Return true only if we have at least one available timeblock
|
|
74
75
|
return Object.keys(availableTimeblocks).length > 0;
|
|
@@ -71,9 +71,10 @@ function timeHasGiftcard(data, dateStr, timeStr, giftcard) {
|
|
|
71
71
|
* @param {number} guests - The number of guests for the reservation.
|
|
72
72
|
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
73
73
|
* @param {string|null} selectedGiftcard - The selected giftcard (if any).
|
|
74
|
+
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
74
75
|
* @returns {boolean} - Returns true if the date passes all checks to be available.
|
|
75
76
|
*/
|
|
76
|
-
function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null) {
|
|
77
|
+
function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, blockedSlots = [], selectedGiftcard = null, isAdmin = false) {
|
|
77
78
|
// 0) Optionally filter by selected menu item's date range (only when normalOpeningTimes = false)
|
|
78
79
|
const currentItem = typeof window !== 'undefined' ? window.currentReservationTicketItem : null;
|
|
79
80
|
if (
|
|
@@ -113,7 +114,7 @@ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, bloc
|
|
|
113
114
|
console.log(`Table assignment is ${isTableAssignmentEnabled ? 'ENABLED' : 'DISABLED'} for date ${dateStr}`);
|
|
114
115
|
|
|
115
116
|
// 1) First, do the standard day-level checks (including simple giftcard check).
|
|
116
|
-
const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard);
|
|
117
|
+
const basicDateAvailable = isDateAvailable(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
|
|
117
118
|
if (!basicDateAvailable) {
|
|
118
119
|
console.log(`Date ${dateStr} fails basic availability check`);
|
|
119
120
|
return false;
|
|
@@ -127,7 +128,7 @@ function isDateAvailableWithTableCheck(data, dateStr, reservations, guests, bloc
|
|
|
127
128
|
|
|
128
129
|
// 2) Get all available timeblocks for this date
|
|
129
130
|
console.log(`Getting available timeblocks for ${dateStr}`);
|
|
130
|
-
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard);
|
|
131
|
+
const availableTimeblocks = getAvailableTimeblocks(data, dateStr, reservations, guests, blockedSlots, selectedGiftcard, isAdmin);
|
|
131
132
|
console.log(`Found ${Object.keys(availableTimeblocks).length} available timeblocks before table check`);
|
|
132
133
|
|
|
133
134
|
// If no timeblocks are available at all, exit early
|
package/isTimeAvailable.js
CHANGED
|
@@ -9,11 +9,12 @@ const { timeblocksAvailable } = require('./processing/timeblocksAvailable');
|
|
|
9
9
|
* @param {number} guests - The number of guests for the new reservation request.
|
|
10
10
|
* @param {Array} blockedSlots - Optional array of blocked time slots to exclude.
|
|
11
11
|
* @param {string|null} giftcard - Optional giftcard to filter times by meal.
|
|
12
|
+
* @param {boolean} isAdmin - Optional flag to bypass time restrictions for admin users.
|
|
12
13
|
* @returns {boolean} - Returns true if the time is available, false otherwise.
|
|
13
14
|
*/
|
|
14
|
-
function isTimeAvailable(data, dateStr, timeStr, reservations, guests, blockedSlots = [], giftcard = null) {
|
|
15
|
+
function isTimeAvailable(data, dateStr, timeStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
15
16
|
// Get all available timeblocks for the specified date and guest count
|
|
16
|
-
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard);
|
|
17
|
+
const availableTimeblocks = timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots, giftcard, isAdmin);
|
|
17
18
|
|
|
18
19
|
// Check if the specific timeStr is one of the available keys
|
|
19
20
|
return Object.prototype.hasOwnProperty.call(availableTimeblocks, timeStr);
|
package/package.json
CHANGED
|
@@ -89,7 +89,7 @@ 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) {
|
|
92
|
+
function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots = [], giftcard = null, isAdmin = false) {
|
|
93
93
|
const duurReservatie = getDuurReservatie(data);
|
|
94
94
|
const intervalReservatie = getInterval(data);
|
|
95
95
|
|
|
@@ -149,8 +149,8 @@ function timeblocksAvailable(data, dateStr, reservations, guests, blockedSlots =
|
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
// Filter out blocked time slots
|
|
153
|
-
if (blockedSlots && blockedSlots.length > 0) {
|
|
152
|
+
// Filter out blocked time slots (skip for admin)
|
|
153
|
+
if (!isAdmin && blockedSlots && blockedSlots.length > 0) {
|
|
154
154
|
for (const blockedSlot of blockedSlots) {
|
|
155
155
|
// Check if the blocked slot matches the current date
|
|
156
156
|
if (blockedSlot.date === dateStr && blockedSlot.time) {
|
|
@@ -135,7 +135,11 @@ function calculateDistance(tableA, tableB) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/**
|
|
138
|
-
* Backtracking function to find a combination of tables (multi-table assignment).
|
|
138
|
+
* OPTIMIZED: Backtracking function to find a combination of tables (multi-table assignment).
|
|
139
|
+
* Improvements:
|
|
140
|
+
* - Pre-computed valid tables list to avoid redundant checks
|
|
141
|
+
* - Better pruning with capacity tracking
|
|
142
|
+
* - Early termination when exact match found
|
|
139
143
|
*/
|
|
140
144
|
function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet, best, requiredSlots, tableOccupiedSlots, reservationDateStr, reservationTimeStr) {
|
|
141
145
|
// Base case: All guests are seated
|
|
@@ -152,7 +156,7 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
|
|
|
152
156
|
best.minDistance = distanceSum;
|
|
153
157
|
best.tables = [...currentSet]; // Store a copy
|
|
154
158
|
best.tableCount = currentSet.length;
|
|
155
|
-
console.log(`Found new best table combination: ${best.tables.map(t => t.tableNumber).join(', ')}
|
|
159
|
+
console.log(`Found new best table combination: ${best.tables.map(t => t.tableNumber).join(', ')}`);
|
|
156
160
|
}
|
|
157
161
|
return;
|
|
158
162
|
}
|
|
@@ -161,21 +165,23 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
|
|
|
161
165
|
return;
|
|
162
166
|
}
|
|
163
167
|
|
|
168
|
+
// OPTIMIZATION: Prune if we already have enough tables in current set
|
|
164
169
|
if (currentSet.length >= best.tableCount && best.tableCount !== Infinity) {
|
|
165
|
-
return; //
|
|
170
|
+
return; // Can't improve on current best
|
|
166
171
|
}
|
|
167
172
|
|
|
173
|
+
// OPTIMIZATION: Calculate max possible capacity from remaining tables
|
|
168
174
|
let maxPossibleCapacity = 0;
|
|
169
175
|
for (let i = startIndex; i < tables.length; i++) {
|
|
170
176
|
const tbl = tables[i];
|
|
171
177
|
// Only consider valid & free tables for potential capacity
|
|
172
178
|
if (isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr) &&
|
|
173
|
-
isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
|
|
179
|
+
isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
|
|
174
180
|
{
|
|
175
181
|
maxPossibleCapacity += tbl.maxCapacity;
|
|
176
182
|
}
|
|
177
183
|
}
|
|
178
|
-
|
|
184
|
+
|
|
179
185
|
if (maxPossibleCapacity < guestsNeeded) {
|
|
180
186
|
return; // Impossible to seat remaining guests
|
|
181
187
|
}
|
|
@@ -188,24 +194,23 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
|
|
|
188
194
|
if (!isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr)) {
|
|
189
195
|
continue; // Skip invalid temporary table
|
|
190
196
|
}
|
|
191
|
-
|
|
197
|
+
|
|
192
198
|
// 2. Check if table is free for all required slots
|
|
193
199
|
if (!isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots)) {
|
|
194
200
|
continue; // Skip occupied table
|
|
195
201
|
}
|
|
196
|
-
|
|
202
|
+
|
|
197
203
|
// 3. Check if table contributes meaningfully
|
|
198
204
|
const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
|
|
199
205
|
if (canSeat < tbl.minCapacity && canSeat < guestsNeeded) {
|
|
200
206
|
continue; // Don't use a table for fewer than its min capacity unless it fulfills the remainder
|
|
201
207
|
}
|
|
202
|
-
|
|
208
|
+
|
|
203
209
|
if (canSeat <= 0) continue; // Table doesn't help
|
|
204
210
|
|
|
205
211
|
// --- Recurse ---
|
|
206
212
|
currentSet.push(tbl); // Add table to current combination
|
|
207
|
-
|
|
208
|
-
|
|
213
|
+
|
|
209
214
|
findMultiTableCombination(
|
|
210
215
|
tables,
|
|
211
216
|
guestsNeeded - canSeat, // Reduce guests needed
|
|
@@ -217,35 +222,26 @@ function findMultiTableCombination(tables, guestsNeeded, startIndex, currentSet,
|
|
|
217
222
|
reservationDateStr,
|
|
218
223
|
reservationTimeStr
|
|
219
224
|
);
|
|
220
|
-
|
|
225
|
+
|
|
221
226
|
currentSet.pop(); // Backtrack: remove table to explore other combinations
|
|
227
|
+
|
|
228
|
+
// OPTIMIZATION: Early exit if we found a single-table solution (best possible)
|
|
229
|
+
if (best.tableCount === 1) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
222
232
|
}
|
|
223
233
|
}
|
|
224
234
|
|
|
225
235
|
function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccupiedSlots, selectedZitplaats = null) {
|
|
226
236
|
console.log(`[assignTablesForGivenTime] Processing ${date} ${time} for ${guests} guests`);
|
|
227
|
-
|
|
237
|
+
|
|
228
238
|
// FIXED: More robust parsing of settings with detailed logging
|
|
229
239
|
const generalSettings = restaurantData["general-settings"] || {};
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// Check what format the data is in
|
|
233
|
-
console.log("[assignTablesForGivenTime] duurReservatie format:", typeof generalSettings.duurReservatie);
|
|
234
|
-
console.log("[assignTablesForGivenTime] intervalReservatie format:", typeof generalSettings.intervalReservatie);
|
|
235
|
-
|
|
236
|
-
// Check for MongoDB format
|
|
237
|
-
if (typeof generalSettings.duurReservatie === 'object' && generalSettings.duurReservatie?.$numberInt) {
|
|
238
|
-
console.log("[assignTablesForGivenTime] duurReservatie is in MongoDB format:", generalSettings.duurReservatie?.$numberInt);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (typeof generalSettings.intervalReservatie === 'object' && generalSettings.intervalReservatie?.$numberInt) {
|
|
242
|
-
console.log("[assignTablesForGivenTime] intervalReservatie is in MongoDB format:", generalSettings.intervalReservatie?.$numberInt);
|
|
243
|
-
}
|
|
244
|
-
|
|
240
|
+
|
|
245
241
|
// DEFAULT VALUES - this approach mirrors the TypeScript implementation
|
|
246
242
|
let duurReservatie = 120; // Default: 2 hours in minutes
|
|
247
243
|
let intervalReservatie = 15; // Default: 15 minute intervals
|
|
248
|
-
|
|
244
|
+
|
|
249
245
|
// Use safeParseInt for robust parsing
|
|
250
246
|
duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
|
|
251
247
|
intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
|
|
@@ -256,7 +252,7 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
|
|
|
256
252
|
console.error("Invalid interval settings.");
|
|
257
253
|
return [];
|
|
258
254
|
}
|
|
259
|
-
|
|
255
|
+
|
|
260
256
|
const requiredSlots = computeRequiredSlots(time, duurReservatie, intervalReservatie);
|
|
261
257
|
if (!requiredSlots || requiredSlots.length === 0) {
|
|
262
258
|
console.error(`Could not compute required slots for ${time}`);
|
|
@@ -267,17 +263,33 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
|
|
|
267
263
|
const allTables = getAllTables(restaurantData, selectedZitplaats);
|
|
268
264
|
console.log(`[assignTablesForGivenTime] Checking ${allTables.length} tables`);
|
|
269
265
|
|
|
266
|
+
// OPTIMIZATION: Pre-filter and sort tables for better performance
|
|
267
|
+
// Filter out invalid and occupied tables first
|
|
268
|
+
const validTables = allTables.filter(t =>
|
|
269
|
+
isTemporaryTableValid(t, date, time) &&
|
|
270
|
+
isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots)
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
if (validTables.length === 0) {
|
|
274
|
+
console.log(`[assignTablesForGivenTime] No valid tables available`);
|
|
275
|
+
return [];
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// OPTIMIZATION: Sort tables by capacity (prefer exact matches first)
|
|
279
|
+
// This helps find optimal solutions faster
|
|
280
|
+
validTables.sort((a, b) => {
|
|
281
|
+
// Prioritize tables that can seat exactly the number of guests
|
|
282
|
+
const aExact = (a.minCapacity <= guests && guests <= a.maxCapacity) ? 0 : 1;
|
|
283
|
+
const bExact = (b.minCapacity <= guests && guests <= b.maxCapacity) ? 0 : 1;
|
|
284
|
+
if (aExact !== bExact) return aExact - bExact;
|
|
285
|
+
|
|
286
|
+
// Then sort by capacity (smaller tables first to minimize waste)
|
|
287
|
+
return a.maxCapacity - b.maxCapacity;
|
|
288
|
+
});
|
|
289
|
+
|
|
270
290
|
// --- Try single-table assignment first ---
|
|
271
|
-
for (const t of
|
|
272
|
-
|
|
273
|
-
if (!isTemporaryTableValid(t, date, time)) {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (t.minCapacity <= guests &&
|
|
278
|
-
guests <= t.maxCapacity &&
|
|
279
|
-
isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots))
|
|
280
|
-
{
|
|
291
|
+
for (const t of validTables) {
|
|
292
|
+
if (t.minCapacity <= guests && guests <= t.maxCapacity) {
|
|
281
293
|
console.log(`[assignTablesForGivenTime] Assigned single table ${t.tableNumber} for ${guests} guests`);
|
|
282
294
|
return [t.tableNumber];
|
|
283
295
|
}
|
|
@@ -288,7 +300,7 @@ function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccup
|
|
|
288
300
|
// --- Try multi-table assignment ---
|
|
289
301
|
const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
|
|
290
302
|
findMultiTableCombination(
|
|
291
|
-
|
|
303
|
+
validTables, // Use pre-filtered and sorted tables
|
|
292
304
|
guests,
|
|
293
305
|
0, // Start index
|
|
294
306
|
[], // Initial empty set
|
|
@@ -409,14 +421,28 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
409
421
|
const allTables = getAllTables(restaurantData, selectedZitplaats); // Get all tables
|
|
410
422
|
console.log(`[isTimeAvailableSync] Checking ${allTables.length} tables for new reservation`);
|
|
411
423
|
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
|
|
424
|
+
// OPTIMIZATION: Pre-filter valid tables
|
|
425
|
+
const validTables = allTables.filter(t =>
|
|
426
|
+
isTemporaryTableValid(t, date, time) &&
|
|
427
|
+
isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots)
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
if (validTables.length === 0) {
|
|
431
|
+
console.log(`[isTimeAvailableSync] No valid tables available`);
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// OPTIMIZATION: Sort for faster exact matches
|
|
436
|
+
validTables.sort((a, b) => {
|
|
437
|
+
const aExact = (a.minCapacity <= guests && guests <= a.maxCapacity) ? 0 : 1;
|
|
438
|
+
const bExact = (b.minCapacity <= guests && guests <= b.maxCapacity) ? 0 : 1;
|
|
439
|
+
if (aExact !== bExact) return aExact - bExact;
|
|
440
|
+
return a.maxCapacity - b.maxCapacity;
|
|
441
|
+
});
|
|
415
442
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
{
|
|
443
|
+
// 1. Try single table assignment
|
|
444
|
+
for (const t of validTables) {
|
|
445
|
+
if (t.minCapacity <= guests && guests <= t.maxCapacity) {
|
|
420
446
|
console.log(`[isTimeAvailableSync] Found single table ${t.tableNumber} for ${guests} guests at ${time}`);
|
|
421
447
|
return true; // Available
|
|
422
448
|
}
|
|
@@ -427,7 +453,7 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
427
453
|
// 2. Try multi-table assignment
|
|
428
454
|
const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
|
|
429
455
|
findMultiTableCombination(
|
|
430
|
-
|
|
456
|
+
validTables, // Use pre-filtered tables
|
|
431
457
|
guests,
|
|
432
458
|
0, [], best,
|
|
433
459
|
requiredSlots,
|
|
@@ -441,7 +467,7 @@ function isTimeAvailableSync(restaurantData, date, time, guests, reservations, s
|
|
|
441
467
|
} else {
|
|
442
468
|
console.log(`[isTimeAvailableSync] No table combination available`);
|
|
443
469
|
}
|
|
444
|
-
|
|
470
|
+
|
|
445
471
|
return result; // Available if a combination was found
|
|
446
472
|
|
|
447
473
|
} catch (error) {
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
const safeParseInt = require('../utils/safeParseInt');
|
|
2
|
-
const computeRequiredSlots = require('../time/computeRequiredSlots');
|
|
3
|
-
const getAllTables = require('../data/getAllTables');
|
|
4
|
-
const isTemporaryTableValid = require('../utils/isTemporaryTableValid');
|
|
5
|
-
const findMultiTableCombination = require('../availability/findMultiTableCombination');
|
|
6
|
-
const isTableFreeForAllSlots = require('../utils/isTableFreeForAllSlots');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Attempts to assign tables for a single reservation at a given time.
|
|
10
|
-
* Returns an array of table numbers (single-table or multi-table).
|
|
11
|
-
*/
|
|
12
|
-
function assignTablesForGivenTime(restaurantData, date, time, guests, tableOccupiedSlots, selectedZitplaats = null) {
|
|
13
|
-
|
|
14
|
-
// FIXED: More robust parsing of settings with detailed logging
|
|
15
|
-
const generalSettings = restaurantData["general-settings"] || {};
|
|
16
|
-
|
|
17
|
-
// Check what format the data is in
|
|
18
|
-
// Check for MongoDB format
|
|
19
|
-
if (typeof generalSettings.duurReservatie === 'object' && generalSettings.duurReservatie?.$numberInt) {
|
|
20
|
-
}
|
|
21
|
-
if (typeof generalSettings.intervalReservatie === 'object' && generalSettings.intervalReservatie?.$numberInt) {
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// DEFAULT VALUES - this approach mirrors the TypeScript implementation
|
|
25
|
-
let duurReservatie = 120; // Default: 2 hours in minutes
|
|
26
|
-
let intervalReservatie = 15; // Default: 15 minute intervals
|
|
27
|
-
|
|
28
|
-
// Use safeParseInt for robust parsing
|
|
29
|
-
duurReservatie = safeParseInt(generalSettings.duurReservatie, 120);
|
|
30
|
-
intervalReservatie = safeParseInt(generalSettings.intervalReservatie, 15);
|
|
31
|
-
|
|
32
|
-
if (intervalReservatie <= 0) {
|
|
33
|
-
console.error("Invalid interval settings.");
|
|
34
|
-
return [];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const requiredSlots = computeRequiredSlots(time, duurReservatie, intervalReservatie);
|
|
38
|
-
if (!requiredSlots || requiredSlots.length === 0) {
|
|
39
|
-
console.error(`Could not compute required slots for ${time}`);
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Fetch all tables
|
|
44
|
-
const allTables = getAllTables(restaurantData, selectedZitplaats);
|
|
45
|
-
|
|
46
|
-
// Try single-table assignment first
|
|
47
|
-
for (const t of allTables) {
|
|
48
|
-
if (!isTemporaryTableValid(t, date, time)) continue;
|
|
49
|
-
|
|
50
|
-
if (t.minCapacity <= guests &&
|
|
51
|
-
guests <= t.maxCapacity &&
|
|
52
|
-
isTableFreeForAllSlots(t.tableNumber, requiredSlots, tableOccupiedSlots))
|
|
53
|
-
{
|
|
54
|
-
return [t.tableNumber];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Try multi-table assignment
|
|
59
|
-
const best = { minDistance: Infinity, tables: [], tableCount: Infinity };
|
|
60
|
-
findMultiTableCombination(
|
|
61
|
-
allTables,
|
|
62
|
-
guests,
|
|
63
|
-
0,
|
|
64
|
-
[],
|
|
65
|
-
best,
|
|
66
|
-
requiredSlots,
|
|
67
|
-
tableOccupiedSlots,
|
|
68
|
-
date,
|
|
69
|
-
time
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
if (best.tables.length > 0) {
|
|
73
|
-
} else {
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return best.tables.map(t => t.tableNumber);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
module.exports = assignTablesForGivenTime;
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
// Import directly to avoid circular dependency with tables/main.js
|
|
2
|
-
const isTimeAvailableSync = require('../availability/isTimeAvailableSync');
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Filters timeblocks based on availability for a given guest count and date.
|
|
6
|
-
*/
|
|
7
|
-
function filterTimeblocksByTableAvailability(
|
|
8
|
-
restaurantData,
|
|
9
|
-
date,
|
|
10
|
-
timeblocks,
|
|
11
|
-
guests,
|
|
12
|
-
reservations,
|
|
13
|
-
selectedZitplaats = null
|
|
14
|
-
) {
|
|
15
|
-
if (guests < 0) {
|
|
16
|
-
guests = 0; // Use a reasonable default
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const tableSettings = restaurantData?.['table-settings'] || {};
|
|
20
|
-
const isTableAssignmentEnabled =
|
|
21
|
-
tableSettings.isInstalled === true &&
|
|
22
|
-
tableSettings.assignmentMode === 'automatic';
|
|
23
|
-
|
|
24
|
-
if (!isTableAssignmentEnabled) {
|
|
25
|
-
return timeblocks;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const filteredTimeblocks = {};
|
|
29
|
-
let availableCount = 0;
|
|
30
|
-
let unavailableCount = 0;
|
|
31
|
-
|
|
32
|
-
for (const time in timeblocks) {
|
|
33
|
-
if (isTimeAvailableSync(restaurantData, date, time, guests, reservations, selectedZitplaats)) {
|
|
34
|
-
filteredTimeblocks[time] = timeblocks[time];
|
|
35
|
-
availableCount++;
|
|
36
|
-
} else {
|
|
37
|
-
unavailableCount++;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (availableCount > 0) {
|
|
42
|
-
// no-op, preserve original structure
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return filteredTimeblocks;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
module.exports = filterTimeblocksByTableAvailability;
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
const isTemporaryTableValid = require('../utils/isTemporaryTableValid');
|
|
2
|
-
const isTableFreeForAllSlots = require('../utils/isTableFreeForAllSlots');
|
|
3
|
-
const calculateDistance = require('../utils/calculateDistance');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Backtracking function to find a combination of tables (multi-table assignment).
|
|
7
|
-
*/
|
|
8
|
-
function findMultiTableCombination(
|
|
9
|
-
tables,
|
|
10
|
-
guestsNeeded,
|
|
11
|
-
startIndex,
|
|
12
|
-
currentSet,
|
|
13
|
-
best,
|
|
14
|
-
requiredSlots,
|
|
15
|
-
tableOccupiedSlots,
|
|
16
|
-
reservationDateStr,
|
|
17
|
-
reservationTimeStr
|
|
18
|
-
) {
|
|
19
|
-
// Base case: All guests are seated
|
|
20
|
-
if (guestsNeeded <= 0) {
|
|
21
|
-
// Calculate total distance for the current combination
|
|
22
|
-
let distanceSum = 0;
|
|
23
|
-
for (let i = 0; i < currentSet.length; i++) {
|
|
24
|
-
for (let j = i + 1; j < currentSet.length; j++) {
|
|
25
|
-
distanceSum += calculateDistance(currentSet[i], currentSet[j]);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
// Update best solution if this one is better (fewer tables, then lower distance)
|
|
29
|
-
if (currentSet.length < best.tableCount || (currentSet.length === best.tableCount && distanceSum < best.minDistance)) {
|
|
30
|
-
best.minDistance = distanceSum;
|
|
31
|
-
best.tables = [...currentSet]; // Store a copy
|
|
32
|
-
best.tableCount = currentSet.length;
|
|
33
|
-
}
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (startIndex >= tables.length) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (currentSet.length >= best.tableCount && best.tableCount !== Infinity) {
|
|
42
|
-
return; // Pruning based on table count
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let maxPossibleCapacity = 0;
|
|
46
|
-
for (let i = startIndex; i < tables.length; i++) {
|
|
47
|
-
const tbl = tables[i];
|
|
48
|
-
// Only consider valid & free tables for potential capacity
|
|
49
|
-
if (isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr) &&
|
|
50
|
-
isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots))
|
|
51
|
-
{
|
|
52
|
-
maxPossibleCapacity += tbl.maxCapacity;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (maxPossibleCapacity < guestsNeeded) {
|
|
57
|
-
return; // Impossible to seat remaining guests
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
for (let i = startIndex; i < tables.length; i++) {
|
|
61
|
-
const tbl = tables[i];
|
|
62
|
-
|
|
63
|
-
// 1. Check if temporary table is valid for this date/time
|
|
64
|
-
if (!isTemporaryTableValid(tbl, reservationDateStr, reservationTimeStr)) {
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 2. Check if table is free for all required slots
|
|
69
|
-
if (!isTableFreeForAllSlots(tbl.tableNumber, requiredSlots, tableOccupiedSlots)) {
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 3. Check if table contributes meaningfully
|
|
74
|
-
const canSeat = Math.min(tbl.maxCapacity, guestsNeeded);
|
|
75
|
-
if (canSeat < tbl.minCapacity && canSeat < guestsNeeded) {
|
|
76
|
-
continue; // Don't use a table for fewer than its min capacity unless it fulfills the remainder
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (canSeat <= 0) continue; // Table doesn't help
|
|
80
|
-
|
|
81
|
-
// Recurse
|
|
82
|
-
currentSet.push(tbl);
|
|
83
|
-
|
|
84
|
-
findMultiTableCombination(
|
|
85
|
-
tables,
|
|
86
|
-
guestsNeeded - canSeat,
|
|
87
|
-
i + 1,
|
|
88
|
-
currentSet,
|
|
89
|
-
best,
|
|
90
|
-
requiredSlots,
|
|
91
|
-
tableOccupiedSlots,
|
|
92
|
-
reservationDateStr,
|
|
93
|
-
reservationTimeStr
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
currentSet.pop(); // Backtrack
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
module.exports = findMultiTableCombination;
|