From 9cbda65ab09ff6e53dacfa327b79b4bf7f397d8d Mon Sep 17 00:00:00 2001 From: Lokman Caliskan Date: Wed, 20 May 2026 22:45:09 +0200 Subject: [PATCH] fix: all booking endpoints create new slots for parallel rendering - POST /api/public/bookings: creates new slot instead of reusing - POST /api/bookings/manual: creates new slot instead of reusing - POST /api/slots (with customer): creates new slot for the booking - POST /api/public/book-slot: already fixed previously - All 4 endpoints now produce parallel slot blocks --- .../page-2026-05-20T18-28-03-362Z.yml | 43 +++++++++++++ .../page-2026-05-20T18-29-30-579Z.yml | 37 ++++++++++++ .../page-2026-05-20T18-30-47-955Z.yml | 33 ++++++++++ .../page-2026-05-20T18-32-25-631Z.png | Bin 0 -> 34287 bytes .../page-2026-05-20T19-48-35-519Z.yml | 3 + .../page-2026-05-20T19-49-58-304Z.yml | 33 ++++++++++ .../page-2026-05-20T19-52-53-560Z.yml | 33 ++++++++++ .../page-2026-05-20T19-54-22-699Z.yml | 33 ++++++++++ .../page-2026-05-20T19-54-31-770Z.yml | 57 ++++++++++++++++++ server/src/routes/bookings.ts | 24 ++++---- server/src/routes/public.ts | 35 ++++++----- server/src/routes/slots.ts | 9 ++- 12 files changed, 309 insertions(+), 31 deletions(-) create mode 100644 .playwright-mcp/page-2026-05-20T18-28-03-362Z.yml create mode 100644 .playwright-mcp/page-2026-05-20T18-29-30-579Z.yml create mode 100644 .playwright-mcp/page-2026-05-20T18-30-47-955Z.yml create mode 100644 .playwright-mcp/page-2026-05-20T18-32-25-631Z.png create mode 100644 .playwright-mcp/page-2026-05-20T19-48-35-519Z.yml create mode 100644 .playwright-mcp/page-2026-05-20T19-49-58-304Z.yml create mode 100644 .playwright-mcp/page-2026-05-20T19-52-53-560Z.yml create mode 100644 .playwright-mcp/page-2026-05-20T19-54-22-699Z.yml create mode 100644 .playwright-mcp/page-2026-05-20T19-54-31-770Z.yml diff --git a/.playwright-mcp/page-2026-05-20T18-28-03-362Z.yml b/.playwright-mcp/page-2026-05-20T18-28-03-362Z.yml new file mode 100644 index 0000000..34fd26d --- /dev/null +++ b/.playwright-mcp/page-2026-05-20T18-28-03-362Z.yml @@ -0,0 +1,43 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - heading "Update-Termin buchen" [level=1] [ref=e6] + - paragraph [ref=e7]: Klicken Sie auf einen freien Slot zur Buchung + - generic [ref=e8]: + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e13]: + - button "‹" [ref=e14] [cursor=pointer] + - generic [ref=e15]: KW 21 – 18. – 22. Mai 2026 + - button "›" [ref=e16] [cursor=pointer] + - generic [ref=e18]: + - generic [ref=e21]: Mo 18. + - generic [ref=e23]: Di 19. + - generic [ref=e25]: Mi 20. + - generic [ref=e27]: Do 21. + - generic [ref=e29]: Fr 22. + - generic [ref=e30]: + - generic [ref=e31]: 08:00 + - generic [ref=e32]: 09:00 + - generic [ref=e33]: 10:00 + - generic [ref=e34]: 11:00 + - generic [ref=e35]: 12:00 + - generic [ref=e36]: 13:00 + - generic [ref=e37]: 14:00 + - generic [ref=e38]: 15:00 + - generic [ref=e39]: 16:00 + - generic [ref=e42] [cursor=pointer]: + - generic [ref=e43]: + - generic [ref=e45]: Update 2026-05-20 + - generic [ref=e48]: Belegt + - generic [ref=e51]: Belegt + - generic [ref=e54]: Test + - generic [ref=e56] [cursor=pointer]: + - generic [ref=e58]: Update 2026-05-21 + - generic [ref=e61]: Belegt + - generic [ref=e64]: Belegt + - generic [ref=e66]: + - generic [ref=e67]: Frei + - generic [ref=e69]: Belegt + - generic [ref=e71]: Ausstehend + - generic [ref=e73]: Bestätigt + - paragraph [ref=e77]: Wählen Sie einen freien Slot im Kalender \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-20T18-29-30-579Z.yml b/.playwright-mcp/page-2026-05-20T18-29-30-579Z.yml new file mode 100644 index 0000000..8c312a7 --- /dev/null +++ b/.playwright-mcp/page-2026-05-20T18-29-30-579Z.yml @@ -0,0 +1,37 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - heading "Update-Termin buchen" [level=1] [ref=e6] + - paragraph [ref=e7]: Klicken Sie auf einen freien Slot zur Buchung + - generic [ref=e8]: + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e13]: + - button "‹" [ref=e14] [cursor=pointer] + - generic [ref=e15]: KW 22 – 25. – 29. Mai 2026 + - button "›" [active] [ref=e16] [cursor=pointer] + - generic [ref=e18]: + - generic [ref=e79]: Mo 25. + - generic [ref=e81]: Di 26. + - generic [ref=e83]: Mi 27. + - generic [ref=e85]: Do 28. + - generic [ref=e87]: Fr 29. + - generic [ref=e30]: + - generic [ref=e31]: 08:00 + - generic [ref=e32]: 09:00 + - generic [ref=e33]: 10:00 + - generic [ref=e34]: 11:00 + - generic [ref=e35]: 12:00 + - generic [ref=e36]: 13:00 + - generic [ref=e37]: 14:00 + - generic [ref=e38]: 15:00 + - generic [ref=e39]: 16:00 + - generic [ref=e88] [cursor=pointer]: + - generic [ref=e91]: Windows Update + - generic [ref=e94]: Windows Update + - generic [ref=e97]: Server Patch + - generic [ref=e66]: + - generic [ref=e67]: Frei + - generic [ref=e69]: Belegt + - generic [ref=e71]: Ausstehend + - generic [ref=e73]: Bestätigt + - paragraph [ref=e77]: Wählen Sie einen freien Slot im Kalender \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-20T18-30-47-955Z.yml b/.playwright-mcp/page-2026-05-20T18-30-47-955Z.yml new file mode 100644 index 0000000..b0036c1 --- /dev/null +++ b/.playwright-mcp/page-2026-05-20T18-30-47-955Z.yml @@ -0,0 +1,33 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - heading "Update-Termin buchen" [level=1] [ref=e6] + - paragraph [ref=e7]: Klicken Sie auf einen freien Slot zur Buchung + - generic [ref=e8]: + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e13]: + - button "‹" [ref=e14] [cursor=pointer] + - generic [ref=e15]: KW 23 – 1. – 5. Juni 2026 + - button "›" [active] [ref=e16] [cursor=pointer] + - generic [ref=e18]: + - generic [ref=e103]: Mo 01. + - generic [ref=e105]: Di 02. + - generic [ref=e107]: Mi 03. + - generic [ref=e109]: Do 04. + - generic [ref=e111]: Fr 05. + - generic [ref=e30]: + - generic [ref=e31]: 08:00 + - generic [ref=e32]: 09:00 + - generic [ref=e33]: 10:00 + - generic [ref=e34]: 11:00 + - generic [ref=e35]: 12:00 + - generic [ref=e36]: 13:00 + - generic [ref=e37]: 14:00 + - generic [ref=e38]: 15:00 + - generic [ref=e39]: 16:00 + - generic [ref=e66]: + - generic [ref=e67]: Frei + - generic [ref=e69]: Belegt + - generic [ref=e71]: Ausstehend + - generic [ref=e73]: Bestätigt + - paragraph [ref=e77]: Wählen Sie einen freien Slot im Kalender \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-20T18-32-25-631Z.png b/.playwright-mcp/page-2026-05-20T18-32-25-631Z.png new file mode 100644 index 0000000000000000000000000000000000000000..92e39a9ad36668917fc136abf0a0ce0be011f5ae GIT binary patch literal 34287 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 literal 0 HcmV?d00001 diff --git a/.playwright-mcp/page-2026-05-20T19-48-35-519Z.yml b/.playwright-mcp/page-2026-05-20T19-48-35-519Z.yml new file mode 100644 index 0000000..c69a127 --- /dev/null +++ b/.playwright-mcp/page-2026-05-20T19-48-35-519Z.yml @@ -0,0 +1,3 @@ +- generic [ref=e5]: + - heading "Update-Termin buchen" [level=1] [ref=e6] + - paragraph [ref=e7]: Klicken Sie auf einen freien Slot zur Buchung \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-20T19-49-58-304Z.yml b/.playwright-mcp/page-2026-05-20T19-49-58-304Z.yml new file mode 100644 index 0000000..3b6b671 --- /dev/null +++ b/.playwright-mcp/page-2026-05-20T19-49-58-304Z.yml @@ -0,0 +1,33 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - heading "Update-Termin buchen" [level=1] [ref=e6] + - paragraph [ref=e7]: Klicken Sie auf einen freien Slot zur Buchung + - generic [ref=e8]: + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e13]: + - button "‹" [ref=e14] [cursor=pointer] + - generic [ref=e15]: KW 23 – 1. – 5. Juni 2026 + - button "›" [active] [ref=e16] [cursor=pointer] + - generic [ref=e18]: + - generic [ref=e21]: Mo 01. + - generic [ref=e23]: Di 02. + - generic [ref=e25]: Mi 03. + - generic [ref=e27]: Do 04. + - generic [ref=e29]: Fr 05. + - generic [ref=e30]: + - generic [ref=e31]: 08:00 + - generic [ref=e32]: 09:00 + - generic [ref=e33]: 10:00 + - generic [ref=e34]: 11:00 + - generic [ref=e35]: 12:00 + - generic [ref=e36]: 13:00 + - generic [ref=e37]: 14:00 + - generic [ref=e38]: 15:00 + - generic [ref=e39]: 16:00 + - generic [ref=e45]: + - generic [ref=e46]: Frei + - generic [ref=e48]: Belegt + - generic [ref=e50]: Ausstehend + - generic [ref=e52]: Bestätigt + - paragraph [ref=e56]: Wählen Sie einen freien Slot im Kalender \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-20T19-52-53-560Z.yml b/.playwright-mcp/page-2026-05-20T19-52-53-560Z.yml new file mode 100644 index 0000000..0d87e85 --- /dev/null +++ b/.playwright-mcp/page-2026-05-20T19-52-53-560Z.yml @@ -0,0 +1,33 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - heading "Update-Termin buchen" [level=1] [ref=e6] + - paragraph [ref=e7]: Klicken Sie auf einen freien Slot zur Buchung + - generic [ref=e8]: + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e13]: + - button "‹" [ref=e14] [cursor=pointer] + - generic [ref=e15]: KW 24 – 8. – 12. Juni 2026 + - button "›" [active] [ref=e16] [cursor=pointer] + - generic [ref=e18]: + - generic [ref=e58]: Mo 08. + - generic [ref=e60]: Di 09. + - generic [ref=e62]: Mi 10. + - generic [ref=e64]: Do 11. + - generic [ref=e66]: Fr 12. + - generic [ref=e30]: + - generic [ref=e31]: 08:00 + - generic [ref=e32]: 09:00 + - generic [ref=e33]: 10:00 + - generic [ref=e34]: 11:00 + - generic [ref=e35]: 12:00 + - generic [ref=e36]: 13:00 + - generic [ref=e37]: 14:00 + - generic [ref=e38]: 15:00 + - generic [ref=e39]: 16:00 + - generic [ref=e45]: + - generic [ref=e46]: Frei + - generic [ref=e48]: Belegt + - generic [ref=e50]: Ausstehend + - generic [ref=e52]: Bestätigt + - paragraph [ref=e56]: Wählen Sie einen freien Slot im Kalender \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-20T19-54-22-699Z.yml b/.playwright-mcp/page-2026-05-20T19-54-22-699Z.yml new file mode 100644 index 0000000..c789a59 --- /dev/null +++ b/.playwright-mcp/page-2026-05-20T19-54-22-699Z.yml @@ -0,0 +1,33 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - heading "Update-Termin buchen" [level=1] [ref=e6] + - paragraph [ref=e7]: Klicken Sie auf einen freien Slot zur Buchung + - generic [ref=e8]: + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e13]: + - button "‹" [active] [ref=e14] [cursor=pointer] + - generic [ref=e15]: KW 23 – 1. – 5. Juni 2026 + - button "›" [ref=e16] [cursor=pointer] + - generic [ref=e18]: + - generic [ref=e73]: Mo 01. + - generic [ref=e75]: Di 02. + - generic [ref=e77]: Mi 03. + - generic [ref=e79]: Do 04. + - generic [ref=e81]: Fr 05. + - generic [ref=e30]: + - generic [ref=e31]: 08:00 + - generic [ref=e32]: 09:00 + - generic [ref=e33]: 10:00 + - generic [ref=e34]: 11:00 + - generic [ref=e35]: 12:00 + - generic [ref=e36]: 13:00 + - generic [ref=e37]: 14:00 + - generic [ref=e38]: 15:00 + - generic [ref=e39]: 16:00 + - generic [ref=e45]: + - generic [ref=e46]: Frei + - generic [ref=e48]: Belegt + - generic [ref=e50]: Ausstehend + - generic [ref=e52]: Bestätigt + - paragraph [ref=e56]: Wählen Sie einen freien Slot im Kalender \ No newline at end of file diff --git a/.playwright-mcp/page-2026-05-20T19-54-31-770Z.yml b/.playwright-mcp/page-2026-05-20T19-54-31-770Z.yml new file mode 100644 index 0000000..255b88a --- /dev/null +++ b/.playwright-mcp/page-2026-05-20T19-54-31-770Z.yml @@ -0,0 +1,57 @@ +- generic [ref=e4]: + - generic [ref=e5]: + - heading "Update-Termin buchen" [level=1] [ref=e6] + - paragraph [ref=e7]: Klicken Sie auf einen freien Slot zur Buchung + - generic [ref=e8]: + - generic [ref=e9]: + - generic [ref=e10]: + - generic [ref=e13]: + - button "‹" [active] [ref=e14] [cursor=pointer] + - generic [ref=e15]: KW 22 – 25. – 29. Mai 2026 + - button "›" [ref=e16] [cursor=pointer] + - generic [ref=e18]: + - generic [ref=e88]: Mo 25. + - generic [ref=e90]: Di 26. + - generic [ref=e92]: Mi 27. + - generic [ref=e94]: Do 28. + - generic [ref=e96]: Fr 29. + - generic [ref=e30]: + - generic [ref=e31]: 08:00 + - generic [ref=e32]: 09:00 + - generic [ref=e33]: 10:00 + - generic [ref=e34]: 11:00 + - generic [ref=e35]: 12:00 + - generic [ref=e36]: 13:00 + - generic [ref=e37]: 14:00 + - generic [ref=e38]: 15:00 + - generic [ref=e39]: 16:00 + - generic [ref=e97] [cursor=pointer]: + - generic [ref=e100]: Windows Update + - generic [ref=e103]: Windows Update + - generic [ref=e106]: Server Patch + - generic [ref=e107]: + - generic [ref=e109]: Update 2026-05-25 + - generic [ref=e110]: + - generic: Belegt + - generic [ref=e112]: + - generic [ref=e114]: Update 2026-05-25 + - generic [ref=e115]: + - generic: Belegt + - generic [ref=e119] [cursor=pointer]: + - generic [ref=e121]: Update 2026-05-27 + - generic [ref=e124]: Belegt + - generic [ref=e125] [cursor=pointer]: + - generic [ref=e126]: + - generic [ref=e128]: Update 2026-05-28 + - generic [ref=e131]: Belegt + - generic [ref=e134]: Belegt + - generic [ref=e135]: + - generic [ref=e137]: Update 2026-05-28 + - generic [ref=e140]: Belegt + - generic [ref=e143]: Belegt + - generic [ref=e45]: + - generic [ref=e46]: Frei + - generic [ref=e48]: Belegt + - generic [ref=e50]: Ausstehend + - generic [ref=e52]: Bestätigt + - paragraph [ref=e56]: Wählen Sie einen freien Slot im Kalender \ No newline at end of file diff --git a/server/src/routes/bookings.ts b/server/src/routes/bookings.ts index 55e61cb..785d9ea 100644 --- a/server/src/routes/bookings.ts +++ b/server/src/routes/bookings.ts @@ -34,31 +34,31 @@ bookingsRouter.get('/', (req, res) => { res.json(bookings); }); -// IT/AM manually create booking for a customer +// IT/AM manually create booking for a customer → always creates a NEW slot bookingsRouter.post('/manual', (req, res) => { const { slot_id, customer_email, customer_name, customer_company, customer_location, cc_email, notes } = req.body; if (!slot_id || !customer_name) { return res.status(400).json({ error: 'slot_id and customer_name required' }); } const db = getDb(); - const slot = db.prepare('SELECT * FROM slots WHERE id = ?').get(slot_id) as any; - if (!slot) return res.status(404).json({ error: 'Slot not found' }); + const originalSlot = db.prepare('SELECT * FROM slots WHERE id = ?').get(slot_id) as any; + if (!originalSlot) return res.status(404).json({ error: 'Slot not found' }); - const booked = db.prepare( - `SELECT COUNT(*) as c FROM bookings WHERE slot_id = ? AND status IN ('pending','confirmed')` - ).get(slot_id) as any; - if (booked.c >= slot.max_bookings) { - return res.status(409).json({ error: 'Slot fully booked' }); - } - - const overlapErr = checkOverlapSlot(db, slot.date, slot.start_time, slot.end_time, slot_id, getDayCapacity(db, slot.date)); + const dayCap = getDayCapacity(db, originalSlot.date); + const overlapErr = checkOverlapSlot(db, originalSlot.date, originalSlot.start_time, originalSlot.end_time, undefined, dayCap); if (overlapErr) return res.status(409).json({ error: overlapErr }); + // Create a new parallel slot + const r = db.prepare( + `INSERT INTO slots (title, date, start_time, end_time, max_bookings, created_by) VALUES (?, ?, ?, ?, ?, ?)` + ).run(originalSlot.title, originalSlot.date, originalSlot.start_time, originalSlot.end_time, dayCap, 0); + const newSlotId = Number(r.lastInsertRowid); + const bookingToken = uuidv4(); db.prepare( `INSERT INTO bookings (slot_id, customer_email, customer_name, customer_company, customer_location, cc_email, notes, token) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` - ).run(slot_id, customer_email, customer_name, customer_company || '', customer_location || '', cc_email || '', notes || '', bookingToken); + ).run(newSlotId, customer_email, customer_name, customer_company || '', customer_location || '', cc_email || '', notes || '', bookingToken); const booking = db.prepare(` SELECT b.*, s.title as slot_title, s.date as slot_date, s.start_time, s.end_time diff --git a/server/src/routes/public.ts b/server/src/routes/public.ts index d927631..5f6cf92 100644 --- a/server/src/routes/public.ts +++ b/server/src/routes/public.ts @@ -67,37 +67,36 @@ publicRouter.post('/bookings', (req, res) => { const db = getDb(); - const slot = db.prepare(` + const originalSlot = db.prepare(` SELECT s.*, (SELECT COUNT(*) FROM bookings b WHERE b.slot_id = s.id AND b.status IN ('pending','confirmed')) as total_booked FROM slots s WHERE s.id = ? `).get(slot_id) as any; - if (!slot) return res.status(404).json({ error: 'Slot not found' }); + if (!originalSlot) return res.status(404).json({ error: 'Slot not found' }); - const dayCapPub = getDayCapacity(db, slot.date); - if (slot.total_booked >= dayCapPub) { - return res.status(409).json({ error: 'Slot fully booked' }); - } - const overlapErr = checkOverlapSlot(db, slot.date, slot.start_time, slot.end_time, slot_id, dayCapPub); + const dayCapPub = getDayCapacity(db, originalSlot.date); + const overlapErr = checkOverlapSlot(db, originalSlot.date, originalSlot.start_time, originalSlot.end_time, undefined, dayCapPub); if (overlapErr) return res.status(409).json({ error: overlapErr }); + // Create a new parallel slot + const r = db.prepare( + `INSERT INTO slots (title, date, start_time, end_time, max_bookings, created_by) VALUES (?, ?, ?, ?, ?, ?)` + ).run(originalSlot.title, originalSlot.date, originalSlot.start_time, originalSlot.end_time, dayCapPub, 0); + const newSlotId = Number(r.lastInsertRowid); + const bookingToken = uuidv4(); - const result = db.prepare(` - INSERT INTO bookings (slot_id, customer_email, customer_name, customer_company, customer_location, cc_email, notes, token) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - `).run(slot_id, customer_email, customer_name, customer_company || '', customer_location || '', cc_email || '', notes || '', bookingToken); + db.prepare( + `INSERT INTO bookings (slot_id, customer_email, customer_name, customer_company, customer_location, cc_email, notes, token) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)` + ).run(newSlotId, customer_email, customer_name, customer_company || '', customer_location || '', cc_email || '', notes || '', bookingToken); const booking = db.prepare(` SELECT b.*, s.title as slot_title, s.date as slot_date, s.start_time, s.end_time - FROM bookings b JOIN slots s ON b.slot_id = s.id WHERE b.id = ? - `).get(result.lastInsertRowid) as any; + FROM bookings b JOIN slots s ON b.slot_id = s.id WHERE b.token = ? + `).get(bookingToken) as any; - try { - sendBookingReceivedEmail(booking); - } catch (e) { - console.error('Email failed:', e); - } + try { sendBookingReceivedEmail(booking); } catch (e) { console.error('Email failed:', e); } try { sendWebhook({ event: 'booking.created', booking }); } catch (e) { console.error('Webhook:', e); } res.status(201).json(booking); diff --git a/server/src/routes/slots.ts b/server/src/routes/slots.ts index aad9535..ff0936b 100644 --- a/server/src/routes/slots.ts +++ b/server/src/routes/slots.ts @@ -70,10 +70,17 @@ slotsRouter.post('/', (req, res) => { // If customer data provided, also create a booking and update response if (customer_name && customer_email) { const bookingToken = uuidv4(); + // Also create a NEW slot (don't reuse) so it appears as parallel block + const bookingDayCap = getDayCapacity(db, date); + const bookingSlotR = db.prepare( + `INSERT INTO slots (title, date, start_time, end_time, max_bookings, blocked_count, assigned_to, created_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` + ).run(title, date, start_time, end_time, bookingDayCap, blocked_count || 0, assigned_to || null, session.user.id); + const bookingSlotId = Number(bookingSlotR.lastInsertRowid); + db.prepare( `INSERT INTO bookings (slot_id, customer_email, customer_name, customer_company, customer_location, cc_email, notes, token) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` - ).run(slotId, customer_email, customer_name, customer_company || '', customer_location || '', cc_email || '', notes || '', bookingToken); + ).run(bookingSlotId, customer_email, customer_name, customer_company || '', customer_location || '', cc_email || '', notes || '', bookingToken); const booking = db.prepare(` SELECT b.*, s.title as slot_title, s.date as slot_date, s.start_time, s.end_time