import { NextRequest, NextResponse } from "next/server"; import PizZip from "pizzip"; import Docxtemplater from "docxtemplater"; import { readFile, writeFile } from "fs/promises"; import path from "path"; import db from "@/lib/db"; import { formatDate, formatCoordinates } from "@/lib/utils"; export async function POST(request) { try { const { templateId, projectId, customData } = await request.json(); if (!templateId || !projectId) { return NextResponse.json( { error: "templateId and projectId are required" }, { status: 400 } ); } // Get template const template = db.prepare(` SELECT * FROM docx_templates WHERE template_id = ? AND is_active = 1 `).get(templateId); if (!template) { return NextResponse.json( { error: "Template not found" }, { status: 404 } ); } // Get project data const project = db.prepare(` SELECT p.*, c.contract_number, c.customer_contract_number, c.customer, c.investor FROM projects p LEFT JOIN contracts c ON p.contract_id = c.contract_id WHERE p.project_id = ? `).get(projectId); if (!project) { return NextResponse.json( { error: "Project not found" }, { status: 404 } ); } // Get project contacts const contacts = db.prepare(` SELECT pc.*, ct.name, ct.phone, ct.email, ct.company, ct.contact_type FROM project_contacts pc JOIN contacts ct ON pc.contact_id = ct.contact_id WHERE pc.project_id = ? ORDER BY pc.is_primary DESC, ct.name `).all(projectId); // Load template file const templatePath = path.join(process.cwd(), "public", template.file_path); const templateContent = await readFile(templatePath); // Load the docx file as a binary const zip = new PizZip(templateContent); const doc = new Docxtemplater(zip, { paragraphLoop: true, linebreaks: true, }); // Prepare data for template const templateData = { // Project basic info project_name: project.project_name || "", project_number: project.project_number || "", address: project.address || "", city: project.city || "", plot: project.plot || "", district: project.district || "", unit: project.unit || "", investment_number: project.investment_number || "", wp: project.wp || "", coordinates: project.coordinates || "", notes: project.notes || "", // Processed fields (extracted/transformed data) investment_number_short: project.investment_number ? project.investment_number.split('-').pop() : "", project_number_short: project.project_number ? project.project_number.split('-').pop() : "", project_name_upper: project.project_name ? project.project_name.toUpperCase() : "", project_name_lower: project.project_name ? project.project_name.toLowerCase() : "", city_upper: project.city ? project.city.toUpperCase() : "", customer_upper: project.customer ? project.customer.toUpperCase() : "", // Contract info contract_number: project.contract_number || "", customer_contract_number: project.customer_contract_number || "", customer: project.customer || "", investor: project.investor || "", // Dates finish_date: project.finish_date ? formatDate(project.finish_date) : "", completion_date: project.completion_date ? formatDate(project.completion_date) : "", today_date: formatDate(new Date()), // Project type and status project_type: project.project_type || "", project_status: project.project_status || "", // Financial wartosc_zlecenia: project.wartosc_zlecenia ? project.wartosc_zlecenia.toString() : "", // Contacts contacts: contacts.map(contact => ({ name: contact.name || "", phone: contact.phone || "", email: contact.email || "", company: contact.company || "", contact_type: contact.contact_type || "", is_primary: contact.is_primary ? "Tak" : "Nie" })), // Primary contact primary_contact: contacts.find(c => c.is_primary)?.name || "", primary_contact_phone: contacts.find(c => c.is_primary)?.phone || "", primary_contact_email: contacts.find(c => c.is_primary)?.email || "", // Duplicate fields for repeated use (common fields that users might want to repeat) project_name_1: project.project_name || "", project_name_2: project.project_name || "", project_name_3: project.project_name || "", project_number_1: project.project_number || "", project_number_2: project.project_number || "", customer_1: project.customer || "", customer_2: project.customer || "", address_1: project.address || "", address_2: project.address || "", city_1: project.city || "", city_2: project.city || "", wartosc_zlecenia_1: project.wartosc_zlecenia ? project.wartosc_zlecenia.toString() : "", wartosc_zlecenia_2: project.wartosc_zlecenia ? project.wartosc_zlecenia.toString() : "", }; // Merge custom data (custom data takes precedence over project data) if (customData && typeof customData === 'object') { Object.assign(templateData, customData); } // Set the template variables doc.setData(templateData); try { // Render the document doc.render(); } catch (error) { console.error("Template rendering error:", error); // Check if it's a duplicate tags error if (error.name === 'TemplateError' && error.properties?.id === 'duplicate_open_tag') { return NextResponse.json( { error: "Template contains duplicate placeholders. Each placeholder (like {{project_name}}) can only be used once in the template. Please modify your DOCX template to use unique placeholders or remove duplicates.", details: `Duplicate tag found: ${error.properties?.xtag || 'unknown'}` }, { status: 400 } ); } return NextResponse.json( { error: "Failed to render template. Please check template syntax and ensure all placeholders are properly formatted." }, { status: 400 } ); } // Get the generated document const buf = doc.getZip().generate({ type: "nodebuffer", compression: "DEFLATE", }); // Generate filename const timestamp = new Date().toISOString().slice(0, 19).replace(/:/g, "-"); const sanitizedTemplateName = template.template_name.replace(/[^a-zA-Z0-9]/g, "_"); const sanitizedProjectName = project.project_name.replace(/[^a-zA-Z0-9]/g, "_"); const filename = `${sanitizedTemplateName}_${sanitizedProjectName}_${timestamp}.docx`; // Return the file as a downloadable response return new NextResponse(buf, { headers: { "Content-Type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "Content-Disposition": `attachment; filename="${filename}"`, }, }); } catch (error) { console.error("Template generation error:", error); return NextResponse.json( { error: "Failed to generate document" }, { status: 500 } ); } }