311 lines
10 KiB
Go
311 lines
10 KiB
Go
package pdf
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
|
|
"github.com/jung-kurt/gofpdf"
|
|
)
|
|
|
|
// Generator handles PDF generation for documents
|
|
type Generator struct {
|
|
pdf *gofpdf.Fpdf
|
|
outputDir string
|
|
headerText string
|
|
footerText string
|
|
docRef string
|
|
currentPage int
|
|
}
|
|
|
|
// NewGenerator creates a new PDF generator
|
|
func NewGenerator(outputDir string) *Generator {
|
|
pdf := gofpdf.New("P", "mm", "A4", "")
|
|
|
|
return &Generator{
|
|
pdf: pdf,
|
|
outputDir: outputDir,
|
|
headerText: "CMC TECHNOLOGIES",
|
|
footerText: "Copyright © %d CMC Technologies. All rights reserved.",
|
|
}
|
|
}
|
|
|
|
// AddPage adds a new page to the PDF
|
|
func (g *Generator) AddPage() {
|
|
g.pdf.AddPage()
|
|
g.currentPage++
|
|
}
|
|
|
|
// Page1Header adds the standard header for page 1
|
|
func (g *Generator) Page1Header() {
|
|
g.pdf.SetY(10)
|
|
|
|
// Set text color to blue
|
|
g.pdf.SetTextColor(0, 0, 152)
|
|
|
|
// Add logo if available (assuming logo is in static directory)
|
|
logoPath := filepath.Join("static", "images", "cmclogosmall.png")
|
|
// Try to add logo, but don't fail if it doesn't exist or isn't a proper image
|
|
g.pdf.ImageOptions(logoPath, 10, 10, 0, 28, false, gofpdf.ImageOptions{ImageType: "PNG"}, 0, "http://www.cmctechnologies.com.au")
|
|
|
|
// Company name
|
|
g.pdf.SetFont("Helvetica", "B", 30)
|
|
g.pdf.SetX(40)
|
|
g.pdf.CellFormat(0, 0, g.headerText, "", 1, "C", false, 0, "")
|
|
|
|
// Company details
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.SetY(22)
|
|
g.pdf.SetX(40)
|
|
g.pdf.CellFormat(0, 0, "PTY LIMITED ACN: 085 991 224 ABN: 47 085 991 224", "", 1, "C", false, 0, "")
|
|
|
|
// Draw horizontal line
|
|
g.pdf.SetDrawColor(0, 0, 0)
|
|
g.pdf.Line(43, 24, 200, 24)
|
|
|
|
// Contact details
|
|
g.pdf.SetTextColor(0, 0, 0)
|
|
g.pdf.SetY(32)
|
|
|
|
// Left column - labels
|
|
g.pdf.SetX(45)
|
|
g.pdf.MultiCell(30, 5, "Phone:\nFax:\nEmail:\nWeb Site:", "", "L", false)
|
|
|
|
// Middle column - values
|
|
g.pdf.SetY(32)
|
|
g.pdf.SetX(65)
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.MultiCell(55, 5, "+61 2 9669 4000\n+61 2 9669 4111\nsales@cmctechnologies.com.au\nwww.cmctechnologies.net.au", "", "L", false)
|
|
|
|
// Right column - address
|
|
g.pdf.SetY(32)
|
|
g.pdf.SetX(150)
|
|
g.pdf.MultiCell(52, 5, "Unit 19, 77 Bourke Rd\nAlexandria NSW 2015\nAUSTRALIA", "", "L", false)
|
|
|
|
// Engineering text
|
|
g.pdf.SetTextColor(0, 0, 152)
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.SetY(37)
|
|
g.pdf.SetX(10)
|
|
g.pdf.MultiCell(30, 5, "Engineering &\nIndustrial\nInstrumentation", "", "L", false)
|
|
}
|
|
|
|
// Page1Footer adds the standard footer
|
|
func (g *Generator) Page1Footer() {
|
|
g.pdf.SetY(-20)
|
|
|
|
// Footer line
|
|
g.pdf.SetDrawColor(0, 0, 0)
|
|
g.pdf.Line(10, g.pdf.GetY(), 200, g.pdf.GetY())
|
|
|
|
// Footer text
|
|
g.pdf.SetFont("Helvetica", "", 9)
|
|
g.pdf.SetY(-18)
|
|
g.pdf.CellFormat(0, 5, "CMC TECHNOLOGIES Provides Solutions in the Following Fields", "", 1, "C", false, 0, "")
|
|
|
|
// Color-coded services
|
|
g.pdf.SetY(-13)
|
|
g.pdf.SetX(10)
|
|
|
|
// First line of services
|
|
services := []struct {
|
|
text string
|
|
r, g, b int
|
|
}{
|
|
{"EXPLOSION PREVENTION AND PROTECTION", 153, 0, 10},
|
|
{"—", 0, 0, 0},
|
|
{"FIRE PROTECTION", 255, 153, 0},
|
|
{"—", 0, 0, 0},
|
|
{"PRESSURE RELIEF", 255, 0, 25},
|
|
{"—", 0, 0, 0},
|
|
{"VISION IN THE PROCESS", 0, 128, 30},
|
|
}
|
|
|
|
x := 15.0
|
|
for _, service := range services {
|
|
g.pdf.SetTextColor(service.r, service.g, service.b)
|
|
width := g.pdf.GetStringWidth(service.text)
|
|
g.pdf.SetX(x)
|
|
g.pdf.CellFormat(width, 5, service.text, "", 0, "L", false, 0, "")
|
|
x += width + 1
|
|
}
|
|
|
|
// Second line of services
|
|
g.pdf.SetY(-8)
|
|
g.pdf.SetX(60)
|
|
g.pdf.SetTextColor(47, 75, 224)
|
|
g.pdf.CellFormat(0, 5, "FLOW MEASUREMENT", "", 0, "L", false, 0, "")
|
|
g.pdf.SetX(110)
|
|
g.pdf.SetTextColor(171, 49, 248)
|
|
g.pdf.CellFormat(0, 5, "—PROCESS INSTRUMENTATION", "", 0, "L", false, 0, "")
|
|
}
|
|
|
|
// DetailsBox adds the document details box (quote/invoice details)
|
|
func (g *Generator) DetailsBox(docType, companyName, emailTo, attention, fromName, fromEmail, refNumber, yourRef, issueDate string) {
|
|
g.pdf.SetY(60)
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.SetTextColor(0, 0, 0)
|
|
|
|
// Create details table
|
|
lineHeight := 5.0
|
|
col1Width := 40.0
|
|
col2Width := 60.0
|
|
col3Width := 40.0
|
|
col4Width := 50.0
|
|
|
|
// Row 1
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col1Width, lineHeight, "COMPANY NAME:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.CellFormat(col2Width, lineHeight, companyName, "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col3Width, lineHeight, docType+" NO.:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.CellFormat(col4Width, lineHeight, refNumber, "1", 1, "L", false, 0, "")
|
|
|
|
// Row 2
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col1Width, lineHeight, "EMAIL TO:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.CellFormat(col2Width, lineHeight, emailTo, "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col3Width, lineHeight, "YOUR REFERENCE:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.CellFormat(col4Width, lineHeight, yourRef, "1", 1, "L", false, 0, "")
|
|
|
|
// Row 3
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col1Width, lineHeight, "ATTENTION:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.CellFormat(col2Width, lineHeight, attention, "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col3Width, lineHeight, "ISSUE DATE:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.CellFormat(col4Width, lineHeight, issueDate, "1", 1, "L", false, 0, "")
|
|
|
|
// Row 4
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col1Width, lineHeight, "FROM:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.CellFormat(col2Width, lineHeight, fromName, "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col3Width, lineHeight, "PAGES:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
pageText := fmt.Sprintf("%d of {nb}", g.pdf.PageNo())
|
|
g.pdf.CellFormat(col4Width, lineHeight, pageText, "1", 1, "L", false, 0, "")
|
|
|
|
// Row 5
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(col1Width, lineHeight, "EMAIL:", "1", 0, "L", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.CellFormat(col2Width, lineHeight, fromEmail, "1", 0, "L", false, 0, "")
|
|
g.pdf.CellFormat(col3Width+col4Width, lineHeight, "", "1", 1, "L", false, 0, "")
|
|
}
|
|
|
|
// AddContent adds HTML content to the PDF
|
|
func (g *Generator) AddContent(content string) {
|
|
g.pdf.SetFont("Helvetica", "", 10)
|
|
g.pdf.SetTextColor(0, 0, 0)
|
|
g.pdf.SetY(g.pdf.GetY() + 10)
|
|
|
|
// Basic HTML to PDF conversion
|
|
// Note: gofpdf has limited HTML support, so we'll need to parse and convert manually
|
|
// For now, we'll just add the content as plain text
|
|
g.pdf.MultiCell(0, 5, content, "", "L", false)
|
|
}
|
|
|
|
// AddLineItemsTable adds a table of line items
|
|
func (g *Generator) AddLineItemsTable(items []LineItem, currencySymbol string, showGST bool) {
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.SetTextColor(0, 0, 0)
|
|
|
|
// Column widths
|
|
itemWidth := 15.0
|
|
qtyWidth := 15.0
|
|
descWidth := 100.0
|
|
unitWidth := 30.0
|
|
totalWidth := 30.0
|
|
|
|
// Table header
|
|
g.pdf.CellFormat(itemWidth, 7, "ITEM", "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(qtyWidth, 7, "QTY", "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(descWidth, 7, "DESCRIPTION", "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(unitWidth, 7, "UNIT PRICE", "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(totalWidth, 7, "TOTAL", "1", 1, "C", false, 0, "")
|
|
|
|
// Table rows
|
|
g.pdf.SetFont("Helvetica", "", 9)
|
|
|
|
subtotal := 0.0
|
|
for _, item := range items {
|
|
// Check if we need a new page
|
|
if g.pdf.GetY() > 250 {
|
|
g.AddPage()
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
g.pdf.CellFormat(itemWidth, 7, "ITEM", "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(qtyWidth, 7, "QTY", "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(descWidth, 7, "DESCRIPTION", "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(unitWidth, 7, "UNIT PRICE", "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(totalWidth, 7, "TOTAL", "1", 1, "C", false, 0, "")
|
|
g.pdf.SetFont("Helvetica", "", 9)
|
|
}
|
|
|
|
g.pdf.CellFormat(itemWidth, 6, item.ItemNumber, "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(qtyWidth, 6, item.Quantity, "1", 0, "C", false, 0, "")
|
|
g.pdf.CellFormat(descWidth, 6, item.Title, "1", 0, "L", false, 0, "")
|
|
g.pdf.CellFormat(unitWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, item.UnitPrice), "1", 0, "R", false, 0, "")
|
|
g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, item.TotalPrice), "1", 1, "R", false, 0, "")
|
|
|
|
subtotal += item.TotalPrice
|
|
}
|
|
|
|
// Totals
|
|
g.pdf.SetFont("Helvetica", "B", 10)
|
|
|
|
// Subtotal
|
|
g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth)
|
|
g.pdf.CellFormat(unitWidth, 6, "SUBTOTAL:", "1", 0, "R", false, 0, "")
|
|
g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, subtotal), "1", 1, "R", false, 0, "")
|
|
|
|
// GST if applicable
|
|
if showGST {
|
|
gst := subtotal * 0.10
|
|
g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth)
|
|
g.pdf.CellFormat(unitWidth, 6, "GST (10%):", "1", 0, "R", false, 0, "")
|
|
g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, gst), "1", 1, "R", false, 0, "")
|
|
|
|
// Total
|
|
total := subtotal + gst
|
|
g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth)
|
|
g.pdf.CellFormat(unitWidth, 6, "TOTAL DUE:", "1", 0, "R", false, 0, "")
|
|
g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, total), "1", 1, "R", false, 0, "")
|
|
} else {
|
|
// Total without GST
|
|
g.pdf.SetX(g.pdf.GetX() + itemWidth + qtyWidth + descWidth)
|
|
g.pdf.CellFormat(unitWidth, 6, "TOTAL:", "1", 0, "R", false, 0, "")
|
|
g.pdf.CellFormat(totalWidth, 6, fmt.Sprintf("%s%.2f", currencySymbol, subtotal), "1", 1, "R", false, 0, "")
|
|
}
|
|
}
|
|
|
|
// Save saves the PDF to a file
|
|
func (g *Generator) Save(filename string) error {
|
|
g.pdf.AliasNbPages("")
|
|
outputPath := filepath.Join(g.outputDir, filename)
|
|
fmt.Printf("Generator.Save: Saving PDF to path: %s\n", outputPath)
|
|
err := g.pdf.OutputFileAndClose(outputPath)
|
|
if err != nil {
|
|
fmt.Printf("Generator.Save: Error saving PDF: %v\n", err)
|
|
} else {
|
|
fmt.Printf("Generator.Save: PDF saved successfully to: %s\n", outputPath)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// LineItem represents a line item for the PDF
|
|
type LineItem struct {
|
|
ItemNumber string
|
|
Quantity string
|
|
Title string
|
|
UnitPrice float64
|
|
TotalPrice float64
|
|
}
|