Trying to make the invoice look better

This commit is contained in:
Finley Ghosh 2026-01-13 23:26:37 +11:00
parent afa9c9f731
commit 41b5ee2ebb
4 changed files with 174 additions and 52 deletions

View file

@ -43,7 +43,7 @@ func (g *Generator) Page1Header() {
g.pdf.SetTextColor(0, 0, 152)
// Add logo if available (assuming logo is in static directory)
logoPath := filepath.Join("static", "images", "cmclogosmall.png")
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")
@ -62,31 +62,60 @@ func (g *Generator) Page1Header() {
g.pdf.SetDrawColor(0, 0, 0)
g.pdf.Line(43, 24, 200, 24)
// Contact details
// Contact details in table format
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)
startY := 32.0
labelX := 45.0
valueX := 65.0
addressX := 150.0
lineHeight := 5.0
// Engineering text
// 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)
g.pdf.SetY(37)
g.pdf.SetX(10)
g.pdf.MultiCell(30, 5, "Engineering &\nIndustrial\nInstrumentation", "", "L", false)
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
@ -291,11 +320,13 @@ func (g *Generator) AddLineItemsTable(items []LineItem, currencySymbol string, s
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)
@ -316,6 +347,7 @@ Entire Agreement: This document and any attached terms constitute the entire agr
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.)
@ -414,35 +446,66 @@ func (g *Generator) AddInvoiceAddressBoxes(data *InvoicePDFData) {
// 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.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
bankDetails := g.getBankDetails(data.CurrencyCode)
g.pdf.MultiCell(0, 3, bankDetails, "", "L", false)
// 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)
}
// 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)
@ -488,9 +551,34 @@ func (g *Generator) AddInvoiceLineItemsHeader(currencyCode string) {
// 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 _, item := range data.LineItems {
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

View file

@ -2,9 +2,13 @@ package pdf
import (
"fmt"
"log"
"os"
"path/filepath"
"time"
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
"github.com/pdfcpu/pdfcpu/pkg/api"
)
// QuotePDFData contains all data needed to generate a quote PDF
@ -215,25 +219,41 @@ func GenerateInvoicePDF(data *InvoicePDFData, outputDir string) (string, error)
// Line items table header
gen.AddInvoiceLineItemsHeader(data.CurrencyCode)
gen.Page1Footer()
// Add line items on new page(s)
gen.AddPage()
gen.pdf.SetFont("Helvetica", "B", 12)
gen.pdf.CellFormat(0, 6, "CONTINUED: "+data.Invoice.Title, "", 1, "L", false, 0, "")
gen.pdf.Ln(4)
// Add line items content
// Add line items content (handles page breaks internally)
gen.AddInvoiceLineItemsContent(data)
// Add terms and conditions page
gen.AddTermsAndConditions()
// Generate filename and save
filename := fmt.Sprintf("%s.pdf", data.Invoice.Title)
invoicePath := filepath.Join(outputDir, filename)
if err := gen.Save(filename); err != nil {
return "", err
}
// Merge with actual terms and conditions PDF using pdfcpu
termsPath := filepath.Join(outputDir, "CMC_terms_and_conditions2006_A4.pdf")
// Check if terms file exists
if _, err := os.Stat(termsPath); err == nil {
// Terms file exists, merge it
finalPath := invoicePath
tempPath := filepath.Join(outputDir, fmt.Sprintf("%s_merged_temp.pdf", data.Invoice.Title))
if err := MergePDFs(invoicePath, termsPath, tempPath); err != nil {
log.Printf("GenerateInvoicePDF: Warning - could not merge T&C PDF: %v. Returning invoice without T&C.", err)
// Continue with just the invoice if merge fails
return filename, nil
}
// Replace original with merged version
if err := os.Rename(tempPath, finalPath); err != nil {
log.Printf("GenerateInvoicePDF: Warning - could not replace file with merged version: %v", err)
// Continue with original if rename fails
return filename, nil
}
} else {
log.Printf("GenerateInvoicePDF: Warning - terms and conditions PDF not found at %s", termsPath)
}
return filename, nil
}
@ -558,3 +578,17 @@ func GenerateOrderAckPDF(data *OrderAckPDFData, outputDir string) (string, error
err := gen.Save(filename)
return filename, err
}
// MergePDFs merges an invoice PDF with a terms and conditions PDF
// outputPath is the combined PDF file path
func MergePDFs(invoicePath, termsPath, outputPath string) error {
// Merge the PDFs: invoice first, then terms
inFiles := []string{invoicePath, termsPath}
err := api.MergeCreateFile(inFiles, outputPath, false, nil)
if err != nil {
return fmt.Errorf("failed to merge PDFs: %w", err)
}
return nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.