561 lines
16 KiB
Go
561 lines
16 KiB
Go
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 {
|
|
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
|
|
CurrencyCode string
|
|
ShowGST bool
|
|
CommercialComments string
|
|
DeliveryTime string
|
|
PaymentTerms string
|
|
DaysValid int
|
|
DeliveryPoint string
|
|
ExchangeRate string
|
|
CustomsDuty string
|
|
GSTPhrase string
|
|
SalesEngineer string
|
|
BillTo string
|
|
ShipTo string
|
|
IssueDateString string
|
|
Pages []string
|
|
}
|
|
|
|
// 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)
|
|
|
|
// First page with header
|
|
gen.AddPage()
|
|
gen.Page1Header()
|
|
|
|
// Extract data for details box
|
|
companyName := ""
|
|
if data.Customer != nil {
|
|
companyName = data.Customer.Name
|
|
}
|
|
emailTo := data.EmailTo
|
|
attention := data.Attention
|
|
fromName := fmt.Sprintf("%s %s", data.User.FirstName, data.User.LastName)
|
|
fromEmail := data.User.Email
|
|
|
|
// Use CMC reference as the quote number
|
|
quoteNumber := data.Document.CmcReference
|
|
if data.Document.Revision > 0 {
|
|
quoteNumber = fmt.Sprintf("%s.%d", quoteNumber, data.Document.Revision)
|
|
}
|
|
|
|
yourReference := fmt.Sprintf("Enquiry on %s", data.Document.Created.Format("2 Jan 2006"))
|
|
issueDate := data.IssueDateString
|
|
if issueDate == "" {
|
|
issueDate = data.Document.Created.Format("2 January 2006")
|
|
}
|
|
|
|
// Add details box
|
|
gen.DetailsBox("QUOTE", companyName, emailTo, attention, fromName, fromEmail, quoteNumber, yourReference, issueDate)
|
|
|
|
// Add page content if any
|
|
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()
|
|
}
|
|
|
|
// 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)
|
|
|
|
// Convert line items
|
|
pdfItems := make([]LineItem, len(data.LineItems))
|
|
for i, item := range data.LineItems {
|
|
unitPrice := 0.0
|
|
totalPrice := 0.0
|
|
|
|
// Parse prices
|
|
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,
|
|
}
|
|
}
|
|
|
|
// Add line items table
|
|
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Add quote details table (delivery time, payment terms, etc.)
|
|
if data.DeliveryTime != "" || data.PaymentTerms != "" || data.DaysValid > 0 {
|
|
gen.AddQuoteDetailsTable(data)
|
|
}
|
|
|
|
// Add terms and conditions page
|
|
gen.AddTermsAndConditions()
|
|
|
|
// Generate filename
|
|
filename := quoteNumber
|
|
if data.Document.Revision > 0 {
|
|
filename = fmt.Sprintf("%s_%d.pdf", quoteNumber, data.Document.Revision)
|
|
} else {
|
|
filename = fmt.Sprintf("%s.pdf", quoteNumber)
|
|
}
|
|
|
|
// 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 {
|
|
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
|
|
CurrencyCode string
|
|
ShowGST bool
|
|
ShipVia string
|
|
FOB string
|
|
IssueDate time.Time
|
|
IssueDateString string
|
|
EmailTo string
|
|
Attention string
|
|
FromName string
|
|
FromEmail string
|
|
YourReference string
|
|
BillTo string
|
|
ShipTo string
|
|
ShippingDetails string
|
|
CustomerOrderNumber string
|
|
JobTitle string
|
|
PaymentTerms string
|
|
CustomerABN string
|
|
Subtotal interface{} // Can be float or "TBA"
|
|
GSTAmount interface{}
|
|
Total interface{}
|
|
}
|
|
|
|
// GenerateInvoicePDF generates a PDF for an invoice
|
|
func GenerateInvoicePDF(data *InvoicePDFData, outputDir string) (string, error) {
|
|
gen := NewGenerator(outputDir)
|
|
|
|
// First page with header
|
|
gen.AddPage()
|
|
gen.Page1Header()
|
|
|
|
// Title
|
|
gen.pdf.SetFont("Helvetica", "B", 18)
|
|
gen.pdf.SetTextColor(0, 0, 0)
|
|
gen.pdf.CellFormat(0, 10, "TAX INVOICE", "", 1, "C", false, 0, "")
|
|
gen.pdf.Ln(5)
|
|
|
|
// Sold To / Delivery Address boxes
|
|
gen.AddInvoiceAddressBoxes(data)
|
|
|
|
// More details table (Order Number, Job, FOB, Payment Terms, ABN)
|
|
gen.AddInvoiceDetailsTable(data)
|
|
|
|
gen.pdf.Ln(5)
|
|
|
|
// Line items table header
|
|
gen.AddInvoiceLineItemsHeader(data.CurrencyCode)
|
|
|
|
gen.Page1Footer()
|
|
|
|
// Add line items on new page(s)
|
|
gen.AddPage()
|
|
gen.pdf.SetFont("Helvetica", "B", 12)
|
|
gen.pdf.CellFormat(0, 6, "CONTINUED: "+data.Invoice.Title, "", 1, "L", false, 0, "")
|
|
gen.pdf.Ln(4)
|
|
|
|
// Add line items content
|
|
gen.AddInvoiceLineItemsContent(data)
|
|
|
|
// Add terms and conditions page
|
|
gen.AddTermsAndConditions()
|
|
|
|
// Generate filename and save
|
|
filename := fmt.Sprintf("%s.pdf", data.Invoice.Title)
|
|
if err := gen.Save(filename); err != nil {
|
|
return "", err
|
|
}
|
|
return filename, nil
|
|
}
|
|
|
|
// PurchaseOrderPDFData contains all data needed to generate a purchase order PDF
|
|
type PurchaseOrderPDFData struct {
|
|
Document *db.Document
|
|
PurchaseOrder *db.PurchaseOrder
|
|
Principle *db.Principle
|
|
LineItems []db.GetLineItemsTableRow
|
|
Currency interface{} // Currency data
|
|
CurrencySymbol string
|
|
CurrencyCode string
|
|
ShowGST bool
|
|
Subtotal float64
|
|
GSTAmount float64
|
|
Total float64
|
|
IssueDateString string
|
|
}
|
|
|
|
// GeneratePurchaseOrderPDF generates a PDF for a purchase order
|
|
func GeneratePurchaseOrderPDF(data *PurchaseOrderPDFData, outputDir string) (string, error) {
|
|
gen := NewGenerator(outputDir)
|
|
|
|
// First page with header
|
|
gen.AddPage()
|
|
gen.Page1Header()
|
|
|
|
// Extract data for details box
|
|
companyName := data.Principle.Name
|
|
emailTo := "" // TODO: Get from principle contact
|
|
attention := "" // TODO: Get from principle contact
|
|
fromName := "" // TODO: Get from user
|
|
fromEmail := "" // TODO: Get from user
|
|
poNumber := data.PurchaseOrder.Title
|
|
|
|
yourReference := data.PurchaseOrder.PrincipleReference
|
|
issueDate := data.IssueDateString
|
|
if issueDate == "" {
|
|
issueDate = data.PurchaseOrder.IssueDate.Format("Monday, 2 January 2006")
|
|
}
|
|
|
|
// Add details box
|
|
gen.DetailsBox("PURCHASE ORDER", companyName, emailTo, attention, fromName, fromEmail, poNumber, yourReference, issueDate)
|
|
|
|
// 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)
|
|
|
|
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, "")
|
|
|
|
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)
|
|
|
|
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)
|
|
}
|
|
|
|
gen.Page1Footer()
|
|
|
|
// 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)
|
|
|
|
// Convert line items
|
|
pdfItems := make([]LineItem, len(data.LineItems))
|
|
for i, item := range data.LineItems {
|
|
unitPrice := 0.0
|
|
totalPrice := 0.0
|
|
|
|
// Parse prices
|
|
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,
|
|
}
|
|
}
|
|
|
|
// Add line items table with totals
|
|
gen.AddLineItemsTable(pdfItems, data.CurrencySymbol, data.ShowGST)
|
|
|
|
// Add totals section
|
|
gen.pdf.Ln(5)
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(120, 5, "SUBTOTAL:", "T", 0, "R", false, 0, "")
|
|
gen.pdf.CellFormat(0, 5, fmt.Sprintf("%s%.2f", data.CurrencySymbol, data.Subtotal), "T", 1, "R", false, 0, "")
|
|
|
|
if data.ShowGST && data.GSTAmount > 0 {
|
|
gen.pdf.SetFont("Helvetica", "", 10)
|
|
gen.pdf.CellFormat(120, 5, "GST:", "", 0, "R", false, 0, "")
|
|
gen.pdf.CellFormat(0, 5, fmt.Sprintf("%s%.2f", data.CurrencySymbol, data.GSTAmount), "", 1, "R", false, 0, "")
|
|
}
|
|
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(120, 5, "TOTAL:", "T", 0, "R", false, 0, "")
|
|
gen.pdf.CellFormat(0, 5, fmt.Sprintf("%s%.2f", data.CurrencySymbol, data.Total), "T", 1, "R", false, 0, "")
|
|
|
|
// Add terms and conditions page
|
|
gen.AddTermsAndConditions()
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Save PDF
|
|
err := gen.Save(filename)
|
|
return filename, err
|
|
}
|
|
|
|
// PackingListPDFData contains data for a packing list
|
|
type PackingListPDFData struct {
|
|
Document *db.Document
|
|
Customer *db.Customer
|
|
Title string
|
|
JobTitle string
|
|
IssueDate string
|
|
IssueDateString string
|
|
ShipVia string
|
|
FOB string
|
|
LineItems []db.GetLineItemsTableRow
|
|
CurrencySymbol string
|
|
CurrencyCode 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 with proper title and date
|
|
issueDate := data.IssueDateString
|
|
if issueDate == "" {
|
|
issueDate = time.Now().Format("2 January 2006")
|
|
}
|
|
refNumber := data.Title
|
|
if refNumber == "" {
|
|
refNumber = fmt.Sprintf("PL-%d", data.Document.ID)
|
|
}
|
|
gen.DetailsBox("PACKING LIST", data.Customer.Name, "", "", "", "", refNumber, "", issueDate)
|
|
|
|
// Add shipping details section
|
|
gen.pdf.Ln(5)
|
|
if data.JobTitle != "" {
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(40, 5, "Job Reference:", "", 0, "L", false, 0, "")
|
|
gen.pdf.SetFont("Helvetica", "", 10)
|
|
gen.pdf.CellFormat(0, 5, data.JobTitle, "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
if data.ShipVia != "" {
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(40, 5, "Ship Via:", "", 0, "L", false, 0, "")
|
|
gen.pdf.SetFont("Helvetica", "", 10)
|
|
gen.pdf.CellFormat(0, 5, data.ShipVia, "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
if data.FOB != "" {
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(40, 5, "FOB:", "", 0, "L", false, 0, "")
|
|
gen.pdf.SetFont("Helvetica", "", 10)
|
|
gen.pdf.CellFormat(0, 5, data.FOB, "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
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 := data.Title
|
|
if filename == "" {
|
|
filename = fmt.Sprintf("PL-%d", data.Document.ID)
|
|
}
|
|
filename = fmt.Sprintf("%s.pdf", filename)
|
|
err := gen.Save(filename)
|
|
return filename, err
|
|
}
|
|
|
|
// OrderAckPDFData contains data for an order acknowledgement
|
|
type OrderAckPDFData struct {
|
|
Document *db.Document
|
|
Customer *db.Customer
|
|
Title string
|
|
JobTitle string
|
|
IssueDate string
|
|
IssueDateString string
|
|
ShipVia string
|
|
FOB string
|
|
EstimatedDelivery string
|
|
LineItems []db.GetLineItemsTableRow
|
|
CurrencySymbol string
|
|
CurrencyCode 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 with proper title and date
|
|
issueDate := data.IssueDateString
|
|
if issueDate == "" {
|
|
issueDate = time.Now().Format("2 January 2006")
|
|
}
|
|
refNumber := data.Title
|
|
if refNumber == "" {
|
|
refNumber = fmt.Sprintf("OA-%d", data.Document.ID)
|
|
}
|
|
gen.DetailsBox("ORDER ACK", data.Customer.Name, "", "", "", "", refNumber, "", issueDate)
|
|
|
|
// Add shipping/delivery details section
|
|
gen.pdf.Ln(5)
|
|
if data.JobTitle != "" {
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(40, 5, "Job Reference:", "", 0, "L", false, 0, "")
|
|
gen.pdf.SetFont("Helvetica", "", 10)
|
|
gen.pdf.CellFormat(0, 5, data.JobTitle, "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
if data.ShipVia != "" {
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(40, 5, "Ship Via:", "", 0, "L", false, 0, "")
|
|
gen.pdf.SetFont("Helvetica", "", 10)
|
|
gen.pdf.CellFormat(0, 5, data.ShipVia, "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
if data.FOB != "" {
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(40, 5, "FOB:", "", 0, "L", false, 0, "")
|
|
gen.pdf.SetFont("Helvetica", "", 10)
|
|
gen.pdf.CellFormat(0, 5, data.FOB, "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
if data.EstimatedDelivery != "" {
|
|
gen.pdf.SetFont("Helvetica", "B", 10)
|
|
gen.pdf.CellFormat(40, 5, "Est. Delivery:", "", 0, "L", false, 0, "")
|
|
gen.pdf.SetFont("Helvetica", "", 10)
|
|
gen.pdf.CellFormat(0, 5, data.EstimatedDelivery, "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
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 := data.Title
|
|
if filename == "" {
|
|
filename = fmt.Sprintf("OA-%d", data.Document.ID)
|
|
}
|
|
filename = fmt.Sprintf("%s.pdf", filename)
|
|
|
|
err := gen.Save(filename)
|
|
return filename, err
|
|
}
|