From bdf0cb02ffa661548c630b343fce36e7b82841c4 Mon Sep 17 00:00:00 2001 From: Chandler Copeland Date: Mon, 6 Apr 2026 14:50:24 -0600 Subject: [PATCH] feat(20-01): extend POST /api/documents with documentTemplateId branch - Parse documentTemplateId from JSON body alongside formTemplateId - Fetch document template with formTemplate relation for PDF filename - Copy signatureFields with fresh crypto.randomUUID per field (snapshot independence) - Map template signer role labels to client email + contacts array - Return early with inserted doc; existing form-library and upload paths unchanged --- .../src/app/api/documents/route.ts | 67 ++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/teressa-copeland-homes/src/app/api/documents/route.ts b/teressa-copeland-homes/src/app/api/documents/route.ts index f7f4170..bd9479b 100644 --- a/teressa-copeland-homes/src/app/api/documents/route.ts +++ b/teressa-copeland-homes/src/app/api/documents/route.ts @@ -1,6 +1,7 @@ import { auth } from '@/lib/auth'; import { db } from '@/lib/db'; -import { documents, formTemplates } from '@/lib/db/schema'; +import { documents, formTemplates, documentTemplates, clients } from '@/lib/db/schema'; +import type { SignatureFieldData, ClientContact, DocumentSigner } from '@/lib/db/schema'; import { eq } from 'drizzle-orm'; import { copyFile, mkdir, writeFile } from 'node:fs/promises'; import path from 'node:path'; @@ -8,6 +9,8 @@ import path from 'node:path'; const SEEDS_DIR = path.join(process.cwd(), 'seeds', 'forms'); const UPLOADS_DIR = path.join(process.cwd(), 'uploads'); +const SIGNER_COLORS = ['#6366f1', '#f43f5e', '#10b981', '#f59e0b']; + export async function POST(req: Request) { const session = await auth(); if (!session) return new Response('Unauthorized', { status: 401 }); @@ -17,6 +20,7 @@ export async function POST(req: Request) { let clientId: string; let name: string; let formTemplateId: string | undefined; + let documentTemplateId: string | undefined; let fileBuffer: ArrayBuffer | undefined; if (contentType.includes('multipart/form-data')) { @@ -32,6 +36,7 @@ export async function POST(req: Request) { clientId = body.clientId; name = body.name; formTemplateId = body.formTemplateId; + documentTemplateId = body.documentTemplateId; } if (!clientId || !name) { @@ -50,6 +55,66 @@ export async function POST(req: Request) { await mkdir(destDir, { recursive: true }); + // Document template branch — apply saved template with fresh field UUIDs + if (documentTemplateId) { + const docTemplate = await db.query.documentTemplates.findFirst({ + where: eq(documentTemplates.id, documentTemplateId), + with: { formTemplate: true }, + }); + if (!docTemplate) return Response.json({ error: 'Document template not found' }, { status: 404 }); + + // Copy PDF from seeds dir using the form template's filename + const srcPath = path.join(SEEDS_DIR, docTemplate.formTemplate.filename); + await copyFile(srcPath, destPath); + + // Copy fields with fresh UUIDs — hints preserved verbatim + const rawFields: SignatureFieldData[] = (docTemplate.signatureFields as SignatureFieldData[] | null) ?? []; + const copiedFields: SignatureFieldData[] = rawFields.map(f => ({ + ...f, + id: crypto.randomUUID(), + })); + + // Collect unique role labels in order of first appearance + const seenRoles = new Set(); + const uniqueRoles: string[] = []; + for (const f of copiedFields) { + if (f.signerEmail && !seenRoles.has(f.signerEmail)) { + seenRoles.add(f.signerEmail); + uniqueRoles.push(f.signerEmail); + } + } + + // Fetch client for email + contacts for role-to-email mapping + const client = await db.query.clients.findFirst({ + where: eq(clients.id, clientId), + }); + const clientEmails = [ + client?.email, + ...((client?.contacts as ClientContact[] | null) ?? []).map(c => c.email), + ].filter(Boolean) as string[]; + + const mappedSigners: DocumentSigner[] = uniqueRoles.map((role, i) => ({ + email: clientEmails[i] ?? role, + color: SIGNER_COLORS[i % SIGNER_COLORS.length], + })); + + // Use the form template's ID for the DB insert + const resolvedFormTemplateId = docTemplate.formTemplateId; + + const [doc] = await db.insert(documents).values({ + id: docId, + clientId, + name, + formTemplateId: resolvedFormTemplateId, + filePath: relPath, + status: 'Draft', + signatureFields: copiedFields, + signers: mappedSigners.length > 0 ? mappedSigners : null, + }).returning(); + + return Response.json(doc, { status: 201 }); + } + if (fileBuffer !== undefined) { // Custom upload await writeFile(destPath, Buffer.from(fileBuffer));