Altering go pdf template to match old style

This commit is contained in:
Finley Ghosh 2026-01-13 00:04:42 +11:00
parent 091ce77c49
commit 9c10d4c21f
4 changed files with 339 additions and 107 deletions

View file

@ -18,8 +18,18 @@ type InvoiceLineItemRequest struct {
ItemNumber string `json:"item_number"` ItemNumber string `json:"item_number"`
Quantity string `json:"quantity"` Quantity string `json:"quantity"`
Title string `json:"title"` Title string `json:"title"`
Description string `json:"description"`
UnitPrice float64 `json:"unit_price"` UnitPrice float64 `json:"unit_price"`
TotalPrice float64 `json:"total_price"` TotalPrice float64 `json:"total_price"`
NetUnitPrice float64 `json:"net_unit_price"`
NetPrice float64 `json:"net_price"`
DiscountPercent float64 `json:"discount_percent"`
DiscountAmountUnit float64 `json:"discount_amount_unit"`
DiscountAmountTotal float64 `json:"discount_amount_total"`
Option int `json:"option"`
HasTextPrices bool `json:"has_text_prices"`
UnitPriceString string `json:"unit_price_string"`
GrossPriceString string `json:"gross_price_string"`
} }
// InvoicePDFRequest is the expected payload from the PHP app. // InvoicePDFRequest is the expected payload from the PHP app.
@ -36,8 +46,20 @@ type InvoicePDFRequest struct {
ShipVia string `json:"ship_via"` ShipVia string `json:"ship_via"`
FOB string `json:"fob"` FOB string `json:"fob"`
IssueDate string `json:"issue_date"` // ISO date: 2006-01-02 IssueDate string `json:"issue_date"` // ISO date: 2006-01-02
IssueDateString string `json:"issue_date_string"` // Formatted: "12 January 2026"
CurrencySymbol string `json:"currency_symbol"` // e.g. "$" CurrencySymbol string `json:"currency_symbol"` // e.g. "$"
CurrencyCode string `json:"currency_code"` // e.g. "AUD", "USD"
ShowGST bool `json:"show_gst"` ShowGST bool `json:"show_gst"`
BillTo string `json:"bill_to"`
ShipTo string `json:"ship_to"`
ShippingDetails string `json:"shipping_details"`
CustomerOrderNumber string `json:"customer_order_number"`
JobTitle string `json:"job_title"`
PaymentTerms string `json:"payment_terms"`
CustomerABN string `json:"customer_abn"`
Subtotal interface{} `json:"subtotal"` // Can be float or "TBA"
GSTAmount interface{} `json:"gst_amount"`
Total interface{} `json:"total"`
LineItems []InvoiceLineItemRequest `json:"line_items"` LineItems []InvoiceLineItemRequest `json:"line_items"`
OutputDir string `json:"output_dir"` // optional override OutputDir string `json:"output_dir"` // optional override
} }
@ -98,15 +120,27 @@ func GenerateInvoicePDF(w http.ResponseWriter, r *http.Request) {
Customer: cust, Customer: cust,
LineItems: lineItems, LineItems: lineItems,
CurrencySymbol: req.CurrencySymbol, CurrencySymbol: req.CurrencySymbol,
CurrencyCode: req.CurrencyCode,
ShowGST: req.ShowGST, ShowGST: req.ShowGST,
ShipVia: req.ShipVia, ShipVia: req.ShipVia,
FOB: req.FOB, FOB: req.FOB,
IssueDate: issueDate, IssueDate: issueDate,
IssueDateString: req.IssueDateString,
EmailTo: req.ContactEmail, EmailTo: req.ContactEmail,
Attention: req.ContactName, Attention: req.ContactName,
FromName: fmt.Sprintf("%s %s", req.UserFirstName, req.UserLastName), FromName: fmt.Sprintf("%s %s", req.UserFirstName, req.UserLastName),
FromEmail: req.UserEmail, FromEmail: req.UserEmail,
YourReference: req.YourReference, YourReference: req.YourReference,
BillTo: req.BillTo,
ShipTo: req.ShipTo,
ShippingDetails: req.ShippingDetails,
CustomerOrderNumber: req.CustomerOrderNumber,
JobTitle: req.JobTitle,
PaymentTerms: req.PaymentTerms,
CustomerABN: req.CustomerABN,
Subtotal: req.Subtotal,
GSTAmount: req.GSTAmount,
Total: req.Total,
} }
filename, err := pdf.GenerateInvoicePDF(data, outputDir) filename, err := pdf.GenerateInvoicePDF(data, outputDir)

View file

@ -318,6 +318,201 @@ For full terms and conditions, please refer to our website or contact CMC TECHNO
g.pdf.MultiCell(0, 4, disclaimerText, "", "L", false) g.pdf.MultiCell(0, 4, disclaimerText, "", "L", false)
} }
// 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)
detailsText := fmt.Sprintf("Date: %s\nPage: 1 of {nb}", data.IssueDateString)
g.pdf.MultiCell(0, 4, detailsText, "", "L", false)
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
bankDetails := g.getBankDetails(data.CurrencyCode)
g.pdf.MultiCell(0, 3, bankDetails, "", "L", false)
g.pdf.SetY(maxEndY + 2)
}
// getBankDetails returns bank payment details based on currency code
func (g *Generator) getBankDetails(currencyCode string) string {
switch currencyCode {
case "EUR":
return "Account Name: CMC Technologies Pty Ltd\nAccount Number/IBAN: 06200015682004\nBranch code: 06200\nSWIFT Code/BIC: CTBAAU2S"
case "GBP":
return "Account Name: CMC Technologies Pty Ltd\nAccount Number/IBAN: 06200015642694\nBranch code: 06200\nSWIFT Code/BIC: CTBAAU2S"
case "USD":
return "Account Name: CMC Technologies Pty Ltd\nAccount Number/IBAN: 06200015681984\nBranch code: 06200\nSWIFT Code/BIC: CTBAAU2S"
default: // AUD and others
return "Account Name: CMC Technologies Pty Ltd\nBank Number BSB#: 062-458\nAccount Number: 10067982\nSWIFT Code: CTBAAU2S\nIBAN: 06245810067982"
}
}
// 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)
// Line items
for _, item := range data.LineItems {
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 // Save saves the PDF to a file
func (g *Generator) Save(filename string) error { func (g *Generator) Save(filename string) error {
g.pdf.AliasNbPages("") g.pdf.AliasNbPages("")

View file

@ -147,15 +147,27 @@ type InvoicePDFData struct {
LineItems []db.GetLineItemsTableRow LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data Currency interface{} // Currency data
CurrencySymbol string CurrencySymbol string
CurrencyCode string
ShowGST bool ShowGST bool
ShipVia string ShipVia string
FOB string FOB string
IssueDate time.Time IssueDate time.Time
IssueDateString string
EmailTo string EmailTo string
Attention string Attention string
FromName string FromName string
FromEmail string FromEmail string
YourReference string YourReference string
BillTo string
ShipTo string
ShippingDetails string
CustomerOrderNumber string
JobTitle string
PaymentTerms string
CustomerABN string
Subtotal interface{} // Can be float or "TBA"
GSTAmount interface{}
Total interface{}
} }
// GenerateInvoicePDF generates a PDF for an invoice // GenerateInvoicePDF generates a PDF for an invoice
@ -166,70 +178,39 @@ func GenerateInvoicePDF(data *InvoicePDFData, outputDir string) (string, error)
gen.AddPage() gen.AddPage()
gen.Page1Header() gen.Page1Header()
// Extract data for details box // Title
companyName := data.Customer.Name gen.pdf.SetFont("Helvetica", "B", 18)
emailTo := data.EmailTo gen.pdf.SetTextColor(0, 0, 0)
attention := data.Attention gen.pdf.CellFormat(0, 10, "TAX INVOICE", "", 1, "C", false, 0, "")
fromName := data.FromName
fromEmail := data.FromEmail
invoiceNumber := data.Invoice.Title
yourReference := data.YourReference
issueDate := data.IssueDate.Format("2 January 2006")
// Add details box
gen.DetailsBox("INVOICE", companyName, emailTo, attention, fromName, fromEmail, invoiceNumber, yourReference, issueDate)
// Add shipping details
gen.pdf.Ln(5) gen.pdf.Ln(5)
gen.pdf.SetFont("Helvetica", "B", 10)
gen.pdf.CellFormat(30, 5, "Ship Via:", "", 0, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "", 10)
gen.pdf.CellFormat(60, 5, data.ShipVia, "", 1, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "B", 10) // Sold To / Delivery Address boxes
gen.pdf.CellFormat(30, 5, "FOB:", "", 0, "L", false, 0, "") gen.AddInvoiceAddressBoxes(data)
gen.pdf.SetFont("Helvetica", "", 10)
gen.pdf.CellFormat(60, 5, data.FOB, "", 1, "L", false, 0, "") // More details table (Order Number, Job, FOB, Payment Terms, ABN)
gen.AddInvoiceDetailsTable(data)
gen.pdf.Ln(5)
// Line items table header
gen.AddInvoiceLineItemsHeader(data.CurrencyCode)
gen.Page1Footer() gen.Page1Footer()
// Add line items page // Add line items on new page(s)
gen.AddPage() gen.AddPage()
gen.pdf.SetFont("Helvetica", "B", 14) gen.pdf.SetFont("Helvetica", "B", 12)
gen.pdf.CellFormat(0, 10, "INVOICE DETAILS", "", 1, "C", false, 0, "") gen.pdf.CellFormat(0, 6, "CONTINUED: "+data.Invoice.Title, "", 1, "L", false, 0, "")
gen.pdf.Ln(5) gen.pdf.Ln(4)
// Convert line items // Add line items content
pdfItems := make([]LineItem, len(data.LineItems)) gen.AddInvoiceLineItemsContent(data)
for i, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
// Parse prices
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
pdfItems[i] = LineItem{
ItemNumber: item.ItemNumber,
Quantity: item.Quantity,
Title: item.Title,
UnitPrice: unitPrice,
TotalPrice: totalPrice,
}
}
// Add line items table
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
// Add terms and conditions page // Add terms and conditions page
gen.AddTermsAndConditions() gen.AddTermsAndConditions()
// Generate filename and save directly (no merge) // Generate filename and save
filename := fmt.Sprintf("%s.pdf", invoiceNumber) filename := fmt.Sprintf("%s.pdf", data.Invoice.Title)
if err := gen.Save(filename); err != nil { if err := gen.Save(filename); err != nil {
return "", err return "", err
} }

View file

@ -12,8 +12,18 @@ foreach ($document['LineItem'] as $li) {
'item_number' => $li['item_number'], 'item_number' => $li['item_number'],
'quantity' => $li['quantity'], 'quantity' => $li['quantity'],
'title' => $li['title'], 'title' => $li['title'],
'description' => isset($li['description']) ? $li['description'] : '',
'unit_price' => floatval($li['gross_unit_price']), 'unit_price' => floatval($li['gross_unit_price']),
'total_price' => floatval($li['gross_price']) 'total_price' => floatval($li['gross_price']),
'net_unit_price' => floatval($li['net_unit_price']),
'net_price' => floatval($li['net_price']),
'discount_percent' => floatval($li['discount_percent']),
'discount_amount_unit' => floatval($li['discount_amount_unit']),
'discount_amount_total' => floatval($li['discount_amount_total']),
'option' => intval($li['option']),
'has_text_prices' => isset($li['has_text_prices']) ? (bool)$li['has_text_prices'] : false,
'unit_price_string' => isset($li['unit_price_string']) ? $li['unit_price_string'] : '',
'gross_price_string' => isset($li['gross_price_string']) ? $li['gross_price_string'] : ''
); );
} }
@ -28,10 +38,22 @@ $payload = array(
'user_email' => $enquiry['User']['email'], 'user_email' => $enquiry['User']['email'],
'your_reference' => isset($enquiry['Enquiry']['customer_reference']) ? $enquiry['Enquiry']['customer_reference'] : ('Enquiry on '.date('j M Y', strtotime($enquiry['Enquiry']['created']))), 'your_reference' => isset($enquiry['Enquiry']['customer_reference']) ? $enquiry['Enquiry']['customer_reference'] : ('Enquiry on '.date('j M Y', strtotime($enquiry['Enquiry']['created']))),
'ship_via' => $document['Invoice']['ship_via'], 'ship_via' => $document['Invoice']['ship_via'],
'fob' => $document['Invoice']['fob'], 'fob' => $fob,
'issue_date' => $document['Invoice']['issue_date'], // expects YYYY-MM-DD 'issue_date' => $document['Invoice']['issue_date'], // expects YYYY-MM-DD
'issue_date_string' => $issue_date_string,
'currency_symbol' => $currencySymbol, 'currency_symbol' => $currencySymbol,
'currency_code' => $currencyCode,
'show_gst' => (bool)$gst, 'show_gst' => (bool)$gst,
'bill_to' => isset($document['Document']['bill_to']) ? $document['Document']['bill_to'] : '',
'ship_to' => isset($document['Document']['ship_to']) ? $document['Document']['ship_to'] : '',
'shipping_details' => isset($document['Document']['shipping_details']) ? $document['Document']['shipping_details'] : '',
'customer_order_number' => isset($job['Job']['customer_order_number']) ? $job['Job']['customer_order_number'] : '',
'job_title' => isset($job['Job']['title']) ? $job['Job']['title'] : '',
'payment_terms' => isset($job['Customer']['payment_terms']) ? $job['Customer']['payment_terms'] : '',
'customer_abn' => isset($job['Customer']['abn']) ? $job['Customer']['abn'] : '',
'subtotal' => $totals['subtotal'],
'gst_amount' => $totals['gst'],
'total' => $totals['total'],
'line_items' => $lineItems, 'line_items' => $lineItems,
'output_dir' => $outputDir 'output_dir' => $outputDir
); );