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 0000000..92e39a9 Binary files /dev/null and b/.playwright-mcp/page-2026-05-20T18-32-25-631Z.png differ 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