package documents import ( "bytes" "fmt" "html/template" "log" "path/filepath" "strconv" ) // OrderAckLineItemTemplateData represents an order ack line item for template rendering type OrderAckLineItemTemplateData struct { 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 func (g *HTMLDocumentGenerator) BuildOrderAckHTML(data *OrderAckPDFData, totalPages int, currentPage int) string { // Log the CmcReference for debugging log.Printf("=== BuildOrderAckHTML: data.CmcReference='%s', data.OrderAcknowledgement.CmcReference='%s' ===", data.CmcReference, data.OrderAcknowledgement.CmcReference) orderAckNumber := data.CmcReference if orderAckNumber == "" { // Fallback to document's CMC reference if data.CmcReference is empty orderAckNumber = data.OrderAcknowledgement.CmcReference } // Calculate totals subtotal := 0.0 lineItemsData := []OrderAckLineItemTemplateData{} for _, item := range data.LineItems { unitPrice := 0.0 totalPrice := 0.0 discount := 0.0 unitPriceText := "" totalPriceText := "" // 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.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, UnitPriceText: unitPriceText, Discount: discount, TotalPrice: totalPrice, TotalPriceText: totalPriceText, HasTextPrices: hasTextPrices || item.HasTextPrices, }) } gstAmount := 0.0 total := subtotal if data.ShowGST { gstAmount = subtotal * 0.1 total = subtotal + gstAmount } // Prepare template data templateData := struct { OrderAckNumber string CmcReference string CompanyName string EmailTo string Attention string IssueDateString string YourReference string JobTitle string CustomerOrderNumber string BillTo template.HTML ShipTo template.HTML ShipVia string FOB string PaymentTerms string CustomerABN string CurrencyCode string CurrencySymbol string LineItems []OrderAckLineItemTemplateData Subtotal float64 GSTAmount float64 Total float64 ShowGST bool PageCount int CurrentPage int FreightDetails template.HTML LogoDataURI string }{ OrderAckNumber: orderAckNumber, CmcReference: data.CmcReference, CompanyName: data.Customer.Name, EmailTo: data.EmailTo, Attention: data.Attention, IssueDateString: data.IssueDateString, YourReference: data.YourReference, JobTitle: data.JobTitle, CustomerOrderNumber: data.CustomerOrderNumber, BillTo: template.HTML(data.BillTo), ShipTo: template.HTML(data.ShipTo), ShipVia: data.ShipVia, FOB: data.FOB, PaymentTerms: data.PaymentTerms, CustomerABN: data.CustomerABN, CurrencyCode: data.CurrencyCode, CurrencySymbol: data.CurrencySymbol, LineItems: lineItemsData, Subtotal: subtotal, 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, 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)) }, } // Parse and execute template possiblePathSets := [][]string{ { filepath.Join("internal", "cmc", "documents", "templates", "order-acknowledgement.html"), filepath.Join("internal", "cmc", "documents", "templates", "header.html"), }, { filepath.Join("go", "internal", "cmc", "documents", "templates", "order-acknowledgement.html"), filepath.Join("go", "internal", "cmc", "documents", "templates", "header.html"), }, { filepath.Join("templates", "pdf", "order-acknowledgement.html"), filepath.Join("templates", "pdf", "header.html"), }, { "/app/templates/pdf/order-acknowledgement.html", "/app/templates/pdf/header.html", }, } var tmpl *template.Template var err error for _, pathSet := range possiblePathSets { tmpl, err = template.New("order-acknowledgement.html").Funcs(funcMap).ParseFiles(pathSet...) 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() }