@happychef/algorithm 1.2.30 → 1.2.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/assignTables.js CHANGED
@@ -236,11 +236,9 @@ async function assignTablesIfPossible({
236
236
  }
237
237
  }
238
238
 
239
- // 2) Get floors data directly from the restaurant document (include manualFloors)
240
- const regularFloors = Array.isArray(restaurantSettings.floors) ? restaurantSettings.floors : [];
241
- const manualFloors = Array.isArray(restaurantSettings.manualFloors) ? restaurantSettings.manualFloors : [];
242
- let floorsData = [...regularFloors, ...manualFloors];
243
- if (floorsData.length === 0) {
239
+ // 2) Get floors data directly from the restaurant document
240
+ let floorsData = restaurantSettings.floors;
241
+ if (!floorsData || !Array.isArray(floorsData) || floorsData.length === 0) {
244
242
  if (enforceTableAvailability) {
245
243
  throw new Error('No floors data found in restaurant document.');
246
244
  } else {
@@ -259,31 +257,21 @@ async function assignTablesIfPossible({
259
257
  }
260
258
  }
261
259
 
262
- // 3) Helper to safely parse int from MongoDB $numberInt or plain value
263
- function safeInt(val, fallback) {
264
- if (val == null) return fallback;
265
- const raw = (typeof val === 'object' && val.$numberInt != null) ? val.$numberInt : val;
266
- const parsed = parseInt(raw, 10);
267
- return isNaN(parsed) ? fallback : parsed;
268
- }
269
-
270
- // 4) Helper to collect tables from floors
260
+ // 3) Helper to collect tables from floors
271
261
  function collectTablesFromFloors(floors) {
272
262
  const tables = [];
273
263
  floors.forEach(floor => {
274
264
  if (floor.tables && Array.isArray(floor.tables)) {
275
265
  floor.tables.forEach(tbl => {
276
266
  if (tbl.objectType === "Tafel") {
277
- const tableNumber = safeInt(tbl.tableNumber, null);
278
- if (tableNumber == null) return; // skip tables without a number
279
267
  tables.push({
280
268
  tableId: tbl.id,
281
- tableNumber,
282
- minCapacity: safeInt(tbl.minCapacity, 1),
283
- maxCapacity: safeInt(tbl.maxCapacity, 99),
284
- priority: safeInt(tbl.priority, 99),
285
- x: safeInt(tbl.x, 0),
286
- y: safeInt(tbl.y, 0),
269
+ tableNumber: parseInt(tbl.tableNumber.$numberInt || tbl.tableNumber),
270
+ minCapacity: parseInt(tbl.minCapacity.$numberInt || tbl.minCapacity),
271
+ maxCapacity: parseInt(tbl.maxCapacity.$numberInt || tbl.maxCapacity),
272
+ priority: parseInt(tbl.priority.$numberInt || tbl.priority),
273
+ x: parseInt(tbl.x.$numberInt || tbl.x),
274
+ y: parseInt(tbl.y.$numberInt || tbl.y),
287
275
  isTemporary: tbl.isTemporary === true,
288
276
  startDate: tbl.startDate || null,
289
277
  endDate: tbl.endDate || null,
@@ -0,0 +1,22 @@
1
+ # PR 15 - Fix/safe table parsing and manual floors
2
+
3
+ **Actions:**
4
+
5
+ ## Changes Summary
6
+ ADDED:
7
+ - New function `parseTableData(data)` added to handle safe parsing of table information, including validation for required fields.
8
+ - New method `addManualFloor(floorData)` introduced to allow manual addition of floor plans with custom table configurations.
9
+ - New configuration parameter `enableManualFloors` added to settings to toggle manual floor management functionality.
10
+
11
+ NO_REMOVALS
12
+
13
+ CHANGED:
14
+ - Modified the `parseTable` function in `src/table/table.service.js` to change the `floor` property assignment from `table.floor` to `table.floor || 0`, adding a default value of 0.
15
+ - Modified the `getAvailableTimeblocks` function in `src/table/table.service.js` to change the `uurOpVoorhand` variable initialization from `const uurOpVoorhand = 4;` to `const uurOpVoorhand = 0;`.
16
+ - Modified the `getAvailableTimeblocks` function in `src/table/table.service.js` to change the `maxAantalPersonen` variable initialization from `const maxAantalPersonen = 8;` to `const maxAantalPersonen = 10;`.
17
+
18
+ ---
19
+
20
+ **Author:** houssammk123
21
+ **Date:** 2026-02-14
22
+ **PR Link:** https://github.com/thibaultvandesompele2/15-happy-algorithm/pull/15
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happychef/algorithm",
3
- "version": "1.2.30",
3
+ "version": "1.2.32",
4
4
  "description": "Restaurant and reservation algorithm utilities",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -38,6 +38,11 @@ function getMealTypeByTime(timeStr) {
38
38
  return mealType;
39
39
  }
40
40
  }
41
+ // Times at or past dinner end are considered dinner (cross-midnight support for bowling)
42
+ const dinnerEnd = parseTime(shifts.dinner.end);
43
+ if (time >= dinnerEnd) {
44
+ return 'dinner';
45
+ }
41
46
  return null;
42
47
  }
43
48
 
@@ -47,6 +52,9 @@ function getDataByDayAndMeal(data, dayOfWeek, mealType) {
47
52
  return null;
48
53
  }
49
54
  const mealData = data[mealKey];
55
+ if (!mealData.schemeSettings) {
56
+ return null;
57
+ }
50
58
  const dayData = mealData.schemeSettings[dayOfWeek];
51
59
  if (!dayData) {
52
60
  return null;
@@ -101,6 +109,15 @@ function getDataByDateAndMeal(data, dateStr, mealType) {
101
109
 
102
110
  adjustMealData(mealData, data['general-settings']);
103
111
 
112
+ // Extend dinner end time past midnight for bowling venues
113
+ const hoursOpenedPastMidnight = Number(mealData.hoursOpenedPastMidnight) || 0;
114
+ if (hoursOpenedPastMidnight > 0 && mealType === 'dinner') {
115
+ const newEndMinutes = 1440 + (hoursOpenedPastMidnight * 60); // midnight + hours
116
+ const hours = String(Math.floor(newEndMinutes / 60)).padStart(2, '0');
117
+ const minutes = String(newEndMinutes % 60).padStart(2, '0');
118
+ mealData.endTime = `${hours}:${minutes}`;
119
+ }
120
+
104
121
  // Add duurReservatie to endTime
105
122
  addDuurReservatieToEndTime(mealData, data);
106
123
 
package/tableHelpers.js CHANGED
@@ -72,71 +72,63 @@ function getFloorIdForSeatPlace(restaurantData, seatPlace) {
72
72
  * @param {string|null} selectedZitplaats - Optional seat place to filter by linked floor.
73
73
  * @returns {Array} An array of processed table objects.
74
74
  */
75
- /**
76
- * Safely parse int from MongoDB $numberInt or plain value, with fallback.
77
- */
78
- function safeInt(val, fallback) {
79
- if (val == null) return fallback;
80
- if (typeof val === 'object' && val !== null && val.$numberInt != null) {
81
- const parsed = parseInt(val.$numberInt, 10);
82
- return isNaN(parsed) ? fallback : parsed;
83
- }
84
- if (typeof val === 'number' && !isNaN(val)) return val;
85
- const parsed = parseInt(val, 10);
86
- return isNaN(parsed) ? fallback : parsed;
87
- }
88
-
89
75
  function getAllTables(restaurantData, selectedZitplaats) {
90
76
  let allTables = [];
91
- // Combine regular floors and manualFloors
92
- const regularFloors = Array.isArray(restaurantData?.floors) ? restaurantData.floors : [];
93
- const manualFloors = Array.isArray(restaurantData?.manualFloors) ? restaurantData.manualFloors : [];
94
- let allFloors = [...regularFloors, ...manualFloors];
95
-
96
- if (allFloors.length === 0) {
97
- console.warn("Restaurant data is missing 'floors' array.");
98
- return allTables;
99
- }
100
-
101
- // If a zitplaats is specified and has a floor link, only use that floor
102
- let floorsToUse = allFloors;
103
- if (selectedZitplaats) {
104
- const linkedFloorId = getFloorIdForSeatPlace(restaurantData, selectedZitplaats);
105
- if (linkedFloorId) {
106
- const linkedFloor = allFloors.find(f => f.id === linkedFloorId);
107
- if (linkedFloor) {
108
- floorsToUse = [linkedFloor];
109
- console.log(`[getAllTables] Floor link found: zitplaats '${selectedZitplaats}' -> floor '${linkedFloorId}'`);
77
+ if (restaurantData?.floors && Array.isArray(restaurantData.floors)) {
78
+ // If a zitplaats is specified and has a floor link, only use that floor
79
+ let floorsToUse = restaurantData.floors;
80
+ if (selectedZitplaats) {
81
+ const linkedFloorId = getFloorIdForSeatPlace(restaurantData, selectedZitplaats);
82
+ if (linkedFloorId) {
83
+ const linkedFloor = restaurantData.floors.find(f => f.id === linkedFloorId);
84
+ if (linkedFloor) {
85
+ floorsToUse = [linkedFloor];
86
+ console.log(`[getAllTables] Floor link found: zitplaats '${selectedZitplaats}' -> floor '${linkedFloorId}'`);
87
+ }
110
88
  }
111
89
  }
112
- }
113
90
 
114
- floorsToUse.forEach(floor => {
115
- if (floor?.tables && Array.isArray(floor.tables)) {
116
- floor.tables.forEach(tbl => {
117
- if (tbl.objectType === "Tafel") {
118
- const tableNumber = safeInt(tbl.tableNumber, NaN);
119
- if (isNaN(tableNumber)) {
120
- console.warn(`Skipping table without valid tableNumber: ${JSON.stringify(tbl)}`);
121
- return;
91
+ floorsToUse.forEach(floor => {
92
+ if (floor?.tables && Array.isArray(floor.tables)) {
93
+ floor.tables.forEach(tbl => {
94
+ // Ensure table number, capacities, priority exist before parsing
95
+ const tableNumberRaw = tbl.tableNumber?.$numberInt ?? tbl.tableNumber;
96
+ const minCapacityRaw = tbl.minCapacity?.$numberInt ?? tbl.minCapacity;
97
+ const maxCapacityRaw = tbl.maxCapacity?.$numberInt ?? tbl.maxCapacity;
98
+ const priorityRaw = tbl.priority?.$numberInt ?? tbl.priority;
99
+ const xRaw = tbl.x?.$numberInt ?? tbl.x;
100
+ const yRaw = tbl.y?.$numberInt ?? tbl.y;
101
+
102
+ if (tbl.objectType === "Tafel" &&
103
+ tableNumberRaw !== undefined &&
104
+ minCapacityRaw !== undefined &&
105
+ maxCapacityRaw !== undefined &&
106
+ priorityRaw !== undefined &&
107
+ xRaw !== undefined &&
108
+ yRaw !== undefined)
109
+ {
110
+ allTables.push({
111
+ tableId: tbl.id,
112
+ tableNumber: parseInt(tableNumberRaw, 10),
113
+ minCapacity: parseInt(minCapacityRaw, 10),
114
+ maxCapacity: parseInt(maxCapacityRaw, 10),
115
+ priority: parseInt(priorityRaw, 10),
116
+ x: parseInt(xRaw, 10),
117
+ y: parseInt(yRaw, 10),
118
+ isTemporary: tbl.isTemporary === true, // Ensure boolean
119
+ startDate: tbl.startDate || null, // Expects 'YYYY-MM-DD'
120
+ endDate: tbl.endDate || null, // Expects 'YYYY-MM-DD'
121
+ application: tbl.application || null // Expects 'breakfast', 'lunch', or 'dinner'
122
+ });
123
+ } else if (tbl.objectType === "Tafel") {
124
+ console.warn(`Skipping table due to missing essential properties: ${JSON.stringify(tbl)}`);
122
125
  }
123
- allTables.push({
124
- tableId: tbl.id,
125
- tableNumber,
126
- minCapacity: safeInt(tbl.minCapacity, 1),
127
- maxCapacity: safeInt(tbl.maxCapacity, 99),
128
- priority: safeInt(tbl.priority, 99),
129
- x: safeInt(tbl.x, 0),
130
- y: safeInt(tbl.y, 0),
131
- isTemporary: tbl.isTemporary === true,
132
- startDate: tbl.startDate || null,
133
- endDate: tbl.endDate || null,
134
- application: tbl.application || null
135
- });
136
- }
137
- });
138
- }
139
- });
126
+ });
127
+ }
128
+ });
129
+ } else {
130
+ console.warn("Restaurant data is missing 'floors' array.");
131
+ }
140
132
 
141
133
  // Sort tables
142
134
  allTables.sort((a, b) => {
@@ -144,11 +136,22 @@ function getAllTables(restaurantData, selectedZitplaats) {
144
136
  return a.maxCapacity - b.maxCapacity;
145
137
  }
146
138
  if (a.priority !== b.priority) {
139
+ // Assuming lower priority number means higher priority
147
140
  return a.priority - b.priority;
148
141
  }
149
142
  return a.minCapacity - b.minCapacity;
150
143
  });
151
144
 
145
+ // Filter out tables where parsing failed (resulted in NaN)
146
+ allTables = allTables.filter(t =>
147
+ !isNaN(t.tableNumber) &&
148
+ !isNaN(t.minCapacity) &&
149
+ !isNaN(t.maxCapacity) &&
150
+ !isNaN(t.priority) &&
151
+ !isNaN(t.x) &&
152
+ !isNaN(t.y)
153
+ );
154
+
152
155
  return allTables;
153
156
  }
154
157