diff --git a/go/internal/cmc/pdf/generator.go b/go/internal/cmc/pdf/generator.go index 1cf27170..1af0523a 100644 --- a/go/internal/cmc/pdf/generator.go +++ b/go/internal/cmc/pdf/generator.go @@ -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 diff --git a/go/internal/cmc/pdf/templates.go b/go/internal/cmc/pdf/templates.go index dad77af1..6c5847ec 100644 --- a/go/internal/cmc/pdf/templates.go +++ b/go/internal/cmc/pdf/templates.go @@ -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 +} diff --git a/go/static/images/CMC-Mobile-Logo.png b/go/static/images/CMC-Mobile-Logo.png new file mode 100644 index 00000000..94f4ca86 Binary files /dev/null and b/go/static/images/CMC-Mobile-Logo.png differ diff --git a/php/app/webroot/pdf/CMCIN9387.pdf b/php/app/webroot/pdf/CMCIN9387.pdf new file mode 100644 index 00000000..9108a610 Binary files /dev/null and b/php/app/webroot/pdf/CMCIN9387.pdf differ