@happychef/algorithm 1.2.14 → 1.2.15

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,247 +1,247 @@
1
- const {
2
- parseTime,
3
- getMealTypeByTime,
4
- getAllTables,
5
- isTemporaryTableValid,
6
- shifts
7
- } = require('../tableHelpers');
8
-
9
- describe('tableHelpers - parseTime', () => {
10
- test('should parse valid time string correctly', () => {
11
- expect(parseTime('12:30')).toBe(750); // 12*60 + 30 = 750
12
- expect(parseTime('00:00')).toBe(0);
13
- expect(parseTime('23:59')).toBe(1439);
14
- });
15
-
16
- test('should return NaN for invalid time strings', () => {
17
- expect(parseTime('')).toBeNaN();
18
- expect(parseTime(null)).toBeNaN();
19
- expect(parseTime(undefined)).toBeNaN();
20
- expect(parseTime('25:00')).toBeNaN(); // Invalid hour
21
- expect(parseTime('12:60')).toBeNaN(); // Invalid minute
22
- expect(parseTime('12')).toBeNaN(); // Missing colon
23
- expect(parseTime('ab:cd')).toBeNaN(); // Not numbers
24
- });
25
-
26
- test('should handle edge cases', () => {
27
- expect(parseTime('0:0')).toBe(0);
28
- expect(parseTime('1:1')).toBe(61);
29
- });
30
- });
31
-
32
- describe('tableHelpers - getMealTypeByTime', () => {
33
- test('should identify breakfast times correctly', () => {
34
- expect(getMealTypeByTime('07:00')).toBe('breakfast');
35
- expect(getMealTypeByTime('09:30')).toBe('breakfast');
36
- expect(getMealTypeByTime('10:59')).toBe('breakfast');
37
- });
38
-
39
- test('should identify lunch times correctly', () => {
40
- expect(getMealTypeByTime('11:00')).toBe('lunch');
41
- expect(getMealTypeByTime('12:30')).toBe('lunch');
42
- expect(getMealTypeByTime('15:59')).toBe('lunch');
43
- });
44
-
45
- test('should identify dinner times correctly', () => {
46
- expect(getMealTypeByTime('16:00')).toBe('dinner');
47
- expect(getMealTypeByTime('18:30')).toBe('dinner');
48
- expect(getMealTypeByTime('22:59')).toBe('dinner');
49
- });
50
-
51
- test('should return null for times outside defined shifts', () => {
52
- expect(getMealTypeByTime('06:59')).toBeNull();
53
- expect(getMealTypeByTime('23:00')).toBeNull();
54
- expect(getMealTypeByTime('02:00')).toBeNull();
55
- });
56
-
57
- test('should return null for invalid time strings', () => {
58
- expect(getMealTypeByTime('invalid')).toBeNull();
59
- expect(getMealTypeByTime('')).toBeNull();
60
- expect(getMealTypeByTime(null)).toBeNull();
61
- });
62
- });
63
-
64
- describe('tableHelpers - getAllTables', () => {
65
- test('should extract tables correctly from restaurant data', () => {
66
- const restaurantData = {
67
- floors: [
68
- {
69
- tables: [
70
- {
71
- objectType: 'Tafel',
72
- id: 'table1',
73
- tableNumber: 1,
74
- minCapacity: 2,
75
- maxCapacity: 4,
76
- priority: 1,
77
- x: 10,
78
- y: 20,
79
- isTemporary: false
80
- },
81
- {
82
- objectType: 'Tafel',
83
- id: 'table2',
84
- tableNumber: { $numberInt: '2' },
85
- minCapacity: { $numberInt: '4' },
86
- maxCapacity: { $numberInt: '6' },
87
- priority: { $numberInt: '2' },
88
- x: { $numberInt: '30' },
89
- y: { $numberInt: '40' },
90
- isTemporary: false
91
- }
92
- ]
93
- }
94
- ]
95
- };
96
-
97
- const tables = getAllTables(restaurantData);
98
- expect(tables).toHaveLength(2);
99
- expect(tables[0].tableNumber).toBe(1);
100
- expect(tables[0].maxCapacity).toBe(4);
101
- expect(tables[1].tableNumber).toBe(2);
102
- expect(tables[1].maxCapacity).toBe(6);
103
- });
104
-
105
- test('should sort tables by maxCapacity, priority, and minCapacity', () => {
106
- const restaurantData = {
107
- floors: [
108
- {
109
- tables: [
110
- {
111
- objectType: 'Tafel',
112
- id: 'table1',
113
- tableNumber: 1,
114
- minCapacity: 2,
115
- maxCapacity: 6,
116
- priority: 1,
117
- x: 10,
118
- y: 20
119
- },
120
- {
121
- objectType: 'Tafel',
122
- id: 'table2',
123
- tableNumber: 2,
124
- minCapacity: 2,
125
- maxCapacity: 4,
126
- priority: 1,
127
- x: 30,
128
- y: 40
129
- }
130
- ]
131
- }
132
- ]
133
- };
134
-
135
- const tables = getAllTables(restaurantData);
136
- expect(tables[0].tableNumber).toBe(2); // Smaller maxCapacity comes first
137
- expect(tables[1].tableNumber).toBe(1);
138
- });
139
-
140
- test('should handle empty or missing data gracefully', () => {
141
- expect(getAllTables({})).toEqual([]);
142
- expect(getAllTables({ floors: [] })).toEqual([]);
143
- expect(getAllTables(null)).toEqual([]);
144
- });
145
-
146
- test('should filter out non-Tafel objects', () => {
147
- const restaurantData = {
148
- floors: [
149
- {
150
- tables: [
151
- {
152
- objectType: 'Wall',
153
- id: 'wall1',
154
- tableNumber: 1
155
- },
156
- {
157
- objectType: 'Tafel',
158
- id: 'table1',
159
- tableNumber: 2,
160
- minCapacity: 2,
161
- maxCapacity: 4,
162
- priority: 1,
163
- x: 10,
164
- y: 20
165
- }
166
- ]
167
- }
168
- ]
169
- };
170
-
171
- const tables = getAllTables(restaurantData);
172
- expect(tables).toHaveLength(1);
173
- expect(tables[0].tableNumber).toBe(2);
174
- });
175
- });
176
-
177
- describe('tableHelpers - isTemporaryTableValid', () => {
178
- test('should return true for non-temporary tables', () => {
179
- const table = {
180
- tableNumber: 1,
181
- isTemporary: false
182
- };
183
- expect(isTemporaryTableValid(table, '2025-06-15', '12:00')).toBe(true);
184
- });
185
-
186
- test('should validate temporary table within date range and correct meal type', () => {
187
- const table = {
188
- tableNumber: 1,
189
- isTemporary: true,
190
- startDate: '2025-06-01',
191
- endDate: '2025-06-30',
192
- application: 'lunch'
193
- };
194
- expect(isTemporaryTableValid(table, '2025-06-15', '12:00')).toBe(true);
195
- });
196
-
197
- test('should reject temporary table outside date range', () => {
198
- const table = {
199
- tableNumber: 1,
200
- isTemporary: true,
201
- startDate: '2025-06-01',
202
- endDate: '2025-06-30',
203
- application: 'lunch'
204
- };
205
- expect(isTemporaryTableValid(table, '2025-07-01', '12:00')).toBe(false);
206
- expect(isTemporaryTableValid(table, '2025-05-31', '12:00')).toBe(false);
207
- });
208
-
209
- test('should reject temporary table with wrong meal type', () => {
210
- const table = {
211
- tableNumber: 1,
212
- isTemporary: true,
213
- startDate: '2025-06-01',
214
- endDate: '2025-06-30',
215
- application: 'dinner'
216
- };
217
- expect(isTemporaryTableValid(table, '2025-06-15', '12:00')).toBe(false); // lunch time
218
- });
219
-
220
- test('should reject temporary table with missing date range', () => {
221
- const table = {
222
- tableNumber: 1,
223
- isTemporary: true,
224
- application: 'lunch'
225
- };
226
- expect(isTemporaryTableValid(table, '2025-06-15', '12:00')).toBe(false);
227
- });
228
-
229
- test('should reject temporary table with invalid time', () => {
230
- const table = {
231
- tableNumber: 1,
232
- isTemporary: true,
233
- startDate: '2025-06-01',
234
- endDate: '2025-06-30',
235
- application: 'lunch'
236
- };
237
- expect(isTemporaryTableValid(table, '2025-06-15', 'invalid')).toBe(false);
238
- });
239
- });
240
-
241
- describe('tableHelpers - shifts constant', () => {
242
- test('should have correct shift definitions', () => {
243
- expect(shifts.breakfast).toEqual({ start: '07:00', end: '11:00' });
244
- expect(shifts.lunch).toEqual({ start: '11:00', end: '16:00' });
245
- expect(shifts.dinner).toEqual({ start: '16:00', end: '23:00' });
246
- });
247
- });
1
+ const {
2
+ parseTime,
3
+ getMealTypeByTime,
4
+ getAllTables,
5
+ isTemporaryTableValid,
6
+ shifts
7
+ } = require('../tableHelpers');
8
+
9
+ describe('tableHelpers - parseTime', () => {
10
+ test('should parse valid time string correctly', () => {
11
+ expect(parseTime('12:30')).toBe(750); // 12*60 + 30 = 750
12
+ expect(parseTime('00:00')).toBe(0);
13
+ expect(parseTime('23:59')).toBe(1439);
14
+ });
15
+
16
+ test('should return NaN for invalid time strings', () => {
17
+ expect(parseTime('')).toBeNaN();
18
+ expect(parseTime(null)).toBeNaN();
19
+ expect(parseTime(undefined)).toBeNaN();
20
+ expect(parseTime('25:00')).toBeNaN(); // Invalid hour
21
+ expect(parseTime('12:60')).toBeNaN(); // Invalid minute
22
+ expect(parseTime('12')).toBeNaN(); // Missing colon
23
+ expect(parseTime('ab:cd')).toBeNaN(); // Not numbers
24
+ });
25
+
26
+ test('should handle edge cases', () => {
27
+ expect(parseTime('0:0')).toBe(0);
28
+ expect(parseTime('1:1')).toBe(61);
29
+ });
30
+ });
31
+
32
+ describe('tableHelpers - getMealTypeByTime', () => {
33
+ test('should identify breakfast times correctly', () => {
34
+ expect(getMealTypeByTime('07:00')).toBe('breakfast');
35
+ expect(getMealTypeByTime('09:30')).toBe('breakfast');
36
+ expect(getMealTypeByTime('10:59')).toBe('breakfast');
37
+ });
38
+
39
+ test('should identify lunch times correctly', () => {
40
+ expect(getMealTypeByTime('11:00')).toBe('lunch');
41
+ expect(getMealTypeByTime('12:30')).toBe('lunch');
42
+ expect(getMealTypeByTime('15:59')).toBe('lunch');
43
+ });
44
+
45
+ test('should identify dinner times correctly', () => {
46
+ expect(getMealTypeByTime('16:00')).toBe('dinner');
47
+ expect(getMealTypeByTime('18:30')).toBe('dinner');
48
+ expect(getMealTypeByTime('22:59')).toBe('dinner');
49
+ });
50
+
51
+ test('should return null for times outside defined shifts', () => {
52
+ expect(getMealTypeByTime('06:59')).toBeNull();
53
+ expect(getMealTypeByTime('23:00')).toBeNull();
54
+ expect(getMealTypeByTime('02:00')).toBeNull();
55
+ });
56
+
57
+ test('should return null for invalid time strings', () => {
58
+ expect(getMealTypeByTime('invalid')).toBeNull();
59
+ expect(getMealTypeByTime('')).toBeNull();
60
+ expect(getMealTypeByTime(null)).toBeNull();
61
+ });
62
+ });
63
+
64
+ describe('tableHelpers - getAllTables', () => {
65
+ test('should extract tables correctly from restaurant data', () => {
66
+ const restaurantData = {
67
+ floors: [
68
+ {
69
+ tables: [
70
+ {
71
+ objectType: 'Tafel',
72
+ id: 'table1',
73
+ tableNumber: 1,
74
+ minCapacity: 2,
75
+ maxCapacity: 4,
76
+ priority: 1,
77
+ x: 10,
78
+ y: 20,
79
+ isTemporary: false
80
+ },
81
+ {
82
+ objectType: 'Tafel',
83
+ id: 'table2',
84
+ tableNumber: { $numberInt: '2' },
85
+ minCapacity: { $numberInt: '4' },
86
+ maxCapacity: { $numberInt: '6' },
87
+ priority: { $numberInt: '2' },
88
+ x: { $numberInt: '30' },
89
+ y: { $numberInt: '40' },
90
+ isTemporary: false
91
+ }
92
+ ]
93
+ }
94
+ ]
95
+ };
96
+
97
+ const tables = getAllTables(restaurantData);
98
+ expect(tables).toHaveLength(2);
99
+ expect(tables[0].tableNumber).toBe(1);
100
+ expect(tables[0].maxCapacity).toBe(4);
101
+ expect(tables[1].tableNumber).toBe(2);
102
+ expect(tables[1].maxCapacity).toBe(6);
103
+ });
104
+
105
+ test('should sort tables by maxCapacity, priority, and minCapacity', () => {
106
+ const restaurantData = {
107
+ floors: [
108
+ {
109
+ tables: [
110
+ {
111
+ objectType: 'Tafel',
112
+ id: 'table1',
113
+ tableNumber: 1,
114
+ minCapacity: 2,
115
+ maxCapacity: 6,
116
+ priority: 1,
117
+ x: 10,
118
+ y: 20
119
+ },
120
+ {
121
+ objectType: 'Tafel',
122
+ id: 'table2',
123
+ tableNumber: 2,
124
+ minCapacity: 2,
125
+ maxCapacity: 4,
126
+ priority: 1,
127
+ x: 30,
128
+ y: 40
129
+ }
130
+ ]
131
+ }
132
+ ]
133
+ };
134
+
135
+ const tables = getAllTables(restaurantData);
136
+ expect(tables[0].tableNumber).toBe(2); // Smaller maxCapacity comes first
137
+ expect(tables[1].tableNumber).toBe(1);
138
+ });
139
+
140
+ test('should handle empty or missing data gracefully', () => {
141
+ expect(getAllTables({})).toEqual([]);
142
+ expect(getAllTables({ floors: [] })).toEqual([]);
143
+ expect(getAllTables(null)).toEqual([]);
144
+ });
145
+
146
+ test('should filter out non-Tafel objects', () => {
147
+ const restaurantData = {
148
+ floors: [
149
+ {
150
+ tables: [
151
+ {
152
+ objectType: 'Wall',
153
+ id: 'wall1',
154
+ tableNumber: 1
155
+ },
156
+ {
157
+ objectType: 'Tafel',
158
+ id: 'table1',
159
+ tableNumber: 2,
160
+ minCapacity: 2,
161
+ maxCapacity: 4,
162
+ priority: 1,
163
+ x: 10,
164
+ y: 20
165
+ }
166
+ ]
167
+ }
168
+ ]
169
+ };
170
+
171
+ const tables = getAllTables(restaurantData);
172
+ expect(tables).toHaveLength(1);
173
+ expect(tables[0].tableNumber).toBe(2);
174
+ });
175
+ });
176
+
177
+ describe('tableHelpers - isTemporaryTableValid', () => {
178
+ test('should return true for non-temporary tables', () => {
179
+ const table = {
180
+ tableNumber: 1,
181
+ isTemporary: false
182
+ };
183
+ expect(isTemporaryTableValid(table, '2025-06-15', '12:00')).toBe(true);
184
+ });
185
+
186
+ test('should validate temporary table within date range and correct meal type', () => {
187
+ const table = {
188
+ tableNumber: 1,
189
+ isTemporary: true,
190
+ startDate: '2025-06-01',
191
+ endDate: '2025-06-30',
192
+ application: 'lunch'
193
+ };
194
+ expect(isTemporaryTableValid(table, '2025-06-15', '12:00')).toBe(true);
195
+ });
196
+
197
+ test('should reject temporary table outside date range', () => {
198
+ const table = {
199
+ tableNumber: 1,
200
+ isTemporary: true,
201
+ startDate: '2025-06-01',
202
+ endDate: '2025-06-30',
203
+ application: 'lunch'
204
+ };
205
+ expect(isTemporaryTableValid(table, '2025-07-01', '12:00')).toBe(false);
206
+ expect(isTemporaryTableValid(table, '2025-05-31', '12:00')).toBe(false);
207
+ });
208
+
209
+ test('should reject temporary table with wrong meal type', () => {
210
+ const table = {
211
+ tableNumber: 1,
212
+ isTemporary: true,
213
+ startDate: '2025-06-01',
214
+ endDate: '2025-06-30',
215
+ application: 'dinner'
216
+ };
217
+ expect(isTemporaryTableValid(table, '2025-06-15', '12:00')).toBe(false); // lunch time
218
+ });
219
+
220
+ test('should reject temporary table with missing date range', () => {
221
+ const table = {
222
+ tableNumber: 1,
223
+ isTemporary: true,
224
+ application: 'lunch'
225
+ };
226
+ expect(isTemporaryTableValid(table, '2025-06-15', '12:00')).toBe(false);
227
+ });
228
+
229
+ test('should reject temporary table with invalid time', () => {
230
+ const table = {
231
+ tableNumber: 1,
232
+ isTemporary: true,
233
+ startDate: '2025-06-01',
234
+ endDate: '2025-06-30',
235
+ application: 'lunch'
236
+ };
237
+ expect(isTemporaryTableValid(table, '2025-06-15', 'invalid')).toBe(false);
238
+ });
239
+ });
240
+
241
+ describe('tableHelpers - shifts constant', () => {
242
+ test('should have correct shift definitions', () => {
243
+ expect(shifts.breakfast).toEqual({ start: '07:00', end: '11:00' });
244
+ expect(shifts.lunch).toEqual({ start: '11:00', end: '16:00' });
245
+ expect(shifts.dinner).toEqual({ start: '16:00', end: '23:00' });
246
+ });
247
+ });
package/assignTables.js CHANGED
@@ -416,18 +416,19 @@ async function assignTablesIfPossible({
416
416
  return false;
417
417
  }
418
418
 
419
- // 11) Try preferred floor first (if specified), then fall back to all floors
419
+ // 11) If a linked floor is specified for the Zitplaats, ONLY use that floor (no fallback)
420
420
  if (preferredFloorTables && preferredFloorTables.length > 0) {
421
- console.log(`[Zitplaats] Trying preferred floor first (${preferredFloorTables.length} tables)`);
422
- if (tryAssignTables(preferredFloorTables, 'PreferredFloor')) {
423
- return; // Success on preferred floor
421
+ console.log(`[Zitplaats] Trying linked floor only (${preferredFloorTables.length} tables)`);
422
+ if (tryAssignTables(preferredFloorTables, 'LinkedFloor')) {
423
+ return; // Success on linked floor
424
+ }
425
+ console.log(`[Zitplaats] No availability on linked floor - not falling back to other floors`);
426
+ // Do NOT fall back to all floors when a floor link is specified
427
+ } else {
428
+ // 12) Try all floors only when no floor link is specified
429
+ if (tryAssignTables(allTables, 'AllFloors')) {
430
+ return; // Success
424
431
  }
425
- console.log(`[Zitplaats] No availability on preferred floor, falling back to all floors`);
426
- }
427
-
428
- // 12) Try all floors
429
- if (tryAssignTables(allTables, 'AllFloors')) {
430
- return; // Success
431
432
  }
432
433
 
433
434
  // 13) No valid table combo found
@@ -0,0 +1,20 @@
1
+ # PR 11 - Duration
2
+
3
+ **Actions:**
4
+
5
+ ## Changes Summary
6
+ ADDED:
7
+ - New function `duration(start, end)` added to calculate the time difference between two datetime strings.
8
+ - New import `datetime` from the `datetime` module to support date and time operations.
9
+
10
+ NO_REMOVALS
11
+
12
+ CHANGED:
13
+ - Modified the `getAvailableTimeblocks` function to change the calculation of `uurOpVoorhand` from `4` to `0`, affecting the time window for available timeblocks.
14
+ - Updated the `getAvailableTimeblocks` function to adjust the `duration` parameter handling, now using `duration` directly instead of a hardcoded value, impacting the timeblock duration logic.
15
+
16
+ ---
17
+
18
+ **Author:** Fakhar-Rashid
19
+ **Date:** 2026-01-13
20
+ **PR Link:** https://github.com/thibaultvandesompele2/15-happy-algorithm/pull/11
package/jest.config.js CHANGED
@@ -1,23 +1,23 @@
1
- module.exports = {
2
- testEnvironment: 'node',
3
- coverageDirectory: 'coverage',
4
- collectCoverageFrom: [
5
- '**/*.js',
6
- '!**/node_modules/**',
7
- '!**/coverage/**',
8
- '!jest.config.js',
9
- '!test.js',
10
- '!test-*.js',
11
- '!**/__tests__/**'
12
- ],
13
- testMatch: [
14
- '**/__tests__/**/*.test.js'
15
- ],
16
- testPathIgnorePatterns: [
17
- '/node_modules/',
18
- '/test\\.js$',
19
- '/test-.*\\.js$'
20
- ],
21
- verbose: true,
22
- testTimeout: 10000
23
- };
1
+ module.exports = {
2
+ testEnvironment: 'node',
3
+ coverageDirectory: 'coverage',
4
+ collectCoverageFrom: [
5
+ '**/*.js',
6
+ '!**/node_modules/**',
7
+ '!**/coverage/**',
8
+ '!jest.config.js',
9
+ '!test.js',
10
+ '!test-*.js',
11
+ '!**/__tests__/**'
12
+ ],
13
+ testMatch: [
14
+ '**/__tests__/**/*.test.js'
15
+ ],
16
+ testPathIgnorePatterns: [
17
+ '/node_modules/',
18
+ '/test\\.js$',
19
+ '/test-.*\\.js$'
20
+ ],
21
+ verbose: true,
22
+ testTimeout: 10000
23
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.2.14",
3
+ "version": "1.2.15",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "scripts": {