fix: all booking endpoints create new slots for parallel rendering
Build & Push Docker Image / build (push) Successful in 13s
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:
@@ -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
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user