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

441 lines
13 KiB
Go
Raw Normal View History

2025-07-02 05:04:36 -07:00
package pdf
import (
"fmt"
"time"
"code.springupsoftware.com/cmc/cmc-sales/internal/cmc/db"
)
// QuotePDFData contains all data needed to generate a quote PDF
type QuotePDFData struct {
2026-01-12 03:00:48 -08:00
Document *db.Document
Quote interface{} // Quote specific data
Enquiry *db.Enquiry
Customer *db.Customer
EmailTo string
Attention string
User *db.GetUserRow
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
2025-07-02 05:04:36 -07:00
CommercialComments string
2026-01-12 03:00:48 -08:00
Pages []string
2025-07-02 05:04:36 -07:00
}
// GenerateQuotePDF generates a PDF for a quote
func GenerateQuotePDF(data *QuotePDFData, outputDir string) (string, error) {
fmt.Printf("GenerateQuotePDF called with outputDir: %s\n", outputDir)
gen := NewGenerator(outputDir)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// First page with header
gen.AddPage()
gen.Page1Header()
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Extract data for details box
2026-01-12 03:00:48 -08:00
companyName := ""
2025-07-06 14:34:12 -07:00
if data.Customer != nil {
companyName = data.Customer.Name
}
2026-01-12 03:00:48 -08:00
emailTo := data.EmailTo
attention := data.Attention
2025-07-02 05:04:36 -07:00
fromName := fmt.Sprintf("%s %s", data.User.FirstName, data.User.LastName)
fromEmail := data.User.Email
2026-01-12 03:00:48 -08:00
2025-07-06 14:34:12 -07:00
// Use CMC reference as the quote number
quoteNumber := data.Document.CmcReference
2025-07-02 05:04:36 -07:00
if data.Document.Revision > 0 {
2025-07-06 14:34:12 -07:00
quoteNumber = fmt.Sprintf("%s.%d", quoteNumber, data.Document.Revision)
2025-07-02 05:04:36 -07:00
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
yourReference := fmt.Sprintf("Enquiry on %s", data.Document.Created.Format("2 Jan 2006"))
2025-07-06 14:34:12 -07:00
issueDate := data.Document.Created.Format("2 January 2006")
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add details box
2025-07-06 14:34:12 -07:00
gen.DetailsBox("QUOTE", companyName, emailTo, attention, fromName, fromEmail, quoteNumber, yourReference, issueDate)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add page content if any
2026-01-12 03:00:48 -08:00
for i, content := range data.Pages {
if i == 0 {
// Content under first page header
gen.AddContent(content)
gen.Page1Footer()
} else {
gen.AddPage()
// Continued header
gen.pdf.SetFont("Helvetica", "B", 12)
gen.pdf.CellFormat(0, 6, "CONTINUED: "+data.Document.CmcReference, "", 1, "L", false, 0, "")
gen.pdf.Ln(4)
gen.AddContent(content)
}
}
if len(data.Pages) == 0 {
gen.Page1Footer()
}
2025-07-02 05:04:36 -07:00
// Add pricing page
gen.AddPage()
gen.pdf.SetFont("Helvetica", "B", 14)
gen.pdf.CellFormat(0, 10, "PRICING & SPECIFICATIONS", "", 1, "C", false, 0, "")
gen.pdf.Ln(5)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Convert line items
pdfItems := make([]LineItem, len(data.LineItems))
for i, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Parse prices
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
pdfItems[i] = LineItem{
ItemNumber: item.ItemNumber,
Quantity: item.Quantity,
Title: item.Title,
UnitPrice: unitPrice,
TotalPrice: totalPrice,
}
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add line items table
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add commercial comments if any
if data.CommercialComments != "" {
gen.pdf.Ln(10)
gen.pdf.SetFont("Helvetica", "B", 10)
gen.pdf.CellFormat(0, 5, "COMMERCIAL COMMENTS", "", 1, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "", 9)
gen.pdf.MultiCell(0, 5, data.CommercialComments, "", "L", false)
}
2026-01-12 03:00:48 -08:00
// Add terms and conditions page
gen.AddTermsAndConditions()
2025-07-02 05:04:36 -07:00
// Generate filename
2025-07-06 14:34:12 -07:00
filename := quoteNumber
2025-07-02 05:04:36 -07:00
if data.Document.Revision > 0 {
2025-07-06 14:34:12 -07:00
filename = fmt.Sprintf("%s_%d.pdf", quoteNumber, data.Document.Revision)
2025-07-02 05:04:36 -07:00
} else {
2025-07-06 14:34:12 -07:00
filename = fmt.Sprintf("%s.pdf", quoteNumber)
2025-07-02 05:04:36 -07:00
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Save PDF
fmt.Printf("Saving PDF with filename: %s to outputDir: %s\n", filename, outputDir)
err := gen.Save(filename)
if err != nil {
fmt.Printf("Error saving PDF: %v\n", err)
} else {
fmt.Printf("PDF saved successfully: %s\n", filename)
}
return filename, err
}
// InvoicePDFData contains all data needed to generate an invoice PDF
type InvoicePDFData struct {
2026-01-12 03:00:48 -08:00
Document *db.Document
Invoice *db.Invoice
Enquiry *db.Enquiry
Customer *db.Customer
Job interface{} // Job data
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
ShipVia string
FOB string
IssueDate time.Time
EmailTo string
Attention string
FromName string
FromEmail string
YourReference string
2025-07-02 05:04:36 -07:00
}
// GenerateInvoicePDF generates a PDF for an invoice
func GenerateInvoicePDF(data *InvoicePDFData, outputDir string) (string, error) {
gen := NewGenerator(outputDir)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// First page with header
gen.AddPage()
gen.Page1Header()
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Extract data for details box
companyName := data.Customer.Name
2026-01-12 03:00:48 -08:00
emailTo := data.EmailTo
attention := data.Attention
fromName := data.FromName
fromEmail := data.FromEmail
2025-07-02 05:04:36 -07:00
invoiceNumber := data.Invoice.Title
2026-01-12 03:00:48 -08:00
yourReference := data.YourReference
2025-07-02 05:04:36 -07:00
issueDate := data.IssueDate.Format("2 January 2006")
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add details box
gen.DetailsBox("INVOICE", companyName, emailTo, attention, fromName, fromEmail, invoiceNumber, yourReference, issueDate)
// Add shipping details
gen.pdf.Ln(5)
gen.pdf.SetFont("Helvetica", "B", 10)
gen.pdf.CellFormat(30, 5, "Ship Via:", "", 0, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "", 10)
gen.pdf.CellFormat(60, 5, data.ShipVia, "", 1, "L", false, 0, "")
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
gen.pdf.SetFont("Helvetica", "B", 10)
gen.pdf.CellFormat(30, 5, "FOB:", "", 0, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "", 10)
gen.pdf.CellFormat(60, 5, data.FOB, "", 1, "L", false, 0, "")
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
gen.Page1Footer()
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add line items page
gen.AddPage()
gen.pdf.SetFont("Helvetica", "B", 14)
gen.pdf.CellFormat(0, 10, "INVOICE DETAILS", "", 1, "C", false, 0, "")
gen.pdf.Ln(5)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Convert line items
pdfItems := make([]LineItem, len(data.LineItems))
for i, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Parse prices
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
pdfItems[i] = LineItem{
ItemNumber: item.ItemNumber,
Quantity: item.Quantity,
Title: item.Title,
UnitPrice: unitPrice,
TotalPrice: totalPrice,
}
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add line items table
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
2026-01-12 03:00:48 -08:00
// Add terms and conditions page
gen.AddTermsAndConditions()
// Generate filename and save directly (no merge)
2025-07-02 05:04:36 -07:00
filename := fmt.Sprintf("%s.pdf", invoiceNumber)
2026-01-12 03:00:48 -08:00
if err := gen.Save(filename); err != nil {
return "", err
}
return filename, nil
2025-07-02 05:04:36 -07:00
}
// PurchaseOrderPDFData contains all data needed to generate a purchase order PDF
type PurchaseOrderPDFData struct {
2026-01-12 03:00:48 -08:00
Document *db.Document
PurchaseOrder *db.PurchaseOrder
Principle *db.Principle
LineItems []db.GetLineItemsTableRow
Currency interface{} // Currency data
CurrencySymbol string
ShowGST bool
2025-07-02 05:04:36 -07:00
}
// GeneratePurchaseOrderPDF generates a PDF for a purchase order
func GeneratePurchaseOrderPDF(data *PurchaseOrderPDFData, outputDir string) (string, error) {
gen := NewGenerator(outputDir)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// First page with header
gen.AddPage()
gen.Page1Header()
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Extract data for details box
companyName := data.Principle.Name
2026-01-12 03:00:48 -08:00
emailTo := "" // TODO: Get from principle contact
2025-07-02 05:04:36 -07:00
attention := "" // TODO: Get from principle contact
2026-01-12 03:00:48 -08:00
fromName := "" // TODO: Get from user
2025-07-02 05:04:36 -07:00
fromEmail := "" // TODO: Get from user
poNumber := data.PurchaseOrder.Title
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
yourReference := data.PurchaseOrder.PrincipleReference
issueDate := data.PurchaseOrder.IssueDate.Format("Monday, 2 January 2006")
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add details box
gen.DetailsBox("PURCHASE ORDER", companyName, emailTo, attention, fromName, fromEmail, poNumber, yourReference, issueDate)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add PO specific details
gen.pdf.Ln(5)
gen.pdf.SetFont("Helvetica", "B", 10)
gen.pdf.CellFormat(40, 5, "Ordered From:", "", 0, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "", 10)
gen.pdf.MultiCell(0, 5, data.PurchaseOrder.OrderedFrom, "", "L", false)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
gen.pdf.SetFont("Helvetica", "B", 10)
gen.pdf.CellFormat(40, 5, "Dispatch By:", "", 0, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "", 10)
gen.pdf.CellFormat(0, 5, data.PurchaseOrder.DispatchBy, "", 1, "L", false, 0, "")
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
gen.pdf.SetFont("Helvetica", "B", 10)
gen.pdf.CellFormat(40, 5, "Deliver To:", "", 0, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "", 10)
gen.pdf.MultiCell(0, 5, data.PurchaseOrder.DeliverTo, "", "L", false)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
if data.PurchaseOrder.ShippingInstructions != "" {
gen.pdf.SetFont("Helvetica", "B", 10)
gen.pdf.CellFormat(0, 5, "Shipping Instructions:", "", 1, "L", false, 0, "")
gen.pdf.SetFont("Helvetica", "", 10)
gen.pdf.MultiCell(0, 5, data.PurchaseOrder.ShippingInstructions, "", "L", false)
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
gen.Page1Footer()
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add line items page
gen.AddPage()
gen.pdf.SetFont("Helvetica", "B", 14)
gen.pdf.CellFormat(0, 10, "ORDER DETAILS", "", 1, "C", false, 0, "")
gen.pdf.Ln(5)
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Convert line items
pdfItems := make([]LineItem, len(data.LineItems))
for i, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Parse prices
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
pdfItems[i] = LineItem{
ItemNumber: item.ItemNumber,
Quantity: item.Quantity,
Title: item.Title,
UnitPrice: unitPrice,
TotalPrice: totalPrice,
}
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Add line items table
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
2026-01-12 03:00:48 -08:00
// Add terms and conditions page
gen.AddTermsAndConditions()
2025-07-02 05:04:36 -07:00
// Generate filename
filename := poNumber
if data.Document.Revision > 0 {
filename = fmt.Sprintf("%s-Rev%d.pdf", data.PurchaseOrder.Title, data.Document.Revision)
} else {
filename = fmt.Sprintf("%s.pdf", data.PurchaseOrder.Title)
}
2026-01-12 03:00:48 -08:00
2025-07-02 05:04:36 -07:00
// Save PDF
err := gen.Save(filename)
return filename, err
2026-01-12 03:00:48 -08:00
}
// PackingListPDFData contains data for a packing list
type PackingListPDFData struct {
Document *db.Document
Customer *db.Customer
LineItems []db.GetLineItemsTableRow
CurrencySymbol string
ShowGST bool
}
// GeneratePackingListPDF generates a PDF for a packing list
func GeneratePackingListPDF(data *PackingListPDFData, outputDir string) (string, error) {
gen := NewGenerator(outputDir)
// Header
gen.AddPage()
gen.Page1Header()
gen.pdf.SetFont("Helvetica", "B", 16)
gen.pdf.CellFormat(0, 10, "PACKING LIST", "", 1, "C", false, 0, "")
gen.pdf.Ln(5)
// Details box (minimal)
gen.DetailsBox("PACKING LIST", data.Customer.Name, "", "", "", "", fmt.Sprintf("PL-%d", data.Document.ID), "", time.Now().Format("2 January 2006"))
gen.Page1Footer()
// Line items
gen.AddPage()
pdfItems := make([]LineItem, len(data.LineItems))
for i, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
pdfItems[i] = LineItem{ItemNumber: item.ItemNumber, Quantity: item.Quantity, Title: item.Title, UnitPrice: unitPrice, TotalPrice: totalPrice}
}
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
// Add terms and conditions page
gen.AddTermsAndConditions()
filename := fmt.Sprintf("PL-%d.pdf", data.Document.ID)
err := gen.Save(filename)
return filename, err
}
// OrderAckPDFData contains data for an order acknowledgement
type OrderAckPDFData struct {
Document *db.Document
Customer *db.Customer
LineItems []db.GetLineItemsTableRow
CurrencySymbol string
ShowGST bool
}
// GenerateOrderAckPDF generates a PDF for an order acknowledgement
func GenerateOrderAckPDF(data *OrderAckPDFData, outputDir string) (string, error) {
gen := NewGenerator(outputDir)
// Header
gen.AddPage()
gen.Page1Header()
gen.pdf.SetFont("Helvetica", "B", 16)
gen.pdf.CellFormat(0, 10, "ORDER ACKNOWLEDGEMENT", "", 1, "C", false, 0, "")
gen.pdf.Ln(5)
// Details box (minimal)
gen.DetailsBox("ORDER ACK", data.Customer.Name, "", "", "", "", fmt.Sprintf("OA-%d", data.Document.ID), "", time.Now().Format("2 January 2006"))
gen.Page1Footer()
// Line items
gen.AddPage()
pdfItems := make([]LineItem, len(data.LineItems))
for i, item := range data.LineItems {
unitPrice := 0.0
totalPrice := 0.0
if item.GrossUnitPrice.Valid {
fmt.Sscanf(item.GrossUnitPrice.String, "%f", &unitPrice)
}
if item.GrossPrice.Valid {
fmt.Sscanf(item.GrossPrice.String, "%f", &totalPrice)
}
pdfItems[i] = LineItem{ItemNumber: item.ItemNumber, Quantity: item.Quantity, Title: item.Title, UnitPrice: unitPrice, TotalPrice: totalPrice}
}
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
// Add terms and conditions page
gen.AddTermsAndConditions()
filename := fmt.Sprintf("OA-%d.pdf", data.Document.ID)
err := gen.Save(filename)
return filename, err
}