package pdf import ( "fmt" "os" "path/filepath" "github.com/go-pdf/fpdf" ) type DocMeta struct { CompanyName string CompanyNIF string CompanyAddress string CompanyIBAN string CompanyPhone string CompanyEmail string DocType string // "Fatura" or "Orçamento" DocNumber string // e.g. "FAT/2026/001" IssuedAt string // e.g. "30/06/2026" ClientName string ClientNIF string VehiclePlate string } type LineItem struct { Description string Qty float64 UnitPrice float64 DiscountPct float64 Total float64 } func Generate(meta DocMeta, items []LineItem, staffTotal float64, outPath string) error { if err := os.MkdirAll(filepath.Dir(outPath), 0755); err != nil { return fmt.Errorf("pdf: mkdir: %w", err) } f := fpdf.New("P", "mm", "A4", "") f.AddPage() f.SetMargins(15, 15, 15) // Header f.SetFont("Helvetica", "B", 18) f.CellFormat(120, 10, meta.CompanyName, "", 0, "L", false, 0, "") f.SetFont("Helvetica", "B", 14) f.CellFormat(60, 10, meta.DocType, "", 1, "R", false, 0, "") f.SetFont("Helvetica", "", 9) if meta.CompanyNIF != "" { f.CellFormat(120, 5, "NIF: "+meta.CompanyNIF, "", 0, "L", false, 0, "") } else { f.CellFormat(120, 5, "", "", 0, "L", false, 0, "") } f.SetFont("Helvetica", "", 11) f.CellFormat(60, 5, meta.DocNumber, "", 1, "R", false, 0, "") f.SetFont("Helvetica", "", 9) if meta.CompanyAddress != "" { f.MultiCell(120, 5, meta.CompanyAddress, "", "L", false) } f.Ln(3) curY := f.GetY() f.SetXY(135, curY-3) f.CellFormat(60, 5, "Data: "+meta.IssuedAt, "", 1, "R", false, 0, "") f.SetY(curY + 3) f.Ln(3) // Client block if meta.ClientName != "" { f.SetFont("Helvetica", "B", 9) f.CellFormat(180, 5, "Cliente", "", 1, "L", false, 0, "") f.SetFont("Helvetica", "", 9) f.CellFormat(180, 5, meta.ClientName, "", 1, "L", false, 0, "") if meta.ClientNIF != "" { f.CellFormat(180, 5, "NIF: "+meta.ClientNIF, "", 1, "L", false, 0, "") } if meta.VehiclePlate != "" { f.CellFormat(180, 5, "Matrícula: "+meta.VehiclePlate, "", 1, "L", false, 0, "") } f.Ln(4) } // Table header f.SetFillColor(50, 50, 50) f.SetTextColor(255, 255, 255) f.SetFont("Helvetica", "B", 9) f.CellFormat(90, 7, "Descricao", "1", 0, "L", true, 0, "") f.CellFormat(20, 7, "Qtd.", "1", 0, "C", true, 0, "") f.CellFormat(25, 7, "Preco Unit.", "1", 0, "R", true, 0, "") f.CellFormat(20, 7, "Desc.%", "1", 0, "C", true, 0, "") f.CellFormat(25, 7, "Total", "1", 1, "R", true, 0, "") // Table rows f.SetFillColor(245, 245, 245) f.SetTextColor(0, 0, 0) f.SetFont("Helvetica", "", 9) fill := false for _, item := range items { f.CellFormat(90, 6, item.Description, "1", 0, "L", fill, 0, "") f.CellFormat(20, 6, fmt.Sprintf("%.2f", item.Qty), "1", 0, "C", fill, 0, "") f.CellFormat(25, 6, fmt.Sprintf("%.2f EUR", item.UnitPrice), "1", 0, "R", fill, 0, "") f.CellFormat(20, 6, fmt.Sprintf("%.0f%%", item.DiscountPct), "1", 0, "C", fill, 0, "") f.CellFormat(25, 6, fmt.Sprintf("%.2f EUR", item.Total), "1", 1, "R", fill, 0, "") fill = !fill } // Totals var subtotal float64 for _, item := range items { subtotal += item.Total } grandTotal := subtotal + staffTotal f.Ln(3) f.SetFont("Helvetica", "", 9) if staffTotal > 0 { f.CellFormat(155, 6, "Subtotal pecas/servicos", "", 0, "R", false, 0, "") f.CellFormat(25, 6, fmt.Sprintf("%.2f EUR", subtotal), "1", 1, "R", false, 0, "") f.CellFormat(155, 6, "Mao de obra", "", 0, "R", false, 0, "") f.CellFormat(25, 6, fmt.Sprintf("%.2f EUR", staffTotal), "1", 1, "R", false, 0, "") } f.SetFont("Helvetica", "B", 10) f.CellFormat(155, 7, "TOTAL", "", 0, "R", false, 0, "") f.CellFormat(25, 7, fmt.Sprintf("%.2f EUR", grandTotal), "1", 1, "R", false, 0, "") // IBAN footer if meta.CompanyIBAN != "" { f.Ln(8) f.SetFont("Helvetica", "", 8) f.CellFormat(180, 5, "IBAN: "+meta.CompanyIBAN, "", 1, "C", false, 0, "") } return f.OutputFileAndClose(outPath) }