package pdf import ( "bytes" "database/sql" "fmt" "html/template" "path/filepath" "strconv" ) // QuoteLineItemTemplateData represents a quote line item for template rendering type QuoteLineItemTemplateData struct { ItemNumber string Title string Description template.HTML Quantity string GrossUnitPrice sql.NullString GrossPrice sql.NullString } // BuildQuoteHTML generates the complete HTML for a quote using templates func (g *HTMLDocumentGenerator) BuildQuoteHTML(data *QuotePDFData, totalPages int, currentPage int) string { // Get quote number from Document.CmcReference quoteNum := "" if data.Document != nil { quoteNum = data.Document.CmcReference if data.Document.Revision > 0 { quoteNum = fmt.Sprintf("%s.%d", quoteNum, data.Document.Revision) } } fmt.Printf("=== buildQuoteHTML: Quote number: %s ===\n", quoteNum) // Prepare FROM name fromName := "" if data.User != nil { fromName = fmt.Sprintf("%s %s", data.User.FirstName, data.User.LastName) } fromEmail := "" if data.User != nil { fromEmail = data.User.Email } // Prepare company name companyName := "" if data.Customer != nil { companyName = data.Customer.Name } // Prepare page content (first page only for now, multi-page support later) pageContent := template.HTML("") if len(data.Pages) > 0 { pageContent = template.HTML(data.Pages[0]) } // Calculate totals subtotal := 0.0 for _, item := range data.LineItems { if item.GrossPrice.Valid { price, _ := strconv.ParseFloat(item.GrossPrice.String, 64) subtotal += price } } gstAmount := 0.0 total := subtotal if data.ShowGST { gstAmount = subtotal * 0.1 total = subtotal + gstAmount } // Prepare template data templateData := struct { QuoteNumber string CompanyName string EmailTo string Attention string FromName string FromEmail string YourReference string IssueDateString string PageContent template.HTML CurrencyCode string CurrencySymbol string LineItems []QuoteLineItemTemplateData Subtotal float64 GSTAmount float64 Total float64 ShowGST bool CommercialComments string DeliveryTime string PaymentTerms string DaysValid int DeliveryPoint string ExchangeRate string CustomsDuty string GSTPhrase string SalesEngineer string PageCount int CurrentPage int LogoDataURI string }{ QuoteNumber: quoteNum, CompanyName: companyName, EmailTo: data.EmailTo, Attention: data.Attention, FromName: fromName, FromEmail: fromEmail, YourReference: fmt.Sprintf("Enquiry on %s", data.IssueDateString), IssueDateString: data.IssueDateString, PageContent: pageContent, CurrencyCode: data.CurrencyCode, CurrencySymbol: data.CurrencySymbol, Subtotal: subtotal, GSTAmount: gstAmount, Total: total, ShowGST: data.ShowGST, CommercialComments: data.CommercialComments, DeliveryTime: data.DeliveryTime, PaymentTerms: data.PaymentTerms, DaysValid: data.DaysValid, DeliveryPoint: data.DeliveryPoint, ExchangeRate: data.ExchangeRate, CustomsDuty: data.CustomsDuty, GSTPhrase: data.GSTPhrase, SalesEngineer: data.SalesEngineer, PageCount: totalPages, CurrentPage: currentPage, LogoDataURI: g.loadLogo("quote_logo.png"), } // Convert line items to template format for _, item := range data.LineItems { templateData.LineItems = append(templateData.LineItems, QuoteLineItemTemplateData{ ItemNumber: item.ItemNumber, Title: item.Title, Quantity: item.Quantity, Description: template.HTML(""), // Quotes don't have descriptions in line items by default GrossUnitPrice: item.GrossUnitPrice, 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)) }, "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 // Try multiple possible path sets to find the quote and shared header templates together possiblePathSets := [][]string{ { filepath.Join("internal", "cmc", "pdf", "templates", "quote.html"), filepath.Join("internal", "cmc", "pdf", "templates", "header.html"), }, { filepath.Join("go", "internal", "cmc", "pdf", "templates", "quote.html"), filepath.Join("go", "internal", "cmc", "pdf", "templates", "header.html"), }, { "/app/go/internal/cmc/pdf/templates/quote.html", "/app/go/internal/cmc/pdf/templates/header.html", }, } var tmpl *template.Template var err error for _, pathSet := range possiblePathSets { tmpl, err = template.New("quote.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() }