cmc-sales/go/internal/cmc/pdf/quote_builder.go

216 lines
5.9 KiB
Go
Raw Normal View History

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
2026-01-20 23:43:55 -08:00
// 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"),
},
2026-01-21 01:05:08 -08:00
{
filepath.Join("templates", "pdf", "quote.html"),
filepath.Join("templates", "pdf", "header.html"),
},
{
"/app/templates/pdf/quote.html",
"/app/templates/pdf/header.html",
},
2026-01-21 00:53:57 -08:00
{
filepath.Join("templates", "quote.html"),
filepath.Join("templates", "header.html"),
},
{
"/app/templates/quote.html",
"/app/templates/header.html",
},
2026-01-20 23:43:55 -08:00
{
"/app/go/internal/cmc/pdf/templates/quote.html",
"/app/go/internal/cmc/pdf/templates/header.html",
},
}
var tmpl *template.Template
var err error
2026-01-20 23:43:55 -08:00
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()
}