Files
boc/backend/pdf/document.go
T

208 lines
4.9 KiB
Go
Raw Normal View History

// 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"
}