import { pgTable, text, boolean, integer, timestamp, jsonb, index, uniqueIndex, } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; import { ports } from './ports'; import { clients } from './clients'; export const files = pgTable( 'files', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), clientId: text('client_id').references(() => clients.id), yachtId: text('yacht_id'), // FK wired in relations.ts companyId: text('company_id'), // FK wired in relations.ts filename: text('filename').notNull(), originalName: text('original_name').notNull(), mimeType: text('mime_type'), sizeBytes: text('size_bytes'), // stored as text to avoid bigint issues; parse as number in app storagePath: text('storage_path').notNull(), storageBucket: text('storage_bucket').notNull().default('crm-files'), category: text('category'), // eoi, contract, image, receipt, correspondence, misc uploadedBy: text('uploaded_by').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ index('idx_files_port').on(table.portId), index('idx_files_client').on(table.clientId), index('idx_files_yacht').on(table.yachtId), index('idx_files_company').on(table.companyId), ], ); export const documents = pgTable( 'documents', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), interestId: text('interest_id'), // references interests.id clientId: text('client_id').references(() => clients.id), yachtId: text('yacht_id'), // FK wired in relations.ts companyId: text('company_id'), // FK wired in relations.ts documentType: text('document_type').notNull(), // eoi, contract, nda, reservation_agreement, other title: text('title').notNull(), status: text('status').notNull().default('draft'), // draft, sent, partially_signed, completed, expired, cancelled documensoId: text('documenso_id'), fileId: text('file_id').references(() => files.id), signedFileId: text('signed_file_id').references(() => files.id), isManualUpload: boolean('is_manual_upload').notNull().default(false), notes: text('notes'), createdBy: text('created_by').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ index('idx_docs_port').on(table.portId), index('idx_docs_interest').on(table.interestId), index('idx_docs_client').on(table.clientId), index('idx_documents_yacht').on(table.yachtId), index('idx_documents_company').on(table.companyId), index('idx_docs_type').on(table.portId, table.documentType), ], ); export const documentSigners = pgTable( 'document_signers', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), documentId: text('document_id') .notNull() .references(() => documents.id, { onDelete: 'cascade' }), signerName: text('signer_name').notNull(), signerEmail: text('signer_email').notNull(), signerRole: text('signer_role').notNull(), // client, developer, sales, approver, other signingOrder: integer('signing_order').notNull(), status: text('status').notNull().default('pending'), // pending, signed, declined signedAt: timestamp('signed_at', { withTimezone: true }), signingUrl: text('signing_url'), embeddedUrl: text('embedded_url'), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [index('idx_ds_doc').on(table.documentId)], ); export const documentEvents = pgTable( 'document_events', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), documentId: text('document_id') .notNull() .references(() => documents.id, { onDelete: 'cascade' }), eventType: text('event_type').notNull(), // created, sent, viewed, signed, completed, expired, reminder_sent signerId: text('signer_id').references(() => documentSigners.id), eventData: jsonb('event_data').default({}), signatureHash: text('signature_hash'), // deduplication createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ index('idx_de_doc').on(table.documentId), uniqueIndex('idx_de_dedup') .on(table.documentId, table.signatureHash) .where(sql`${table.signatureHash} IS NOT NULL`), ], ); export const documentTemplates = pgTable( 'document_templates', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), name: text('name').notNull(), description: text('description'), templateType: text('template_type').notNull(), // welcome_letter, handover_checklist, acknowledgment, correspondence, custom bodyHtml: text('body_html').notNull(), mergeFields: jsonb('merge_fields').notNull().default([]), isActive: boolean('is_active').notNull().default(true), createdBy: text('created_by').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ index('idx_dt_port').on(table.portId), index('idx_dt_type').on(table.portId, table.templateType), ], ); export const formTemplates = pgTable( 'form_templates', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), name: text('name').notNull(), description: text('description'), fields: jsonb('fields').notNull(), branding: jsonb('branding').default({}), isActive: boolean('is_active').notNull().default(true), createdBy: text('created_by').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [index('idx_ft_port').on(table.portId)], ); export const formSubmissions = pgTable( 'form_submissions', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), formTemplateId: text('form_template_id') .notNull() .references(() => formTemplates.id), clientId: text('client_id').references(() => clients.id), interestId: text('interest_id'), // references interests.id token: text('token').notNull().unique(), prefilledData: jsonb('prefilled_data').default({}), submittedData: jsonb('submitted_data'), status: text('status').notNull().default('pending'), // pending, submitted, expired expiresAt: timestamp('expires_at', { withTimezone: true }), submittedAt: timestamp('submitted_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [uniqueIndex('idx_fs_token').on(table.token)], ); export type File = typeof files.$inferSelect; export type NewFile = typeof files.$inferInsert; export type Document = typeof documents.$inferSelect; export type NewDocument = typeof documents.$inferInsert; export type DocumentSigner = typeof documentSigners.$inferSelect; export type NewDocumentSigner = typeof documentSigners.$inferInsert; export type DocumentEvent = typeof documentEvents.$inferSelect; export type NewDocumentEvent = typeof documentEvents.$inferInsert; export type DocumentTemplate = typeof documentTemplates.$inferSelect; export type NewDocumentTemplate = typeof documentTemplates.$inferInsert; export type FormTemplate = typeof formTemplates.$inferSelect; export type NewFormTemplate = typeof formTemplates.$inferInsert; export type FormSubmission = typeof formSubmissions.$inferSelect; export type NewFormSubmission = typeof formSubmissions.$inferInsert;