bookings.ts

56 lines2.1 KBOct 21, 2026, 09:14 AM

import { z } from './schema';
import { db } from './db';
​
/** Opening hours per weekday, 0 is Sunday */
export const HOURS = {
1: ['08:00', '18:00'],
2: ['08:00', '18:00'],
3: ['08:00', '18:00'],
4: ['08:00', '21:00'],
5: ['08:00', '22:00'],
6: ['09:00', '22:00'],
0: ['10:00', '17:00'],
} as const;
​
export interface Booking {
id: string;
name: string;
guests: number;
starts: Date;
table?: number;
}
​
const BookingInput = z.object({
name: z.string().min(2),
guests: z.number().int().min(1).max(12),
starts: z.coerce.date(),
});
​
/** True when the café is open for the whole visit */
export function isOpen(starts: Date, minutes = 90) {
const day = HOURS[starts.getDay() as keyof typeof HOURS];
const [from, to] = day.map((t) => Number(t.replace(':', '')));
const begin = starts.getHours() * 100 + starts.getMinutes();
const end = begin + Math.floor(minutes / 60) * 100 + (minutes % 60);
return begin >= from && end <= to;
}
​
export async function findTable(guests: number, starts: Date) {
const taken = await db.bookings.tablesAt(starts);
const tables = await db.tables.withSeats(guests);
return tables.find((t) => !taken.includes(t.id)) ?? null;
}
​
export async function createBooking(input: unknown): Promise<Booking> {
const data = BookingInput.parse(input);
if (!isOpen(data.starts)) throw new Error('closed');
const table = await findTable(data.guests, data.starts);
// Large groups get a call back instead of a table right away
if (!table || data.guests > 8) return db.bookings.request(data);
return db.bookings.insert({ ...data, table: table.id });
}
​
export async function cancelBooking(id: string) {
await db.bookings.update(id, { cancelled: true });
return { ok: true };
}
Ln 1, Col 1Spaces 2UTF-8TS