cmc-sales/go/internal/cmc/pdf/generator.go

674 lines
23 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", "CMC-Mobile-Logo.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 in table format
g.pdf.SetTextColor(0, 0, 0)
g.pdf.SetFont("Helvetica", "", 10)
startY := 32.0
labelX := 45.0
valueX := 65.0
addressX := 150.0
lineHeight := 5.0
// Row 1: Phone
g.pdf.SetXY(labelX, startY)
g.pdf.Cell(20, lineHeight, "Phone:")
g.pdf.SetXY(valueX, startY)
g.pdf.Cell(55, lineHeight, "+61 2 9669 4000")
g.pdf.SetXY(addressX, startY)
g.pdf.Cell(52, lineHeight, "Unit 19, 77 Bourke Rd")
// Row 2: Fax
g.pdf.SetXY(labelX, startY+lineHeight)
g.pdf.Cell(20, lineHeight, "Fax:")
g.pdf.SetXY(valueX, startY+lineHeight)
g.pdf.Cell(55, lineHeight, "+61 2 9669 4111")
g.pdf.SetXY(addressX, startY+lineHeight)
g.pdf.Cell(52, lineHeight, "Alexandria NSW 2015")
// Row 3: Email
g.pdf.SetXY(labelX, startY+lineHeight*2)
g.pdf.Cell(20, lineHeight, "Email:")
g.pdf.SetXY(valueX, startY+lineHeight*2)
g.pdf.Cell(55, lineHeight, "sales@cmctechnologies.com.au")
g.pdf.SetXY(addressX, startY+lineHeight*2)
g.pdf.Cell(52, lineHeight, "AUSTRALIA")
// Row 4: Web Site
g.pdf.SetXY(labelX, startY+lineHeight*3)
g.pdf.Cell(20, lineHeight, "Web Site:")
g.pdf.SetXY(valueX, startY+lineHeight*3)
g.pdf.Cell(55, lineHeight, "www.cmctechnologies.net.au")
// Engineering text on left
g.pdf.SetTextColor(0, 0, 152)
g.pdf.SetFont("Helvetica", "B", 10)
engineeringX := 10.0
engineeringY := 37.0
g.pdf.SetXY(engineeringX, engineeringY)
g.pdf.Cell(30, lineHeight, "Engineering &")
g.pdf.SetXY(engineeringX, engineeringY+lineHeight)
g.pdf.Cell(30, lineHeight, "Industrial")
g.pdf.SetXY(engineeringX, engineeringY+lineHeight*2)
g.pdf.Cell(30, lineHeight, "Instrumentation")
// Reset text color to black for subsequent content
g.pdf.SetTextColor(0, 0, 0)
}
// 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, "")
}
}
// AddTermsAndConditions adds a T&C page at the end of the PDF
// This renders a simple text page indicating T&Cs apply
func (g *Generator) AddTermsAndConditions() {
g.AddPage()
g.pdf.SetFont("Helvetica", "B", 14)
g.pdf.SetTextColor(171, 49, 248) // Purple/magenta color matching old format
g.pdf.SetY(20)
g.pdf.CellFormat(0, 10, "TERMS AND CONDITIONS", "", 1, "C", false, 0, "")
g.pdf.Ln(5)
g.pdf.SetFont("Helvetica", "", 9)
g.pdf.SetTextColor(171, 49, 248) // Keep purple for body text
g.pdf.SetLeftMargin(15)
g.pdf.SetRightMargin(15)
// Add disclaimer text
disclaimerText := `These Terms and Conditions apply to all quotations, invoices, purchase orders, and other documents issued by CMC TECHNOLOGIES.
By accepting this document, you agree to be bound by these terms.
Payment Terms: Invoices are due within 30 days of issue unless otherwise agreed in writing.
Delivery: Delivery dates are estimates only and not guaranteed. CMC TECHNOLOGIES is not liable for delays in delivery.
Intellectual Property: All designs, drawings, and specifications remain the property of CMC TECHNOLOGIES unless otherwise agreed.
Limitation of Liability: CMC TECHNOLOGIES shall not be liable for any indirect, incidental, or consequential damages arising out of or related to this document or transaction.
Entire Agreement: This document and any attached terms constitute the entire agreement between the parties.
For full terms and conditions, please refer to our website or contact CMC TECHNOLOGIES directly.`
g.pdf.MultiCell(0, 4, disclaimerText, "", "L", false)
g.pdf.SetTextColor(0, 0, 0) // Reset to black
}
// AddQuoteDetailsTable adds quote commercial details table (delivery time, payment terms, etc.)
func (g *Generator) AddQuoteDetailsTable(data *QuotePDFData) {
g.pdf.Ln(8)
g.pdf.SetFont("Helvetica", "B", 10)
g.pdf.CellFormat(0, 5, "QUOTE DETAILS", "", 1, "L", false, 0, "")
g.pdf.Ln(2)
// Create a simple table for quote details
g.pdf.SetFont("Helvetica", "", 9)
g.pdf.SetLeftMargin(15)
// Set column widths for a 3-column table
colWidth := 50.0
// Row 1: Delivery Time | Payment Terms | Days Valid
g.pdf.SetX(15)
g.pdf.SetLeftMargin(15)
if data.DeliveryTime != "" {
g.pdf.SetFont("Helvetica", "B", 8)
g.pdf.CellFormat(colWidth, 4, "Delivery Time:", "L", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "", 8)
g.pdf.CellFormat(colWidth, 4, data.DeliveryTime, "", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "B", 8)
g.pdf.CellFormat(colWidth, 4, "Payment Terms:", "", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "", 8)
g.pdf.CellFormat(0, 4, data.PaymentTerms, "R", 1, "L", false, 0, "")
}
// Row 2: Delivery Point | Days Valid
if data.DeliveryPoint != "" || data.DaysValid > 0 {
g.pdf.SetFont("Helvetica", "B", 8)
g.pdf.CellFormat(colWidth, 4, "Delivery Point:", "L", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "", 8)
g.pdf.CellFormat(colWidth, 4, data.DeliveryPoint, "", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "B", 8)
daysStr := ""
if data.DaysValid > 0 {
daysStr = fmt.Sprintf("%d days", data.DaysValid)
}
g.pdf.CellFormat(colWidth, 4, "Valid For:", "", 0, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "", 8)
g.pdf.CellFormat(0, 4, daysStr, "R", 1, "L", false, 0, "")
}
g.pdf.SetLeftMargin(10)
}
// AddInvoiceAddressBoxes adds the Sold To / Delivery Address boxes for invoices
func (g *Generator) AddInvoiceAddressBoxes(data *InvoicePDFData) {
g.pdf.SetFont("Helvetica", "", 9)
// Top row with headers and invoice details
x := g.pdf.GetX()
y := g.pdf.GetY()
// Left: Sold To header
g.pdf.SetXY(x, y)
g.pdf.SetFillColor(242, 242, 242)
g.pdf.CellFormat(58, 5, "Sold To / Invoice Address:", "1", 0, "L", true, 0, "")
// Middle: Delivery Address header
g.pdf.CellFormat(58, 5, "Delivery Address:", "1", 0, "L", true, 0, "")
// Right: Invoice details (starts here, spans 2 rows)
g.pdf.SetXY(x+122, y)
g.pdf.SetFont("Helvetica", "U", 11)
g.pdf.CellFormat(0, 5, fmt.Sprintf("CMC INVOICE#: %s", data.Invoice.Title), "", 1, "L", false, 0, "")
// Second row with address content
y += 5
g.pdf.SetXY(x, y)
g.pdf.SetFont("Helvetica", "", 9)
// Sold To content
billToHeight := 30.0
g.pdf.MultiCell(58, 4, data.BillTo, "1", "L", false)
soldToEndY := g.pdf.GetY()
// Ship To content
g.pdf.SetXY(x+58, y)
g.pdf.MultiCell(58, 4, data.ShipTo, "1", "L", false)
shipToEndY := g.pdf.GetY()
// Ensure both cells have same height
maxEndY := soldToEndY
if shipToEndY > maxEndY {
maxEndY = shipToEndY
}
if maxEndY < y+billToHeight {
maxEndY = y + billToHeight
}
// Add remaining invoice details on the right
g.pdf.SetXY(x+122, y)
g.pdf.SetFont("Helvetica", "", 9)
g.pdf.Cell(0, 4, fmt.Sprintf("Date: %s", data.IssueDateString))
g.pdf.SetXY(x+122, y+4)
g.pdf.Cell(0, 4, "Page: 1 of {nb}")
g.pdf.Ln(2)
g.pdf.SetFont("Helvetica", "U", 9)
g.pdf.CellFormat(0, 4, "MAKE PAYMENT TO:", "", 1, "L", false, 0, "")
g.pdf.SetFont("Helvetica", "", 8)
// Bank details based on currency in table format
bankY := g.pdf.GetY()
bankLineHeight := 3.0
switch data.CurrencyCode {
case "EUR":
g.pdf.SetXY(10, bankY)
g.pdf.Cell(0, bankLineHeight, "Account Name: CMC Technologies Pty Ltd")
g.pdf.SetXY(10, bankY+bankLineHeight)
g.pdf.Cell(0, bankLineHeight, "Account Number/IBAN: 06200015682004")
g.pdf.SetXY(10, bankY+bankLineHeight*2)
g.pdf.Cell(0, bankLineHeight, "Branch code: 06200")
g.pdf.SetXY(10, bankY+bankLineHeight*3)
g.pdf.Cell(0, bankLineHeight, "SWIFT Code/BIC: CTBAAU2S")
g.pdf.SetY(bankY + bankLineHeight*4)
case "GBP":
g.pdf.SetXY(10, bankY)
g.pdf.Cell(0, bankLineHeight, "Account Name: CMC Technologies Pty Ltd")
g.pdf.SetXY(10, bankY+bankLineHeight)
g.pdf.Cell(0, bankLineHeight, "Account Number/IBAN: 06200015642694")
g.pdf.SetXY(10, bankY+bankLineHeight*2)
g.pdf.Cell(0, bankLineHeight, "Branch code: 06200")
g.pdf.SetXY(10, bankY+bankLineHeight*3)
g.pdf.Cell(0, bankLineHeight, "SWIFT Code/BIC: CTBAAU2S")
g.pdf.SetY(bankY + bankLineHeight*4)
case "USD":
g.pdf.SetXY(10, bankY)
g.pdf.Cell(0, bankLineHeight, "Account Name: CMC Technologies Pty Ltd")
g.pdf.SetXY(10, bankY+bankLineHeight)
g.pdf.Cell(0, bankLineHeight, "Account Number/IBAN: 06200015681984")
g.pdf.SetXY(10, bankY+bankLineHeight*2)
g.pdf.Cell(0, bankLineHeight, "Branch code: 06200")
g.pdf.SetXY(10, bankY+bankLineHeight*3)
g.pdf.Cell(0, bankLineHeight, "SWIFT Code/BIC: CTBAAU2S")
g.pdf.SetY(bankY + bankLineHeight*4)
default: // AUD and others
g.pdf.SetXY(10, bankY)
g.pdf.Cell(0, bankLineHeight, "Account Name: CMC Technologies Pty Ltd")
g.pdf.SetXY(10, bankY+bankLineHeight)
g.pdf.Cell(0, bankLineHeight, "Bank Number BSB#: 062-458")
g.pdf.SetXY(10, bankY+bankLineHeight*2)
g.pdf.Cell(0, bankLineHeight, "Account Number: 10067982")
g.pdf.SetXY(10, bankY+bankLineHeight*3)
g.pdf.Cell(0, bankLineHeight, "SWIFT Code: CTBAAU2S")
g.pdf.SetXY(10, bankY+bankLineHeight*4)
g.pdf.Cell(0, bankLineHeight, "IBAN: 06245810067982")
g.pdf.SetY(bankY + bankLineHeight*5)
}
g.pdf.SetY(maxEndY + 2)
}
// AddInvoiceDetailsTable adds the order number, job, FOB, payment terms, ABN table
func (g *Generator) AddInvoiceDetailsTable(data *InvoicePDFData) {
g.pdf.SetFont("Helvetica", "B", 9)
g.pdf.SetFillColor(242, 242, 242)
// Header row
colWidth := 38.0
g.pdf.CellFormat(colWidth, 5, "CUSTOMER ORDER NO", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(colWidth, 5, "CMC JOB #", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(colWidth, 5, "INCOTERMS 2010", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(colWidth, 5, "PAYMENT TERMS", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(colWidth, 5, "CUSTOMER ABN", "1", 1, "C", true, 0, "")
// Data row
g.pdf.SetFont("Helvetica", "", 9)
g.pdf.CellFormat(colWidth, 5, data.CustomerOrderNumber, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(colWidth, 5, data.JobTitle, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(colWidth, 5, data.FOB, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(colWidth, 5, data.PaymentTerms, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(colWidth, 5, data.CustomerABN, "1", 1, "C", false, 0, "")
}
// AddInvoiceLineItemsHeader adds just the header row for line items table
func (g *Generator) AddInvoiceLineItemsHeader(currencyCode string) {
g.pdf.SetFont("Helvetica", "B", 9)
g.pdf.SetFillColor(242, 242, 242)
g.pdf.CellFormat(15, 5, "ITEM NO.", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(15, 5, "QTY", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(100, 5, "DESCRIPTION", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(30, 5, "UNIT PRICE", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(30, 5, "TOTAL PRICE", "1", 1, "C", true, 0, "")
// Currency row
g.pdf.SetFont("Helvetica", "", 8)
g.pdf.CellFormat(15, 4, "", "1", 0, "C", false, 0, "")
g.pdf.CellFormat(15, 4, "", "1", 0, "C", false, 0, "")
g.pdf.CellFormat(100, 4, "", "1", 0, "C", false, 0, "")
g.pdf.CellFormat(30, 4, currencyCode, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(30, 4, currencyCode, "1", 1, "C", false, 0, "")
}
// AddInvoiceLineItemsContent adds line items and totals for invoices
func (g *Generator) AddInvoiceLineItemsContent(data *InvoicePDFData) {
g.pdf.SetFont("Helvetica", "", 9)
g.pdf.SetTextColor(0, 0, 0)
pageNum := 1
firstPageBreak := false
// Line items
for i, item := range data.LineItems {
// Check if we need a new page (leave room for footer/totals)
if g.pdf.GetY() > 240 && i > 0 {
g.AddPage()
if !firstPageBreak {
g.pdf.SetFont("Helvetica", "B", 12)
g.pdf.CellFormat(0, 6, "CONTINUED: "+data.Invoice.Title, "", 1, "L", false, 0, "")
g.pdf.Ln(4)
firstPageBreak = true
}
// Re-draw header on new page
g.pdf.SetFont("Helvetica", "B", 9)
g.pdf.SetFillColor(242, 242, 242)
g.pdf.CellFormat(15, 5, "ITEM NO.", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(15, 5, "QTY", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(100, 5, "DESCRIPTION", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(30, 5, "UNIT PRICE", "1", 0, "C", true, 0, "")
g.pdf.CellFormat(30, 5, "TOTAL PRICE", "1", 1, "C", true, 0, "")
g.pdf.SetFont("Helvetica", "", 9)
}
unitPrice := 0.0
totalPrice := 0.0
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
g.pdf.CellFormat(15, 5, item.ItemNumber, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(15, 5, item.Quantity, "1", 0, "C", false, 0, "")
g.pdf.CellFormat(100, 5, item.Title, "1", 0, "L", false, 0, "")
g.pdf.CellFormat(30, 5, fmt.Sprintf("%s%.2f", data.CurrencySymbol, unitPrice), "1", 0, "C", false, 0, "")
g.pdf.CellFormat(30, 5, fmt.Sprintf("%s%.2f", data.CurrencySymbol, totalPrice), "1", 1, "C", false, 0, "")
}
// Freight details and totals
g.pdf.SetFillColor(242, 242, 242)
g.pdf.SetFont("Helvetica", "B", 9)
// Freight details (left side, spans 2 rows)
y := g.pdf.GetY()
g.pdf.CellFormat(130, 5, "FREIGHT DETAILS:", "1", 0, "L", true, 0, "")
// Subtotal
g.pdf.CellFormat(30, 5, "SUBTOTAL", "1", 0, "L", true, 0, "")
g.pdf.SetFont("Helvetica", "", 9)
subtotalStr := fmt.Sprintf("%v", data.Subtotal)
if val, ok := data.Subtotal.(float64); ok {
subtotalStr = fmt.Sprintf("%s%.2f", data.CurrencySymbol, val)
}
g.pdf.CellFormat(30, 5, subtotalStr, "1", 1, "C", false, 0, "")
// Shipping details text (left side)
g.pdf.SetFont("Helvetica", "", 8)
g.pdf.SetXY(10, y+5)
g.pdf.MultiCell(130, 4, data.ShippingDetails, "1", "L", false)
shippingEndY := g.pdf.GetY()
// GST row
g.pdf.SetXY(140, y+5)
g.pdf.SetFont("Helvetica", "B", 9)
g.pdf.SetFillColor(242, 242, 242)
g.pdf.CellFormat(30, 5, "GST (10%)", "1", 0, "L", true, 0, "")
g.pdf.SetFont("Helvetica", "", 9)
gstStr := fmt.Sprintf("%v", data.GSTAmount)
if val, ok := data.GSTAmount.(float64); ok {
gstStr = fmt.Sprintf("%s%.2f", data.CurrencySymbol, val)
}
g.pdf.CellFormat(30, 5, gstStr, "1", 1, "C", false, 0, "")
// Total row
g.pdf.SetFont("Helvetica", "B", 9)
g.pdf.CellFormat(130, 5, "", "", 0, "L", false, 0, "")
g.pdf.SetFillColor(242, 242, 242)
g.pdf.CellFormat(30, 5, "TOTAL DUE", "1", 0, "L", true, 0, "")
g.pdf.SetFont("Helvetica", "", 9)
totalStr := fmt.Sprintf("%v", data.Total)
if val, ok := data.Total.(float64); ok {
totalStr = fmt.Sprintf("%s%.2f", data.CurrencySymbol, val)
}
g.pdf.CellFormat(30, 5, totalStr, "1", 1, "C", false, 0, "")
// Make sure we're past the shipping details
if g.pdf.GetY() < shippingEndY {
g.pdf.SetY(shippingEndY)
}
}
// 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
}