rentease/internal/driver/pdf/html.go
Ruidy 9b2510460a
Some checks are pending
CI / checks (push) Waiting to run
feat: store invoice PDFs in minio
2026-03-20 23:58:57 +01:00

120 lines
3.9 KiB
Go

package pdf
import (
"bytes"
"fmt"
"log"
"os"
"text/template"
"github.com/jung-kurt/gofpdf/v2"
"github.com/rjNemo/rentease/assets"
"github.com/rjNemo/rentease/internal/service/booking"
)
type HTMLPdfClient struct{}
func NewPdfClient() (*HTMLPdfClient, error) {
return &HTMLPdfClient{}, nil
}
func (pc *HTMLPdfClient) BuildInvoice(data booking.Invoice) (*booking.GeneratedFile, error) {
pdf := gofpdf.New("P", "mm", "A4", "")
pdf.SetMargins(16, 16, 16)
pdf.SetAutoPageBreak(true, 16)
pdf.AddPage()
tr := pdf.UnicodeTranslatorFromDescriptor("")
writeLine := func(fontStyle string, fontSize float64, text string) {
pdf.SetFont("Helvetica", fontStyle, fontSize)
pdf.CellFormat(0, 7, tr(text), "", 1, "", false, 0, "")
}
writeLine("B", 20, fmt.Sprintf("Invoice %s", data.ID))
pdf.Ln(2)
writeLine("", 11, data.Host.Name)
writeLine("", 11, data.Host.Address)
writeLine("", 11, fmt.Sprintf("%s %s", data.Host.ZipCode, data.Host.City))
writeLine("", 11, data.Host.PhoneNumber)
writeLine("", 11, data.Host.Email)
pdf.Ln(4)
writeLine("B", 14, "Guest")
writeLine("", 11, data.Name)
if data.PhoneNumber != "" {
writeLine("", 11, data.PhoneNumber)
}
writeLine("", 11, fmt.Sprintf("Stay: %s - %s", data.From, data.To))
writeLine("", 11, fmt.Sprintf("Platform: %s", data.Platform))
writeLine("", 11, fmt.Sprintf("Guests: %d", data.CustomersNumber))
pdf.Ln(4)
writeLine("B", 14, "Charges")
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(90, 8, tr("Item"), "1", 0, "", false, 0, "")
pdf.CellFormat(25, 8, tr("Qty"), "1", 0, "C", false, 0, "")
pdf.CellFormat(35, 8, tr("Unit"), "1", 0, "R", false, 0, "")
pdf.CellFormat(25, 8, tr("Total"), "1", 1, "R", false, 0, "")
pdf.SetFont("Helvetica", "", 11)
for _, line := range data.Lines {
pdf.CellFormat(90, 8, tr(line.Name), "1", 0, "", false, 0, "")
pdf.CellFormat(25, 8, fmt.Sprintf("%d", line.Quantity), "1", 0, "C", false, 0, "")
pdf.CellFormat(35, 8, fmt.Sprintf("%s EUR", line.Price), "1", 0, "R", false, 0, "")
pdf.CellFormat(25, 8, fmt.Sprintf("%s EUR", line.Total), "1", 1, "R", false, 0, "")
}
pdf.Ln(4)
writeLine("B", 14, "Payments")
if len(data.Payments) == 0 {
writeLine("", 11, "No payment recorded")
} else {
pdf.SetFont("Helvetica", "B", 11)
pdf.CellFormat(45, 8, tr("Date"), "1", 0, "", false, 0, "")
pdf.CellFormat(80, 8, tr("Method"), "1", 0, "", false, 0, "")
pdf.CellFormat(50, 8, tr("Amount"), "1", 1, "R", false, 0, "")
pdf.SetFont("Helvetica", "", 11)
for _, payment := range data.Payments {
pdf.CellFormat(45, 8, tr(payment.Date), "1", 0, "", false, 0, "")
pdf.CellFormat(80, 8, tr(payment.Method), "1", 0, "", false, 0, "")
pdf.CellFormat(50, 8, fmt.Sprintf("%s EUR", payment.Amount), "1", 1, "R", false, 0, "")
}
}
pdf.Ln(4)
writeLine("B", 12, fmt.Sprintf("Total: %s EUR", data.Total))
writeLine("", 12, fmt.Sprintf("Paid: %s EUR", data.AmountPaid))
writeLine("B", 12, fmt.Sprintf("Balance due: %s EUR", data.BalanceDue))
var buf bytes.Buffer
if err := pdf.Output(&buf); err != nil {
return nil, fmt.Errorf("error writing PDF file: %v", err)
}
return &booking.GeneratedFile{
Name: fmt.Sprintf("%s.pdf", data.ID),
ContentType: "application/pdf",
Data: buf.Bytes(),
}, nil
}
func (pc *HTMLPdfClient) BuildReport(report booking.ReportData, period string, month int, year int) (string, error) {
tmpl, err := template.ParseFS(assets.Static, "assets/html/report.html")
if err != nil {
return "", fmt.Errorf("error parsing template: %v", err)
}
var buf bytes.Buffer
if err := tmpl.Execute(&buf, report); err != nil {
log.Printf("err: %+v\n", err)
return "", fmt.Errorf("error executing template: %v", err)
}
outputPath := fmt.Sprintf("report-%s-%d-%d.html", period, month, year)
if err := os.WriteFile(outputPath, buf.Bytes(), 0o644); err != nil {
log.Printf("err: %+v\n", err)
return "", fmt.Errorf("error writing HTML file: %v", err)
}
return outputPath, nil
}