import { createBrandedPdf, addBrandedFooter, COLORS, } from "./pdf-template"; interface SupplierPoItem { part_number: string; component_name: string; supplier_part_number?: string | null; quantity: number; unit_price: number; } interface SupplierPoPdfData { po_number: string; date: string; status: string; supplier_name: string; supplier_code: string; supplier_email?: string | null; supplier_phone?: string | null; supplier_address?: string | null; supplier_contact?: string | null; supplier_country?: string | null; supplier_currency?: string | null; shipping_cost?: number; notes?: string | null; items: SupplierPoItem[]; } function makeCurrencyFormatter(currency: string) { return (val: number): string => new Intl.NumberFormat("en-GB", { style: "currency", currency, }).format(val); } export function generateSupplierPoPdf(data: SupplierPoPdfData) { const { doc, contentY } = createBrandedPdf({ title: "PURCHASE ORDER", documentNumber: data.po_number, date: new Date(data.date).toLocaleDateString("en-GB"), recipient: { label: "SUPPLIER", name: data.supplier_name, code: data.supplier_code, contact: data.supplier_contact, address: data.supplier_address, phone: data.supplier_phone, email: data.supplier_email, }, }); const pageWidth = doc.internal.pageSize.getWidth(); const margin = 14; const currency = data.supplier_currency || "EUR"; const formatCurrency = makeCurrencyFormatter(currency); const shippingCost = data.shipping_cost || 0; const isPortugal = (data.supplier_country || "").toLowerCase().trim() === "portugal"; // --- Order Items Section --- let y = contentY; const contentWidth = pageWidth - margin * 2; const rightEdge = pageWidth - margin; const totalValue = data.items.reduce((sum, item) => sum + item.quantity * item.unit_price, 0); const totalQty = data.items.reduce((sum, item) => sum + item.quantity, 0); // Section header doc.setFontSize(9); doc.setFont("helvetica", "bold"); doc.setTextColor(...COLORS.primary); doc.text("ORDER ITEMS", margin, y); y += 5; // Column headers -- no grid, just text labels const colX = { num: margin + 1, part: margin + 10, name: margin + 42, ref: margin + 105, qty: rightEdge - 54, price: rightEdge - 30, total: rightEdge, }; doc.setFontSize(6); doc.setFont("helvetica", "bold"); doc.setTextColor(...COLORS.muted); doc.text("#", colX.num, y); doc.text("PART NUMBER", colX.part, y); doc.text("COMPONENT", colX.name, y); doc.text("SUPPLIER REF", colX.ref, y); doc.text("QTY", colX.qty, y, { align: "right" }); doc.text("UNIT PRICE", colX.price, y, { align: "right" }); doc.text("TOTAL", colX.total, y, { align: "right" }); y += 1.5; doc.setDrawColor(...COLORS.border); doc.setLineWidth(0.3); doc.line(margin, y, rightEdge, y); y += 3; // Item rows -- clean card-like rows with left accent const rowH = 9; data.items.forEach((item, i) => { const isAlt = i % 2 === 1; const rowY = y; // Subtle alternating background if (isAlt) { doc.setFillColor(250, 250, 252); doc.rect(margin, rowY - 2.5, contentWidth, rowH, "F"); } // Left accent bar (blue) doc.setFillColor(...COLORS.accent); doc.rect(margin, rowY - 2.5, 1.2, rowH, "F"); // Row number doc.setFontSize(7); doc.setFont("helvetica", "normal"); doc.setTextColor(...COLORS.muted); doc.text(String(i + 1), colX.num + 2, rowY + 1.5); // Part number -- monospaced feel doc.setFontSize(7.5); doc.setFont("courier", "bold"); doc.setTextColor(...COLORS.primary); doc.text(item.part_number, colX.part, rowY + 1.5); // Component name doc.setFont("helvetica", "normal"); doc.setTextColor(...COLORS.secondary); const nameMaxW = colX.ref - colX.name - 3; const truncatedName = doc.getTextWidth(item.component_name) > nameMaxW ? item.component_name.substring(0, Math.floor(item.component_name.length * nameMaxW / doc.getTextWidth(item.component_name))) + "..." : item.component_name; doc.text(truncatedName, colX.name, rowY + 1.5); // Supplier ref doc.setFontSize(7); doc.setFont("helvetica", "normal"); doc.setTextColor(...COLORS.muted); doc.text(item.supplier_part_number || "-", colX.ref, rowY + 1.5); // Quantity -- emphasized doc.setFontSize(8); doc.setFont("helvetica", "bold"); doc.setTextColor(...COLORS.primary); doc.text(String(item.quantity), colX.qty, rowY + 1.5, { align: "right" }); // Unit price doc.setFontSize(7.5); doc.setFont("helvetica", "normal"); doc.setTextColor(...COLORS.secondary); doc.text(formatCurrency(item.unit_price), colX.price, rowY + 1.5, { align: "right" }); // Line total doc.setFont("helvetica", "bold"); doc.setTextColor(...COLORS.primary); doc.text(formatCurrency(item.quantity * item.unit_price), colX.total, rowY + 1.5, { align: "right" }); y += rowH; }); // Bottom separator y += 1; doc.setDrawColor(...COLORS.border); doc.setLineWidth(0.3); doc.line(margin, y, rightEdge, y); // --- Summary section --- y += 5; const summaryW = 75; const summaryX = rightEdge - summaryW; const labelX = summaryX + 4; const valueX = rightEdge - 4; const lineSpacing = 6; // Subtotal doc.setFontSize(7.5); doc.setFont("helvetica", "normal"); doc.setTextColor(...COLORS.muted); doc.text("Subtotal", labelX, y); doc.setFont("helvetica", "bold"); doc.setTextColor(...COLORS.primary); doc.text(formatCurrency(totalValue), valueX, y, { align: "right" }); y += lineSpacing; // Shipping doc.setFont("helvetica", "normal"); doc.setTextColor(...COLORS.muted); doc.text("Shipping", labelX, y); doc.setFont("helvetica", "bold"); doc.setTextColor(...COLORS.primary); doc.text(formatCurrency(shippingCost), valueX, y, { align: "right" }); y += lineSpacing; // VAT for Portugal let vatAmount = 0; if (isPortugal) { vatAmount = (totalValue + shippingCost) * 0.23; doc.setFont("helvetica", "normal"); doc.setTextColor(...COLORS.muted); doc.text("VAT (23%)", labelX, y); doc.setFont("helvetica", "bold"); doc.setTextColor(...COLORS.primary); doc.text(formatCurrency(vatAmount), valueX, y, { align: "right" }); y += lineSpacing; } // Separator line y += 1; doc.setDrawColor(...COLORS.border); doc.setLineWidth(0.3); doc.line(summaryX, y, rightEdge, y); y += 4; // Grand total box const grandTotal = totalValue + shippingCost + vatAmount; const totalBoxW = summaryW; const totalBoxX = summaryX; const totalBoxY = y - 3; doc.setFillColor(...COLORS.accent); doc.roundedRect(totalBoxX, totalBoxY, totalBoxW, 12, 2, 2, "F"); doc.setFontSize(6.5); doc.setFont("helvetica", "bold"); doc.setTextColor(220, 230, 245); doc.text("TOTAL", totalBoxX + 5, totalBoxY + 5); doc.setFontSize(11); doc.setFont("helvetica", "bold"); doc.setTextColor(255, 255, 255); doc.text(formatCurrency(grandTotal), totalBoxX + totalBoxW - 5, totalBoxY + 9, { align: "right" }); const finalY = totalBoxY + 14; // --- Notes --- if (data.notes) { const noteY = totalBoxY + 18; doc.setFillColor(...COLORS.bgLight); doc.setDrawColor(...COLORS.border); doc.setLineWidth(0.2); doc.roundedRect(margin, noteY, contentWidth, 16, 1.5, 1.5, "FD"); doc.setFontSize(6.5); doc.setFont("helvetica", "bold"); doc.setTextColor(...COLORS.accent); doc.text("NOTES", margin + 4, noteY + 5); doc.setFontSize(7.5); doc.setFont("helvetica", "normal"); doc.setTextColor(...COLORS.secondary); const noteLines = doc.splitTextToSize(data.notes, contentWidth - 8); doc.text(noteLines, margin + 4, noteY + 11); } addBrandedFooter(doc); return doc; }