Files
boc/backend/pdf/generator.go
T
Bernt (LandveX AI) 67a69ab073 feat(boc): v1.0 - Complete Business Operations Center
- Go backend API with full CRUD for all modules
- Rust analytics service with parallel processing
- C runtime with POSIX shared memory IPC
- PostgreSQL schema with 30+ tables
- Redis cache, Kafka event streaming
- WebSocket hub, automation engine
- PDF generation, Resend email integration
- JWT auth, multi-tenant
- Docker Compose deployment
- Nginx reverse proxy

Refs: BOC-001
2026-07-12 13:21:10 +00:00

328 lines
7.9 KiB
Go

package pdf
import (
"bytes"
"fmt"
"time"
"github.com/jung-kurt/gofpdf"
)
// InvoiceData contains all data needed to generate an invoice PDF
type InvoiceData struct {
InvoiceNumber string
InvoiceDate time.Time
DueDate time.Time
CustomerName string
CustomerAddress string
CustomerOrgNr string
Items []InvoiceItem
Subtotal float64
VATRate float64
VATAmount float64
Total float64
Currency string
CompanyName string
CompanyAddress string
CompanyOrgNr string
CompanyBankgiro string
Notes string
}
// InvoiceItem represents a line item on an invoice
type InvoiceItem struct {
Description string
Quantity float64
Unit string
UnitPrice float64
Total float64
}
// QuoteData contains all data needed to generate a quote PDF
type QuoteData struct {
QuoteNumber string
QuoteDate time.Time
ValidUntil time.Time
CustomerName string
CustomerAddress string
Items []QuoteItem
Subtotal float64
VATRate float64
VATAmount float64
Total float64
Currency string
CompanyName string
CompanyAddress string
Notes string
}
// QuoteItem represents a line item on a quote
type QuoteItem struct {
Description string
Quantity float64
Unit string
UnitPrice float64
Total float64
}
// GenerateInvoice creates a professional invoice PDF
func GenerateInvoice(data InvoiceData) ([]byte, error) {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
// Header with company info
pdf.SetFont("Arial", "B", 20)
pdf.SetTextColor(201, 106, 58) // Terracotta
pdf.Cell(0, 12, "FAKTURA")
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)
// Invoice 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, "FAKTURAINFORMATION")
pdf.Ln(6)
pdf.SetFont("Arial", "", 9)
pdf.SetTextColor(50, 50, 50)
pdf.SetX(135)
pdf.Cell(0, 4, fmt.Sprintf("Fakturanr: %s", data.InvoiceNumber))
pdf.Ln(4)
pdf.SetX(135)
pdf.Cell(0, 4, fmt.Sprintf("Datum: %s", data.InvoiceDate.Format("2006-01-02")))
pdf.Ln(4)
pdf.SetX(135)
pdf.Cell(0, 4, fmt.Sprintf("Förfallo: %s", data.DueDate.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)
pdf.Cell(40, 7, "ATT BETALA:")
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 | Faktura %s | Sida %d", data.CompanyName, data.InvoiceNumber, pdf.PageNo()))
var buf bytes.Buffer
err := pdf.Output(&buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
// GenerateQuote creates a professional quote PDF
func GenerateQuote(data QuoteData) ([]byte, error) {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.AddPage()
// Header
pdf.SetFont("Arial", "B", 20)
pdf.SetTextColor(201, 106, 58)
pdf.Cell(0, 12, "OFFERT")
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(5)
// Quote details box
pdf.SetFillColor(250, 248, 245)
pdf.Rect(130, 30, 70, 30, "F")
pdf.SetXY(135, 33)
pdf.SetFont("Arial", "B", 9)
pdf.SetTextColor(201, 106, 58)
pdf.Cell(0, 5, "OFFERTINFORMATION")
pdf.Ln(6)
pdf.SetFont("Arial", "", 9)
pdf.SetTextColor(50, 50, 50)
pdf.SetX(135)
pdf.Cell(0, 4, fmt.Sprintf("Offertnr: %s", data.QuoteNumber))
pdf.Ln(4)
pdf.SetX(135)
pdf.Cell(0, 4, fmt.Sprintf("Datum: %s", data.QuoteDate.Format("2006-01-02")))
pdf.Ln(4)
pdf.SetX(135)
pdf.Cell(0, 4, fmt.Sprintf("Giltig till: %s", data.ValidUntil.Format("2006-01-02")))
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(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)
pdf.Cell(40, 7, "TOTALT:")
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 | Offert %s | Sida %d", data.CompanyName, data.QuoteNumber, pdf.PageNo()))
var buf bytes.Buffer
err := pdf.Output(&buf)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}