chore: upload parte 1 - logica
DevSecOps Enterprise Pipeline / security-gate-and-deploy (push) Failing after 41s

This commit is contained in:
2026-06-01 15:35:53 +01:00
parent a63625d228
commit 9c14a19a87
127 changed files with 22255 additions and 0 deletions
+264
View File
@@ -0,0 +1,264 @@
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;
}