// Package pdf provides generic document generation. // One function, multiple document types. Linus-style. package pdf import ( "bytes" "fmt" "time" "github.com/jung-kurt/gofpdf" ) // DocType represents the type of document type DocType string const ( InvoiceDoc DocType = "FAKTURA" QuoteDoc DocType = "OFFERT" ) // LineItem represents a single line on a document type LineItem struct { Description string Quantity float64 Unit string UnitPrice float64 Total float64 } // DocumentData contains all data needed to generate a document type DocumentData struct { DocType DocType DocNumber string DocDate time.Time ValidUntil *time.Time CustomerName string CustomerAddress string CustomerOrgNr string Items []LineItem Subtotal float64 VATRate float64 VATAmount float64 Total float64 Currency string CompanyName string CompanyAddress string CompanyOrgNr string CompanyBankgiro string Notes string } // GenerateDocument creates a professional PDF document func GenerateDocument(data DocumentData) ([]byte, error) { pdf := gofpdf.New("P", "mm", "A4", "") pdf.AddPage() // Header with document type pdf.SetFont("Arial", "B", 20) pdf.SetTextColor(201, 106, 58) // Terracotta pdf.Cell(0, 12, string(data.DocType)) pdf.Ln(8) // Company info pdf.SetFont("Arial", "B", 10) pdf.SetTextColor(50, 50, 50) pdf.Cell(0, 5, data.CompanyName) pdf.Ln(5) pdf.SetFont("Arial", "", 9) pdf.Cell(0, 4, data.CompanyAddress) pdf.Ln(4) if data.CompanyOrgNr != "" { pdf.Cell(0, 4, fmt.Sprintf("Org.nr: %s", data.CompanyOrgNr)) pdf.Ln(4) } if data.CompanyBankgiro != "" { pdf.Cell(0, 4, fmt.Sprintf("Bankgiro: %s", data.CompanyBankgiro)) pdf.Ln(4) } pdf.Ln(5) // Document details box pdf.SetFillColor(250, 248, 245) pdf.Rect(130, 30, 70, 35, "F") pdf.SetXY(135, 33) pdf.SetFont("Arial", "B", 9) pdf.SetTextColor(201, 106, 58) pdf.Cell(0, 5, docInfoLabel(data.DocType)) pdf.Ln(6) pdf.SetFont("Arial", "", 9) pdf.SetTextColor(50, 50, 50) pdf.SetX(135) pdf.Cell(0, 4, fmt.Sprintf("%s: %s", docNumberLabel(data.DocType), data.DocNumber)) pdf.Ln(4) pdf.SetX(135) pdf.Cell(0, 4, fmt.Sprintf("Datum: %s", data.DocDate.Format("2006-01-02"))) pdf.Ln(4) if data.ValidUntil != nil { pdf.SetX(135) pdf.Cell(0, 4, fmt.Sprintf("Giltig till: %s", data.ValidUntil.Format("2006-01-02"))) pdf.Ln(4) } pdf.SetX(135) pdf.Cell(0, 4, fmt.Sprintf("Valuta: %s", data.Currency)) pdf.Ln(4) // Customer info pdf.SetXY(10, 75) pdf.SetFont("Arial", "B", 10) pdf.SetTextColor(201, 106, 58) pdf.Cell(0, 5, "KUND") pdf.Ln(6) pdf.SetFont("Arial", "B", 10) pdf.SetTextColor(50, 50, 50) pdf.Cell(0, 5, data.CustomerName) pdf.Ln(5) pdf.SetFont("Arial", "", 9) pdf.Cell(0, 4, data.CustomerAddress) pdf.Ln(4) if data.CustomerOrgNr != "" { pdf.Cell(0, 4, fmt.Sprintf("Org.nr: %s", data.CustomerOrgNr)) pdf.Ln(4) } pdf.Ln(10) // Items table header pdf.SetFillColor(201, 106, 58) pdf.SetTextColor(255, 255, 255) pdf.SetFont("Arial", "B", 9) pdf.Cell(80, 8, "Beskrivning") pdf.Cell(25, 8, "Antal") pdf.Cell(25, 8, "Enhet") pdf.Cell(30, 8, "Pris") pdf.Cell(30, 8, "Belopp") pdf.Ln(8) // Items pdf.SetTextColor(50, 50, 50) pdf.SetFont("Arial", "", 9) for i, item := range data.Items { if i%2 == 0 { pdf.SetFillColor(250, 248, 245) pdf.Rect(10, pdf.GetY(), 190, 6, "F") } pdf.Cell(80, 6, item.Description) pdf.Cell(25, 6, fmt.Sprintf("%.2f", item.Quantity)) pdf.Cell(25, 6, item.Unit) pdf.Cell(30, 6, fmt.Sprintf("%.2f", item.UnitPrice)) pdf.Cell(30, 6, fmt.Sprintf("%.2f", item.Total)) pdf.Ln(6) } pdf.Ln(5) // Totals pdf.SetX(120) pdf.SetFont("Arial", "", 9) pdf.Cell(40, 5, "Delsumma:") pdf.Cell(30, 5, fmt.Sprintf("%.2f %s", data.Subtotal, data.Currency)) pdf.Ln(5) pdf.SetX(120) pdf.Cell(40, 5, fmt.Sprintf("Moms (%.0f%%):", data.VATRate*100)) pdf.Cell(30, 5, fmt.Sprintf("%.2f %s", data.VATAmount, data.Currency)) pdf.Ln(5) pdf.SetX(120) pdf.SetFont("Arial", "B", 11) pdf.SetTextColor(201, 106, 58) totalLabel := "ATT BETALA:" if data.DocType == QuoteDoc { totalLabel = "TOTALT:" } pdf.Cell(40, 7, totalLabel) pdf.Cell(30, 7, fmt.Sprintf("%.2f %s", data.Total, data.Currency)) pdf.Ln(10) // Notes if data.Notes != "" { pdf.SetFont("Arial", "I", 8) pdf.SetTextColor(100, 100, 100) pdf.MultiCell(0, 4, data.Notes, "", "", false) } // Footer pdf.SetY(-20) pdf.SetFont("Arial", "", 8) pdf.SetTextColor(150, 150, 150) pdf.Cell(0, 4, fmt.Sprintf("%s | %s %s | Sida %d", data.CompanyName, data.DocType, data.DocNumber, pdf.PageNo())) var buf bytes.Buffer if err := pdf.Output(&buf); err != nil { return nil, err } return buf.Bytes(), nil } func docInfoLabel(dt DocType) string { if dt == QuoteDoc { return "OFFERTINFORMATION" } return "FAKTURAINFORMATION" } func docNumberLabel(dt DocType) string { if dt == QuoteDoc { return "Offertnr" } return "Fakturanr" }