// Admin smoke-test: take a booking on behalf of a customer end-to-end. // // Usage: // pnpm tsx scripts/book-on-behalf.ts // // Example: // pnpm tsx scripts/book-on-behalf.ts alex@example.com "60-minute Swedish" 2026-05-05T10:00 // // Walks: lookup → loadAvailabilityState → findSlots → pick first candidate // pair → createHold → confirmHold → sendBookingConfirmation. import "dotenv/config"; import { fromZonedTime } from "date-fns-tz"; import { addMinutes } from "date-fns"; import { PrismaPg } from "@prisma/adapter-pg"; import { PrismaClient } from "../src/generated/prisma/client"; import { findSlots } from "../src/lib/availability"; import { loadAvailabilityState } from "../src/lib/availability-loader"; import { confirmHold, createHold } from "../src/lib/booking"; import { sendBookingConfirmation } from "../src/lib/email"; import { scheduleReminderForBooking } from "../src/lib/reminders"; import { stopJobs } from "../src/lib/jobs"; async function main() { const [customerEmail, serviceName, localIso] = process.argv.slice(2); if (!customerEmail || !serviceName || !localIso) { console.error( "usage: pnpm tsx scripts/book-on-behalf.ts ", ); process.exit(2); } const tz = process.env.APP_TZ ?? "America/Detroit"; const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL }); const db = new PrismaClient({ adapter }); try { const customer = await db.user.findUnique({ where: { email: customerEmail }, include: { customer: true }, }); if (!customer || !customer.customer) { throw new Error(`No customer found for email: ${customerEmail}`); } const service = await db.service.findFirst({ where: { name: serviceName, active: true }, }); if (!service) throw new Error(`No active service named: ${serviceName}`); const startsAt = fromZonedTime(localIso, tz); const window = { from: startsAt, to: addMinutes(startsAt, service.durationMin + 1), }; const state = await loadAvailabilityState(db, { from: window.from, to: window.to, serviceId: service.id, }); if (!state) throw new Error("Could not load availability state"); const slots = findSlots({ service: state.service, therapists: state.therapists, rooms: state.rooms, practiceTz: tz, from: window.from, to: window.to, }); const slot = slots.find((s) => s.startsAt.getTime() === startsAt.getTime()); if (!slot) { console.error(`Requested slot ${localIso} (${startsAt.toISOString()}) is not available.`); console.error(`Found ${slots.length} slot(s) in window:`); for (const s of slots) console.error(` - ${s.startsAt.toISOString()}`); process.exit(1); } const therapistId = slot.candidateTherapistIds[0]; const roomId = slot.candidateRoomIds[0]; console.log(`Booking ${service.name} for ${customer.name}:`); console.log(` When: ${startsAt.toISOString()} (${localIso} ${tz})`); console.log(` Therapist: ${therapistId}`); console.log(` Room: ${roomId}`); const hold = await createHold(db, { customerId: customer.id, serviceId: service.id, therapistId, roomId, startsAt, }); await confirmHold(db, hold.id); const result = await sendBookingConfirmation({ db, bookingId: hold.id }); await scheduleReminderForBooking(hold.id, startsAt); console.log(` Booking: ${hold.id} (CONFIRMED)`); console.log(` Email: ${result.status} (notification ${result.notificationId})`); console.log(` Reminder: scheduled for 24h before`); console.log(` View: http://localhost:8025`); } finally { await stopJobs(); await db.$disconnect(); } } main().catch((e) => { console.error(e); process.exit(1); });