@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,986 +1,986 @@
1
- # Handleiding Reserveringssysteem HappyChef
2
-
3
- ## Inhoudsopgave
4
-
5
- 1. [Overzicht van het Systeem](#overzicht-van-het-systeem)
6
- 2. [Hoe Beschikbaarheid Wordt Berekend](#hoe-beschikbaarheid-wordt-berekend)
7
- 3. [Datamodellen en Configuratie](#datamodellen-en-configuratie)
8
- 4. [Boekingsfilters en Validatie](#boekingsfilters-en-validatie)
9
- 5. [Tafeltoewijzing](#tafeltoewijzing)
10
- 6. [Volledige Reserveringsflow](#volledige-reserveringsflow)
11
- 7. [Belangrijke Validatieregels](#belangrijke-validatieregels)
12
-
13
- ---
14
-
15
- ## Overzicht van het Systeem
16
-
17
- Het HappyChef reserveringssysteem is een gelaagd algoritme voor het berekenen van restaurantbeschikbaarheid en het toewijzen van tafels aan reserveringen. Het systeem bestaat uit de volgende kerncomponenten:
18
-
19
- ### Bestandsstructuur
20
-
21
- ```
22
- ├── Core Beschikbaarheidslogica
23
- │ ├── isDateAvailable.js - Controleert of een datum beschikbaar is
24
- │ ├── isTimeAvailable.js - Controleert of een tijdstip beschikbaar is
25
- │ └── getAvailableTimeblocks.js - Haalt beschikbare tijdsblokken op
26
-
27
- ├── Processing Engines (processing/)
28
- │ ├── timeblocksAvailable.js - Kernmotor voor beschikbaarheid
29
- │ ├── dailyGuestCounts.js - Berekent dagelijkse gastentellingen
30
- │ └── mealTypeCount.js - Berekent gasten per maaltijdtype
31
-
32
- ├── Datamodellen (restaurant_data/)
33
- │ ├── openinghours.js - Openingstijden configuratie
34
- │ ├── exceptions.js - Uitzonderingen en sluitingen
35
- │ └── counter.js - Gastentelling functies
36
-
37
- ├── Boekingsfilters (filters/)
38
- │ ├── timeFilter.js - Tijdsfilter
39
- │ ├── maxArrivalsFilter.js - Maximum aankomsten filter
40
- │ └── maxGroupsFilter.js - Maximum groepen filter
41
-
42
- └── Tafelbeheer
43
- ├── assignTables.js - Tafeltoewijzing logica
44
- ├── grouping.js - Tafelgroepering
45
- └── tableHelpers.js - Hulpfuncties voor tafels
46
- ```
47
-
48
- ---
49
-
50
- ## Hoe Beschikbaarheid Wordt Berekend
51
-
52
- De beschikbaarheidberekening gebeurt in meerdere lagen, van datum tot specifiek tijdstip.
53
-
54
- ### 1. Beschikbaarheid op Datumniveau (`isDateAvailable.js`)
55
-
56
- ```
57
- isDateAvailable(data, dateStr, reservations, guests)
58
-
59
- Stap 1: Controleer of datum binnen toegestane toekomstbereik ligt (dagenInToekomst: standaard 90 dagen)
60
- Stap 2: Roep getAvailableTimeblocks() aan om beschikbare tijdsslots te vinden
61
- Stap 3: Geef TRUE terug als er minstens ÉÉN tijdsblok beschikbaar is
62
- ```
63
-
64
- **Belangrijke Instellingen:**
65
- - `dagenInToekomst`: Maximum aantal dagen in de toekomst (standaard: 90)
66
- - `uurOpVoorhand`: Aantal uren van tevoren vereist om te boeken (standaard: 4)
67
- - Tijdzone: Europe/Amsterdam
68
-
69
- ### 2. Tijdsblok Beschikbaarheid (`getAvailableTimeblocks.js`)
70
-
71
- ```
72
- getAvailableTimeblocks(data, dateStr, reservations, guests)
73
-
74
- Stap 1: Valideer dat datum binnen dagenInToekomst bereik ligt
75
- Stap 2: Voor VANDAAG: Verwijder slots binnen uurOpVoorhand venster
76
- Stap 3: Roep timeblocksAvailable() aan voor alle potentiële slots
77
- Stap 4: Geef gefilterd tijdsblok object terug met tijden en beschikbaarheid
78
- ```
79
-
80
- **Voorbeeld output:**
81
- ```javascript
82
- {
83
- "12:00": true, // Beschikbaar
84
- "12:15": true,
85
- "12:30": false, // Niet beschikbaar
86
- "12:45": true
87
- }
88
- ```
89
-
90
- ### 3. Kern Beschikbaarheidsmotor (`processing/timeblocksAvailable.js`)
91
-
92
- Dit is het hart van het systeem en werkt in twee modi:
93
-
94
- #### **A. SHIFTS MODUS** (indien ingeschakeld)
95
-
96
- ```
97
- timeblocksAvailable(data, dateStr, reservations, guests)
98
-
99
- Stap 1: Controleer maaltijdtypes met shifts ingeschakeld
100
- Stap 2: Voor elke shift tijd:
101
- - Tel beschikbare stoelen (maxCapacity - huidigeGasten)
102
- Stap 3: Geef beschikbare shifts terug waar: beschikbareStoelen >= gasten
103
- ```
104
-
105
- **Voorbeeld:**
106
- ```javascript
107
- // Shift configuratie
108
- shifts: [
109
- { name: "Eerste dienst", time: "12:00" },
110
- { name: "Tweede dienst", time: "14:00" }
111
- ]
112
-
113
- // Berekening voor 4 gasten
114
- Shift "12:00": maxCapacity (30) - bezet (25) = 5 beschikbaar → ✓ BESCHIKBAAR
115
- Shift "14:00": maxCapacity (30) - bezet (28) = 2 beschikbaar → ✗ NIET BESCHIKBAAR
116
- ```
117
-
118
- #### **B. TIJDSINTERVAL MODUS** (standaard)
119
-
120
- ```
121
- timeblocksAvailable(data, dateStr, reservations, guests)
122
-
123
- Stap 1: Genereer alle tijdslots binnen maaltijdperiodes met regelmatige intervallen
124
- (intervalReservatie: standaard 15 minuten)
125
-
126
- Stap 2: Voor elk interval:
127
- - Tel beschikbare stoelen met getDailyGuestCounts()
128
- - Controleer of er voldoende opeenvolgende slots bestaan voor volledige reserveringsduur
129
- (duurReservatie: standaard 120 minuten)
130
-
131
- Stap 3: Valideer dat starttijd + duur binnen maaltijdtijdvenster past
132
-
133
- Stap 4: Geef beschikbare tijdsblok objecten terug
134
- ```
135
-
136
- **Voorbeeld berekening voor 4 gasten om 12:00:**
137
- ```
138
- Reserveringsduur: 120 minuten
139
- Vereiste slots: 12:00, 12:15, 12:30, 12:45, 13:00, 13:15, 13:30, 13:45
140
-
141
- Check per slot:
142
- 12:00 → beschikbaar: 6 stoelen ✓
143
- 12:15 → beschikbaar: 5 stoelen ✓
144
- 12:30 → beschikbaar: 3 stoelen ✗ (te weinig!)
145
-
146
- Resultaat: 12:00 NIET BESCHIKBAAR (niet alle slots hebben voldoende capaciteit)
147
- ```
148
-
149
- ### 4. Dagelijkse Gastentelling (`processing/dailyGuestCounts.js`)
150
-
151
- Deze functie berekent hoeveel gasten er op elk moment van de dag aanwezig zijn.
152
-
153
- ```
154
- getDailyGuestCounts(data, dateStr, reservations)
155
-
156
- Stap 1: Verwerk drie maaltijdtypes: ontbijt, lunch, diner
157
-
158
- Stap 2: Voor elke maaltijd:
159
- - Haal gastentellingen op per interval via getGuestCountsForMeal()
160
-
161
- Stap 3: Merge resultaten op basis van maaltijdprioriteit:
162
- DINER (hoogste) > LUNCH > ONTBIJT (laagste)
163
- - Als tijden overlappen tussen maaltijden, wordt capaciteit van
164
- maaltijd met hoogste prioriteit gebruikt
165
-
166
- Stap 4: Geef terug:
167
- - guestCounts: { "12:00": 5, "12:15": 3, ... } (beschikbare stoelen)
168
- - shiftsInfo: [{ mealType, shiftName, time, availableSeats }, ...]
169
- ```
170
-
171
- **Voorbeeld overlapping:**
172
- ```javascript
173
- // Lunch eindigt om 15:00, Diner start om 14:00
174
-
175
- 14:00 → Lunch capaciteit: 25, Diner capaciteit: 30
176
- Systeem kiest: 30 (diner heeft hogere prioriteit)
177
-
178
- 14:30 → Lunch capaciteit: 25, Diner capaciteit: 30
179
- Systeem kiest: 30 (diner heeft hogere prioriteit)
180
- ```
181
-
182
- ### 5. Gastentelling per Maaltijdtype (`processing/mealTypeCount.js`)
183
-
184
- Voor elk maaltijdtype (ontbijt/lunch/diner):
185
-
186
- ```
187
- getGuestCountsForMeal(data, dateStr, mealType, reservations)
188
-
189
- ALS shifts ingeschakeld:
190
- Voor elke gedefinieerde shift tijd:
191
- - Tel bezette gasten op dat moment via getGuestCountAtHour()
192
- - beschikbareStoelen = maxCapacity - bezetteGasten
193
-
194
- ANDERS (normale intervallen):
195
- Genereer tijdslots: startTime tot endTime met intervalReservatie intervallen
196
- Voor elk tijdslot:
197
- - Tel bezette gasten via getGuestCountAtHour()
198
- - beschikbareStoelen = maxCapacity - bezetteGasten
199
- ```
200
-
201
- ### 6. Gastentelling op Specifiek Uur (`reservation_data/counter.js`)
202
-
203
- ```
204
- getGuestCountAtHour(data, reservations, hour, dateStr)
205
-
206
- Som gasten van alle reserveringen waar:
207
- - datum === dateStr
208
- - startTime <= hour < (startTime + duurReservatie)
209
- ```
210
-
211
- **Voorbeeld:**
212
- ```javascript
213
- // Bestaande reserveringen op 2025-01-15:
214
- Reservering A: 12:00, 4 gasten, duur 120 min (12:00 - 14:00)
215
- Reservering B: 12:30, 2 gasten, duur 120 min (12:30 - 14:30)
216
- Reservering C: 14:00, 6 gasten, duur 120 min (14:00 - 16:00)
217
-
218
- // Gastentelling per tijdslot:
219
- 12:00 → 4 gasten (alleen A)
220
- 12:15 → 4 gasten (alleen A)
221
- 12:30 → 6 gasten (A + B)
222
- 12:45 → 6 gasten (A + B)
223
- 13:00 → 6 gasten (A + B)
224
- 13:30 → 6 gasten (A + B)
225
- 14:00 → 8 gasten (B + C, A is beëindigd)
226
- 14:30 → 8 gasten (B + C)
227
- 15:00 → 6 gasten (alleen C, B is beëindigd)
228
- ```
229
-
230
- ---
231
-
232
- ## Datamodellen en Configuratie
233
-
234
- ### 1. Algemene Instellingen (`general-settings`)
235
-
236
- ```javascript
237
- {
238
- zitplaatsen: 30, // Totaal aantal stoelen in restaurant
239
- uurOpVoorhand: 4, // Aantal uren van tevoren vereist voor reservering
240
- dagenInToekomst: 90, // Maximaal aantal dagen vooruit boeken
241
- maxGasten: 8, // Maximum aantal gasten per reservering
242
- intervalReservatie: 15, // Tijdslot interval in minuten
243
- duurReservatie: 120, // Duur van reservering in minuten
244
- minutenTotEinde: 135, // Minuten tot einde van dienst
245
- ontbijtStop: "10:00", // Stoptijd voor ontbijt (dynamische stop)
246
- lunchStop: "14:00", // Stoptijd voor lunch
247
- dinerStop: "20:00" // Stoptijd voor diner
248
- }
249
- ```
250
-
251
- **Uitleg per instelling:**
252
-
253
- - **zitplaatsen**: Het totaal aantal beschikbare stoelen in het hele restaurant
254
- - **uurOpVoorhand**: Voorkomt last-minute reserveringen; gasten moeten X uren van tevoren boeken
255
- - **dagenInToekomst**: Beperkt hoe ver vooruit klanten kunnen reserveren
256
- - **maxGasten**: Maximum groepsgrootte voor online reserveringen
257
- - **intervalReservatie**: Hoe vaak nieuwe tijdslots beginnen (bv. 15 min = 12:00, 12:15, 12:30...)
258
- - **duurReservatie**: Standaard duur van elke reservering
259
- - **minutenTotEinde**: Buffer tijd aan het einde van dienst (voorkomt te late reserveringen)
260
- - **ontbijtStop/lunchStop/dinerStop**: Voor reserveringen op VANDAAG - stopt nieuwe boekingen na deze tijd
261
-
262
- ### 2. Maaltijdconfiguratie (`openinghours-breakfast/lunch/dinner`)
263
-
264
- ```javascript
265
- {
266
- schemeSettings: {
267
- Monday: {
268
- enabled: true, // Is deze maaltijd beschikbaar op maandag?
269
- startTime: "07:00", // Starttijd van maaltijd
270
- endTime: "11:00", // Eindtijd van maaltijd
271
- maxCapacity: 30, // Maximum capaciteit voor deze maaltijd
272
- shiftsEnabled: false, // Gebruik shifts modus?
273
- shifts: [], // Shift configuraties
274
- giftcardsEnabled: false, // Cadeaubonnen beschikbaar?
275
- giftcards: [] // Cadeaubon configuraties
276
- },
277
- Tuesday: { /* ... */ },
278
- Wednesday: { /* ... */ },
279
- Thursday: { /* ... */ },
280
- Friday: { /* ... */ },
281
- Saturday: { /* ... */ },
282
- Sunday: { /* ... */ }
283
- }
284
- }
285
- ```
286
-
287
- **Voorbeeld met shifts:**
288
- ```javascript
289
- {
290
- Monday: {
291
- enabled: true,
292
- startTime: "18:00",
293
- endTime: "22:00",
294
- maxCapacity: 40,
295
- shiftsEnabled: true,
296
- shifts: [
297
- { name: "Vroege dienst", time: "18:00" },
298
- { name: "Late dienst", time: "20:00" }
299
- ]
300
- }
301
- }
302
- ```
303
-
304
- ### 3. Uitzonderingen (`exceptions`)
305
-
306
- Uitzonderingen overschrijven de normale openingstijden.
307
-
308
- ```javascript
309
- {
310
- type: "Sluiting", // Type: "Sluiting", "Opening", "Uitzondering"
311
- startDate: "2024-12-25", // Startdatum van uitzondering
312
- endDate: "2024-12-25", // Einddatum van uitzondering
313
- daysOfWeek: ["maandag"], // Dagen van de week (optioneel)
314
- timeframe: "Volledige Dag", // "Volledige Dag" of specifiek maaltijdtype
315
- startHour: "12:00", // Starttijd (voor gedeeltelijke sluitingen)
316
- endHour: "15:00", // Eindtijd (voor gedeeltelijke sluitingen)
317
- maxSeats: 20 // Aangepaste capaciteit (voor "Uitzondering")
318
- }
319
- ```
320
-
321
- **Prioriteitsvolgorde van uitzonderingen:**
322
- 1. **Opening** (hoogste prioriteit) - Opent het restaurant op normaal gesloten dag
323
- 2. **Sluiting** - Sluit het restaurant (geheel of gedeeltelijk)
324
- 3. **Uitzondering** (laagste prioriteit) - Past capaciteit aan zonder te sluiten
325
-
326
- **Voorbeelden:**
327
-
328
- ```javascript
329
- // Voorbeeld 1: Volledige sluiting op Kerstmis
330
- {
331
- type: "Sluiting",
332
- startDate: "2024-12-25",
333
- endDate: "2024-12-25",
334
- timeframe: "Volledige Dag"
335
- }
336
-
337
- // Voorbeeld 2: Speciale opening op zondag
338
- {
339
- type: "Opening",
340
- startDate: "2024-12-01",
341
- endDate: "2024-12-01",
342
- daysOfWeek: ["zondag"],
343
- timeframe: "Lunch",
344
- startHour: "12:00",
345
- endHour: "16:00",
346
- maxSeats: 25
347
- }
348
-
349
- // Voorbeeld 3: Verminderde capaciteit voor privé evenement
350
- {
351
- type: "Uitzondering",
352
- startDate: "2024-11-20",
353
- endDate: "2024-11-20",
354
- timeframe: "Diner",
355
- maxSeats: 15 // In plaats van normale 30
356
- }
357
- ```
358
-
359
- ---
360
-
361
- ## Boekingsfilters en Validatie
362
-
363
- Na de basis beschikbaarheidsberekening worden extra filters toegepast om de beschikbare tijdslots verder te verfijnen.
364
-
365
- ### 1. Tijdsfilter (`filters/timeFilter.js`)
366
-
367
- Verwijdert tijdslots op basis van stoptijden voor boekingen op dezelfde dag.
368
-
369
- ```
370
- Functionaliteit:
371
- - Verwijdert tijdslots na ontbijtStop/lunchStop/dinerStop
372
- - Geldt ALLEEN voor reserveringen op VANDAAG
373
- - Gebruikt Europe/Brussels tijdzone
374
-
375
- Voorbeeld:
376
- Huidige tijd: 09:30
377
- ontbijtStop: "10:00"
378
- dateStr: vandaag
379
-
380
- Tijdslots voor ontbijt:
381
- 09:00 ✗ (verleden)
382
- 09:15 ✗ (verleden)
383
- 09:30 ✗ (binnen uurOpVoorhand venster)
384
- 09:45 ✗ (binnen uurOpVoorhand venster)
385
- 10:00 ✗ (na ontbijtStop)
386
- 10:15 ✗ (na ontbijtStop)
387
- ```
388
-
389
- ### 2. Maximum Aankomsten Filter (`filters/maxArrivalsFilter.js`)
390
-
391
- Beperkt het aantal gasten dat op exact hetzelfde tijdstip mag aankomen.
392
-
393
- ```
394
- filterTimeblocksByMaxArrivals(restaurantData, date, timeblocks, reservations, guests)
395
-
396
- Voor elk tijdsblok:
397
- Stap 1: Bepaal maaltijdtype op basis van tijd
398
- Stap 2: Haal maxArrivalsConfig[maaltijdtype][tijd] instelling op
399
- Stap 3: Tel huidige aankomsten op exact deze tijd op deze datum
400
- Stap 4: Sta alleen toe als: huidigeAankomsten + nieuweGasten <= maxAankomsten
401
- ```
402
-
403
- **Data structuur:**
404
- ```javascript
405
- {
406
- "max-arrivals-breakfast": {
407
- "08:00": 5, // Max 5 gasten mogen om 08:00 aankomen
408
- "08:15": 5,
409
- "08:30": 8,
410
- "08:45": 8
411
- },
412
- "max-arrivals-lunch": {
413
- "12:00": 10, // Max 10 gasten mogen om 12:00 aankomen
414
- "12:15": 10,
415
- "12:30": 12,
416
- "12:45": 12
417
- },
418
- "max-arrivals-dinner": {
419
- "18:00": 15, // Max 15 gasten mogen om 18:00 aankomen
420
- "18:30": 15,
421
- "19:00": 12,
422
- "19:30": 12
423
- }
424
- }
425
- ```
426
-
427
- **Voorbeeld scenario:**
428
- ```javascript
429
- // Bestaande reserveringen op 2025-01-15 om 12:00:
430
- Reservering A: 12:00, 4 gasten
431
- Reservering B: 12:00, 3 gasten
432
- Totaal: 7 gasten
433
-
434
- // Nieuwe aanvraag: 4 gasten om 12:00
435
- maxArrivals voor 12:00 lunch: 10
436
-
437
- Berekening: 7 (bestaand) + 4 (nieuw) = 11 > 10
438
- Resultaat: ✗ GEWEIGERD (te veel aankomsten op dit tijdstip)
439
-
440
- // Maar 12:15 is nog beschikbaar:
441
- Aankomsten om 12:15: 0 gasten
442
- Berekening: 0 + 4 = 4 ≤ 10
443
- Resultaat: ✓ TOEGESTAAN
444
- ```
445
-
446
- ### 3. Maximum Groepen Filter (`filters/maxGroupsFilter.js`)
447
-
448
- Beperkt het aantal grote groepen tijdens een maaltijdperiode.
449
-
450
- ```
451
- filterTimeblocksByMaxGroups(restaurantData, date, timeblocks, reservations, guests)
452
-
453
- Voor elk tijdsblok:
454
- Stap 1: Bepaal maaltijdtype
455
- Stap 2: Haal max-groups-{maaltijdtype} instellingen op
456
- Stap 3: Instellingenstructuur: { "6": maxAantal, "7": maxAantal, ... }
457
- - Definieert: max groepen van grootte >= 6, >= 7, etc.
458
- Stap 4: Tel bestaande groepen >= gastGrootte in die maaltijd
459
- Stap 5: Sta alleen toe als: bestaandeGroepen + 1 <= maxToegstaneGroepen
460
- ```
461
-
462
- **Data structuur:**
463
- ```javascript
464
- {
465
- "max-groups-breakfast": {
466
- "6": 2, // Maximaal 2 groepen van 6+ personen
467
- "7": 1, // Maximaal 1 groep van 7+ personen
468
- "8": 0 // Geen groepen van 8+ personen toegestaan
469
- },
470
- "max-groups-lunch": {
471
- "6": 3, // Maximaal 3 groepen van 6+ personen
472
- "7": 2, // Maximaal 2 groepen van 7+ personen
473
- "8": 1 // Maximaal 1 groep van 8+ personen
474
- },
475
- "max-groups-dinner": {
476
- "5": 5, // Maximaal 5 groepen van 5+ personen
477
- "6": 3, // Maximaal 3 groepen van 6+ personen
478
- "7": 2 // Maximaal 2 groepen van 7+ personen
479
- }
480
- }
481
- ```
482
-
483
- **Voorbeeld scenario:**
484
- ```javascript
485
- // Bestaande reserveringen tijdens lunch:
486
- Reservering A: 6 gasten
487
- Reservering B: 7 gasten
488
- Reservering C: 4 gasten (telt niet mee voor "6+" filter)
489
-
490
- // Nieuwe aanvraag: 6 gasten voor lunch
491
- max-groups-lunch configuratie: { "6": 3, "7": 2 }
492
-
493
- Berekening voor grootte "6+":
494
- Bestaande groepen >= 6: 2 (Reservering A en B)
495
- Nieuwe groep: 1
496
- Totaal: 2 + 1 = 3 ≤ 3 (max)
497
- Resultaat: ✓ TOEGESTAAN
498
-
499
- // Maar als er een 7e gast bijkomt:
500
- Nieuwe aanvraag: 7 gasten voor lunch
501
-
502
- Berekening voor grootte "7+":
503
- Bestaande groepen >= 7: 1 (Reservering B)
504
- Nieuwe groep: 1
505
- Totaal: 1 + 1 = 2 ≤ 2 (max)
506
- Resultaat: ✓ TOEGESTAAN
507
- ```
508
-
509
- **Hoe de drempel werkt:**
510
- ```javascript
511
- // Voor een groep van 6 personen:
512
- Controleert: max-groups["6"] → controleert limiet voor groepen van 6+ personen
513
-
514
- // Voor een groep van 8 personen:
515
- Controleert: max-groups["8"], max-groups["7"], max-groups["6"]
516
- Moet voldoen aan ALLE limieten:
517
- - Voldoende ruimte binnen limiet voor 8+ groepen
518
- - Voldoende ruimte binnen limiet voor 7+ groepen
519
- - Voldoende ruimte binnen limiet voor 6+ groepen
520
- ```
521
-
522
- ---
523
-
524
- ## Tafeltoewijzing
525
-
526
- Het systeem kan optioneel automatisch tafels toewijzen aan reserveringen.
527
-
528
- ### 1. Tafel Data Structuur
529
-
530
- ```javascript
531
- {
532
- tableNumber: 5, // Uniek tafelnummer
533
- tableId: "id123", // Unieke tafel ID
534
- minCapacity: 2, // Minimum gasten om tafel te vullen
535
- maxCapacity: 4, // Maximum capaciteit
536
- priority: 1, // Lagere waarde = hogere prioriteit
537
- x: 100, // X-coördinaat op plattegrond
538
- y: 150, // Y-coördinaat op plattegrond
539
- isTemporary: false, // Is dit een tijdelijke tafel?
540
- startDate: "2024-12-01", // Startdatum (voor tijdelijke tafels)
541
- endDate: "2024-12-31", // Einddatum (voor tijdelijke tafels)
542
- application: "dinner" // Maaltijdtype restrictie (breakfast/lunch/dinner)
543
- }
544
- ```
545
-
546
- ### 2. Enkele Tafel Toewijzing
547
-
548
- ```
549
- assignTablesIfPossible({db, reservation, enforceTableAvailability})
550
-
551
- Stap 1: Bereken vereiste slots op basis van startTime + duurReservatie
552
- (bv. 12:00 + 120 min = slots: 12:00, 12:15, 12:30, ..., 13:45)
553
-
554
- Stap 2: Haal alle tafels op van restaurant.floors
555
-
556
- Stap 3: Valideer tijdelijke tafels:
557
- - Controleer datum binnen startDate/endDate bereik
558
- - Controleer maaltijdtype restrictie (application veld)
559
-
560
- Stap 4: Bouw tableOccupiedSlots: { tableNumber: Set([slot1, slot2, ...]) }
561
- → Welke tijdslots zijn al bezet per tafel
562
-
563
- Stap 5: Probeer eerst vastgepinde groepen (via tryGroupTables)
564
-
565
- Stap 6: Als geen groep match:
566
- - Vind tafels waar: minCapacity <= gasten <= maxCapacity
567
- - Controleer dat tafel vrij is voor ALLE vereiste slots
568
- - Selecteer op basis van:
569
- 1. Kleinste capaciteit die past (minimaliseert verspilling)
570
- 2. Dan priority (lagere waarde eerst)
571
- 3. Dan minCapacity (kleinere tafels eerst)
572
- ```
573
-
574
- **Voorbeeld tafeltoewijzing:**
575
- ```javascript
576
- // Beschikbare tafels:
577
- Tafel 1: min 2, max 4, priority 1
578
- Tafel 2: min 2, max 4, priority 2
579
- Tafel 3: min 4, max 6, priority 1
580
- Tafel 4: min 6, max 8, priority 1
581
-
582
- // Nieuwe reservering: 4 gasten om 12:00
583
-
584
- Stap 1: Filter tafels waar 4 gasten passen
585
- → Tafel 1 ✓ (2-4)
586
- → Tafel 2 ✓ (2-4)
587
- → Tafel 3 ✓ (4-6)
588
- → Tafel 4 ✗ (6-8, te groot - minCapacity > 4)
589
-
590
- Stap 2: Sorteer op capaciteit, dan priority, dan minCapacity
591
- 1. Tafel 1: maxCap 4, priority 1, minCap 2
592
- 2. Tafel 2: maxCap 4, priority 2, minCap 2
593
- 3. Tafel 3: maxCap 6, priority 1, minCap 4
594
-
595
- Stap 3: Controleer beschikbaarheid in volgorde
596
- Tafel 1: Vrij voor alle slots? → JA
597
-
598
- Resultaat: Tafel 1 toegewezen ✓
599
- ```
600
-
601
- ### 3. Vastgepinde Groepen Toewijzing (`grouping.js`)
602
-
603
- Voor grote groepen kunnen meerdere tafels worden gecombineerd.
604
-
605
- ```
606
- tryGroupTables({restaurantSettings, allTables, guests, ...})
607
-
608
- Stap 1: Controleer of groepering ingeschakeld is in grouping-settings
609
-
610
- Stap 2: Valideer dat vastgepinde groepen bestaan en subtables hebben
611
-
612
- Stap 3: Voor elke vastgepinde groep:
613
- a) Haal alle subtables op
614
- b) Controleer: totalMin <= gasten <= totalMax
615
- c) Alloceer gasten:
616
- - Vul eerst minCapacity van elke subtable
617
- - Distribueer resterende gasten
618
- d) Verifieer dat alle subtables vrij zijn voor alle vereiste slots
619
-
620
- Stap 4: Geef gematchte groep terug met tableNumbers en viaGroup identifier
621
- ```
622
-
623
- **Data structuur vastgepinde groepen:**
624
- ```javascript
625
- {
626
- "grouping-settings": {
627
- groupingEnabled: true,
628
- pinnedGroups: [
629
- {
630
- groupId: "group1",
631
- groupName: "Grote Groep Zone A",
632
- subtables: [
633
- { tableNumber: 5, minCapacity: 2, maxCapacity: 4 },
634
- { tableNumber: 6, minCapacity: 2, maxCapacity: 4 },
635
- { tableNumber: 7, minCapacity: 2, maxCapacity: 4 }
636
- ]
637
- }
638
- ]
639
- }
640
- }
641
-
642
- // Totale capaciteit: min 6 (2+2+2), max 12 (4+4+4)
643
- ```
644
-
645
- **Voorbeeld groepstoewijzing:**
646
- ```javascript
647
- // Vastgepinde groep: Tafels 5, 6, 7
648
- Tafel 5: min 2, max 4
649
- Tafel 6: min 2, max 4
650
- Tafel 7: min 2, max 4
651
- Totaal: min 6, max 12
652
-
653
- // Nieuwe reservering: 10 gasten
654
-
655
- Stap 1: Check capaciteit
656
- 10 >= 6 (min) ✓
657
- 10 <= 12 (max) ✓
658
-
659
- Stap 2: Alloceer gasten
660
- Eerst minCapacity vullen:
661
- Tafel 5: 2 gasten (min)
662
- Tafel 6: 2 gasten (min)
663
- Tafel 7: 2 gasten (min)
664
- Totaal: 6 gasten, resterend: 4 gasten
665
-
666
- Dan resterende gasten verdelen:
667
- Tafel 5: +2 gasten → 4 gasten (max bereikt)
668
- Tafel 6: +2 gasten → 4 gasten (max bereikt)
669
- Tafel 7: +0 gasten → 2 gasten
670
- Totaal: 10 gasten ✓
671
-
672
- Stap 3: Controleer beschikbaarheid
673
- Tafel 5 vrij? ✓
674
- Tafel 6 vrij? ✓
675
- Tafel 7 vrij? ✓
676
-
677
- Resultaat: Tafels 5, 6, 7 toegewezen als groep ✓
678
- ```
679
-
680
- ---
681
-
682
- ## Volledige Reserveringsflow
683
-
684
- Hier is de complete flow van een reservering van begin tot eind:
685
-
686
- ```
687
- ┌─────────────────────────────────────────────────────────────┐
688
- │ Gebruiker vraagt beschikbaarheid voor X gasten op datum D │
689
- └────────────────────────┬────────────────────────────────────┘
690
-
691
- ┌─────────────────────────────────────────────────────────────┐
692
- │ STAP 1: isDateAvailableWithTableCheck() │
693
- │ │
694
- │ a) isDateAvailable() → Basis beschikbaarheidscheck │
695
- │ - Controleer dagenInToekomst bereik │
696
- │ - Haal beschikbare timeblocks op │
697
- │ │
698
- │ b) ALS tafeltoewijzing ingeschakeld: │
699
- │ Voor elk beschikbaar timeblock: │
700
- │ - Check timeHasGiftcard() als cadeaubon geselecteerd │
701
- │ - Check isTimeAvailableSync() → kunnen tafels worden │
702
- │ toegewezen? │
703
- │ Return TRUE als ER EEN timeblock beschikbare tafel heeft │
704
- │ │
705
- │ c) Return beschikbaarheidsflag │
706
- └────────────────────────┬────────────────────────────────────┘
707
-
708
- ┌─────────────────────────────────────────────────────────────┐
709
- │ Gebruiker selecteert tijd T op datum D │
710
- └────────────────────────┬────────────────────────────────────┘
711
-
712
- ┌─────────────────────────────────────────────────────────────┐
713
- │ STAP 2: assignTablesIfPossible() │
714
- │ │
715
- │ Probeer vastgepinde groep → │
716
- │ Probeer enkele tafel → │
717
- │ Wijs toe │
718
- └────────────────────────┬────────────────────────────────────┘
719
-
720
- ┌─────────────────────────────────────────────────────────────┐
721
- │ STAP 3: Maak reservering met toegewezen tafel(s) │
722
- └─────────────────────────────────────────────────────────────┘
723
- ```
724
-
725
- ### Gedetailleerde Stappen
726
-
727
- #### **Stap 1a: Basis Beschikbaarheidscheck**
728
-
729
- ```javascript
730
- // Voorbeeld aanroep
731
- isDateAvailable(restaurantData, "2025-01-15", bestaandeReserveringen, 4)
732
-
733
- // Interne flow:
734
- 1. Check: "2025-01-15" binnen 90 dagen? ✓
735
- 2. Roep getAvailableTimeblocks() aan
736
- → Krijg: { "12:00": true, "12:15": true, "12:30": false, ... }
737
- 3. Filter lege resultaten
738
- 4. Return: true (er zijn beschikbare tijden)
739
- ```
740
-
741
- #### **Stap 1b: Tafelbeschikbaarheidscheck** (optioneel)
742
-
743
- ```javascript
744
- // Voor elk beschikbaar tijdsblok
745
- Voor tijd "12:00":
746
- 1. Check: Heeft deze tijd cadeaubon als vereist? ✓
747
- 2. Roep isTimeAvailableSync("2025-01-15", "12:00", 4) aan
748
- → Probeer tafel toe te wijzen (zonder op te slaan)
749
- → Return: true als tafel beschikbaar
750
- 3. Als true: behoud "12:00" in beschikbare lijst
751
- 4. Als false: verwijder "12:00" uit beschikbare lijst
752
-
753
- Uiteindelijk resultaat: Alleen tijden waar zowel capaciteit ALS tafels beschikbaar zijn
754
- ```
755
-
756
- #### **Stap 2: Tafel Toewijzing**
757
-
758
- ```javascript
759
- // Wanneer gebruiker tijd selecteert
760
- assignTablesIfPossible({
761
- db: database,
762
- reservation: {
763
- date: "2025-01-15",
764
- startTime: "12:00",
765
- guests: 4,
766
- durationMinutes: 120
767
- },
768
- enforceTableAvailability: true
769
- })
770
-
771
- // Interne flow:
772
- 1. Bereken slots: ["12:00", "12:15", "12:30", ..., "13:45"]
773
- 2. Haal alle tafels op en filter tijdelijke/applicatie restricties
774
- 3. Bouw occupied slots map voor elke tafel
775
- 4. Probeer groepstoewijzing (als 4 >= min groepsgrootte)
776
- → Geen match gevonden
777
- 5. Probeer enkele tafel:
778
- → Tafel 3 past (min 2, max 4)
779
- → Tafel 3 is vrij voor alle slots ✓
780
- → Wijs Tafel 3 toe
781
- 6. Return: { success: true, assignedTables: [3] }
782
- ```
783
-
784
- #### **Stap 3: Reservering Opslaan**
785
-
786
- ```javascript
787
- // Reservering object
788
- {
789
- id: "res123",
790
- date: "2025-01-15",
791
- startTime: "12:00",
792
- guests: 4,
793
- assignedTables: [3],
794
- customerName: "Jan Janssen",
795
- email: "jan@example.com",
796
- phone: "+31612345678"
797
- }
798
-
799
- // Opgeslagen in database
800
- // Toekomstige beschikbaarheidschecks zullen deze reservering meenemen
801
- ```
802
-
803
- ---
804
-
805
- ## Belangrijke Validatieregels
806
-
807
- Bij elke reserveringsaanvraag worden de volgende regels gecontroleerd:
808
-
809
- ### 1. **Datumvalidatie**
810
- ```
811
- Regel: Datum moet binnen dagenInToekomst bereik liggen
812
- Check: dateStr >= vandaag EN dateStr <= vandaag + dagenInToekomst
813
-
814
- Voorbeeld:
815
- Vandaag: 2025-01-15
816
- dagenInToekomst: 90
817
- Toegestaan bereik: 2025-01-15 tot 2025-04-15
818
-
819
- Aanvraag: 2025-02-01 ✓ (binnen bereik)
820
- Aanvraag: 2025-05-01 ✗ (buiten bereik)
821
- ```
822
-
823
- ### 2. **Tijdvalidatie**
824
- ```
825
- Regel: Tijd moet binnen maaltijd operationele uren vallen (met uitzonderingen)
826
- Check: startTime >= mealStartTime EN startTime < mealEndTime
827
-
828
- Voorbeeld:
829
- Lunch: 12:00 - 15:00
830
-
831
- Aanvraag: 12:00 ✓ (binnen maaltijd)
832
- Aanvraag: 14:30 ✓ (binnen maaltijd)
833
- Aanvraag: 15:00 ✗ (na eindtijd)
834
- ```
835
-
836
- ### 3. **Duurvalidatie**
837
- ```
838
- Regel: Starttijd + duurReservatie moet binnen maaltijd eindtijd passen
839
- Check: startTime + duurReservatie <= mealEndTime
840
-
841
- Voorbeeld:
842
- Lunch eindigt: 15:00
843
- Duur reservering: 120 minuten
844
-
845
- Aanvraag: 12:00 → eindigt 14:00 ✓ (past binnen lunch)
846
- Aanvraag: 13:30 → eindigt 15:30 ✗ (loopt over eindtijd heen)
847
-
848
- Let op: minutenTotEinde (135 min) wordt ook gecontroleerd
849
- Laatste toegestane start: 15:00 - 135 min = 12:45
850
- ```
851
-
852
- ### 4. **Capaciteitsvalidatie**
853
- ```
854
- Regel: Beschikbare stoelen >= aantal gasten voor ALLE opeenvolgende slots
855
- Check: Voor elke vereiste slot, availableSeats >= requestedGuests
856
-
857
- Voorbeeld:
858
- Reservering: 4 gasten om 12:00, duur 120 min
859
- Vereiste slots: 12:00, 12:15, 12:30, 12:45, 13:00, 13:15, 13:30, 13:45
860
-
861
- Beschikbare stoelen per slot:
862
- 12:00 → 6 stoelen ✓
863
- 12:15 → 5 stoelen ✓
864
- 12:30 → 3 stoelen ✗ (TE WEINIG!)
865
-
866
- Resultaat: ✗ AFGEWEZEN (niet alle slots hebben voldoende capaciteit)
867
- ```
868
-
869
- ### 5. **Vooraf Boekingsvalidatie**
870
- ```
871
- Regel: Voor reserveringen VANDAAG, moet starttijd >= uurOpVoorhand uren van nu
872
- Check: startDateTime >= huidigeDateTime + uurOpVoorhand
873
-
874
- Voorbeeld:
875
- Huidige tijd: 10:00
876
- uurOpVoorhand: 4 uren
877
- Minimum starttijd: 14:00
878
-
879
- Aanvraag: 12:00 ✗ (binnen 4 uur venster)
880
- Aanvraag: 14:00 ✓ (exact 4 uur vooruit)
881
- Aanvraag: 15:00 ✓ (meer dan 4 uur vooruit)
882
- ```
883
-
884
- ### 6. **Stoptijdvalidatie**
885
- ```
886
- Regel: Kan niet boeken na ontbijtStop/lunchStop/dinerStop als datum = vandaag
887
- Check: Als dateStr === vandaag, filter tijden > stopTijd voor die maaltijd
888
-
889
- Voorbeeld:
890
- Huidige tijd: 09:30
891
- ontbijtStop: "10:00"
892
- Datum: vandaag
893
-
894
- Beschikbare ontbijttijden:
895
- 09:00 ✗ (in het verleden)
896
- 09:15 ✗ (in het verleden)
897
- 09:30 ✗ (binnen uurOpVoorhand)
898
- 09:45 ✗ (binnen uurOpVoorhand)
899
- 10:00 ✗ (na stopTijd)
900
- 10:15 ✗ (na stopTijd)
901
- ```
902
-
903
- ### 7. **Tafelvalidatie** (indien tafeltoewijzing ingeschakeld)
904
- ```
905
- Regel: Tafel moet vrije slots hebben + capaciteit matchen + aan restricties voldoen
906
- Check:
907
- a) minCapacity <= gasten <= maxCapacity
908
- b) Alle vereiste tijdslots zijn vrij
909
- c) Als tijdelijk: datum binnen startDate/endDate
910
- d) Als application beperkt: maaltijdtype matcht
911
-
912
- Voorbeeld:
913
- Tafel 5: min 2, max 4, application: "dinner", tijdelijk: 01-12 tot 31-12
914
-
915
- Aanvraag: 4 gasten, lunch, 15 december
916
- Capaciteit: 2 <= 4 <= 4 ✓
917
- Datum: 15-12 binnen 01-12 tot 31-12 ✓
918
- Maaltijdtype: lunch ≠ dinner ✗
919
-
920
- Resultaat: ✗ AFGEWEZEN (maaltijdtype restrictie)
921
- ```
922
-
923
- ### 8. **Maximum Aankomsten Validatie**
924
- ```
925
- Regel: Totale aankomsten op exact tijdstip <= maxArrivals voor die tijd
926
- Check: bestaandeAankomsten[tijd] + nieuweGasten <= maxArrivals[tijd]
927
-
928
- Voorbeeld:
929
- maxArrivals lunch 12:00: 10 gasten
930
- Bestaande aankomsten om 12:00: 7 gasten
931
-
932
- Aanvraag: 4 gasten om 12:00
933
- 7 + 4 = 11 > 10 ✗
934
-
935
- Resultaat: ✗ AFGEWEZEN (te veel gelijktijdige aankomsten)
936
- ```
937
-
938
- ### 9. **Maximum Groepen Validatie**
939
- ```
940
- Regel: Aantal groepen van grootte X in maaltijd <= maxGroups voor die grootte
941
- Check: bestaandeGroepen[grootte] + 1 <= maxGroups[grootte]
942
-
943
- Voorbeeld:
944
- maxGroups lunch: { "6": 3, "7": 2 }
945
- Bestaande lunch reserveringen:
946
- - 6 gasten (groep 1)
947
- - 7 gasten (groep 2)
948
- - 4 gasten (telt niet mee)
949
-
950
- Aanvraag: 6 gasten voor lunch
951
- Voor grootte "6+": 2 bestaande + 1 nieuwe = 3 <= 3 ✓
952
-
953
- Resultaat: ✓ TOEGESTAAN (precies aan limiet)
954
-
955
- Volgende aanvraag: nog 6 gasten
956
- Voor grootte "6+": 3 bestaande + 1 nieuwe = 4 > 3 ✗
957
-
958
- Resultaat: ✗ AFGEWEZEN (te veel grote groepen)
959
- ```
960
-
961
- ---
962
-
963
- ## Samenvatting
964
-
965
- Het HappyChef reserveringssysteem is een geavanceerd, meerlagig algoritme dat:
966
-
967
- 1. **Beschikbaarheid berekent** op basis van capaciteit, openingstijden, en bestaande reserveringen
968
- 2. **Meerdere maaltijdtypes ondersteunt** met verschillende configuraties en prioriteiten
969
- 3. **Uitzonderingen afhandelt** zoals feestdagen, sluitingen, en speciale evenementen
970
- 4. **Filters toepast** om ongewenste scenario's te voorkomen (te veel aankomsten, te grote groepen)
971
- 5. **Automatisch tafels toewijst** met ondersteuning voor enkele tafels en gecombineerde groepen
972
- 6. **Intelligent valideert** met 9 verschillende validatieregels
973
-
974
- Het systeem is gebouwd om flexibel, schaalbaar en betrouwbaar te zijn voor moderne restaurantoperaties.
975
-
976
- ---
977
-
978
- ## Bestandslocaties
979
-
980
- Alle belangrijke bestanden bevinden zich in `/home/user/15-happy-algorithm/`:
981
-
982
- - **Hoofdingang**: `index.js`
983
- - **Restaurant data**: `restaurant_data/openinghours.js`, `restaurant_data/exceptions.js`
984
- - **Processing engines**: `processing/timeblocksAvailable.js`, `processing/dailyGuestCounts.js`
985
- - **Filters**: `filters/timeFilter.js`, `filters/maxArrivalsFilter.js`, `filters/maxGroupsFilter.js`
986
- - **Tafelbeheer**: `assignTables.js`, `grouping.js`, `tableHelpers.js`
1
+ # Handleiding Reserveringssysteem HappyChef
2
+
3
+ ## Inhoudsopgave
4
+
5
+ 1. [Overzicht van het Systeem](#overzicht-van-het-systeem)
6
+ 2. [Hoe Beschikbaarheid Wordt Berekend](#hoe-beschikbaarheid-wordt-berekend)
7
+ 3. [Datamodellen en Configuratie](#datamodellen-en-configuratie)
8
+ 4. [Boekingsfilters en Validatie](#boekingsfilters-en-validatie)
9
+ 5. [Tafeltoewijzing](#tafeltoewijzing)
10
+ 6. [Volledige Reserveringsflow](#volledige-reserveringsflow)
11
+ 7. [Belangrijke Validatieregels](#belangrijke-validatieregels)
12
+
13
+ ---
14
+
15
+ ## Overzicht van het Systeem
16
+
17
+ Het HappyChef reserveringssysteem is een gelaagd algoritme voor het berekenen van restaurantbeschikbaarheid en het toewijzen van tafels aan reserveringen. Het systeem bestaat uit de volgende kerncomponenten:
18
+
19
+ ### Bestandsstructuur
20
+
21
+ ```
22
+ ├── Core Beschikbaarheidslogica
23
+ │ ├── isDateAvailable.js - Controleert of een datum beschikbaar is
24
+ │ ├── isTimeAvailable.js - Controleert of een tijdstip beschikbaar is
25
+ │ └── getAvailableTimeblocks.js - Haalt beschikbare tijdsblokken op
26
+
27
+ ├── Processing Engines (processing/)
28
+ │ ├── timeblocksAvailable.js - Kernmotor voor beschikbaarheid
29
+ │ ├── dailyGuestCounts.js - Berekent dagelijkse gastentellingen
30
+ │ └── mealTypeCount.js - Berekent gasten per maaltijdtype
31
+
32
+ ├── Datamodellen (restaurant_data/)
33
+ │ ├── openinghours.js - Openingstijden configuratie
34
+ │ ├── exceptions.js - Uitzonderingen en sluitingen
35
+ │ └── counter.js - Gastentelling functies
36
+
37
+ ├── Boekingsfilters (filters/)
38
+ │ ├── timeFilter.js - Tijdsfilter
39
+ │ ├── maxArrivalsFilter.js - Maximum aankomsten filter
40
+ │ └── maxGroupsFilter.js - Maximum groepen filter
41
+
42
+ └── Tafelbeheer
43
+ ├── assignTables.js - Tafeltoewijzing logica
44
+ ├── grouping.js - Tafelgroepering
45
+ └── tableHelpers.js - Hulpfuncties voor tafels
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Hoe Beschikbaarheid Wordt Berekend
51
+
52
+ De beschikbaarheidberekening gebeurt in meerdere lagen, van datum tot specifiek tijdstip.
53
+
54
+ ### 1. Beschikbaarheid op Datumniveau (`isDateAvailable.js`)
55
+
56
+ ```
57
+ isDateAvailable(data, dateStr, reservations, guests)
58
+
59
+ Stap 1: Controleer of datum binnen toegestane toekomstbereik ligt (dagenInToekomst: standaard 90 dagen)
60
+ Stap 2: Roep getAvailableTimeblocks() aan om beschikbare tijdsslots te vinden
61
+ Stap 3: Geef TRUE terug als er minstens ÉÉN tijdsblok beschikbaar is
62
+ ```
63
+
64
+ **Belangrijke Instellingen:**
65
+ - `dagenInToekomst`: Maximum aantal dagen in de toekomst (standaard: 90)
66
+ - `uurOpVoorhand`: Aantal uren van tevoren vereist om te boeken (standaard: 4)
67
+ - Tijdzone: Europe/Amsterdam
68
+
69
+ ### 2. Tijdsblok Beschikbaarheid (`getAvailableTimeblocks.js`)
70
+
71
+ ```
72
+ getAvailableTimeblocks(data, dateStr, reservations, guests)
73
+
74
+ Stap 1: Valideer dat datum binnen dagenInToekomst bereik ligt
75
+ Stap 2: Voor VANDAAG: Verwijder slots binnen uurOpVoorhand venster
76
+ Stap 3: Roep timeblocksAvailable() aan voor alle potentiële slots
77
+ Stap 4: Geef gefilterd tijdsblok object terug met tijden en beschikbaarheid
78
+ ```
79
+
80
+ **Voorbeeld output:**
81
+ ```javascript
82
+ {
83
+ "12:00": true, // Beschikbaar
84
+ "12:15": true,
85
+ "12:30": false, // Niet beschikbaar
86
+ "12:45": true
87
+ }
88
+ ```
89
+
90
+ ### 3. Kern Beschikbaarheidsmotor (`processing/timeblocksAvailable.js`)
91
+
92
+ Dit is het hart van het systeem en werkt in twee modi:
93
+
94
+ #### **A. SHIFTS MODUS** (indien ingeschakeld)
95
+
96
+ ```
97
+ timeblocksAvailable(data, dateStr, reservations, guests)
98
+
99
+ Stap 1: Controleer maaltijdtypes met shifts ingeschakeld
100
+ Stap 2: Voor elke shift tijd:
101
+ - Tel beschikbare stoelen (maxCapacity - huidigeGasten)
102
+ Stap 3: Geef beschikbare shifts terug waar: beschikbareStoelen >= gasten
103
+ ```
104
+
105
+ **Voorbeeld:**
106
+ ```javascript
107
+ // Shift configuratie
108
+ shifts: [
109
+ { name: "Eerste dienst", time: "12:00" },
110
+ { name: "Tweede dienst", time: "14:00" }
111
+ ]
112
+
113
+ // Berekening voor 4 gasten
114
+ Shift "12:00": maxCapacity (30) - bezet (25) = 5 beschikbaar → ✓ BESCHIKBAAR
115
+ Shift "14:00": maxCapacity (30) - bezet (28) = 2 beschikbaar → ✗ NIET BESCHIKBAAR
116
+ ```
117
+
118
+ #### **B. TIJDSINTERVAL MODUS** (standaard)
119
+
120
+ ```
121
+ timeblocksAvailable(data, dateStr, reservations, guests)
122
+
123
+ Stap 1: Genereer alle tijdslots binnen maaltijdperiodes met regelmatige intervallen
124
+ (intervalReservatie: standaard 15 minuten)
125
+
126
+ Stap 2: Voor elk interval:
127
+ - Tel beschikbare stoelen met getDailyGuestCounts()
128
+ - Controleer of er voldoende opeenvolgende slots bestaan voor volledige reserveringsduur
129
+ (duurReservatie: standaard 120 minuten)
130
+
131
+ Stap 3: Valideer dat starttijd + duur binnen maaltijdtijdvenster past
132
+
133
+ Stap 4: Geef beschikbare tijdsblok objecten terug
134
+ ```
135
+
136
+ **Voorbeeld berekening voor 4 gasten om 12:00:**
137
+ ```
138
+ Reserveringsduur: 120 minuten
139
+ Vereiste slots: 12:00, 12:15, 12:30, 12:45, 13:00, 13:15, 13:30, 13:45
140
+
141
+ Check per slot:
142
+ 12:00 → beschikbaar: 6 stoelen ✓
143
+ 12:15 → beschikbaar: 5 stoelen ✓
144
+ 12:30 → beschikbaar: 3 stoelen ✗ (te weinig!)
145
+
146
+ Resultaat: 12:00 NIET BESCHIKBAAR (niet alle slots hebben voldoende capaciteit)
147
+ ```
148
+
149
+ ### 4. Dagelijkse Gastentelling (`processing/dailyGuestCounts.js`)
150
+
151
+ Deze functie berekent hoeveel gasten er op elk moment van de dag aanwezig zijn.
152
+
153
+ ```
154
+ getDailyGuestCounts(data, dateStr, reservations)
155
+
156
+ Stap 1: Verwerk drie maaltijdtypes: ontbijt, lunch, diner
157
+
158
+ Stap 2: Voor elke maaltijd:
159
+ - Haal gastentellingen op per interval via getGuestCountsForMeal()
160
+
161
+ Stap 3: Merge resultaten op basis van maaltijdprioriteit:
162
+ DINER (hoogste) > LUNCH > ONTBIJT (laagste)
163
+ - Als tijden overlappen tussen maaltijden, wordt capaciteit van
164
+ maaltijd met hoogste prioriteit gebruikt
165
+
166
+ Stap 4: Geef terug:
167
+ - guestCounts: { "12:00": 5, "12:15": 3, ... } (beschikbare stoelen)
168
+ - shiftsInfo: [{ mealType, shiftName, time, availableSeats }, ...]
169
+ ```
170
+
171
+ **Voorbeeld overlapping:**
172
+ ```javascript
173
+ // Lunch eindigt om 15:00, Diner start om 14:00
174
+
175
+ 14:00 → Lunch capaciteit: 25, Diner capaciteit: 30
176
+ Systeem kiest: 30 (diner heeft hogere prioriteit)
177
+
178
+ 14:30 → Lunch capaciteit: 25, Diner capaciteit: 30
179
+ Systeem kiest: 30 (diner heeft hogere prioriteit)
180
+ ```
181
+
182
+ ### 5. Gastentelling per Maaltijdtype (`processing/mealTypeCount.js`)
183
+
184
+ Voor elk maaltijdtype (ontbijt/lunch/diner):
185
+
186
+ ```
187
+ getGuestCountsForMeal(data, dateStr, mealType, reservations)
188
+
189
+ ALS shifts ingeschakeld:
190
+ Voor elke gedefinieerde shift tijd:
191
+ - Tel bezette gasten op dat moment via getGuestCountAtHour()
192
+ - beschikbareStoelen = maxCapacity - bezetteGasten
193
+
194
+ ANDERS (normale intervallen):
195
+ Genereer tijdslots: startTime tot endTime met intervalReservatie intervallen
196
+ Voor elk tijdslot:
197
+ - Tel bezette gasten via getGuestCountAtHour()
198
+ - beschikbareStoelen = maxCapacity - bezetteGasten
199
+ ```
200
+
201
+ ### 6. Gastentelling op Specifiek Uur (`reservation_data/counter.js`)
202
+
203
+ ```
204
+ getGuestCountAtHour(data, reservations, hour, dateStr)
205
+
206
+ Som gasten van alle reserveringen waar:
207
+ - datum === dateStr
208
+ - startTime <= hour < (startTime + duurReservatie)
209
+ ```
210
+
211
+ **Voorbeeld:**
212
+ ```javascript
213
+ // Bestaande reserveringen op 2025-01-15:
214
+ Reservering A: 12:00, 4 gasten, duur 120 min (12:00 - 14:00)
215
+ Reservering B: 12:30, 2 gasten, duur 120 min (12:30 - 14:30)
216
+ Reservering C: 14:00, 6 gasten, duur 120 min (14:00 - 16:00)
217
+
218
+ // Gastentelling per tijdslot:
219
+ 12:00 → 4 gasten (alleen A)
220
+ 12:15 → 4 gasten (alleen A)
221
+ 12:30 → 6 gasten (A + B)
222
+ 12:45 → 6 gasten (A + B)
223
+ 13:00 → 6 gasten (A + B)
224
+ 13:30 → 6 gasten (A + B)
225
+ 14:00 → 8 gasten (B + C, A is beëindigd)
226
+ 14:30 → 8 gasten (B + C)
227
+ 15:00 → 6 gasten (alleen C, B is beëindigd)
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Datamodellen en Configuratie
233
+
234
+ ### 1. Algemene Instellingen (`general-settings`)
235
+
236
+ ```javascript
237
+ {
238
+ zitplaatsen: 30, // Totaal aantal stoelen in restaurant
239
+ uurOpVoorhand: 4, // Aantal uren van tevoren vereist voor reservering
240
+ dagenInToekomst: 90, // Maximaal aantal dagen vooruit boeken
241
+ maxGasten: 8, // Maximum aantal gasten per reservering
242
+ intervalReservatie: 15, // Tijdslot interval in minuten
243
+ duurReservatie: 120, // Duur van reservering in minuten
244
+ minutenTotEinde: 135, // Minuten tot einde van dienst
245
+ ontbijtStop: "10:00", // Stoptijd voor ontbijt (dynamische stop)
246
+ lunchStop: "14:00", // Stoptijd voor lunch
247
+ dinerStop: "20:00" // Stoptijd voor diner
248
+ }
249
+ ```
250
+
251
+ **Uitleg per instelling:**
252
+
253
+ - **zitplaatsen**: Het totaal aantal beschikbare stoelen in het hele restaurant
254
+ - **uurOpVoorhand**: Voorkomt last-minute reserveringen; gasten moeten X uren van tevoren boeken
255
+ - **dagenInToekomst**: Beperkt hoe ver vooruit klanten kunnen reserveren
256
+ - **maxGasten**: Maximum groepsgrootte voor online reserveringen
257
+ - **intervalReservatie**: Hoe vaak nieuwe tijdslots beginnen (bv. 15 min = 12:00, 12:15, 12:30...)
258
+ - **duurReservatie**: Standaard duur van elke reservering
259
+ - **minutenTotEinde**: Buffer tijd aan het einde van dienst (voorkomt te late reserveringen)
260
+ - **ontbijtStop/lunchStop/dinerStop**: Voor reserveringen op VANDAAG - stopt nieuwe boekingen na deze tijd
261
+
262
+ ### 2. Maaltijdconfiguratie (`openinghours-breakfast/lunch/dinner`)
263
+
264
+ ```javascript
265
+ {
266
+ schemeSettings: {
267
+ Monday: {
268
+ enabled: true, // Is deze maaltijd beschikbaar op maandag?
269
+ startTime: "07:00", // Starttijd van maaltijd
270
+ endTime: "11:00", // Eindtijd van maaltijd
271
+ maxCapacity: 30, // Maximum capaciteit voor deze maaltijd
272
+ shiftsEnabled: false, // Gebruik shifts modus?
273
+ shifts: [], // Shift configuraties
274
+ giftcardsEnabled: false, // Cadeaubonnen beschikbaar?
275
+ giftcards: [] // Cadeaubon configuraties
276
+ },
277
+ Tuesday: { /* ... */ },
278
+ Wednesday: { /* ... */ },
279
+ Thursday: { /* ... */ },
280
+ Friday: { /* ... */ },
281
+ Saturday: { /* ... */ },
282
+ Sunday: { /* ... */ }
283
+ }
284
+ }
285
+ ```
286
+
287
+ **Voorbeeld met shifts:**
288
+ ```javascript
289
+ {
290
+ Monday: {
291
+ enabled: true,
292
+ startTime: "18:00",
293
+ endTime: "22:00",
294
+ maxCapacity: 40,
295
+ shiftsEnabled: true,
296
+ shifts: [
297
+ { name: "Vroege dienst", time: "18:00" },
298
+ { name: "Late dienst", time: "20:00" }
299
+ ]
300
+ }
301
+ }
302
+ ```
303
+
304
+ ### 3. Uitzonderingen (`exceptions`)
305
+
306
+ Uitzonderingen overschrijven de normale openingstijden.
307
+
308
+ ```javascript
309
+ {
310
+ type: "Sluiting", // Type: "Sluiting", "Opening", "Uitzondering"
311
+ startDate: "2024-12-25", // Startdatum van uitzondering
312
+ endDate: "2024-12-25", // Einddatum van uitzondering
313
+ daysOfWeek: ["maandag"], // Dagen van de week (optioneel)
314
+ timeframe: "Volledige Dag", // "Volledige Dag" of specifiek maaltijdtype
315
+ startHour: "12:00", // Starttijd (voor gedeeltelijke sluitingen)
316
+ endHour: "15:00", // Eindtijd (voor gedeeltelijke sluitingen)
317
+ maxSeats: 20 // Aangepaste capaciteit (voor "Uitzondering")
318
+ }
319
+ ```
320
+
321
+ **Prioriteitsvolgorde van uitzonderingen:**
322
+ 1. **Opening** (hoogste prioriteit) - Opent het restaurant op normaal gesloten dag
323
+ 2. **Sluiting** - Sluit het restaurant (geheel of gedeeltelijk)
324
+ 3. **Uitzondering** (laagste prioriteit) - Past capaciteit aan zonder te sluiten
325
+
326
+ **Voorbeelden:**
327
+
328
+ ```javascript
329
+ // Voorbeeld 1: Volledige sluiting op Kerstmis
330
+ {
331
+ type: "Sluiting",
332
+ startDate: "2024-12-25",
333
+ endDate: "2024-12-25",
334
+ timeframe: "Volledige Dag"
335
+ }
336
+
337
+ // Voorbeeld 2: Speciale opening op zondag
338
+ {
339
+ type: "Opening",
340
+ startDate: "2024-12-01",
341
+ endDate: "2024-12-01",
342
+ daysOfWeek: ["zondag"],
343
+ timeframe: "Lunch",
344
+ startHour: "12:00",
345
+ endHour: "16:00",
346
+ maxSeats: 25
347
+ }
348
+
349
+ // Voorbeeld 3: Verminderde capaciteit voor privé evenement
350
+ {
351
+ type: "Uitzondering",
352
+ startDate: "2024-11-20",
353
+ endDate: "2024-11-20",
354
+ timeframe: "Diner",
355
+ maxSeats: 15 // In plaats van normale 30
356
+ }
357
+ ```
358
+
359
+ ---
360
+
361
+ ## Boekingsfilters en Validatie
362
+
363
+ Na de basis beschikbaarheidsberekening worden extra filters toegepast om de beschikbare tijdslots verder te verfijnen.
364
+
365
+ ### 1. Tijdsfilter (`filters/timeFilter.js`)
366
+
367
+ Verwijdert tijdslots op basis van stoptijden voor boekingen op dezelfde dag.
368
+
369
+ ```
370
+ Functionaliteit:
371
+ - Verwijdert tijdslots na ontbijtStop/lunchStop/dinerStop
372
+ - Geldt ALLEEN voor reserveringen op VANDAAG
373
+ - Gebruikt Europe/Brussels tijdzone
374
+
375
+ Voorbeeld:
376
+ Huidige tijd: 09:30
377
+ ontbijtStop: "10:00"
378
+ dateStr: vandaag
379
+
380
+ Tijdslots voor ontbijt:
381
+ 09:00 ✗ (verleden)
382
+ 09:15 ✗ (verleden)
383
+ 09:30 ✗ (binnen uurOpVoorhand venster)
384
+ 09:45 ✗ (binnen uurOpVoorhand venster)
385
+ 10:00 ✗ (na ontbijtStop)
386
+ 10:15 ✗ (na ontbijtStop)
387
+ ```
388
+
389
+ ### 2. Maximum Aankomsten Filter (`filters/maxArrivalsFilter.js`)
390
+
391
+ Beperkt het aantal gasten dat op exact hetzelfde tijdstip mag aankomen.
392
+
393
+ ```
394
+ filterTimeblocksByMaxArrivals(restaurantData, date, timeblocks, reservations, guests)
395
+
396
+ Voor elk tijdsblok:
397
+ Stap 1: Bepaal maaltijdtype op basis van tijd
398
+ Stap 2: Haal maxArrivalsConfig[maaltijdtype][tijd] instelling op
399
+ Stap 3: Tel huidige aankomsten op exact deze tijd op deze datum
400
+ Stap 4: Sta alleen toe als: huidigeAankomsten + nieuweGasten <= maxAankomsten
401
+ ```
402
+
403
+ **Data structuur:**
404
+ ```javascript
405
+ {
406
+ "max-arrivals-breakfast": {
407
+ "08:00": 5, // Max 5 gasten mogen om 08:00 aankomen
408
+ "08:15": 5,
409
+ "08:30": 8,
410
+ "08:45": 8
411
+ },
412
+ "max-arrivals-lunch": {
413
+ "12:00": 10, // Max 10 gasten mogen om 12:00 aankomen
414
+ "12:15": 10,
415
+ "12:30": 12,
416
+ "12:45": 12
417
+ },
418
+ "max-arrivals-dinner": {
419
+ "18:00": 15, // Max 15 gasten mogen om 18:00 aankomen
420
+ "18:30": 15,
421
+ "19:00": 12,
422
+ "19:30": 12
423
+ }
424
+ }
425
+ ```
426
+
427
+ **Voorbeeld scenario:**
428
+ ```javascript
429
+ // Bestaande reserveringen op 2025-01-15 om 12:00:
430
+ Reservering A: 12:00, 4 gasten
431
+ Reservering B: 12:00, 3 gasten
432
+ Totaal: 7 gasten
433
+
434
+ // Nieuwe aanvraag: 4 gasten om 12:00
435
+ maxArrivals voor 12:00 lunch: 10
436
+
437
+ Berekening: 7 (bestaand) + 4 (nieuw) = 11 > 10
438
+ Resultaat: ✗ GEWEIGERD (te veel aankomsten op dit tijdstip)
439
+
440
+ // Maar 12:15 is nog beschikbaar:
441
+ Aankomsten om 12:15: 0 gasten
442
+ Berekening: 0 + 4 = 4 ≤ 10
443
+ Resultaat: ✓ TOEGESTAAN
444
+ ```
445
+
446
+ ### 3. Maximum Groepen Filter (`filters/maxGroupsFilter.js`)
447
+
448
+ Beperkt het aantal grote groepen tijdens een maaltijdperiode.
449
+
450
+ ```
451
+ filterTimeblocksByMaxGroups(restaurantData, date, timeblocks, reservations, guests)
452
+
453
+ Voor elk tijdsblok:
454
+ Stap 1: Bepaal maaltijdtype
455
+ Stap 2: Haal max-groups-{maaltijdtype} instellingen op
456
+ Stap 3: Instellingenstructuur: { "6": maxAantal, "7": maxAantal, ... }
457
+ - Definieert: max groepen van grootte >= 6, >= 7, etc.
458
+ Stap 4: Tel bestaande groepen >= gastGrootte in die maaltijd
459
+ Stap 5: Sta alleen toe als: bestaandeGroepen + 1 <= maxToegstaneGroepen
460
+ ```
461
+
462
+ **Data structuur:**
463
+ ```javascript
464
+ {
465
+ "max-groups-breakfast": {
466
+ "6": 2, // Maximaal 2 groepen van 6+ personen
467
+ "7": 1, // Maximaal 1 groep van 7+ personen
468
+ "8": 0 // Geen groepen van 8+ personen toegestaan
469
+ },
470
+ "max-groups-lunch": {
471
+ "6": 3, // Maximaal 3 groepen van 6+ personen
472
+ "7": 2, // Maximaal 2 groepen van 7+ personen
473
+ "8": 1 // Maximaal 1 groep van 8+ personen
474
+ },
475
+ "max-groups-dinner": {
476
+ "5": 5, // Maximaal 5 groepen van 5+ personen
477
+ "6": 3, // Maximaal 3 groepen van 6+ personen
478
+ "7": 2 // Maximaal 2 groepen van 7+ personen
479
+ }
480
+ }
481
+ ```
482
+
483
+ **Voorbeeld scenario:**
484
+ ```javascript
485
+ // Bestaande reserveringen tijdens lunch:
486
+ Reservering A: 6 gasten
487
+ Reservering B: 7 gasten
488
+ Reservering C: 4 gasten (telt niet mee voor "6+" filter)
489
+
490
+ // Nieuwe aanvraag: 6 gasten voor lunch
491
+ max-groups-lunch configuratie: { "6": 3, "7": 2 }
492
+
493
+ Berekening voor grootte "6+":
494
+ Bestaande groepen >= 6: 2 (Reservering A en B)
495
+ Nieuwe groep: 1
496
+ Totaal: 2 + 1 = 3 ≤ 3 (max)
497
+ Resultaat: ✓ TOEGESTAAN
498
+
499
+ // Maar als er een 7e gast bijkomt:
500
+ Nieuwe aanvraag: 7 gasten voor lunch
501
+
502
+ Berekening voor grootte "7+":
503
+ Bestaande groepen >= 7: 1 (Reservering B)
504
+ Nieuwe groep: 1
505
+ Totaal: 1 + 1 = 2 ≤ 2 (max)
506
+ Resultaat: ✓ TOEGESTAAN
507
+ ```
508
+
509
+ **Hoe de drempel werkt:**
510
+ ```javascript
511
+ // Voor een groep van 6 personen:
512
+ Controleert: max-groups["6"] → controleert limiet voor groepen van 6+ personen
513
+
514
+ // Voor een groep van 8 personen:
515
+ Controleert: max-groups["8"], max-groups["7"], max-groups["6"]
516
+ Moet voldoen aan ALLE limieten:
517
+ - Voldoende ruimte binnen limiet voor 8+ groepen
518
+ - Voldoende ruimte binnen limiet voor 7+ groepen
519
+ - Voldoende ruimte binnen limiet voor 6+ groepen
520
+ ```
521
+
522
+ ---
523
+
524
+ ## Tafeltoewijzing
525
+
526
+ Het systeem kan optioneel automatisch tafels toewijzen aan reserveringen.
527
+
528
+ ### 1. Tafel Data Structuur
529
+
530
+ ```javascript
531
+ {
532
+ tableNumber: 5, // Uniek tafelnummer
533
+ tableId: "id123", // Unieke tafel ID
534
+ minCapacity: 2, // Minimum gasten om tafel te vullen
535
+ maxCapacity: 4, // Maximum capaciteit
536
+ priority: 1, // Lagere waarde = hogere prioriteit
537
+ x: 100, // X-coördinaat op plattegrond
538
+ y: 150, // Y-coördinaat op plattegrond
539
+ isTemporary: false, // Is dit een tijdelijke tafel?
540
+ startDate: "2024-12-01", // Startdatum (voor tijdelijke tafels)
541
+ endDate: "2024-12-31", // Einddatum (voor tijdelijke tafels)
542
+ application: "dinner" // Maaltijdtype restrictie (breakfast/lunch/dinner)
543
+ }
544
+ ```
545
+
546
+ ### 2. Enkele Tafel Toewijzing
547
+
548
+ ```
549
+ assignTablesIfPossible({db, reservation, enforceTableAvailability})
550
+
551
+ Stap 1: Bereken vereiste slots op basis van startTime + duurReservatie
552
+ (bv. 12:00 + 120 min = slots: 12:00, 12:15, 12:30, ..., 13:45)
553
+
554
+ Stap 2: Haal alle tafels op van restaurant.floors
555
+
556
+ Stap 3: Valideer tijdelijke tafels:
557
+ - Controleer datum binnen startDate/endDate bereik
558
+ - Controleer maaltijdtype restrictie (application veld)
559
+
560
+ Stap 4: Bouw tableOccupiedSlots: { tableNumber: Set([slot1, slot2, ...]) }
561
+ → Welke tijdslots zijn al bezet per tafel
562
+
563
+ Stap 5: Probeer eerst vastgepinde groepen (via tryGroupTables)
564
+
565
+ Stap 6: Als geen groep match:
566
+ - Vind tafels waar: minCapacity <= gasten <= maxCapacity
567
+ - Controleer dat tafel vrij is voor ALLE vereiste slots
568
+ - Selecteer op basis van:
569
+ 1. Kleinste capaciteit die past (minimaliseert verspilling)
570
+ 2. Dan priority (lagere waarde eerst)
571
+ 3. Dan minCapacity (kleinere tafels eerst)
572
+ ```
573
+
574
+ **Voorbeeld tafeltoewijzing:**
575
+ ```javascript
576
+ // Beschikbare tafels:
577
+ Tafel 1: min 2, max 4, priority 1
578
+ Tafel 2: min 2, max 4, priority 2
579
+ Tafel 3: min 4, max 6, priority 1
580
+ Tafel 4: min 6, max 8, priority 1
581
+
582
+ // Nieuwe reservering: 4 gasten om 12:00
583
+
584
+ Stap 1: Filter tafels waar 4 gasten passen
585
+ → Tafel 1 ✓ (2-4)
586
+ → Tafel 2 ✓ (2-4)
587
+ → Tafel 3 ✓ (4-6)
588
+ → Tafel 4 ✗ (6-8, te groot - minCapacity > 4)
589
+
590
+ Stap 2: Sorteer op capaciteit, dan priority, dan minCapacity
591
+ 1. Tafel 1: maxCap 4, priority 1, minCap 2
592
+ 2. Tafel 2: maxCap 4, priority 2, minCap 2
593
+ 3. Tafel 3: maxCap 6, priority 1, minCap 4
594
+
595
+ Stap 3: Controleer beschikbaarheid in volgorde
596
+ Tafel 1: Vrij voor alle slots? → JA
597
+
598
+ Resultaat: Tafel 1 toegewezen ✓
599
+ ```
600
+
601
+ ### 3. Vastgepinde Groepen Toewijzing (`grouping.js`)
602
+
603
+ Voor grote groepen kunnen meerdere tafels worden gecombineerd.
604
+
605
+ ```
606
+ tryGroupTables({restaurantSettings, allTables, guests, ...})
607
+
608
+ Stap 1: Controleer of groepering ingeschakeld is in grouping-settings
609
+
610
+ Stap 2: Valideer dat vastgepinde groepen bestaan en subtables hebben
611
+
612
+ Stap 3: Voor elke vastgepinde groep:
613
+ a) Haal alle subtables op
614
+ b) Controleer: totalMin <= gasten <= totalMax
615
+ c) Alloceer gasten:
616
+ - Vul eerst minCapacity van elke subtable
617
+ - Distribueer resterende gasten
618
+ d) Verifieer dat alle subtables vrij zijn voor alle vereiste slots
619
+
620
+ Stap 4: Geef gematchte groep terug met tableNumbers en viaGroup identifier
621
+ ```
622
+
623
+ **Data structuur vastgepinde groepen:**
624
+ ```javascript
625
+ {
626
+ "grouping-settings": {
627
+ groupingEnabled: true,
628
+ pinnedGroups: [
629
+ {
630
+ groupId: "group1",
631
+ groupName: "Grote Groep Zone A",
632
+ subtables: [
633
+ { tableNumber: 5, minCapacity: 2, maxCapacity: 4 },
634
+ { tableNumber: 6, minCapacity: 2, maxCapacity: 4 },
635
+ { tableNumber: 7, minCapacity: 2, maxCapacity: 4 }
636
+ ]
637
+ }
638
+ ]
639
+ }
640
+ }
641
+
642
+ // Totale capaciteit: min 6 (2+2+2), max 12 (4+4+4)
643
+ ```
644
+
645
+ **Voorbeeld groepstoewijzing:**
646
+ ```javascript
647
+ // Vastgepinde groep: Tafels 5, 6, 7
648
+ Tafel 5: min 2, max 4
649
+ Tafel 6: min 2, max 4
650
+ Tafel 7: min 2, max 4
651
+ Totaal: min 6, max 12
652
+
653
+ // Nieuwe reservering: 10 gasten
654
+
655
+ Stap 1: Check capaciteit
656
+ 10 >= 6 (min) ✓
657
+ 10 <= 12 (max) ✓
658
+
659
+ Stap 2: Alloceer gasten
660
+ Eerst minCapacity vullen:
661
+ Tafel 5: 2 gasten (min)
662
+ Tafel 6: 2 gasten (min)
663
+ Tafel 7: 2 gasten (min)
664
+ Totaal: 6 gasten, resterend: 4 gasten
665
+
666
+ Dan resterende gasten verdelen:
667
+ Tafel 5: +2 gasten → 4 gasten (max bereikt)
668
+ Tafel 6: +2 gasten → 4 gasten (max bereikt)
669
+ Tafel 7: +0 gasten → 2 gasten
670
+ Totaal: 10 gasten ✓
671
+
672
+ Stap 3: Controleer beschikbaarheid
673
+ Tafel 5 vrij? ✓
674
+ Tafel 6 vrij? ✓
675
+ Tafel 7 vrij? ✓
676
+
677
+ Resultaat: Tafels 5, 6, 7 toegewezen als groep ✓
678
+ ```
679
+
680
+ ---
681
+
682
+ ## Volledige Reserveringsflow
683
+
684
+ Hier is de complete flow van een reservering van begin tot eind:
685
+
686
+ ```
687
+ ┌─────────────────────────────────────────────────────────────┐
688
+ │ Gebruiker vraagt beschikbaarheid voor X gasten op datum D │
689
+ └────────────────────────┬────────────────────────────────────┘
690
+
691
+ ┌─────────────────────────────────────────────────────────────┐
692
+ │ STAP 1: isDateAvailableWithTableCheck() │
693
+ │ │
694
+ │ a) isDateAvailable() → Basis beschikbaarheidscheck │
695
+ │ - Controleer dagenInToekomst bereik │
696
+ │ - Haal beschikbare timeblocks op │
697
+ │ │
698
+ │ b) ALS tafeltoewijzing ingeschakeld: │
699
+ │ Voor elk beschikbaar timeblock: │
700
+ │ - Check timeHasGiftcard() als cadeaubon geselecteerd │
701
+ │ - Check isTimeAvailableSync() → kunnen tafels worden │
702
+ │ toegewezen? │
703
+ │ Return TRUE als ER EEN timeblock beschikbare tafel heeft │
704
+ │ │
705
+ │ c) Return beschikbaarheidsflag │
706
+ └────────────────────────┬────────────────────────────────────┘
707
+
708
+ ┌─────────────────────────────────────────────────────────────┐
709
+ │ Gebruiker selecteert tijd T op datum D │
710
+ └────────────────────────┬────────────────────────────────────┘
711
+
712
+ ┌─────────────────────────────────────────────────────────────┐
713
+ │ STAP 2: assignTablesIfPossible() │
714
+ │ │
715
+ │ Probeer vastgepinde groep → │
716
+ │ Probeer enkele tafel → │
717
+ │ Wijs toe │
718
+ └────────────────────────┬────────────────────────────────────┘
719
+
720
+ ┌─────────────────────────────────────────────────────────────┐
721
+ │ STAP 3: Maak reservering met toegewezen tafel(s) │
722
+ └─────────────────────────────────────────────────────────────┘
723
+ ```
724
+
725
+ ### Gedetailleerde Stappen
726
+
727
+ #### **Stap 1a: Basis Beschikbaarheidscheck**
728
+
729
+ ```javascript
730
+ // Voorbeeld aanroep
731
+ isDateAvailable(restaurantData, "2025-01-15", bestaandeReserveringen, 4)
732
+
733
+ // Interne flow:
734
+ 1. Check: "2025-01-15" binnen 90 dagen? ✓
735
+ 2. Roep getAvailableTimeblocks() aan
736
+ → Krijg: { "12:00": true, "12:15": true, "12:30": false, ... }
737
+ 3. Filter lege resultaten
738
+ 4. Return: true (er zijn beschikbare tijden)
739
+ ```
740
+
741
+ #### **Stap 1b: Tafelbeschikbaarheidscheck** (optioneel)
742
+
743
+ ```javascript
744
+ // Voor elk beschikbaar tijdsblok
745
+ Voor tijd "12:00":
746
+ 1. Check: Heeft deze tijd cadeaubon als vereist? ✓
747
+ 2. Roep isTimeAvailableSync("2025-01-15", "12:00", 4) aan
748
+ → Probeer tafel toe te wijzen (zonder op te slaan)
749
+ → Return: true als tafel beschikbaar
750
+ 3. Als true: behoud "12:00" in beschikbare lijst
751
+ 4. Als false: verwijder "12:00" uit beschikbare lijst
752
+
753
+ Uiteindelijk resultaat: Alleen tijden waar zowel capaciteit ALS tafels beschikbaar zijn
754
+ ```
755
+
756
+ #### **Stap 2: Tafel Toewijzing**
757
+
758
+ ```javascript
759
+ // Wanneer gebruiker tijd selecteert
760
+ assignTablesIfPossible({
761
+ db: database,
762
+ reservation: {
763
+ date: "2025-01-15",
764
+ startTime: "12:00",
765
+ guests: 4,
766
+ durationMinutes: 120
767
+ },
768
+ enforceTableAvailability: true
769
+ })
770
+
771
+ // Interne flow:
772
+ 1. Bereken slots: ["12:00", "12:15", "12:30", ..., "13:45"]
773
+ 2. Haal alle tafels op en filter tijdelijke/applicatie restricties
774
+ 3. Bouw occupied slots map voor elke tafel
775
+ 4. Probeer groepstoewijzing (als 4 >= min groepsgrootte)
776
+ → Geen match gevonden
777
+ 5. Probeer enkele tafel:
778
+ → Tafel 3 past (min 2, max 4)
779
+ → Tafel 3 is vrij voor alle slots ✓
780
+ → Wijs Tafel 3 toe
781
+ 6. Return: { success: true, assignedTables: [3] }
782
+ ```
783
+
784
+ #### **Stap 3: Reservering Opslaan**
785
+
786
+ ```javascript
787
+ // Reservering object
788
+ {
789
+ id: "res123",
790
+ date: "2025-01-15",
791
+ startTime: "12:00",
792
+ guests: 4,
793
+ assignedTables: [3],
794
+ customerName: "Jan Janssen",
795
+ email: "jan@example.com",
796
+ phone: "+31612345678"
797
+ }
798
+
799
+ // Opgeslagen in database
800
+ // Toekomstige beschikbaarheidschecks zullen deze reservering meenemen
801
+ ```
802
+
803
+ ---
804
+
805
+ ## Belangrijke Validatieregels
806
+
807
+ Bij elke reserveringsaanvraag worden de volgende regels gecontroleerd:
808
+
809
+ ### 1. **Datumvalidatie**
810
+ ```
811
+ Regel: Datum moet binnen dagenInToekomst bereik liggen
812
+ Check: dateStr >= vandaag EN dateStr <= vandaag + dagenInToekomst
813
+
814
+ Voorbeeld:
815
+ Vandaag: 2025-01-15
816
+ dagenInToekomst: 90
817
+ Toegestaan bereik: 2025-01-15 tot 2025-04-15
818
+
819
+ Aanvraag: 2025-02-01 ✓ (binnen bereik)
820
+ Aanvraag: 2025-05-01 ✗ (buiten bereik)
821
+ ```
822
+
823
+ ### 2. **Tijdvalidatie**
824
+ ```
825
+ Regel: Tijd moet binnen maaltijd operationele uren vallen (met uitzonderingen)
826
+ Check: startTime >= mealStartTime EN startTime < mealEndTime
827
+
828
+ Voorbeeld:
829
+ Lunch: 12:00 - 15:00
830
+
831
+ Aanvraag: 12:00 ✓ (binnen maaltijd)
832
+ Aanvraag: 14:30 ✓ (binnen maaltijd)
833
+ Aanvraag: 15:00 ✗ (na eindtijd)
834
+ ```
835
+
836
+ ### 3. **Duurvalidatie**
837
+ ```
838
+ Regel: Starttijd + duurReservatie moet binnen maaltijd eindtijd passen
839
+ Check: startTime + duurReservatie <= mealEndTime
840
+
841
+ Voorbeeld:
842
+ Lunch eindigt: 15:00
843
+ Duur reservering: 120 minuten
844
+
845
+ Aanvraag: 12:00 → eindigt 14:00 ✓ (past binnen lunch)
846
+ Aanvraag: 13:30 → eindigt 15:30 ✗ (loopt over eindtijd heen)
847
+
848
+ Let op: minutenTotEinde (135 min) wordt ook gecontroleerd
849
+ Laatste toegestane start: 15:00 - 135 min = 12:45
850
+ ```
851
+
852
+ ### 4. **Capaciteitsvalidatie**
853
+ ```
854
+ Regel: Beschikbare stoelen >= aantal gasten voor ALLE opeenvolgende slots
855
+ Check: Voor elke vereiste slot, availableSeats >= requestedGuests
856
+
857
+ Voorbeeld:
858
+ Reservering: 4 gasten om 12:00, duur 120 min
859
+ Vereiste slots: 12:00, 12:15, 12:30, 12:45, 13:00, 13:15, 13:30, 13:45
860
+
861
+ Beschikbare stoelen per slot:
862
+ 12:00 → 6 stoelen ✓
863
+ 12:15 → 5 stoelen ✓
864
+ 12:30 → 3 stoelen ✗ (TE WEINIG!)
865
+
866
+ Resultaat: ✗ AFGEWEZEN (niet alle slots hebben voldoende capaciteit)
867
+ ```
868
+
869
+ ### 5. **Vooraf Boekingsvalidatie**
870
+ ```
871
+ Regel: Voor reserveringen VANDAAG, moet starttijd >= uurOpVoorhand uren van nu
872
+ Check: startDateTime >= huidigeDateTime + uurOpVoorhand
873
+
874
+ Voorbeeld:
875
+ Huidige tijd: 10:00
876
+ uurOpVoorhand: 4 uren
877
+ Minimum starttijd: 14:00
878
+
879
+ Aanvraag: 12:00 ✗ (binnen 4 uur venster)
880
+ Aanvraag: 14:00 ✓ (exact 4 uur vooruit)
881
+ Aanvraag: 15:00 ✓ (meer dan 4 uur vooruit)
882
+ ```
883
+
884
+ ### 6. **Stoptijdvalidatie**
885
+ ```
886
+ Regel: Kan niet boeken na ontbijtStop/lunchStop/dinerStop als datum = vandaag
887
+ Check: Als dateStr === vandaag, filter tijden > stopTijd voor die maaltijd
888
+
889
+ Voorbeeld:
890
+ Huidige tijd: 09:30
891
+ ontbijtStop: "10:00"
892
+ Datum: vandaag
893
+
894
+ Beschikbare ontbijttijden:
895
+ 09:00 ✗ (in het verleden)
896
+ 09:15 ✗ (in het verleden)
897
+ 09:30 ✗ (binnen uurOpVoorhand)
898
+ 09:45 ✗ (binnen uurOpVoorhand)
899
+ 10:00 ✗ (na stopTijd)
900
+ 10:15 ✗ (na stopTijd)
901
+ ```
902
+
903
+ ### 7. **Tafelvalidatie** (indien tafeltoewijzing ingeschakeld)
904
+ ```
905
+ Regel: Tafel moet vrije slots hebben + capaciteit matchen + aan restricties voldoen
906
+ Check:
907
+ a) minCapacity <= gasten <= maxCapacity
908
+ b) Alle vereiste tijdslots zijn vrij
909
+ c) Als tijdelijk: datum binnen startDate/endDate
910
+ d) Als application beperkt: maaltijdtype matcht
911
+
912
+ Voorbeeld:
913
+ Tafel 5: min 2, max 4, application: "dinner", tijdelijk: 01-12 tot 31-12
914
+
915
+ Aanvraag: 4 gasten, lunch, 15 december
916
+ Capaciteit: 2 <= 4 <= 4 ✓
917
+ Datum: 15-12 binnen 01-12 tot 31-12 ✓
918
+ Maaltijdtype: lunch ≠ dinner ✗
919
+
920
+ Resultaat: ✗ AFGEWEZEN (maaltijdtype restrictie)
921
+ ```
922
+
923
+ ### 8. **Maximum Aankomsten Validatie**
924
+ ```
925
+ Regel: Totale aankomsten op exact tijdstip <= maxArrivals voor die tijd
926
+ Check: bestaandeAankomsten[tijd] + nieuweGasten <= maxArrivals[tijd]
927
+
928
+ Voorbeeld:
929
+ maxArrivals lunch 12:00: 10 gasten
930
+ Bestaande aankomsten om 12:00: 7 gasten
931
+
932
+ Aanvraag: 4 gasten om 12:00
933
+ 7 + 4 = 11 > 10 ✗
934
+
935
+ Resultaat: ✗ AFGEWEZEN (te veel gelijktijdige aankomsten)
936
+ ```
937
+
938
+ ### 9. **Maximum Groepen Validatie**
939
+ ```
940
+ Regel: Aantal groepen van grootte X in maaltijd <= maxGroups voor die grootte
941
+ Check: bestaandeGroepen[grootte] + 1 <= maxGroups[grootte]
942
+
943
+ Voorbeeld:
944
+ maxGroups lunch: { "6": 3, "7": 2 }
945
+ Bestaande lunch reserveringen:
946
+ - 6 gasten (groep 1)
947
+ - 7 gasten (groep 2)
948
+ - 4 gasten (telt niet mee)
949
+
950
+ Aanvraag: 6 gasten voor lunch
951
+ Voor grootte "6+": 2 bestaande + 1 nieuwe = 3 <= 3 ✓
952
+
953
+ Resultaat: ✓ TOEGESTAAN (precies aan limiet)
954
+
955
+ Volgende aanvraag: nog 6 gasten
956
+ Voor grootte "6+": 3 bestaande + 1 nieuwe = 4 > 3 ✗
957
+
958
+ Resultaat: ✗ AFGEWEZEN (te veel grote groepen)
959
+ ```
960
+
961
+ ---
962
+
963
+ ## Samenvatting
964
+
965
+ Het HappyChef reserveringssysteem is een geavanceerd, meerlagig algoritme dat:
966
+
967
+ 1. **Beschikbaarheid berekent** op basis van capaciteit, openingstijden, en bestaande reserveringen
968
+ 2. **Meerdere maaltijdtypes ondersteunt** met verschillende configuraties en prioriteiten
969
+ 3. **Uitzonderingen afhandelt** zoals feestdagen, sluitingen, en speciale evenementen
970
+ 4. **Filters toepast** om ongewenste scenario's te voorkomen (te veel aankomsten, te grote groepen)
971
+ 5. **Automatisch tafels toewijst** met ondersteuning voor enkele tafels en gecombineerde groepen
972
+ 6. **Intelligent valideert** met 9 verschillende validatieregels
973
+
974
+ Het systeem is gebouwd om flexibel, schaalbaar en betrouwbaar te zijn voor moderne restaurantoperaties.
975
+
976
+ ---
977
+
978
+ ## Bestandslocaties
979
+
980
+ Alle belangrijke bestanden bevinden zich in `/home/user/15-happy-algorithm/`:
981
+
982
+ - **Hoofdingang**: `index.js`
983
+ - **Restaurant data**: `restaurant_data/openinghours.js`, `restaurant_data/exceptions.js`
984
+ - **Processing engines**: `processing/timeblocksAvailable.js`, `processing/dailyGuestCounts.js`
985
+ - **Filters**: `filters/timeFilter.js`, `filters/maxArrivalsFilter.js`, `filters/maxGroupsFilter.js`
986
+ - **Tafelbeheer**: `assignTables.js`, `grouping.js`, `tableHelpers.js`