fix: all booking endpoints create new slots for parallel rendering
Build & Push Docker Image / build (push) Successful in 13s

- 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
This commit is contained in:
2026-05-20 22:45:09 +02:00
parent 4a731ab5c6
commit 9cbda65ab0
12 changed files with 309 additions and 31 deletions
@@ -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
@@ -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
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+12 -12
View File
@@ -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
+17 -18
View File
@@ -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);
+8 -1
View File
@@ -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