mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
Some checks failed
CI / checks (push) Has been cancelled
Redesigned the invoice PDF with a more modern, modular layout. Updated colors, spacing, and section organization for better readability and visual appeal. Added a summary block, improved table formatting, and refined header/footer presentation. Also updated dependencies in go.mod and go.sum to support new features.
447 lines
16 KiB
Go
447 lines
16 KiB
Go
package pdf
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/jung-kurt/gofpdf/v2"
|
|
|
|
"github.com/rjNemo/rentease/assets"
|
|
"github.com/rjNemo/rentease/internal/service/booking"
|
|
)
|
|
|
|
type HTMLPdfClient struct{}
|
|
|
|
const (
|
|
invoiceMarginX = 10.0
|
|
invoiceContentWidth = 190.0
|
|
invoiceHeaderHeight = 32.0
|
|
invoiceTableRowH = 9.0
|
|
invoiceSummaryLineH = 9.0
|
|
invoiceNotesPadding = 4.0
|
|
invoiceColGap = 6.0
|
|
invoiceColWidth = (invoiceContentWidth - invoiceColGap) / 2.0
|
|
)
|
|
|
|
type rgbColor struct {
|
|
r int
|
|
g int
|
|
b int
|
|
}
|
|
|
|
var (
|
|
invoiceTeal = rgbColor{0, 123, 143}
|
|
invoiceTealDark = rgbColor{0, 98, 114}
|
|
invoiceLightGray = rgbColor{248, 249, 250}
|
|
invoiceMidGray = rgbColor{222, 226, 230}
|
|
invoiceTextGray = rgbColor{73, 80, 87}
|
|
invoiceTextMuted = rgbColor{108, 117, 125}
|
|
invoiceNoteBg = rgbColor{237, 247, 249}
|
|
invoiceWhite = rgbColor{255, 255, 255}
|
|
)
|
|
|
|
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(invoiceMarginX, 10, invoiceMarginX)
|
|
pdf.SetAutoPageBreak(true, 14)
|
|
pdf.AddPage()
|
|
|
|
tr := pdf.UnicodeTranslatorFromDescriptor("")
|
|
drawInvoiceHeader(pdf, tr, data)
|
|
drawInvoiceDetails(pdf, tr, data)
|
|
drawInvoiceItemsTable(pdf, tr, data)
|
|
drawInvoicePayments(pdf, tr, data)
|
|
drawInvoiceSummary(pdf, tr, data)
|
|
drawInvoiceNotes(pdf, tr, data)
|
|
drawInvoiceFooter(pdf, tr, data)
|
|
|
|
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
|
|
}
|
|
|
|
func drawInvoiceHeader(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
|
topY := 10.0
|
|
|
|
// Full-width teal banner
|
|
pdf.SetFillColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
|
pdf.Rect(invoiceMarginX, topY, invoiceContentWidth, invoiceHeaderHeight, "F")
|
|
|
|
// Subtle darker teal accent strip at very top
|
|
pdf.SetFillColor(invoiceTealDark.r, invoiceTealDark.g, invoiceTealDark.b)
|
|
pdf.Rect(invoiceMarginX, topY, invoiceContentWidth, 1.2, "F")
|
|
|
|
// Logo in a white rounded rectangle
|
|
if logo, err := assets.Static.ReadFile("assets/img/logo.png"); err == nil {
|
|
info := pdf.RegisterImageOptionsReader(
|
|
"invoice-logo",
|
|
gofpdf.ImageOptions{ImageType: "PNG"},
|
|
bytes.NewReader(logo),
|
|
)
|
|
if info != nil {
|
|
logoX := invoiceMarginX + 5
|
|
logoY := topY + 4
|
|
logoSize := 24.0
|
|
// white background circle behind logo
|
|
pdf.SetFillColor(255, 255, 255)
|
|
pdf.SetDrawColor(255, 255, 255)
|
|
radius := logoSize/2 + 1.5
|
|
pdf.Circle(logoX+logoSize/2, logoY+logoSize/2, radius, "F")
|
|
pdf.ImageOptions("invoice-logo", logoX+1.5, logoY+1.5, logoSize-3, logoSize-3, false, gofpdf.ImageOptions{ImageType: "PNG"}, 0, "")
|
|
}
|
|
}
|
|
|
|
// Host info on the right side of banner
|
|
pdf.SetTextColor(255, 255, 255)
|
|
rightBlockX := 100.0
|
|
rightBlockW := invoiceMarginX + invoiceContentWidth - rightBlockX - 4
|
|
|
|
pdf.SetXY(rightBlockX, topY+5)
|
|
pdf.SetFont("Helvetica", "B", 13)
|
|
pdf.CellFormat(rightBlockW, 5.5, tr(data.Host.Name), "", 1, "R", false, 0, "")
|
|
|
|
pdf.SetFont("Helvetica", "", 8.5)
|
|
hostLines := []string{
|
|
data.Host.Address,
|
|
fmt.Sprintf("%s %s", data.Host.ZipCode, data.Host.City),
|
|
fmt.Sprintf("Tel : %s", data.Host.PhoneNumber),
|
|
fmt.Sprintf("Mail : %s", data.Host.Email),
|
|
}
|
|
for _, line := range hostLines {
|
|
pdf.SetX(rightBlockX)
|
|
pdf.CellFormat(rightBlockW, 4.2, tr(line), "", 1, "R", false, 0, "")
|
|
}
|
|
|
|
// Invoice number badge below banner
|
|
pdf.Ln(5)
|
|
badgeY := pdf.GetY()
|
|
pdf.SetFont("Helvetica", "", 9)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.SetX(invoiceMarginX)
|
|
pdf.CellFormat(80, 6, tr("FACTURE"), "", 0, "L", false, 0, "")
|
|
|
|
pdf.SetFont("Helvetica", "B", 14)
|
|
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
|
pdf.CellFormat(invoiceContentWidth-80, 6, tr(data.ID), "", 1, "R", false, 0, "")
|
|
|
|
pdf.SetDrawColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
|
pdf.SetLineWidth(0.4)
|
|
pdf.Line(invoiceMarginX, badgeY+8, invoiceMarginX+invoiceContentWidth, badgeY+8)
|
|
pdf.SetLineWidth(0.2)
|
|
|
|
pdf.SetY(badgeY + 12)
|
|
}
|
|
|
|
func drawInvoiceDetails(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
|
startY := pdf.GetY()
|
|
leftX := invoiceMarginX
|
|
rightX := invoiceMarginX + invoiceColWidth + invoiceColGap
|
|
cardPad := 5.0
|
|
cardH := 36.0
|
|
|
|
// Left card: Customer info - light gray background
|
|
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
|
pdf.RoundedRect(leftX, startY, invoiceColWidth, cardH, 2, "1234", "F")
|
|
|
|
// Section label
|
|
pdf.SetXY(leftX+cardPad, startY+cardPad)
|
|
pdf.SetFont("Helvetica", "", 7.5)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.CellFormat(invoiceColWidth-cardPad*2, 4, tr("CLIENT"), "", 1, "L", false, 0, "")
|
|
|
|
// Customer name
|
|
pdf.SetX(leftX + cardPad)
|
|
pdf.SetFont("Helvetica", "B", 12)
|
|
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
|
pdf.CellFormat(invoiceColWidth-cardPad*2, 7, tr(data.Name), "", 1, "L", false, 0, "")
|
|
|
|
// Customer details
|
|
detailLineH := 5.5
|
|
writeLeftDetail := func(label, value string) {
|
|
pdf.SetX(leftX + cardPad)
|
|
pdf.SetFont("Helvetica", "", 8.5)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.CellFormat(28, detailLineH, tr(label), "", 0, "L", false, 0, "")
|
|
pdf.SetFont("Helvetica", "", 9)
|
|
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
|
pdf.CellFormat(invoiceColWidth-cardPad*2-28, detailLineH, tr(value), "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
writeLeftDetail("Tel", emptyFallback(data.PhoneNumber))
|
|
writeLeftDetail("Clients", fmt.Sprintf("%d", data.CustomersNumber))
|
|
writeLeftDetail("Plateforme", data.Platform)
|
|
|
|
// Right card: Booking info - light gray background
|
|
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
|
pdf.RoundedRect(rightX, startY, invoiceColWidth, cardH, 2, "1234", "F")
|
|
|
|
pdf.SetXY(rightX+cardPad, startY+cardPad)
|
|
pdf.SetFont("Helvetica", "", 7.5)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.CellFormat(invoiceColWidth-cardPad*2, 4, tr("SÉJOUR"), "", 1, "L", false, 0, "")
|
|
|
|
writeRightDetail := func(label, value string, bold bool) {
|
|
pdf.SetX(rightX + cardPad)
|
|
pdf.SetFont("Helvetica", "", 8.5)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.CellFormat(28, detailLineH, tr(label), "", 0, "L", false, 0, "")
|
|
style := ""
|
|
if bold {
|
|
style = "B"
|
|
}
|
|
pdf.SetFont("Helvetica", style, 9.5)
|
|
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
|
pdf.CellFormat(invoiceColWidth-cardPad*2-28, detailLineH, tr(value), "", 1, "L", false, 0, "")
|
|
}
|
|
|
|
// Date range as main heading
|
|
pdf.SetX(rightX + cardPad)
|
|
pdf.SetFont("Helvetica", "B", 12)
|
|
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
|
pdf.CellFormat(invoiceColWidth-cardPad*2, 7, tr(fmt.Sprintf("%s — %s", data.From, data.To)), "", 1, "L", false, 0, "")
|
|
|
|
writeRightDetail("Nuits", nightsCount(data.From, data.To), false)
|
|
writeRightDetail("Montant", euroAmount(data.Total), true)
|
|
|
|
pdf.SetY(startY + cardH + 8)
|
|
}
|
|
|
|
func drawInvoiceItemsTable(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
|
// Section title
|
|
pdf.SetFont("Helvetica", "B", 11)
|
|
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
|
pdf.CellFormat(0, 7, tr("Prestations"), "", 1, "L", false, 0, "")
|
|
pdf.Ln(1)
|
|
|
|
widths := []float64{72, 30, 40, 48}
|
|
headers := []string{"Objet", "Quantité", "Prix (€)", "Total (€)"}
|
|
|
|
writeTableHeader(pdf, tr, widths, headers)
|
|
|
|
pdf.SetFont("Helvetica", "", 10)
|
|
for i, line := range data.Lines {
|
|
fill := i%2 == 0
|
|
if fill {
|
|
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
|
}
|
|
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
|
pdf.CellFormat(widths[0], invoiceTableRowH, " "+tr(line.Name), "", 0, "", fill, 0, "")
|
|
pdf.CellFormat(widths[1], invoiceTableRowH, fmt.Sprintf("%d", line.Quantity), "", 0, "C", fill, 0, "")
|
|
pdf.CellFormat(widths[2], invoiceTableRowH, line.Price, "", 0, "R", fill, 0, "")
|
|
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
|
pdf.SetFont("Helvetica", "B", 10)
|
|
pdf.CellFormat(widths[3], invoiceTableRowH, line.Total+" ", "", 1, "R", fill, 0, "")
|
|
pdf.SetFont("Helvetica", "", 10)
|
|
|
|
// Subtle separator line
|
|
lineY := pdf.GetY()
|
|
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
|
pdf.Line(invoiceMarginX, lineY, invoiceMarginX+invoiceContentWidth, lineY)
|
|
}
|
|
|
|
pdf.Ln(8)
|
|
}
|
|
|
|
func drawInvoicePayments(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
|
pdf.SetFont("Helvetica", "B", 11)
|
|
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
|
pdf.CellFormat(0, 7, tr("Historique des Paiements"), "", 1, "L", false, 0, "")
|
|
pdf.Ln(1)
|
|
|
|
widths := []float64{54, 88, 48}
|
|
headers := []string{"Date", "Mode de Paiement", "Montant (€)"}
|
|
writeTableHeader(pdf, tr, widths, headers)
|
|
|
|
pdf.SetFont("Helvetica", "", 10)
|
|
if len(data.Payments) == 0 {
|
|
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.CellFormat(widths[0], invoiceTableRowH, " "+tr("-"), "", 0, "", true, 0, "")
|
|
pdf.CellFormat(widths[1], invoiceTableRowH, tr("Aucun paiement enregistré"), "", 0, "", true, 0, "")
|
|
pdf.CellFormat(widths[2], invoiceTableRowH, tr("0.00")+" ", "", 1, "R", true, 0, "")
|
|
} else {
|
|
for i, payment := range data.Payments {
|
|
fill := i%2 == 0
|
|
if fill {
|
|
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
|
}
|
|
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
|
pdf.CellFormat(widths[0], invoiceTableRowH, " "+tr(payment.Date), "", 0, "", fill, 0, "")
|
|
pdf.CellFormat(widths[1], invoiceTableRowH, tr(payment.Method), "", 0, "", fill, 0, "")
|
|
pdf.SetFont("Helvetica", "B", 10)
|
|
pdf.CellFormat(widths[2], invoiceTableRowH, payment.Amount+" ", "", 1, "R", fill, 0, "")
|
|
pdf.SetFont("Helvetica", "", 10)
|
|
|
|
lineY := pdf.GetY()
|
|
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
|
pdf.Line(invoiceMarginX, lineY, invoiceMarginX+invoiceContentWidth, lineY)
|
|
}
|
|
}
|
|
|
|
pdf.Ln(8)
|
|
}
|
|
|
|
func drawInvoiceSummary(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
|
// Right-aligned summary block
|
|
summaryX := invoiceMarginX + invoiceContentWidth - 80
|
|
summaryW := 80.0
|
|
|
|
labels := []string{"Montant Total", "Montant Payé", "Solde Restant"}
|
|
values := []string{euroAmount(data.Total), euroAmount(data.AmountPaid), euroAmount(data.BalanceDue)}
|
|
|
|
for i := range labels {
|
|
pdf.SetX(summaryX)
|
|
pdf.SetFont("Helvetica", "", 9.5)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.CellFormat(summaryW/2, invoiceSummaryLineH, tr(labels[i]), "", 0, "L", false, 0, "")
|
|
pdf.SetFont("Helvetica", "B", 10)
|
|
pdf.SetTextColor(invoiceTextGray.r, invoiceTextGray.g, invoiceTextGray.b)
|
|
pdf.CellFormat(summaryW/2, invoiceSummaryLineH, tr(values[i]), "", 1, "R", false, 0, "")
|
|
|
|
if i < len(labels)-1 {
|
|
lineY := pdf.GetY()
|
|
pdf.SetDrawColor(invoiceMidGray.r, invoiceMidGray.g, invoiceMidGray.b)
|
|
pdf.Line(summaryX, lineY, summaryX+summaryW, lineY)
|
|
}
|
|
}
|
|
|
|
// Highlight the balance due
|
|
balanceY := pdf.GetY() - invoiceSummaryLineH
|
|
pdf.SetFillColor(invoiceNoteBg.r, invoiceNoteBg.g, invoiceNoteBg.b)
|
|
pdf.RoundedRect(summaryX-2, balanceY-0.5, summaryW+4, invoiceSummaryLineH+1, 1.5, "1234", "F")
|
|
pdf.SetXY(summaryX, balanceY)
|
|
pdf.SetFont("Helvetica", "B", 9.5)
|
|
pdf.SetTextColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
|
pdf.CellFormat(summaryW/2, invoiceSummaryLineH, tr("Solde Restant"), "", 0, "L", false, 0, "")
|
|
pdf.SetFont("Helvetica", "B", 11)
|
|
pdf.CellFormat(summaryW/2, invoiceSummaryLineH, tr(euroAmount(data.BalanceDue)), "", 1, "R", false, 0, "")
|
|
|
|
pdf.Ln(8)
|
|
}
|
|
|
|
func drawInvoiceNotes(pdf *gofpdf.Fpdf, tr func(string) string, _ booking.Invoice) {
|
|
notes := "TVA non applicable, art. 293 B du CGI\nDispensé d'immatriculation au registre du commerce et des sociétés (RCS) et au répertoire des métiers.\nConditions de paiement : paiement à réception de facture. Aucun escompte consenti pour règlement anticipé ou désistement. Tout incident de paiement est passible d'intérêts de retard. Le montant des pénalités résulte de l'application aux sommes restant dues d'un taux d'intérêt légal en vigueur au moment de l'incident.\nIl sera demandé 15€ par jour par personne supplémentaire."
|
|
|
|
pdf.SetFont("Helvetica", "", 7)
|
|
lines := pdf.SplitLines([]byte(tr(notes)), invoiceContentWidth-(invoiceNotesPadding*2)-8)
|
|
noteBoxHeight := float64(len(lines))*3.0 + invoiceNotesPadding*2 + 6
|
|
|
|
if pdf.GetY()+noteBoxHeight+30 > 282 {
|
|
pdf.AddPage()
|
|
}
|
|
|
|
y := pdf.GetY()
|
|
pdf.SetFillColor(invoiceLightGray.r, invoiceLightGray.g, invoiceLightGray.b)
|
|
pdf.RoundedRect(invoiceMarginX, y, invoiceContentWidth, noteBoxHeight, 2, "1234", "F")
|
|
|
|
// Notes title
|
|
pdf.SetXY(invoiceMarginX+invoiceNotesPadding, y+invoiceNotesPadding)
|
|
pdf.SetFont("Helvetica", "B", 7.5)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.CellFormat(invoiceContentWidth-(invoiceNotesPadding*2), 4, tr("MENTIONS LÉGALES"), "", 1, "L", false, 0, "")
|
|
|
|
pdf.SetX(invoiceMarginX + invoiceNotesPadding)
|
|
pdf.SetFont("Helvetica", "", 7)
|
|
pdf.SetTextColor(invoiceTextMuted.r, invoiceTextMuted.g, invoiceTextMuted.b)
|
|
pdf.MultiCell(invoiceContentWidth-(invoiceNotesPadding*2), 3.0, tr(notes), "", "L", false)
|
|
|
|
pdf.SetY(y + noteBoxHeight + 6)
|
|
}
|
|
|
|
func drawInvoiceFooter(pdf *gofpdf.Fpdf, tr func(string) string, data booking.Invoice) {
|
|
// Total banner at the bottom
|
|
footerH := 14.0
|
|
y := pdf.GetY()
|
|
|
|
pdf.SetFillColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
|
pdf.RoundedRect(invoiceMarginX, y, invoiceContentWidth, footerH, 2, "1234", "F")
|
|
|
|
pdf.SetXY(invoiceMarginX+6, y+2)
|
|
pdf.SetFont("Helvetica", "", 9)
|
|
pdf.SetTextColor(255, 255, 255)
|
|
pdf.CellFormat(60, 10, tr("TOTAL À PAYER"), "", 0, "L", false, 0, "")
|
|
|
|
pdf.SetFont("Helvetica", "B", 16)
|
|
pdf.CellFormat(invoiceContentWidth-72, 10, tr(euroAmount(data.Total)), "", 0, "R", false, 0, "")
|
|
}
|
|
|
|
func writeTableHeader(pdf *gofpdf.Fpdf, tr func(string) string, widths []float64, headers []string) {
|
|
pdf.SetFillColor(invoiceTeal.r, invoiceTeal.g, invoiceTeal.b)
|
|
pdf.SetTextColor(255, 255, 255)
|
|
pdf.SetFont("Helvetica", "B", 9)
|
|
alignments := []string{"L", "C", "R", "R"}
|
|
if len(widths) == 3 {
|
|
alignments = []string{"L", "L", "R"}
|
|
}
|
|
for i, header := range headers {
|
|
end := 0
|
|
if i == len(headers)-1 {
|
|
end = 1
|
|
}
|
|
pad := ""
|
|
if i == 0 {
|
|
pad = " "
|
|
}
|
|
pdf.CellFormat(widths[i], 8, pad+tr(header), "", end, alignments[i], true, 0, "")
|
|
}
|
|
}
|
|
|
|
func nightsCount(from, to string) string {
|
|
f, err1 := time.Parse("02/01/2006", from)
|
|
t, err2 := time.Parse("02/01/2006", to)
|
|
if err1 != nil || err2 != nil {
|
|
return "-"
|
|
}
|
|
days := int(t.Sub(f).Hours() / 24)
|
|
if days < 0 {
|
|
days = 0
|
|
}
|
|
return fmt.Sprintf("%d", days)
|
|
}
|
|
|
|
func euroAmount(value string) string {
|
|
return fmt.Sprintf("%s €", value)
|
|
}
|
|
|
|
func emptyFallback(value string) string {
|
|
if value == "" {
|
|
return "-"
|
|
}
|
|
return value
|
|
}
|