diff --git a/teressa-copeland-homes/src/lib/db/schema.ts b/teressa-copeland-homes/src/lib/db/schema.ts index c609994..59ca130 100644 --- a/teressa-copeland-homes/src/lib/db/schema.ts +++ b/teressa-copeland-homes/src/lib/db/schema.ts @@ -18,6 +18,7 @@ export interface SignatureFieldData { width: number; // PDF points (default: 144 — 2 inches) height: number; // PDF points (default: 36 — 0.5 inches) type?: SignatureFieldType; // Optional — v1.0 documents have no type; fallback = 'client-signature' + signerEmail?: string; // Optional — absent = legacy single-signer or agent-owned field } /** @@ -39,6 +40,16 @@ export function isClientVisibleField(field: SignatureFieldData): boolean { return t !== 'agent-signature' && t !== 'agent-initials'; } +/** + * Safe signer email reader — returns the field's signerEmail or a fallback. + * Legacy single-signer documents have no signerEmail on their fields; + * this coalesces to the fallback (typically the document's single recipient email). + * ALWAYS use this instead of reading field.signerEmail directly. + */ +export function getSignerEmail(field: SignatureFieldData, fallbackEmail: string): string { + return field.signerEmail ?? fallbackEmail; +} + export const users = pgTable("users", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), email: text("email").notNull().unique(), @@ -72,6 +83,12 @@ export const formTemplates = pgTable("form_templates", { updatedAt: timestamp("updated_at").defaultNow().notNull(), }); +/** Shape of each entry in documents.signers JSONB array. */ +export interface DocumentSigner { + email: string; + color: string; +} + export const documents = pgTable("documents", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), name: text("name").notNull(), @@ -92,6 +109,10 @@ export const documents = pgTable("documents", { signedFilePath: text("signed_file_path"), pdfHash: text("pdf_hash"), signedAt: timestamp("signed_at"), + /** Per-signer list with assigned colors. NULL = legacy single-signer document. */ + signers: jsonb("signers").$type(), + /** Atomic completion guard — set once by the last signer's handler. NULL = not yet completed. */ + completionTriggeredAt: timestamp("completion_triggered_at"), }); export const documentsRelations = relations(documents, ({ one }) => ({ @@ -115,6 +136,8 @@ export const signingTokens = pgTable('signing_tokens', { jti: text('jti').primaryKey(), documentId: text('document_id').notNull() .references(() => documents.id, { onDelete: 'cascade' }), + /** Signer this token belongs to. NULL = legacy single-signer token. */ + signerEmail: text('signer_email'), createdAt: timestamp('created_at').defaultNow().notNull(), expiresAt: timestamp('expires_at').notNull(), usedAt: timestamp('used_at'),