feat(reservations): detail page with agreement flow + contract mirror

Adds /berth-reservations/[id] with state-aware agreement card (none /
in-flight / completed) and the Generate-agreement entry point that
opens the wizard prefilled. handleDocumentCompleted now mirrors a
signed reservation_agreement onto berth_reservations.contractFileId
so the portal can resolve contracts without joining through documents.
Reservation merge tokens (startDate/endDate/tenureType/termSummary/
signedDate) added to the catalog.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt Ciaccio
2026-04-28 02:45:05 +02:00
parent 6795db9aa8
commit 6212c118e5
4 changed files with 320 additions and 0 deletions

View File

@@ -725,6 +725,17 @@ export async function handleDocumentCompleted(eventData: { documentId: string })
.update(documents)
.set({ status: 'completed', signedFileId: fileRecord!.id, updatedAt: new Date() })
.where(eq(documents.id, doc.id));
// Reservation agreements mirror their signed PDF onto
// berth_reservations.contractFileId so the portal "My Reservations" view
// can resolve the contract without joining through documents.
if (doc.documentType === 'reservation_agreement' && doc.reservationId) {
const { berthReservations } = await import('@/lib/db/schema/reservations');
await db
.update(berthReservations)
.set({ contractFileId: fileRecord!.id, updatedAt: new Date() })
.where(eq(berthReservations.id, doc.reservationId));
}
} catch (err) {
logger.error({ err, documentId: doc.id }, 'Failed to download/store signed PDF');
await db

View File

@@ -61,6 +61,13 @@ export const MERGE_FIELDS: MergeFieldCatalog = {
{ token: '{{berth.tenureType}}', label: 'Tenure Type', required: false },
{ token: '{{berth.tenureYears}}', label: 'Tenure Years', required: false },
],
reservation: [
{ token: '{{reservation.startDate}}', label: 'Reservation Start Date', required: false },
{ token: '{{reservation.endDate}}', label: 'Reservation End Date', required: false },
{ token: '{{reservation.tenureType}}', label: 'Reservation Tenure Type', required: false },
{ token: '{{reservation.termSummary}}', label: 'Reservation Term Summary', required: false },
{ token: '{{reservation.signedDate}}', label: 'Reservation Signed Date', required: false },
],
port: [
{ token: '{{port.name}}', label: 'Port Name', required: false },
{ token: '{{port.defaultCurrency}}', label: 'Default Currency', required: false },