Files
touchbase/scripts/book-on-behalf.ts

107 lines
3.6 KiB
TypeScript

// Admin smoke-test: take a booking on behalf of a customer end-to-end.
//
// Usage:
// pnpm tsx scripts/book-on-behalf.ts <customerEmail> <serviceName> <localISO>
//
// 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";
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 <customerEmail> <serviceName> <localISO>",
);
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 });
console.log(` Booking: ${hold.id} (CONFIRMED)`);
console.log(` Email: ${result.status} (notification ${result.notificationId})`);
console.log(` View: http://localhost:8025`);
} finally {
await db.$disconnect();
}
}
main().catch((e) => {
console.error(e);
process.exit(1);
});