Trying to make the invoice look better
This commit is contained in:
parent
afa9c9f731
commit
41b5ee2ebb
|
|
@ -43,7 +43,7 @@ func (g *Generator) Page1Header() {
|
||||||
g.pdf.SetTextColor(0, 0, 152)
|
g.pdf.SetTextColor(0, 0, 152)
|
||||||
|
|
||||||
// Add logo if available (assuming logo is in static directory)
|
// 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
|
// 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")
|
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.SetDrawColor(0, 0, 0)
|
||||||
g.pdf.Line(43, 24, 200, 24)
|
g.pdf.Line(43, 24, 200, 24)
|
||||||
|
|
||||||
// Contact details
|
// Contact details in table format
|
||||||
g.pdf.SetTextColor(0, 0, 0)
|
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.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
|
startY := 32.0
|
||||||
g.pdf.SetY(32)
|
labelX := 45.0
|
||||||
g.pdf.SetX(150)
|
valueX := 65.0
|
||||||
g.pdf.MultiCell(52, 5, "Unit 19, 77 Bourke Rd\nAlexandria NSW 2015\nAUSTRALIA", "", "L", false)
|
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.SetTextColor(0, 0, 152)
|
||||||
g.pdf.SetFont("Helvetica", "B", 10)
|
g.pdf.SetFont("Helvetica", "B", 10)
|
||||||
g.pdf.SetY(37)
|
engineeringX := 10.0
|
||||||
g.pdf.SetX(10)
|
engineeringY := 37.0
|
||||||
g.pdf.MultiCell(30, 5, "Engineering &\nIndustrial\nInstrumentation", "", "L", false)
|
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
|
// Page1Footer adds the standard footer
|
||||||
|
|
@ -291,11 +320,13 @@ func (g *Generator) AddLineItemsTable(items []LineItem, currencySymbol string, s
|
||||||
func (g *Generator) AddTermsAndConditions() {
|
func (g *Generator) AddTermsAndConditions() {
|
||||||
g.AddPage()
|
g.AddPage()
|
||||||
g.pdf.SetFont("Helvetica", "B", 14)
|
g.pdf.SetFont("Helvetica", "B", 14)
|
||||||
|
g.pdf.SetTextColor(171, 49, 248) // Purple/magenta color matching old format
|
||||||
g.pdf.SetY(20)
|
g.pdf.SetY(20)
|
||||||
g.pdf.CellFormat(0, 10, "TERMS AND CONDITIONS", "", 1, "C", false, 0, "")
|
g.pdf.CellFormat(0, 10, "TERMS AND CONDITIONS", "", 1, "C", false, 0, "")
|
||||||
g.pdf.Ln(5)
|
g.pdf.Ln(5)
|
||||||
|
|
||||||
g.pdf.SetFont("Helvetica", "", 9)
|
g.pdf.SetFont("Helvetica", "", 9)
|
||||||
|
g.pdf.SetTextColor(171, 49, 248) // Keep purple for body text
|
||||||
g.pdf.SetLeftMargin(15)
|
g.pdf.SetLeftMargin(15)
|
||||||
g.pdf.SetRightMargin(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.`
|
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.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.)
|
// 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
|
// Add remaining invoice details on the right
|
||||||
g.pdf.SetXY(x+122, y)
|
g.pdf.SetXY(x+122, y)
|
||||||
g.pdf.SetFont("Helvetica", "", 9)
|
g.pdf.SetFont("Helvetica", "", 9)
|
||||||
detailsText := fmt.Sprintf("Date: %s\nPage: 1 of {nb}", data.IssueDateString)
|
g.pdf.Cell(0, 4, fmt.Sprintf("Date: %s", data.IssueDateString))
|
||||||
g.pdf.MultiCell(0, 4, detailsText, "", "L", false)
|
g.pdf.SetXY(x+122, y+4)
|
||||||
|
g.pdf.Cell(0, 4, "Page: 1 of {nb}")
|
||||||
|
|
||||||
g.pdf.Ln(2)
|
g.pdf.Ln(2)
|
||||||
g.pdf.SetFont("Helvetica", "U", 9)
|
g.pdf.SetFont("Helvetica", "U", 9)
|
||||||
g.pdf.CellFormat(0, 4, "MAKE PAYMENT TO:", "", 1, "L", false, 0, "")
|
g.pdf.CellFormat(0, 4, "MAKE PAYMENT TO:", "", 1, "L", false, 0, "")
|
||||||
g.pdf.SetFont("Helvetica", "", 8)
|
g.pdf.SetFont("Helvetica", "", 8)
|
||||||
|
|
||||||
// Bank details based on currency
|
// Bank details based on currency in table format
|
||||||
bankDetails := g.getBankDetails(data.CurrencyCode)
|
bankY := g.pdf.GetY()
|
||||||
g.pdf.MultiCell(0, 3, bankDetails, "", "L", false)
|
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)
|
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
|
// AddInvoiceDetailsTable adds the order number, job, FOB, payment terms, ABN table
|
||||||
func (g *Generator) AddInvoiceDetailsTable(data *InvoicePDFData) {
|
func (g *Generator) AddInvoiceDetailsTable(data *InvoicePDFData) {
|
||||||
g.pdf.SetFont("Helvetica", "B", 9)
|
g.pdf.SetFont("Helvetica", "B", 9)
|
||||||
|
|
@ -488,9 +551,34 @@ func (g *Generator) AddInvoiceLineItemsHeader(currencyCode string) {
|
||||||
// AddInvoiceLineItemsContent adds line items and totals for invoices
|
// AddInvoiceLineItemsContent adds line items and totals for invoices
|
||||||
func (g *Generator) AddInvoiceLineItemsContent(data *InvoicePDFData) {
|
func (g *Generator) AddInvoiceLineItemsContent(data *InvoicePDFData) {
|
||||||
g.pdf.SetFont("Helvetica", "", 9)
|
g.pdf.SetFont("Helvetica", "", 9)
|
||||||
|
g.pdf.SetTextColor(0, 0, 0)
|
||||||
|
|
||||||
|
pageNum := 1
|
||||||
|
firstPageBreak := false
|
||||||
|
|
||||||
// Line items
|
// 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
|
unitPrice := 0.0
|
||||||
totalPrice := 0.0
|
totalPrice := 0.0
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,13 @@ package pdf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
|
"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
|
// 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
|
// Line items table header
|
||||||
gen.AddInvoiceLineItemsHeader(data.CurrencyCode)
|
gen.AddInvoiceLineItemsHeader(data.CurrencyCode)
|
||||||
|
|
||||||
gen.Page1Footer()
|
// Add line items content (handles page breaks internally)
|
||||||
|
|
||||||
// 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
|
|
||||||
gen.AddInvoiceLineItemsContent(data)
|
gen.AddInvoiceLineItemsContent(data)
|
||||||
|
|
||||||
// Add terms and conditions page
|
|
||||||
gen.AddTermsAndConditions()
|
|
||||||
|
|
||||||
// Generate filename and save
|
// Generate filename and save
|
||||||
filename := fmt.Sprintf("%s.pdf", data.Invoice.Title)
|
filename := fmt.Sprintf("%s.pdf", data.Invoice.Title)
|
||||||
|
invoicePath := filepath.Join(outputDir, filename)
|
||||||
if err := gen.Save(filename); err != nil {
|
if err := gen.Save(filename); err != nil {
|
||||||
return "", err
|
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
|
return filename, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -558,3 +578,17 @@ func GenerateOrderAckPDF(data *OrderAckPDFData, outputDir string) (string, error
|
||||||
err := gen.Save(filename)
|
err := gen.Save(filename)
|
||||||
return filename, err
|
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
|
||||||
|
}
|
||||||
|
|
|
||||||
BIN
go/static/images/CMC-Mobile-Logo.png
Normal file
BIN
go/static/images/CMC-Mobile-Logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 190 KiB |
BIN
php/app/webroot/pdf/CMCIN9387.pdf
Normal file
BIN
php/app/webroot/pdf/CMCIN9387.pdf
Normal file
Binary file not shown.
Loading…
Reference in a new issue