import { and, eq } from 'drizzle-orm'; import { db } from '@/lib/db'; import { berths } from '@/lib/db/schema/berths'; import { berthReservations } from '@/lib/db/schema/reservations'; import { clients } from '@/lib/db/schema/clients'; import { ports } from '@/lib/db/schema/ports'; import { yachts } from '@/lib/db/schema/yachts'; import { NotFoundError } from '@/lib/errors'; export type ReservationAgreementContext = { client: { id: string; fullName: string; nationality: string | null; }; yacht: { id: string; name: string; lengthFt: string | null; flag: string | null; }; berth: { id: string; mooringNumber: string; area: string | null; lengthFt: string | null; priceCurrency: string; }; reservation: { id: string; status: string; startDate: Date; endDate: Date | null; tenureType: string; termSummary: string; signedDate: string | null; }; port: { name: string; defaultCurrency: string; }; date: { today: string; year: string; }; }; /** * Build the merge-context shape used when generating a reservation agreement * document. Mirrors `buildEoiContext` for consistency: pure read-only, * tenant-scoped via `portId`, throws on missing rows. * * `termSummary` is a human-readable rendering of `tenureType` + dates that * templates can use as `{{reservation.termSummary}}` without needing date * formatting helpers in the template language. */ export async function buildReservationAgreementContext( reservationId: string, portId: string, ): Promise { const reservation = await db.query.berthReservations.findFirst({ where: and(eq(berthReservations.id, reservationId), eq(berthReservations.portId, portId)), }); if (!reservation) throw new NotFoundError('Reservation'); const [client, yacht, berth, port] = await Promise.all([ db.query.clients.findFirst({ where: and(eq(clients.id, reservation.clientId), eq(clients.portId, portId)), }), db.query.yachts.findFirst({ where: and(eq(yachts.id, reservation.yachtId), eq(yachts.portId, portId)), }), db.query.berths.findFirst({ where: and(eq(berths.id, reservation.berthId), eq(berths.portId, portId)), }), db.query.ports.findFirst({ where: eq(ports.id, portId) }), ]); if (!client) throw new NotFoundError('Client'); if (!yacht) throw new NotFoundError('Yacht'); if (!berth) throw new NotFoundError('Berth'); if (!port) throw new NotFoundError('Port'); const start = reservation.startDate.toISOString().slice(0, 10); const end = reservation.endDate ? reservation.endDate.toISOString().slice(0, 10) : null; let termSummary: string; if (reservation.tenureType === 'permanent') { termSummary = `Permanent berth, commencing ${start}`; } else if (reservation.tenureType === 'fixed_term' && end) { termSummary = `Fixed term: ${start} to ${end}`; } else if (reservation.tenureType === 'seasonal' && end) { termSummary = `Seasonal: ${start} to ${end}`; } else { termSummary = `${reservation.tenureType} from ${start}`; } const now = new Date(); return { client: { id: client.id, fullName: client.fullName, nationality: client.nationality, }, yacht: { id: yacht.id, name: yacht.name, lengthFt: yacht.lengthFt, flag: yacht.flag, }, berth: { id: berth.id, mooringNumber: berth.mooringNumber, area: berth.area, lengthFt: berth.lengthFt, priceCurrency: berth.priceCurrency, }, reservation: { id: reservation.id, status: reservation.status, startDate: reservation.startDate, endDate: reservation.endDate, tenureType: reservation.tenureType, termSummary, signedDate: null, }, port: { name: port.name, defaultCurrency: port.defaultCurrency, }, date: { today: now.toISOString().slice(0, 10), year: String(now.getFullYear()), }, }; }