@happychef/algorithm 1.4.0 → 1.4.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.
@@ -1,312 +0,0 @@
1
- // __tests__/crossMidnightTimeblocks.test.js
2
- // Tests for timeblocksAvailable cross-midnight slot generation
3
-
4
- jest.mock('../restaurant_data/exceptions', () => ({
5
- getDataByDateAndMealWithExceptions: jest.fn(),
6
- getDataByDateAndTimeWithExceptions: jest.fn(),
7
- }));
8
-
9
- jest.mock('../restaurant_data/openinghours', () => ({
10
- getMealTypeByTime: jest.fn(),
11
- parseTime: jest.fn((timeStr) => {
12
- const [h, m] = timeStr.split(':').map(Number);
13
- return h * 60 + m;
14
- }),
15
- daysOfWeekEnglish: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
16
- }));
17
-
18
- jest.mock('../processing/dailyGuestCounts', () => ({
19
- getDailyGuestCounts: jest.fn(),
20
- }));
21
-
22
- jest.mock('../processing/mealTypeCount', () => ({
23
- getMealTypesWithShifts: jest.fn(),
24
- }));
25
-
26
- jest.mock('../reservation_data/counter', () => ({
27
- getGuestCountAtHour: jest.fn(),
28
- }));
29
-
30
- const { timeblocksAvailable } = require('../processing/timeblocksAvailable');
31
- const { getDataByDateAndMealWithExceptions } = require('../restaurant_data/exceptions');
32
- const { getDailyGuestCounts } = require('../processing/dailyGuestCounts');
33
- const { getMealTypesWithShifts } = require('../processing/mealTypeCount');
34
- const { getGuestCountAtHour: mockGetGuestCountAtHour } = require('../reservation_data/counter');
35
- const { getMealTypeByTime } = require('../restaurant_data/openinghours');
36
-
37
- describe('timeblocksAvailable - cross-midnight', () => {
38
- beforeEach(() => {
39
- jest.clearAllMocks();
40
- });
41
-
42
- // Per-day format: hoursOpenedPastMidnight in openinghours-dinner.schemeSettings
43
- function setupBowlingData(hoursOpenedPastMidnight = 3) {
44
- const days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
45
- const schemeSettings = {};
46
- for (const day of days) {
47
- schemeSettings[day] = { hoursOpenedPastMidnight };
48
- }
49
-
50
- const data = {
51
- 'general-settings': {
52
- duurReservatie: '60',
53
- intervalReservatie: '30',
54
- },
55
- 'openinghours-dinner': {
56
- schemeSettings,
57
- },
58
- };
59
-
60
- // Bowling restaurant: dinner 16:00-24:00
61
- getDataByDateAndMealWithExceptions.mockImplementation((_data, dateStr, mealType) => {
62
- if (mealType === 'dinner') {
63
- return {
64
- enabled: true,
65
- startTime: '16:00',
66
- endTime: '24:00',
67
- maxCapacity: 40,
68
- };
69
- }
70
- return null;
71
- });
72
-
73
- getMealTypesWithShifts.mockReturnValue([]);
74
-
75
- return data;
76
- }
77
-
78
- // Per-day format with different values per day
79
- function setupPerDayData(perDayValues) {
80
- const schemeSettings = {};
81
- for (const [day, val] of Object.entries(perDayValues)) {
82
- schemeSettings[day] = { hoursOpenedPastMidnight: val };
83
- }
84
-
85
- const data = {
86
- 'general-settings': {
87
- duurReservatie: '60',
88
- intervalReservatie: '30',
89
- },
90
- 'openinghours-dinner': {
91
- schemeSettings,
92
- },
93
- };
94
-
95
- getDataByDateAndMealWithExceptions.mockImplementation((_data, dateStr, mealType) => {
96
- if (mealType === 'dinner') {
97
- return {
98
- enabled: true,
99
- startTime: '16:00',
100
- endTime: '24:00',
101
- maxCapacity: 40,
102
- };
103
- }
104
- return null;
105
- });
106
-
107
- getMealTypesWithShifts.mockReturnValue([]);
108
-
109
- return data;
110
- }
111
-
112
- function makeDinnerGuestCounts() {
113
- const guestCounts = {};
114
- for (let t = 960; t <= 1410; t += 30) {
115
- const h = Math.floor(t / 60);
116
- const m = t % 60;
117
- const key = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
118
- guestCounts[key] = 38; // 40 - 2 = 38 seats available
119
- }
120
- return guestCounts;
121
- }
122
-
123
- test('generates late-evening slots when hoursOpenedPastMidnight is set', () => {
124
- const data = setupBowlingData(3);
125
-
126
- getDailyGuestCounts.mockReturnValue({
127
- guestCounts: makeDinnerGuestCounts(),
128
- shiftsInfo: [],
129
- });
130
-
131
- mockGetGuestCountAtHour.mockReturnValue(0);
132
-
133
- // 2025-02-20 is Thursday
134
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, 180);
135
-
136
- // 23:00 + 180min = 02:00, within 3h limit
137
- expect(result['23:00']).toBeDefined();
138
- });
139
-
140
- test('rejects late slot when next-day spillover guests exceed capacity', () => {
141
- const data = setupBowlingData(3);
142
-
143
- getDailyGuestCounts.mockReturnValue({
144
- guestCounts: makeDinnerGuestCounts(),
145
- shiftsInfo: [],
146
- });
147
-
148
- // Next day has 40 guests already (full capacity)
149
- mockGetGuestCountAtHour.mockImplementation((_data, _res, timeStr, dateStr) => {
150
- if (dateStr === '2025-02-21') return 40;
151
- return 0;
152
- });
153
-
154
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, 180);
155
-
156
- expect(result['23:00']).toBeUndefined();
157
- });
158
-
159
- test('rejects cross-midnight when hoursOpenedPastMidnight is 0', () => {
160
- const data = setupBowlingData(0);
161
-
162
- getDailyGuestCounts.mockReturnValue({
163
- guestCounts: makeDinnerGuestCounts(),
164
- shiftsInfo: [],
165
- });
166
-
167
- mockGetGuestCountAtHour.mockReturnValue(0);
168
-
169
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, 180);
170
-
171
- // 23:00 + 180min crosses midnight, but hoursOpenedPastMidnight=0 disallows it
172
- expect(result['23:00']).toBeUndefined();
173
- });
174
-
175
- test('rejects slot when spillover exceeds hoursOpenedPastMidnight limit', () => {
176
- const data = setupBowlingData(1); // Only 1 hour past midnight
177
-
178
- getDailyGuestCounts.mockReturnValue({
179
- guestCounts: makeDinnerGuestCounts(),
180
- shiftsInfo: [],
181
- });
182
-
183
- mockGetGuestCountAtHour.mockReturnValue(0);
184
-
185
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, 180);
186
-
187
- // 23:00 + 180min = 02:00, which is 2h past midnight > 1h limit
188
- expect(result['23:00']).toBeUndefined();
189
- });
190
-
191
- test('per-day: Friday allows cross-midnight, Monday does not', () => {
192
- // Mon-Thu: 0, Fri-Sun: 2
193
- const data = setupPerDayData({
194
- Monday: 0, Tuesday: 0, Wednesday: 0, Thursday: 0,
195
- Friday: 2, Saturday: 2, Sunday: 2,
196
- });
197
-
198
- getDailyGuestCounts.mockReturnValue({
199
- guestCounts: makeDinnerGuestCounts(),
200
- shiftsInfo: [],
201
- });
202
-
203
- mockGetGuestCountAtHour.mockReturnValue(0);
204
-
205
- // 2025-02-21 is Friday
206
- const fridayResult = timeblocksAvailable(data, '2025-02-21', [], 2, [], null, false, 180);
207
- // 23:00 + 180min = 02:00, within 2h limit on Friday
208
- expect(fridayResult['23:00']).toBeDefined();
209
-
210
- // 2025-02-17 is Monday
211
- const mondayResult = timeblocksAvailable(data, '2025-02-17', [], 2, [], null, false, 180);
212
- // 23:00 + 180min = 02:00, but hoursOpenedPastMidnight=0 on Monday
213
- expect(mondayResult['23:00']).toBeUndefined();
214
- });
215
-
216
- test('backward compat: reads from general-settings when dinner schemeSettings missing', () => {
217
- // Old format: hoursOpenedPastMidnight in general-settings only
218
- const data = {
219
- 'general-settings': {
220
- duurReservatie: '60',
221
- intervalReservatie: '30',
222
- hoursOpenedPastMidnight: '3',
223
- },
224
- };
225
-
226
- getDataByDateAndMealWithExceptions.mockImplementation((_data, dateStr, mealType) => {
227
- if (mealType === 'dinner') {
228
- return {
229
- enabled: true,
230
- startTime: '16:00',
231
- endTime: '24:00',
232
- maxCapacity: 40,
233
- };
234
- }
235
- return null;
236
- });
237
-
238
- getMealTypesWithShifts.mockReturnValue([]);
239
-
240
- getDailyGuestCounts.mockReturnValue({
241
- guestCounts: makeDinnerGuestCounts(),
242
- shiftsInfo: [],
243
- });
244
-
245
- mockGetGuestCountAtHour.mockReturnValue(0);
246
-
247
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, 180);
248
-
249
- // Should still work via general-settings fallback
250
- expect(result['23:00']).toBeDefined();
251
- });
252
-
253
- test('non-bowling: 22:30 is last slot, 23:00 not bookable', () => {
254
- const data = {
255
- 'general-settings': {
256
- duurReservatie: '120',
257
- intervalReservatie: '30',
258
- },
259
- };
260
-
261
- getDataByDateAndMealWithExceptions.mockImplementation((_data, dateStr, mealType) => {
262
- if (mealType === 'dinner') {
263
- return {
264
- enabled: true,
265
- startTime: '16:00',
266
- endTime: '25:00', // 23:00 + 120min default (via addDuurReservatieToEndTime)
267
- maxCapacity: 40,
268
- };
269
- }
270
- return null;
271
- });
272
-
273
- getMealTypesWithShifts.mockReturnValue([]);
274
-
275
- // Mimic original production shift boundaries (strict < for end)
276
- getMealTypeByTime.mockImplementation((timeStr) => {
277
- const [h, m] = timeStr.split(':').map(Number);
278
- const time = h * 60 + m;
279
- if (time >= 420 && time < 660) return 'breakfast';
280
- if (time >= 660 && time < 960) return 'lunch';
281
- if (time >= 960 && time < 1380) return 'dinner';
282
- return null;
283
- });
284
-
285
- // guestCounts covers the full extended meal period
286
- const guestCounts = {};
287
- for (let t = 960; t <= 1500; t += 30) {
288
- const h = Math.floor(t / 60);
289
- const m = t % 60;
290
- const key = `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
291
- guestCounts[key] = 38;
292
- }
293
-
294
- getDailyGuestCounts.mockReturnValue({
295
- guestCounts,
296
- shiftsInfo: [],
297
- });
298
-
299
- mockGetGuestCountAtHour.mockReturnValue(0);
300
-
301
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, null);
302
-
303
- // Standard restaurant: 18:00 should be available
304
- expect(result['18:00']).toBeDefined();
305
- // 22:30 is last valid slot (getMealTypeByTime returns 'dinner' for < 23:00)
306
- expect(result['22:30']).toBeDefined();
307
- // 23:00: getMealTypeByTime returns null (strict < 23:00) → not bookable
308
- expect(result['23:00']).toBeUndefined();
309
- expect(result['23:30']).toBeUndefined();
310
- expect(result['24:00']).toBeUndefined();
311
- });
312
- });
@@ -1,271 +0,0 @@
1
- // __tests__/edgeCases.test.js
2
- // Edge case tests for bowling vs non-bowling and per-day hoursOpenedPastMidnight
3
-
4
- jest.mock('../restaurant_data/exceptions', () => ({
5
- getDataByDateAndMealWithExceptions: jest.fn(),
6
- getDataByDateAndTimeWithExceptions: jest.fn(),
7
- }));
8
-
9
- jest.mock('../restaurant_data/openinghours', () => ({
10
- getMealTypeByTime: jest.fn(),
11
- parseTime: jest.fn((timeStr) => {
12
- const [h, m] = timeStr.split(':').map(Number);
13
- return h * 60 + m;
14
- }),
15
- daysOfWeekEnglish: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
16
- }));
17
-
18
- jest.mock('../processing/dailyGuestCounts', () => ({
19
- getDailyGuestCounts: jest.fn(),
20
- }));
21
-
22
- jest.mock('../processing/mealTypeCount', () => ({
23
- getMealTypesWithShifts: jest.fn(),
24
- }));
25
-
26
- jest.mock('../reservation_data/counter', () => ({
27
- getGuestCountAtHour: jest.fn(),
28
- }));
29
-
30
- const { timeblocksAvailable } = require('../processing/timeblocksAvailable');
31
- const { getDataByDateAndMealWithExceptions } = require('../restaurant_data/exceptions');
32
- const { getDailyGuestCounts } = require('../processing/dailyGuestCounts');
33
- const { getMealTypesWithShifts } = require('../processing/mealTypeCount');
34
- const { getGuestCountAtHour: mockGetGuestCountAtHour } = require('../reservation_data/counter');
35
- const { getMealTypeByTime } = require('../restaurant_data/openinghours');
36
-
37
- // Helper: generate guestCounts from startMin to endMin (inclusive) at given interval
38
- function makeGuestCounts(startMin, endMin, interval, available = 38) {
39
- const gc = {};
40
- for (let t = startMin; t <= endMin; t += interval) {
41
- const h = Math.floor(t / 60);
42
- const m = t % 60;
43
- gc[`${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`] = available;
44
- }
45
- return gc;
46
- }
47
-
48
- describe('Edge cases: non-bowling restaurants', () => {
49
- beforeEach(() => {
50
- jest.clearAllMocks();
51
- getMealTypesWithShifts.mockReturnValue([]);
52
- mockGetGuestCountAtHour.mockReturnValue(0);
53
- // Mimic original production shift boundaries (strict < for end)
54
- getMealTypeByTime.mockImplementation((timeStr) => {
55
- const [h, m] = timeStr.split(':').map(Number);
56
- const time = h * 60 + m;
57
- if (time >= 420 && time < 660) return 'breakfast';
58
- if (time >= 660 && time < 960) return 'lunch';
59
- if (time >= 960 && time < 1380) return 'dinner';
60
- return null;
61
- });
62
- });
63
-
64
- test('regular restaurant (no midnight setting): 22:45 is last slot, 23:00 not bookable', () => {
65
- const data = {
66
- 'general-settings': {
67
- duurReservatie: '120',
68
- intervalReservatie: '15',
69
- },
70
- };
71
-
72
- getDataByDateAndMealWithExceptions.mockImplementation((_data, _date, mealType) => {
73
- if (mealType === 'dinner') {
74
- return { enabled: true, startTime: '16:00', endTime: '25:00', maxCapacity: 40 };
75
- }
76
- return null;
77
- });
78
-
79
- // guestCounts 16:00 to 25:00 at 15min interval
80
- getDailyGuestCounts.mockReturnValue({
81
- guestCounts: makeGuestCounts(960, 1500, 15),
82
- shiftsInfo: [],
83
- });
84
-
85
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, null);
86
-
87
- expect(result['22:00']).toBeDefined();
88
- expect(result['22:45']).toBeDefined(); // last valid slot (getMealTypeByTime returns 'dinner' for < 23:00)
89
- expect(result['23:00']).toBeUndefined(); // getMealTypeByTime returns null for 23:00 (strict <)
90
- expect(result['23:15']).toBeUndefined();
91
- expect(result['23:30']).toBeUndefined();
92
- expect(result['24:00']).toBeUndefined();
93
- });
94
-
95
- test('regular restaurant with 15min interval and 60min duration: 22:45 is last slot', () => {
96
- const data = {
97
- 'general-settings': {
98
- duurReservatie: '60',
99
- intervalReservatie: '15',
100
- },
101
- };
102
-
103
- getDataByDateAndMealWithExceptions.mockImplementation((_data, _date, mealType) => {
104
- if (mealType === 'dinner') {
105
- // 23:00 + 60min extension = 24:00
106
- return { enabled: true, startTime: '16:00', endTime: '24:00', maxCapacity: 40 };
107
- }
108
- return null;
109
- });
110
-
111
- getDailyGuestCounts.mockReturnValue({
112
- guestCounts: makeGuestCounts(960, 1440, 15),
113
- shiftsInfo: [],
114
- });
115
-
116
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, null);
117
-
118
- expect(result['22:45']).toBeDefined(); // last valid (getMealTypeByTime returns 'dinner')
119
- expect(result['23:00']).toBeUndefined(); // getMealTypeByTime returns null for 23:00
120
- expect(result['23:15']).toBeUndefined();
121
- expect(result['23:30']).toBeUndefined();
122
- expect(result['24:00']).toBeUndefined();
123
- });
124
-
125
- test('regular restaurant lunch: no slots after lunch closing time', () => {
126
- const data = {
127
- 'general-settings': {
128
- duurReservatie: '120',
129
- intervalReservatie: '15',
130
- },
131
- };
132
-
133
- getDataByDateAndMealWithExceptions.mockImplementation((_data, _date, mealType) => {
134
- if (mealType === 'lunch') {
135
- // lunch 11:00-14:00, extended by 120min to 16:00
136
- return { enabled: true, startTime: '11:00', endTime: '16:00', maxCapacity: 40 };
137
- }
138
- return null;
139
- });
140
-
141
- getDailyGuestCounts.mockReturnValue({
142
- guestCounts: makeGuestCounts(660, 960, 15),
143
- shiftsInfo: [],
144
- });
145
-
146
- const result = timeblocksAvailable(data, '2025-02-20', [], 2, [], null, false, null);
147
-
148
- expect(result['13:45']).toBeDefined();
149
- expect(result['14:00']).toBeDefined(); // original closing
150
- expect(result['14:15']).toBeUndefined(); // past closing
151
- expect(result['15:00']).toBeUndefined();
152
- });
153
- });
154
-
155
- describe('Edge cases: bowling per-day', () => {
156
- beforeEach(() => {
157
- jest.clearAllMocks();
158
- getMealTypesWithShifts.mockReturnValue([]);
159
- mockGetGuestCountAtHour.mockReturnValue(0);
160
- });
161
-
162
- test('Friday=1hr, Saturday=2hr: Saturday allows more spillover than Friday', () => {
163
- const data = {
164
- 'general-settings': { duurReservatie: '60', intervalReservatie: '15' },
165
- 'openinghours-dinner': {
166
- schemeSettings: {
167
- Monday: { hoursOpenedPastMidnight: 0 },
168
- Tuesday: { hoursOpenedPastMidnight: 0 },
169
- Wednesday: { hoursOpenedPastMidnight: 0 },
170
- Thursday: { hoursOpenedPastMidnight: 0 },
171
- Friday: { hoursOpenedPastMidnight: 1 },
172
- Saturday: { hoursOpenedPastMidnight: 2 },
173
- Sunday: { hoursOpenedPastMidnight: 1 },
174
- },
175
- },
176
- };
177
-
178
- getDataByDateAndMealWithExceptions.mockImplementation((_data, _date, mealType) => {
179
- if (mealType === 'dinner') {
180
- return { enabled: true, startTime: '16:00', endTime: '24:00', maxCapacity: 40 };
181
- }
182
- return null;
183
- });
184
-
185
- getDailyGuestCounts.mockReturnValue({
186
- guestCounts: makeGuestCounts(960, 1440, 15),
187
- shiftsInfo: [],
188
- });
189
-
190
- // 2025-02-21 is Friday
191
- const fri = timeblocksAvailable(data, '2025-02-21', [], 2, [], null, false, 90);
192
- // 23:30 + 90min = 01:00, within 1hr limit on Friday
193
- expect(fri['23:30']).toBeDefined();
194
- // 23:45 + 90min = 01:15, exceeds 1hr limit on Friday
195
- expect(fri['23:45']).toBeUndefined();
196
-
197
- // 2025-02-22 is Saturday
198
- const sat = timeblocksAvailable(data, '2025-02-22', [], 2, [], null, false, 90);
199
- // 23:45 + 90min = 01:15, within 2hr limit on Saturday
200
- expect(sat['23:45']).toBeDefined();
201
- });
202
-
203
- test('bowling Monday (hoursOpenedPastMidnight=0): 23:00 with short duration OK, 23:15 not', () => {
204
- const data = {
205
- 'general-settings': { duurReservatie: '60', intervalReservatie: '15' },
206
- 'openinghours-dinner': {
207
- schemeSettings: {
208
- Monday: { hoursOpenedPastMidnight: 0 },
209
- Friday: { hoursOpenedPastMidnight: 1 },
210
- },
211
- },
212
- };
213
-
214
- getDataByDateAndMealWithExceptions.mockImplementation((_data, _date, mealType) => {
215
- if (mealType === 'dinner') {
216
- return { enabled: true, startTime: '16:00', endTime: '24:00', maxCapacity: 40 };
217
- }
218
- return null;
219
- });
220
-
221
- getDailyGuestCounts.mockReturnValue({
222
- guestCounts: makeGuestCounts(960, 1440, 15),
223
- shiftsInfo: [],
224
- });
225
-
226
- // 2025-02-17 is Monday, 60min duration
227
- const result = timeblocksAvailable(data, '2025-02-17', [], 2, [], null, false, 60);
228
-
229
- // 23:00 + 60min = 24:00 (midnight exactly), hoursOpenedPastMidnight=0 allows up to midnight
230
- expect(result['23:00']).toBeDefined();
231
- // 23:15 + 60min = 24:15, past midnight, not allowed
232
- expect(result['23:15']).toBeUndefined();
233
- // 24:00 + 60min = 25:00, past midnight, not allowed
234
- expect(result['24:00']).toBeUndefined();
235
- });
236
-
237
- test('bowling Friday (hoursOpenedPastMidnight=1): 23:30, 24:00 show', () => {
238
- const data = {
239
- 'general-settings': { duurReservatie: '60', intervalReservatie: '15' },
240
- 'openinghours-dinner': {
241
- schemeSettings: {
242
- Monday: { hoursOpenedPastMidnight: 0 },
243
- Friday: { hoursOpenedPastMidnight: 1 },
244
- },
245
- },
246
- };
247
-
248
- getDataByDateAndMealWithExceptions.mockImplementation((_data, _date, mealType) => {
249
- if (mealType === 'dinner') {
250
- return { enabled: true, startTime: '16:00', endTime: '24:00', maxCapacity: 40 };
251
- }
252
- return null;
253
- });
254
-
255
- getDailyGuestCounts.mockReturnValue({
256
- guestCounts: makeGuestCounts(960, 1440, 15),
257
- shiftsInfo: [],
258
- });
259
-
260
- // 2025-02-21 is Friday, 60min duration
261
- const result = timeblocksAvailable(data, '2025-02-21', [], 2, [], null, false, 60);
262
-
263
- expect(result['23:00']).toBeDefined();
264
- expect(result['23:15']).toBeDefined();
265
- expect(result['23:30']).toBeDefined();
266
- expect(result['23:45']).toBeDefined();
267
- expect(result['24:00']).toBeDefined(); // 24:00 + 60 = 25:00, within 1hr? No...
268
-
269
- // Wait: 24:00 + 60min = 25:00 → endMinutes=1500, limit=1440+60=1500 → 1500>1500 false → OK
270
- });
271
- });
@@ -1,20 +0,0 @@
1
- # PR 16 - Add getDateClosingReasons diagnostic function
2
-
3
- **Actions:**
4
-
5
- ## Changes Summary
6
- ADDED:
7
- - New file `getDateClosingReasons.js` containing the diagnostic function `getDateClosingReasons(data, dateStr, reservations, guests, blockedSlots, giftcard, duration, zitplaats)`.
8
- - New exported function `getDateClosingReasons` added to the test exports in `index.js`.
9
- - New diagnostic logic that runs through 10 sequential stages of availability filtering to identify closure causes.
10
- - New supported closure reason constants: `DATE_OUT_OF_RANGE`, `OPENING_HOURS_DISABLED`, `EXCEPTION_CLOSURE`, `CAPACITY_INSUFFICIENT`, `CAPACITY_REACHED`, `GIFTCARD_MISMATCH`, `BLOCKED_SLOTS`, `TODAY_TIME_RESTRICTION`, `MAX_GROUPS`, `MAX_ARRIVALS`, and `TABLE_PLAN`.
11
-
12
- NO_REMOVALS
13
-
14
- NO_CHANGES
15
-
16
- ---
17
-
18
- **Author:** thibaultvandesompele2
19
- **Date:** 2026-02-19
20
- **PR Link:** https://github.com/thibaultvandesompele2/15-happy-algorithm/pull/16
@@ -1,31 +0,0 @@
1
- # PR 16 - Add getDateClosingReasons diagnostic function
2
-
3
- **Actions:**
4
-
5
- ## Changes Summary
6
- ADDED:
7
- - New function `getDateClosingReasons(data, dateStr, reservations, guests, blockedSlots, giftcard, duration, zitplaats)` that diagnoses why a date is closed by running the availability pipeline stage-by-stage and identifying the key bottleneck reason.
8
- - New file `getDateClosingReasons.js` containing the diagnostic logic.
9
- - Exported `getDateClosingReasons` from the package index.
10
-
11
- Supported reason types:
12
- - `DATE_OUT_OF_RANGE` - Date outside booking window
13
- - `OPENING_HOURS_DISABLED` - No meals enabled for this day of the week
14
- - `EXCEPTION_CLOSURE` - Exception rule (e.g. holiday) closes the date
15
- - `CAPACITY_INSUFFICIENT` - Base capacity too small for group size
16
- - `CAPACITY_REACHED` - All time slots fully booked
17
- - `GIFTCARD_MISMATCH` - Selected giftcard not valid on this day
18
- - `BLOCKED_SLOTS` - Manually blocked time slots close remaining availability
19
- - `TODAY_TIME_RESTRICTION` - Advance booking cutoff or stop times passed
20
- - `MAX_GROUPS` - Group size limits reached
21
- - `MAX_ARRIVALS` - Arrival count limits reached
22
- - `TABLE_PLAN` - No table combination available for group size
23
-
24
- NO_REMOVALS
25
-
26
- CHANGED:
27
- - Version bumped from 1.2.31 to 1.3.0.
28
-
29
- ---
30
-
31
- **Date:** 2026-02-19
package/dateHelpers.js DELETED
@@ -1,31 +0,0 @@
1
- // dateHelpers.js — Simple date string arithmetic for cross-midnight support
2
-
3
- /**
4
- * Returns the next day's date string in YYYY-MM-DD format.
5
- * @param {string} dateStr - Date in "YYYY-MM-DD" format.
6
- * @returns {string} Next day in "YYYY-MM-DD" format.
7
- */
8
- function getNextDateStr(dateStr) {
9
- const [y, m, d] = dateStr.split('-').map(Number);
10
- const next = new Date(y, m - 1, d + 1);
11
- const ny = next.getFullYear();
12
- const nm = String(next.getMonth() + 1).padStart(2, '0');
13
- const nd = String(next.getDate()).padStart(2, '0');
14
- return `${ny}-${nm}-${nd}`;
15
- }
16
-
17
- /**
18
- * Returns the previous day's date string in YYYY-MM-DD format.
19
- * @param {string} dateStr - Date in "YYYY-MM-DD" format.
20
- * @returns {string} Previous day in "YYYY-MM-DD" format.
21
- */
22
- function getPrevDateStr(dateStr) {
23
- const [y, m, d] = dateStr.split('-').map(Number);
24
- const prev = new Date(y, m - 1, d - 1);
25
- const py = prev.getFullYear();
26
- const pm = String(prev.getMonth() + 1).padStart(2, '0');
27
- const pd = String(prev.getDate()).padStart(2, '0');
28
- return `${py}-${pm}-${pd}`;
29
- }
30
-
31
- module.exports = { getNextDateStr, getPrevDateStr };