182 lines
5.3 KiB
Go
182 lines
5.3 KiB
Go
package pdf
|
|
|
|
import (
|
|
"bytes"
|
|
"database/sql"
|
|
"fmt"
|
|
"html/template"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// BuildInvoiceHTML generates the complete HTML for an invoice using templates
|
|
func (g *HTMLDocumentGenerator) BuildInvoiceHTML(data *InvoicePDFData, totalPages int, currentPage int) string {
|
|
// Get invoice number, fall back to invoice title so the template always shows something meaningful
|
|
invoiceNum := ""
|
|
if data.Document != nil {
|
|
invoiceNum = data.Document.CmcReference
|
|
}
|
|
if invoiceNum == "" {
|
|
invoiceNum = data.Invoice.Title
|
|
}
|
|
fmt.Printf("=== buildInvoiceHTML: Invoice number: %s ===\n", invoiceNum)
|
|
|
|
// Prepare template data
|
|
templateData := struct {
|
|
InvoiceNumber string
|
|
IssueDateString string
|
|
BillTo template.HTML
|
|
ShipTo template.HTML
|
|
CustomerOrderNumber string
|
|
JobTitle string
|
|
FOB string
|
|
PaymentTerms string
|
|
CustomerABN string
|
|
CurrencyCode string
|
|
CurrencySymbol string
|
|
LineItems []LineItemTemplateData
|
|
Subtotal interface{}
|
|
GSTAmount interface{}
|
|
Total interface{}
|
|
ShowGST bool
|
|
PageCount int
|
|
CurrentPage int
|
|
LogoDataURI string
|
|
}{
|
|
InvoiceNumber: invoiceNum,
|
|
IssueDateString: data.IssueDateString,
|
|
BillTo: template.HTML(data.BillTo),
|
|
ShipTo: template.HTML(data.ShipTo),
|
|
CustomerOrderNumber: data.CustomerOrderNumber,
|
|
JobTitle: data.JobTitle,
|
|
FOB: data.FOB,
|
|
PaymentTerms: data.PaymentTerms,
|
|
CustomerABN: data.CustomerABN,
|
|
CurrencyCode: data.CurrencyCode,
|
|
CurrencySymbol: data.CurrencySymbol,
|
|
Subtotal: data.Subtotal,
|
|
GSTAmount: data.GSTAmount,
|
|
Total: data.Total,
|
|
ShowGST: data.ShowGST,
|
|
PageCount: totalPages,
|
|
CurrentPage: currentPage,
|
|
LogoDataURI: g.loadLogo("invoice_logo.png"),
|
|
}
|
|
|
|
// Convert line items to template format
|
|
for _, item := range data.LineItems {
|
|
templateData.LineItems = append(templateData.LineItems, LineItemTemplateData{
|
|
Title: item.Title,
|
|
Description: template.HTML(item.Description), // Allow HTML in description
|
|
Quantity: item.Quantity,
|
|
GrossUnitPrice: item.GrossUnitPrice,
|
|
DiscountAmountTotal: item.DiscountAmountTotal,
|
|
GrossPrice: item.GrossPrice,
|
|
})
|
|
}
|
|
|
|
// Define template functions
|
|
funcMap := template.FuncMap{
|
|
"formatPrice": func(price sql.NullString) template.HTML {
|
|
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 {
|
|
if !discount.Valid || discount.String == "" {
|
|
return template.HTML(fmt.Sprintf("%s0.00", data.CurrencySymbol))
|
|
}
|
|
if discount.String == "0" || discount.String == "0.00" {
|
|
return template.HTML(fmt.Sprintf("%s0.00", data.CurrencySymbol))
|
|
}
|
|
formatted := FormatPriceWithCommas(discount.String)
|
|
return template.HTML(fmt.Sprintf("-%s%s", data.CurrencySymbol, formatted))
|
|
},
|
|
"formatTotal": func(amount interface{}) template.HTML {
|
|
if amount == nil {
|
|
return ""
|
|
}
|
|
if val, ok := amount.(float64); ok {
|
|
formatted := FormatPriceWithCommas(fmt.Sprintf("%.2f", val))
|
|
return template.HTML(fmt.Sprintf("%s%s", data.CurrencySymbol, formatted))
|
|
}
|
|
return template.HTML(fmt.Sprintf("%v", amount))
|
|
},
|
|
}
|
|
|
|
// Parse and execute template
|
|
// Try multiple possible paths to find the template
|
|
possiblePaths := []string{
|
|
filepath.Join("internal", "cmc", "pdf", "templates", "invoice.html"),
|
|
filepath.Join("go", "internal", "cmc", "pdf", "templates", "invoice.html"),
|
|
"/app/go/internal/cmc/pdf/templates/invoice.html",
|
|
}
|
|
|
|
var tmpl *template.Template
|
|
var err error
|
|
|
|
for _, tmplPath := range possiblePaths {
|
|
tmpl, err = template.New("invoice.html").Funcs(funcMap).ParseFiles(tmplPath)
|
|
if err == nil {
|
|
break
|
|
}
|
|
}
|
|
|
|
if tmpl == nil || err != nil {
|
|
fmt.Printf("Error parsing template from any path: %v\n", err)
|
|
return ""
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, templateData); err != nil {
|
|
fmt.Printf("Error executing template: %v\n", err)
|
|
return ""
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// LineItemTemplateData represents a line item for template rendering
|
|
type LineItemTemplateData struct {
|
|
Title string
|
|
Description template.HTML
|
|
Quantity string
|
|
GrossUnitPrice sql.NullString
|
|
DiscountAmountTotal sql.NullString
|
|
GrossPrice sql.NullString
|
|
}
|
|
|
|
// FormatPriceWithCommas formats a price string with comma separators
|
|
func FormatPriceWithCommas(priceStr string) string {
|
|
if priceStr == "" {
|
|
return ""
|
|
}
|
|
|
|
// Try to parse as float
|
|
price, err := strconv.ParseFloat(priceStr, 64)
|
|
if err != nil {
|
|
// Return as-is if not parseable
|
|
return priceStr
|
|
}
|
|
|
|
// Format with 2 decimal places and commas
|
|
amountStr := strconv.FormatFloat(price, 'f', 2, 64)
|
|
parts := strings.Split(amountStr, ".")
|
|
intPart := parts[0]
|
|
decPart := "." + parts[1]
|
|
|
|
// Add comma separators
|
|
var result strings.Builder
|
|
for i, ch := range intPart {
|
|
if i > 0 && (len(intPart)-i)%3 == 0 {
|
|
result.WriteString(",")
|
|
}
|
|
result.WriteRune(ch)
|
|
}
|
|
|
|
return result.String() + decPart
|
|
}
|