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
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { auth } from '@/lib/auth';
|
import { auth } from '@/lib/auth';
|
||||||
import { db } from '@/lib/db';
|
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 { eq } from 'drizzle-orm';
|
||||||
import { copyFile, mkdir, writeFile } from 'node:fs/promises';
|
import { copyFile, mkdir, writeFile } from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
@@ -8,6 +9,8 @@ import path from 'node:path';
|
|||||||
const SEEDS_DIR = path.join(process.cwd(), 'seeds', 'forms');
|
const SEEDS_DIR = path.join(process.cwd(), 'seeds', 'forms');
|
||||||
const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
|
const UPLOADS_DIR = path.join(process.cwd(), 'uploads');
|
||||||
|
|
||||||
|
const SIGNER_COLORS = ['#6366f1', '#f43f5e', '#10b981', '#f59e0b'];
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
const session = await auth();
|
const session = await auth();
|
||||||
if (!session) return new Response('Unauthorized', { status: 401 });
|
if (!session) return new Response('Unauthorized', { status: 401 });
|
||||||
@@ -17,6 +20,7 @@ export async function POST(req: Request) {
|
|||||||
let clientId: string;
|
let clientId: string;
|
||||||
let name: string;
|
let name: string;
|
||||||
let formTemplateId: string | undefined;
|
let formTemplateId: string | undefined;
|
||||||
|
let documentTemplateId: string | undefined;
|
||||||
let fileBuffer: ArrayBuffer | undefined;
|
let fileBuffer: ArrayBuffer | undefined;
|
||||||
|
|
||||||
if (contentType.includes('multipart/form-data')) {
|
if (contentType.includes('multipart/form-data')) {
|
||||||
@@ -32,6 +36,7 @@ export async function POST(req: Request) {
|
|||||||
clientId = body.clientId;
|
clientId = body.clientId;
|
||||||
name = body.name;
|
name = body.name;
|
||||||
formTemplateId = body.formTemplateId;
|
formTemplateId = body.formTemplateId;
|
||||||
|
documentTemplateId = body.documentTemplateId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!clientId || !name) {
|
if (!clientId || !name) {
|
||||||
@@ -50,6 +55,66 @@ export async function POST(req: Request) {
|
|||||||
|
|
||||||
await mkdir(destDir, { recursive: true });
|
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<string>();
|
||||||
|
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) {
|
if (fileBuffer !== undefined) {
|
||||||
// Custom upload
|
// Custom upload
|
||||||
await writeFile(destPath, Buffer.from(fileBuffer));
|
await writeFile(destPath, Buffer.from(fileBuffer));
|
||||||
|
|||||||
Reference in New Issue
Block a user