diff --git a/go/cmd/server/main.go b/go/cmd/server/main.go
index 5121c2f6..dc86ae7b 100644
--- a/go/cmd/server/main.go
+++ b/go/cmd/server/main.go
@@ -63,6 +63,7 @@ func main() {
// Load handlers
quoteHandler := quotes.NewQuotesHandler(queries, tmpl, emailService)
attachmentHandler := attachments.NewAttachmentHandler(queries)
+ documentHandler := documents.NewDocumentHandler(queries)
// Setup routes
r := mux.NewRouter()
@@ -83,12 +84,12 @@ func main() {
goRouter.HandleFunc("/attachments/{id}", attachmentHandler.Delete).Methods("DELETE")
// Document generation routes
- goRouter.HandleFunc("/document/generate/invoice", documents.GenerateInvoicePDF).Methods("POST")
- goRouter.HandleFunc("/document/generate/quote", documents.GenerateQuotePDF).Methods("POST")
- goRouter.HandleFunc("/document/generate/purchase-order", documents.GeneratePurchaseOrderPDF).Methods("POST")
- goRouter.HandleFunc("/document/generate/packing-list", documents.GeneratePackingListPDF).Methods("POST")
- goRouter.HandleFunc("/document/generate/order-acknowledgement", documents.GenerateOrderAckPDF).Methods("POST")
- goRouter.HandleFunc("/document/page-count", documents.CountPages).Methods("POST")
+ goRouter.HandleFunc("/document/generate/invoice", documentHandler.GenerateInvoicePDF).Methods("POST")
+ goRouter.HandleFunc("/document/generate/quote", documentHandler.GenerateQuotePDF).Methods("POST")
+ goRouter.HandleFunc("/document/generate/purchase-order", documentHandler.GeneratePurchaseOrderPDF).Methods("POST")
+ goRouter.HandleFunc("/document/generate/packing-list", documentHandler.GeneratePackingListPDF).Methods("POST")
+ goRouter.HandleFunc("/document/generate/order-acknowledgement", documentHandler.GenerateOrderAckPDF).Methods("POST")
+ goRouter.HandleFunc("/document/page-count", documentHandler.CountPages).Methods("POST")
// Serve generated PDFs
pdfDir := os.Getenv("PDF_OUTPUT_DIR")
diff --git a/go/internal/cmc/documents/counter.go b/go/internal/cmc/documents/counter.go
index 162bb837..bf4d769a 100644
--- a/go/internal/cmc/documents/counter.go
+++ b/go/internal/cmc/documents/counter.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"fmt"
diff --git a/go/internal/cmc/documents/html_generator.go b/go/internal/cmc/documents/html_generator.go
index 25044037..910469d3 100644
--- a/go/internal/cmc/documents/html_generator.go
+++ b/go/internal/cmc/documents/html_generator.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"context"
@@ -376,12 +376,17 @@ func (g *HTMLDocumentGenerator) loadLogo(logoFileName string) string {
return logoFileName
}
-// GeneratePurchaseOrderPDF creates a PDF purchase order from HTML template
+// GeneratePurchaseOrderPDF creates a PDF purchase order from HTML template with T&C merging
// Returns (filename, error)
func (g *HTMLDocumentGenerator) GeneratePurchaseOrderPDF(data *PurchaseOrderPDFData) (string, error) {
- fmt.Println("=== HTML Generator: Starting purchase order generation ===")
+ fmt.Println("=== HTML Generator: Starting purchase order generation (two-pass with T&C page count) ===")
+ // FIRST PASS: Generate PDF without page numbers to determine total pages (including T&C)
+ fmt.Println("=== HTML Generator: First pass - generating without page count ===")
html := g.BuildPurchaseOrderHTML(data, 0, 0)
+
+ fmt.Printf("=== HTML Generator: Generated %d bytes of HTML ===\n", len(html))
+
tempHTML := filepath.Join(g.outputDir, "temp_po.html")
if err := ioutil.WriteFile(tempHTML, []byte(html), 0644); err != nil {
return "", fmt.Errorf("failed to write temp HTML: %w", err)
@@ -389,27 +394,106 @@ func (g *HTMLDocumentGenerator) GeneratePurchaseOrderPDF(data *PurchaseOrderPDFD
defer os.Remove(tempHTML)
defer os.Remove(filepath.Join(g.outputDir, "quote_logo.png"))
- poNumber := data.PurchaseOrder.Title
+ // Generate temp PDF
+ tempPDFPath := filepath.Join(g.outputDir, "temp_po_first_pass.pdf")
+ if err := g.htmlToPDF(tempHTML, tempPDFPath); err != nil {
+ return "", fmt.Errorf("failed to convert HTML to PDF (first pass): %w", err)
+ }
+
+ // Get initial page count from PO
+ poPageCount, err := g.getPageCount(tempPDFPath)
+ if err != nil {
+ fmt.Printf("Warning: Could not extract PO page count: %v\n", err)
+ poPageCount = 1
+ }
+
+ // Check if T&C exists and merge to get total page count
+ totalPageCount := poPageCount
+ termsPath := filepath.Join(g.outputDir, "CMC_terms_and_conditions2006_A4.pdf")
+ tempMergedPath := filepath.Join(g.outputDir, "temp_po_merged_first_pass.pdf")
+
+ if _, err := os.Stat(termsPath); err == nil {
+ fmt.Println("=== HTML Generator: T&C found, merging to determine total pages ===")
+ if err := MergePDFs(tempPDFPath, termsPath, tempMergedPath); err == nil {
+ // Get total page count from merged PDF
+ totalPageCount, err = g.getPageCount(tempMergedPath)
+ if err != nil {
+ fmt.Printf("Warning: Could not extract merged page count, using PO count: %v\n", err)
+ totalPageCount = poPageCount
+ } else {
+ fmt.Printf("=== HTML Generator: Total pages (PO + T&C): %d ===\n", totalPageCount)
+ }
+ } else {
+ fmt.Printf("Warning: Could not merge T&C for counting: %v\n", err)
+ }
+ }
+
+ fmt.Printf("=== HTML Generator: First pass complete, detected %d total pages ===\n", totalPageCount)
+ os.Remove(tempPDFPath)
+ os.Remove(tempMergedPath)
+
+ // SECOND PASS: Generate final PDF
+ fmt.Println("=== HTML Generator: Second pass - regenerating final PDF ===")
+ html = g.BuildPurchaseOrderHTML(data, 0, 0)
+
+ if err := ioutil.WriteFile(tempHTML, []byte(html), 0644); err != nil {
+ return "", fmt.Errorf("failed to write temp HTML (second pass): %w", err)
+ }
+
+ // Generate final PDF filename
+ poNumber := data.Document.CmcReference
if poNumber == "" {
- poNumber = fmt.Sprintf("PO-%d", data.PurchaseOrder.ID)
+ poNumber = fmt.Sprintf("PO-%d", data.Document.ID)
}
filename := fmt.Sprintf("%s.pdf", poNumber)
pdfPath := filepath.Join(g.outputDir, filename)
if err := g.htmlToPDF(tempHTML, pdfPath); err != nil {
- return "", fmt.Errorf("failed to convert HTML to PDF: %w", err)
+ return "", fmt.Errorf("failed to convert HTML to PDF (second pass): %w", err)
}
+ fmt.Println("=== HTML Generator: PDF generation complete ===")
+
+ // Merge with T&C PDF if it exists
+ if _, err := os.Stat(termsPath); err == nil {
+ fmt.Println("=== HTML Generator: Found T&C PDF, merging ===")
+ tempMergedPath := filepath.Join(g.outputDir, fmt.Sprintf("%s_merged_temp.pdf", poNumber))
+ if err := MergePDFs(pdfPath, termsPath, tempMergedPath); err != nil {
+ fmt.Printf("=== HTML Generator: Warning - could not merge T&C PDF: %v. Returning PO without T&C.\n", err)
+ return filename, nil
+ }
+
+ // Replace original PDF with merged version
+ if err := os.Rename(tempMergedPath, pdfPath); err != nil {
+ fmt.Printf("=== HTML Generator: Warning - could not replace original with merged: %v. Returning unmerged.\n", err)
+ return filename, nil
+ }
+
+ fmt.Println("=== HTML Generator: Replaced PDF successfully")
+ }
+
+ // Verify the file exists and get final size
+ fileInfo, err := os.Stat(pdfPath)
+ if err != nil {
+ return "", fmt.Errorf("failed to verify final PDF: %w", err)
+ }
+
+ fmt.Printf("=== HTML Generator: Final PDF verified, size=%d bytes\n", fileInfo.Size())
return filename, nil
}
-// GeneratePackingListPDF creates a PDF packing list from HTML template
+// GeneratePackingListPDF creates a PDF packing list from HTML template with T&C merging
// Returns (filename, error)
func (g *HTMLDocumentGenerator) GeneratePackingListPDF(data *PackingListPDFData) (string, error) {
- fmt.Println("=== HTML Generator: Starting packing list generation ===")
+ fmt.Println("=== HTML Generator: Starting packing list generation (two-pass with T&C page count) ===")
+ // FIRST PASS: Generate PDF without page numbers to determine total pages (including T&C)
+ fmt.Println("=== HTML Generator: First pass - generating without page count ===")
html := g.BuildPackingListHTML(data, 0, 0)
+
+ fmt.Printf("=== HTML Generator: Generated %d bytes of HTML ===\n", len(html))
+
tempHTML := filepath.Join(g.outputDir, "temp_packinglist.html")
if err := ioutil.WriteFile(tempHTML, []byte(html), 0644); err != nil {
return "", fmt.Errorf("failed to write temp HTML: %w", err)
@@ -417,6 +501,53 @@ func (g *HTMLDocumentGenerator) GeneratePackingListPDF(data *PackingListPDFData)
defer os.Remove(tempHTML)
defer os.Remove(filepath.Join(g.outputDir, "quote_logo.png"))
+ // Generate temp PDF
+ tempPDFPath := filepath.Join(g.outputDir, "temp_packinglist_first_pass.pdf")
+ if err := g.htmlToPDF(tempHTML, tempPDFPath); err != nil {
+ return "", fmt.Errorf("failed to convert HTML to PDF (first pass): %w", err)
+ }
+
+ // Get initial page count from packing list
+ packingListPageCount, err := g.getPageCount(tempPDFPath)
+ if err != nil {
+ fmt.Printf("Warning: Could not extract packing list page count: %v\n", err)
+ packingListPageCount = 1
+ }
+
+ // Check if T&C exists and merge to get total page count
+ totalPageCount := packingListPageCount
+ termsPath := filepath.Join(g.outputDir, "CMC_terms_and_conditions2006_A4.pdf")
+ tempMergedPath := filepath.Join(g.outputDir, "temp_packinglist_merged_first_pass.pdf")
+
+ if _, err := os.Stat(termsPath); err == nil {
+ fmt.Println("=== HTML Generator: T&C found, merging to determine total pages ===")
+ if err := MergePDFs(tempPDFPath, termsPath, tempMergedPath); err == nil {
+ // Get total page count from merged PDF
+ totalPageCount, err = g.getPageCount(tempMergedPath)
+ if err != nil {
+ fmt.Printf("Warning: Could not extract merged page count, using packing list count: %v\n", err)
+ totalPageCount = packingListPageCount
+ } else {
+ fmt.Printf("=== HTML Generator: Total pages (packing list + T&C): %d ===\n", totalPageCount)
+ }
+ } else {
+ fmt.Printf("Warning: Could not merge T&C for counting: %v\n", err)
+ }
+ }
+
+ fmt.Printf("=== HTML Generator: First pass complete, detected %d total pages ===\n", totalPageCount)
+ os.Remove(tempPDFPath)
+ os.Remove(tempMergedPath)
+
+ // SECOND PASS: Generate final PDF
+ fmt.Println("=== HTML Generator: Second pass - regenerating final PDF ===")
+ html = g.BuildPackingListHTML(data, 0, 0)
+
+ if err := ioutil.WriteFile(tempHTML, []byte(html), 0644); err != nil {
+ return "", fmt.Errorf("failed to write temp HTML (second pass): %w", err)
+ }
+
+ // Generate final PDF filename
packingListNumber := data.PackingList.CmcReference
if packingListNumber == "" {
packingListNumber = fmt.Sprintf("PackingList-%d", data.PackingList.ID)
@@ -426,18 +557,50 @@ func (g *HTMLDocumentGenerator) GeneratePackingListPDF(data *PackingListPDFData)
pdfPath := filepath.Join(g.outputDir, filename)
if err := g.htmlToPDF(tempHTML, pdfPath); err != nil {
- return "", fmt.Errorf("failed to convert HTML to PDF: %w", err)
+ return "", fmt.Errorf("failed to convert HTML to PDF (second pass): %w", err)
}
+ fmt.Println("=== HTML Generator: PDF generation complete ===")
+
+ // Merge with T&C PDF if it exists
+ if _, err := os.Stat(termsPath); err == nil {
+ fmt.Println("=== HTML Generator: Found T&C PDF, merging ===")
+ tempMergedPath := filepath.Join(g.outputDir, fmt.Sprintf("%s_merged_temp.pdf", packingListNumber))
+ if err := MergePDFs(pdfPath, termsPath, tempMergedPath); err != nil {
+ fmt.Printf("=== HTML Generator: Warning - could not merge T&C PDF: %v. Returning packing list without T&C.\n", err)
+ return filename, nil
+ }
+
+ // Replace original PDF with merged version
+ if err := os.Rename(tempMergedPath, pdfPath); err != nil {
+ fmt.Printf("=== HTML Generator: Warning - could not replace original with merged: %v. Returning unmerged.\n", err)
+ return filename, nil
+ }
+
+ fmt.Println("=== HTML Generator: Replaced PDF successfully")
+ }
+
+ // Verify the file exists and get final size
+ fileInfo, err := os.Stat(pdfPath)
+ if err != nil {
+ return "", fmt.Errorf("failed to verify final PDF: %w", err)
+ }
+
+ fmt.Printf("=== HTML Generator: Final PDF verified, size=%d bytes\n", fileInfo.Size())
return filename, nil
}
-// GenerateOrderAckPDF creates a PDF order acknowledgement from HTML template
+// GenerateOrderAckPDF creates a PDF order acknowledgement from HTML template with T&C merging
// Returns (filename, error)
func (g *HTMLDocumentGenerator) GenerateOrderAckPDF(data *OrderAckPDFData) (string, error) {
- fmt.Println("=== HTML Generator: Starting order acknowledgement generation ===")
+ fmt.Println("=== HTML Generator: Starting order acknowledgement generation (two-pass with T&C page count) ===")
+ // FIRST PASS: Generate PDF without page numbers to determine total pages (including T&C)
+ fmt.Println("=== HTML Generator: First pass - generating without page count ===")
html := g.BuildOrderAckHTML(data, 0, 0)
+
+ fmt.Printf("=== HTML Generator: Generated %d bytes of HTML ===\n", len(html))
+
tempHTML := filepath.Join(g.outputDir, "temp_orderack.html")
if err := ioutil.WriteFile(tempHTML, []byte(html), 0644); err != nil {
return "", fmt.Errorf("failed to write temp HTML: %w", err)
@@ -445,6 +608,53 @@ func (g *HTMLDocumentGenerator) GenerateOrderAckPDF(data *OrderAckPDFData) (stri
defer os.Remove(tempHTML)
defer os.Remove(filepath.Join(g.outputDir, "quote_logo.png"))
+ // Generate temp PDF
+ tempPDFPath := filepath.Join(g.outputDir, "temp_orderack_first_pass.pdf")
+ if err := g.htmlToPDF(tempHTML, tempPDFPath); err != nil {
+ return "", fmt.Errorf("failed to convert HTML to PDF (first pass): %w", err)
+ }
+
+ // Get initial page count from order ack
+ orderAckPageCount, err := g.getPageCount(tempPDFPath)
+ if err != nil {
+ fmt.Printf("Warning: Could not extract order ack page count: %v\n", err)
+ orderAckPageCount = 1
+ }
+
+ // Check if T&C exists and merge to get total page count
+ totalPageCount := orderAckPageCount
+ termsPath := filepath.Join(g.outputDir, "CMC_terms_and_conditions2006_A4.pdf")
+ tempMergedPath := filepath.Join(g.outputDir, "temp_orderack_merged_first_pass.pdf")
+
+ if _, err := os.Stat(termsPath); err == nil {
+ fmt.Println("=== HTML Generator: T&C found, merging to determine total pages ===")
+ if err := MergePDFs(tempPDFPath, termsPath, tempMergedPath); err == nil {
+ // Get total page count from merged PDF
+ totalPageCount, err = g.getPageCount(tempMergedPath)
+ if err != nil {
+ fmt.Printf("Warning: Could not extract merged page count, using order ack count: %v\n", err)
+ totalPageCount = orderAckPageCount
+ } else {
+ fmt.Printf("=== HTML Generator: Total pages (order ack + T&C): %d ===\n", totalPageCount)
+ }
+ } else {
+ fmt.Printf("Warning: Could not merge T&C for counting: %v\n", err)
+ }
+ }
+
+ fmt.Printf("=== HTML Generator: First pass complete, detected %d total pages ===\n", totalPageCount)
+ os.Remove(tempPDFPath)
+ os.Remove(tempMergedPath)
+
+ // SECOND PASS: Generate final PDF
+ fmt.Println("=== HTML Generator: Second pass - regenerating final PDF ===")
+ html = g.BuildOrderAckHTML(data, 0, 0)
+
+ if err := ioutil.WriteFile(tempHTML, []byte(html), 0644); err != nil {
+ return "", fmt.Errorf("failed to write temp HTML (second pass): %w", err)
+ }
+
+ // Generate final PDF filename
orderAckNumber := data.OrderAcknowledgement.CmcReference
if orderAckNumber == "" {
orderAckNumber = fmt.Sprintf("OrderAck-%d", data.OrderAcknowledgement.ID)
@@ -454,8 +664,35 @@ func (g *HTMLDocumentGenerator) GenerateOrderAckPDF(data *OrderAckPDFData) (stri
pdfPath := filepath.Join(g.outputDir, filename)
if err := g.htmlToPDF(tempHTML, pdfPath); err != nil {
- return "", fmt.Errorf("failed to convert HTML to PDF: %w", err)
+ return "", fmt.Errorf("failed to convert HTML to PDF (second pass): %w", err)
}
+ fmt.Println("=== HTML Generator: PDF generation complete ===")
+
+ // Merge with T&C PDF if it exists
+ if _, err := os.Stat(termsPath); err == nil {
+ fmt.Println("=== HTML Generator: Found T&C PDF, merging ===")
+ tempMergedPath := filepath.Join(g.outputDir, fmt.Sprintf("%s_merged_temp.pdf", orderAckNumber))
+ if err := MergePDFs(pdfPath, termsPath, tempMergedPath); err != nil {
+ fmt.Printf("=== HTML Generator: Warning - could not merge T&C PDF: %v. Returning order ack without T&C.\n", err)
+ return filename, nil
+ }
+
+ // Replace original PDF with merged version
+ if err := os.Rename(tempMergedPath, pdfPath); err != nil {
+ fmt.Printf("=== HTML Generator: Warning - could not replace original with merged: %v. Returning unmerged.\n", err)
+ return filename, nil
+ }
+
+ fmt.Println("=== HTML Generator: Replaced PDF successfully")
+ }
+
+ // Verify the file exists and get final size
+ fileInfo, err := os.Stat(pdfPath)
+ if err != nil {
+ return "", fmt.Errorf("failed to verify final PDF: %w", err)
+ }
+
+ fmt.Printf("=== HTML Generator: Final PDF verified, size=%d bytes\n", fileInfo.Size())
return filename, nil
}
diff --git a/go/internal/cmc/documents/html_types.go b/go/internal/cmc/documents/html_types.go
index 64f88e6f..5cf8bd77 100644
--- a/go/internal/cmc/documents/html_types.go
+++ b/go/internal/cmc/documents/html_types.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"fmt"
@@ -111,6 +111,7 @@ type OrderAckPDFData struct {
ShipVia string
FOB string
PaymentTerms string
+ FreightDetails string
CurrencyCode string
CurrencySymbol string
ShowGST bool
diff --git a/go/internal/cmc/documents/invoice_builder.go b/go/internal/cmc/documents/invoice_builder.go
index 890b0b83..66f67e70 100644
--- a/go/internal/cmc/documents/invoice_builder.go
+++ b/go/internal/cmc/documents/invoice_builder.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"bytes"
@@ -42,6 +42,7 @@ func (g *HTMLDocumentGenerator) BuildInvoiceHTML(data *InvoicePDFData, totalPage
ShowGST bool
PageCount int
CurrentPage int
+ FreightDetails template.HTML
LogoDataURI string
}{
InvoiceNumber: invoiceNum,
@@ -61,6 +62,7 @@ func (g *HTMLDocumentGenerator) BuildInvoiceHTML(data *InvoicePDFData, totalPage
ShowGST: data.ShowGST,
PageCount: totalPages,
CurrentPage: currentPage,
+ FreightDetails: template.HTML(data.ShippingDetails),
LogoDataURI: g.loadLogo("invoice_logo.png"),
}
@@ -71,21 +73,32 @@ func (g *HTMLDocumentGenerator) BuildInvoiceHTML(data *InvoicePDFData, totalPage
Description: template.HTML(item.Description), // Allow HTML in description
Quantity: item.Quantity,
GrossUnitPrice: item.GrossUnitPrice,
+ GrossUnitPriceText: item.UnitPriceString,
DiscountAmountTotal: item.DiscountAmountTotal,
GrossPrice: item.GrossPrice,
+ GrossPriceText: item.GrossPriceString,
+ HasTextPrices: item.HasTextPrices,
})
}
// Define template functions
funcMap := template.FuncMap{
- "formatPrice": func(price sql.NullString) template.HTML {
+ "formatPrice": func(price sql.NullString, textPrice sql.NullString) template.HTML {
+ // If a text price string is provided, use it as-is
+ if textPrice.Valid && textPrice.String != "" {
+ return template.HTML(textPrice.String)
+ }
+ // Otherwise format the numeric price
if !price.Valid || price.String == "" {
return ""
}
formatted := FormatPriceWithCommas(price.String)
return template.HTML(fmt.Sprintf("%s%s", data.CurrencySymbol, formatted))
},
- "formatDiscount": func(discount sql.NullString) template.HTML {
+ "formatDiscount": func(discount sql.NullString, hasTextPrices bool) template.HTML {
+ if hasTextPrices {
+ return template.HTML("-")
+ }
if !discount.Valid || discount.String == "" {
return template.HTML(fmt.Sprintf("%s0.00", data.CurrencySymbol))
}
@@ -170,8 +183,11 @@ type LineItemTemplateData struct {
Description template.HTML
Quantity string
GrossUnitPrice sql.NullString
+ GrossUnitPriceText sql.NullString
DiscountAmountTotal sql.NullString
GrossPrice sql.NullString
+ GrossPriceText sql.NullString
+ HasTextPrices bool
}
// FormatPriceWithCommas formats a price string with comma separators
diff --git a/go/internal/cmc/documents/merge.go b/go/internal/cmc/documents/merge.go
index da064c60..19d64724 100644
--- a/go/internal/cmc/documents/merge.go
+++ b/go/internal/cmc/documents/merge.go
@@ -1,7 +1,7 @@
//go:build never
// +build never
-package pdf
+package documents
import (
"fmt"
diff --git a/go/internal/cmc/documents/merge_test.go b/go/internal/cmc/documents/merge_test.go
index 440b3249..618ebae6 100644
--- a/go/internal/cmc/documents/merge_test.go
+++ b/go/internal/cmc/documents/merge_test.go
@@ -1,7 +1,7 @@
//go:build never
// +build never
-package pdf
-package pdf
+package documents
+package documents
import (
"os"
diff --git a/go/internal/cmc/documents/order_ack_builder.go b/go/internal/cmc/documents/order_ack_builder.go
index 7d97d4cd..b634f479 100644
--- a/go/internal/cmc/documents/order_ack_builder.go
+++ b/go/internal/cmc/documents/order_ack_builder.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"bytes"
@@ -10,12 +10,15 @@ import (
// OrderAckLineItemTemplateData represents an order ack line item for template rendering
type OrderAckLineItemTemplateData struct {
- Title string
- Description template.HTML
- Quantity string
- UnitPrice float64
- Discount float64
- TotalPrice float64
+ Title string
+ Description template.HTML
+ Quantity string
+ UnitPrice float64
+ UnitPriceText string
+ Discount float64
+ TotalPrice float64
+ TotalPriceText string
+ HasTextPrices bool
}
// BuildOrderAckHTML generates the complete HTML for an order acknowledgement using templates
@@ -30,25 +33,41 @@ func (g *HTMLDocumentGenerator) BuildOrderAckHTML(data *OrderAckPDFData, totalPa
unitPrice := 0.0
totalPrice := 0.0
discount := 0.0
+ unitPriceText := ""
+ totalPriceText := ""
- if item.GrossUnitPrice.Valid {
+ // Check for text price strings first
+ hasTextPrices := false
+ if item.UnitPriceString.Valid && item.UnitPriceString.String != "" {
+ hasTextPrices = true
+ unitPriceText = item.UnitPriceString.String
+ } else if item.GrossUnitPrice.Valid {
unitPrice, _ = strconv.ParseFloat(item.GrossUnitPrice.String, 64)
}
- if item.GrossPrice.Valid {
+
+ if item.GrossPriceString.Valid && item.GrossPriceString.String != "" {
+ hasTextPrices = true
+ totalPriceText = item.GrossPriceString.String
+ // Don't add text prices to subtotal
+ } else if item.GrossPrice.Valid {
totalPrice, _ = strconv.ParseFloat(item.GrossPrice.String, 64)
subtotal += totalPrice
}
+
if item.DiscountAmountTotal.Valid {
discount, _ = strconv.ParseFloat(item.DiscountAmountTotal.String, 64)
}
lineItemsData = append(lineItemsData, OrderAckLineItemTemplateData{
- Title: item.Title,
- Description: template.HTML(item.Description),
- Quantity: item.Quantity,
- UnitPrice: unitPrice,
- Discount: discount,
- TotalPrice: totalPrice,
+ Title: item.Title,
+ Description: template.HTML(item.Description),
+ Quantity: item.Quantity,
+ UnitPrice: unitPrice,
+ UnitPriceText: unitPriceText,
+ Discount: discount,
+ TotalPrice: totalPrice,
+ TotalPriceText: totalPriceText,
+ HasTextPrices: hasTextPrices || item.HasTextPrices,
})
}
@@ -68,11 +87,12 @@ func (g *HTMLDocumentGenerator) BuildOrderAckHTML(data *OrderAckPDFData, totalPa
IssueDateString string
YourReference string
JobTitle string
- BillTo string
- ShipTo string
+ BillTo template.HTML
+ ShipTo template.HTML
ShipVia string
FOB string
PaymentTerms string
+ CustomerABN string
CurrencyCode string
CurrencySymbol string
LineItems []OrderAckLineItemTemplateData
@@ -80,6 +100,9 @@ func (g *HTMLDocumentGenerator) BuildOrderAckHTML(data *OrderAckPDFData, totalPa
GSTAmount float64
Total float64
ShowGST bool
+ PageCount int
+ CurrentPage int
+ FreightDetails template.HTML
LogoDataURI string
}{
OrderAckNumber: orderAckNumber,
@@ -89,11 +112,12 @@ func (g *HTMLDocumentGenerator) BuildOrderAckHTML(data *OrderAckPDFData, totalPa
IssueDateString: data.IssueDateString,
YourReference: data.YourReference,
JobTitle: data.JobTitle,
- BillTo: data.BillTo,
- ShipTo: data.ShipTo,
+ BillTo: template.HTML(data.BillTo),
+ ShipTo: template.HTML(data.ShipTo),
ShipVia: data.ShipVia,
FOB: data.FOB,
PaymentTerms: data.PaymentTerms,
+ CustomerABN: "",
CurrencyCode: data.CurrencyCode,
CurrencySymbol: data.CurrencySymbol,
LineItems: lineItemsData,
@@ -101,15 +125,34 @@ func (g *HTMLDocumentGenerator) BuildOrderAckHTML(data *OrderAckPDFData, totalPa
GSTAmount: gstAmount,
Total: total,
ShowGST: data.ShowGST,
+ PageCount: totalPages,
+ CurrentPage: currentPage,
+ FreightDetails: template.HTML(data.FreightDetails),
LogoDataURI: g.loadLogo("quote_logo.png"),
}
// Define template functions
funcMap := template.FuncMap{
- "formatPrice": func(price float64) template.HTML {
+ "formatPrice": func(price float64, textPrice string) template.HTML {
+ // If a text price string is provided, use it as-is
+ if textPrice != "" {
+ return template.HTML(textPrice)
+ }
+ // Otherwise format the numeric price
formatted := FormatPriceWithCommas(fmt.Sprintf("%.2f", price))
return template.HTML(fmt.Sprintf("%s%s", data.CurrencySymbol, formatted))
},
+ "formatDiscount": func(discount float64, hasTextPrices bool) template.HTML {
+ if hasTextPrices {
+ return template.HTML("-")
+ }
+ // Show 0.00 when no discount, otherwise prefix with minus
+ if discount <= 0 {
+ return template.HTML(fmt.Sprintf("%s0.00", data.CurrencySymbol))
+ }
+ formatted := FormatPriceWithCommas(fmt.Sprintf("%.2f", discount))
+ return template.HTML(fmt.Sprintf("-%s%s", data.CurrencySymbol, formatted))
+ },
"formatTotal": func(amount float64) template.HTML {
formatted := FormatPriceWithCommas(fmt.Sprintf("%.2f", amount))
return template.HTML(fmt.Sprintf("%s%s", data.CurrencySymbol, formatted))
diff --git a/go/internal/cmc/documents/packing_list_builder.go b/go/internal/cmc/documents/packing_list_builder.go
index 888e79e8..c3894ee6 100644
--- a/go/internal/cmc/documents/packing_list_builder.go
+++ b/go/internal/cmc/documents/packing_list_builder.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"bytes"
diff --git a/go/internal/cmc/documents/page_numbers.go b/go/internal/cmc/documents/page_numbers.go
index da12dcf4..3f5a8692 100644
--- a/go/internal/cmc/documents/page_numbers.go
+++ b/go/internal/cmc/documents/page_numbers.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"fmt"
diff --git a/go/internal/cmc/documents/purchase_order_builder.go b/go/internal/cmc/documents/purchase_order_builder.go
index d11bcbba..6779bea0 100644
--- a/go/internal/cmc/documents/purchase_order_builder.go
+++ b/go/internal/cmc/documents/purchase_order_builder.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"bytes"
@@ -14,6 +14,7 @@ type PurchaseOrderLineItemTemplateData struct {
Description template.HTML
Quantity string
UnitPrice float64
+ Discount float64
TotalPrice float64
}
@@ -28,6 +29,7 @@ func (g *HTMLDocumentGenerator) BuildPurchaseOrderHTML(data *PurchaseOrderPDFDat
for _, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
+ discount := 0.0
if item.NetUnitPrice.Valid {
unitPrice, _ = strconv.ParseFloat(item.NetUnitPrice.String, 64)
@@ -36,12 +38,16 @@ func (g *HTMLDocumentGenerator) BuildPurchaseOrderHTML(data *PurchaseOrderPDFDat
totalPrice, _ = strconv.ParseFloat(item.NetPrice.String, 64)
subtotal += totalPrice
}
+ if item.DiscountAmountTotal.Valid {
+ discount, _ = strconv.ParseFloat(item.DiscountAmountTotal.String, 64)
+ }
lineItemsData = append(lineItemsData, PurchaseOrderLineItemTemplateData{
Title: item.Title,
Description: template.HTML(item.Description),
Quantity: item.Quantity,
UnitPrice: unitPrice,
+ Discount: discount,
TotalPrice: totalPrice,
})
}
@@ -57,12 +63,12 @@ func (g *HTMLDocumentGenerator) BuildPurchaseOrderHTML(data *PurchaseOrderPDFDat
templateData := struct {
PONumber string
PrincipleName string
- YourReference string
+ YourReference template.HTML
IssueDateString string
- OrderedFrom string
- DeliverTo string
+ OrderedFrom template.HTML
+ DeliverTo template.HTML
DispatchBy string
- ShippingInstructions string
+ ShippingInstructions template.HTML
CurrencyCode string
CurrencySymbol string
LineItems []PurchaseOrderLineItemTemplateData
@@ -74,12 +80,12 @@ func (g *HTMLDocumentGenerator) BuildPurchaseOrderHTML(data *PurchaseOrderPDFDat
}{
PONumber: poNumber,
PrincipleName: data.Principle.Name,
- YourReference: data.PurchaseOrder.PrincipleReference,
+ YourReference: template.HTML(data.PurchaseOrder.PrincipleReference),
IssueDateString: data.IssueDateString,
- OrderedFrom: data.PurchaseOrder.OrderedFrom,
- DeliverTo: data.PurchaseOrder.DeliverTo,
+ OrderedFrom: template.HTML(data.PurchaseOrder.OrderedFrom),
+ DeliverTo: template.HTML(data.PurchaseOrder.DeliverTo),
DispatchBy: data.PurchaseOrder.DispatchBy,
- ShippingInstructions: data.PurchaseOrder.ShippingInstructions,
+ ShippingInstructions: template.HTML(data.PurchaseOrder.ShippingInstructions),
CurrencyCode: data.CurrencyCode,
CurrencySymbol: data.CurrencySymbol,
LineItems: lineItemsData,
diff --git a/go/internal/cmc/documents/quote_builder.go b/go/internal/cmc/documents/quote_builder.go
index dd125297..b5d33c0a 100644
--- a/go/internal/cmc/documents/quote_builder.go
+++ b/go/internal/cmc/documents/quote_builder.go
@@ -1,4 +1,4 @@
-package pdf
+package documents
import (
"bytes"
diff --git a/go/internal/cmc/documents/templates/invoice.html b/go/internal/cmc/documents/templates/invoice.html
index e2bdd473..eebc7a6d 100644
--- a/go/internal/cmc/documents/templates/invoice.html
+++ b/go/internal/cmc/documents/templates/invoice.html
@@ -384,9 +384,9 @@
{{.Title}} {{.Description}} |
{{.Quantity}} |
- {{formatPrice .GrossUnitPrice}} |
- {{formatDiscount .DiscountAmountTotal}} |
- {{formatPrice .GrossPrice}} |
+ {{formatPrice .GrossUnitPrice .GrossUnitPriceText}} |
+ {{formatDiscount .DiscountAmountTotal .HasTextPrices}} |
+ {{formatPrice .GrossPrice .GrossPriceText}} |
{{end}}
@@ -395,13 +395,28 @@
-
MAKE PAYMENT TO:
-
- | Account Name: | CMC Technologies Pty Ltd |
- | BSB: | 062-458 |
- | Account Number: | 10067982 |
- | SWIFT Code: | CTBAAU2S |
- | IBAN: | 0624581006782 |
+ MAKE PAYMENT TO:
+
+
+ | Account Name: |
+ CMC Technologies Pty Ltd |
+
+
+ | BSB: |
+ 062-458 |
+
+
+ | Account Number: |
+ 10067982 |
+
+
+ | SWIFT: |
+ CTBAAU2S |
+
+
+ | IBAN: |
+ 0624581006782 |
+
@@ -414,5 +429,15 @@
+
+
+
+
FREIGHT DETAILS:
+
+ {{if .FreightDetails}}
+ {{.FreightDetails}}
+ {{end}}
+
+