/** * Konfliktpruefung fuer Reservierungen (plan.md Abschnitt 6). * * Zwei Zeitraume kollidieren, wenn sie sich ueberlappen: * existing.start < new.end UND new.start < existing.end * * Die Pruefung MUSS serverseitig innerhalb einer Transaktion mit * konsistentem Isolation-Level erfolgen, damit gleichzeitige Buchungen * zweier Benutzer nicht zu einer Doppelbuchung fuehren koennen. */ import type { Reservation } from '@prisma/client'; /** Fehler, der bei einer Zeitkollision geworfen wird. */ export class ReservationConflictError extends Error { constructor() { super('Der gewuenschte Zeitraum ist bereits belegt.'); this.name = 'ReservationConflictError'; } } /** Fehler, wenn der Zeitraum ungueltig ist (Vergangenheit, Start>=Ende). */ export class ReservationValidationError extends Error { constructor(message: string) { super(message); this.name = 'ReservationValidationError'; } } /** Prueft rein funktional, ob sich zwei Zeitraume ueberlappen. */ export function intervalsOverlap( aStart: Date, aEnd: Date, bStart: Date, bEnd: Date, ): boolean { return aStart < bEnd && bStart < aEnd; } /** * Findet kollidierende Reservierungen eines Kalenders im Zeitraum. * Rein funktional und damit gut testbar; die Datenbankabfrage wird * als Parameter injiziert. */ export function findConflicts( existing: Pick[], newStart: Date, newEnd: Date, ignoreReservationId?: string, ): Pick[] { return existing.filter( (reservation) => reservation.id !== ignoreReservationId && intervalsOverlap(reservation.startAt, reservation.endAt, newStart, newEnd), ); } /** * Validiert, dass Start vor Ende liegt und der Zeitraum nicht in der * Vergangenheit beginnt. Wirft sonst eine aussagekraeftige Fehlermeldung. */ export function validateReservationPeriod( startAt: Date, endAt: Date, now: Date = new Date(), ): void { if (Number.isNaN(startAt.getTime()) || Number.isNaN(endAt.getTime())) { throw new ReservationValidationError( 'Start- und Endzeit muessen gueltige Zeitpunkte sein.', ); } if (startAt >= endAt) { throw new ReservationValidationError( 'Die Startzeit muss vor der Endzeit liegen.', ); } if (startAt < now) { throw new ReservationValidationError( 'Der Zeitraum liegt in der Vergangenheit.', ); } }