81 lines
2.4 KiB
TypeScript
81 lines
2.4 KiB
TypeScript
/**
|
|
* 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<Reservation, 'id' | 'startAt' | 'endAt'>[],
|
|
newStart: Date,
|
|
newEnd: Date,
|
|
ignoreReservationId?: string,
|
|
): Pick<Reservation, 'id' | 'startAt' | 'endAt'>[] {
|
|
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.',
|
|
);
|
|
}
|
|
} |