fix: PDF reports show local+claim columns, totals in claim currency, remove grand total

This commit is contained in:
Claus Lohmar 2026-07-14 12:17:42 +00:00
parent a7f7dca41d
commit 47fb82d109
2 changed files with 67 additions and 83 deletions

View file

@ -684,26 +684,10 @@ func generatePDF(eventName string, expenses []database.Expense, userName, userDe
pdf.Ln(10)
}
// Determine if we need conversion columns.
hasConversion := false
for _, exp := range expenses {
if exp.ConvertedAmount > 0 && exp.BaseCurrency != "" && exp.BaseCurrency != exp.Currency {
hasConversion = true
break
}
}
// Landscape A4: 297mm wide, 10mm margins → 277mm usable.
// Table header.
pdf.SetFont("Helvetica", "B", 8)
var headers []string
var colWidths []float64
if hasConversion {
headers = []string{"#", "Date", "Merchant", "Amount", "Curr.", "Converted", "Claim", "Category", "Description"}
colWidths = []float64{7, 24, 44, 20, 14, 20, 14, 40, 94}
} else {
headers = []string{"#", "Date", "Merchant", "Amount", "Currency", "Category", "Description"}
colWidths = []float64{8, 28, 58, 24, 18, 48, 93}
}
headers := []string{"#", "Date", "Merchant", "Local Amt", "Cur", "Claim Amt", "Claim", "Category", "Description"}
colWidths := []float64{7, 22, 40, 18, 12, 18, 12, 38, 110}
for i, h := range headers {
pdf.Cell(colWidths[i], 7, h)
}
@ -712,7 +696,7 @@ func generatePDF(eventName string, expenses []database.Expense, userName, userDe
// Table data rows.
pdf.SetFont("Helvetica", "", 8)
marginBottom := 18.0
var totalOrig, totalConv float64
var totalLocal, totalClaim float64
for i, exp := range expenses {
if pdf.GetY() > 210-marginBottom {
pdf.AddPage()
@ -723,63 +707,54 @@ func generatePDF(eventName string, expenses []database.Expense, userName, userDe
pdf.Ln(7)
pdf.SetFont("Helvetica", "", 8)
}
itemNum := i + 1
if hasConversion {
pdf.Cell(colWidths[0], 6, fmt.Sprintf("%d", itemNum))
pdf.Cell(colWidths[1], 6, exp.Date)
pdf.Cell(colWidths[2], 6, exp.Merchant)
pdf.Cell(colWidths[3], 6, fmt.Sprintf("%.2f", exp.Amount))
pdf.Cell(colWidths[4], 6, exp.Currency)
pdf.Cell(colWidths[5], 6, fmt.Sprintf("%.2f", exp.ConvertedAmount))
pdf.Cell(colWidths[6], 6, exp.BaseCurrency)
pdf.Cell(colWidths[7], 6, exp.Category)
pdf.Cell(colWidths[8], 6, exp.Description)
totalOrig += exp.Amount
totalConv += exp.ConvertedAmount
} else {
pdf.Cell(colWidths[0], 6, fmt.Sprintf("%d", itemNum))
pdf.Cell(colWidths[1], 6, exp.Date)
pdf.Cell(colWidths[2], 6, exp.Merchant)
pdf.Cell(colWidths[3], 6, fmt.Sprintf("%.2f", exp.Amount))
pdf.Cell(colWidths[4], 6, exp.Currency)
pdf.Cell(colWidths[5], 6, exp.Category)
pdf.Cell(colWidths[6], 6, exp.Description)
totalOrig += exp.Amount
// Compute claim amount (auto-calc if not set).
claimAmt := exp.ConvertedAmount
claimCur := exp.BaseCurrency
if claimAmt <= 0 {
claimAmt = exp.Amount
}
if claimCur == "" {
claimCur = exp.Currency
}
itemNum := i + 1
pdf.Cell(colWidths[0], 6, fmt.Sprintf("%d", itemNum))
pdf.Cell(colWidths[1], 6, exp.Date)
pdf.Cell(colWidths[2], 6, exp.Merchant)
pdf.Cell(colWidths[3], 6, fmt.Sprintf("%.2f", exp.Amount))
pdf.Cell(colWidths[4], 6, exp.Currency)
pdf.Cell(colWidths[5], 6, fmt.Sprintf("%.2f", claimAmt))
pdf.Cell(colWidths[6], 6, claimCur)
pdf.Cell(colWidths[7], 6, exp.Category)
pdf.Cell(colWidths[8], 6, exp.Description)
pdf.Ln(6)
totalLocal += exp.Amount
totalClaim += claimAmt
}
// Totals row.
// Totals row in claim currency.
pdf.SetDrawColor(71, 85, 105)
pdf.Line(10, pdf.GetY()+1, 287, pdf.GetY()+1)
pdf.Ln(3)
pdf.SetFont("Helvetica", "B", 9)
if hasConversion {
baseCur := ""
for _, exp := range expenses {
if exp.BaseCurrency != "" {
baseCur = exp.BaseCurrency
break
}
claimTotalCur := ""
for _, exp := range expenses {
if exp.BaseCurrency != "" {
claimTotalCur = exp.BaseCurrency
break
}
pdf.Cell(colWidths[0], 8, "")
pdf.Cell(colWidths[1], 8, "")
pdf.Cell(colWidths[2], 8, "TOTAL")
pdf.Cell(colWidths[3], 8, fmt.Sprintf("%.2f", totalOrig))
pdf.Cell(colWidths[4], 8, "")
pdf.Cell(colWidths[5], 8, fmt.Sprintf("%.2f", totalConv))
pdf.Cell(colWidths[6], 8, baseCur)
pdf.Cell(colWidths[7], 8, "")
pdf.Cell(colWidths[8], 8, "")
} else {
pdf.Cell(colWidths[0], 8, "")
pdf.Cell(colWidths[1], 8, "")
pdf.Cell(colWidths[2], 8, "TOTAL")
pdf.Cell(colWidths[3], 8, fmt.Sprintf("%.2f", totalOrig))
pdf.Cell(colWidths[4], 8, "")
pdf.Cell(colWidths[5], 8, "")
pdf.Cell(colWidths[6], 8, "")
}
if claimTotalCur == "" {
claimTotalCur = expenses[0].Currency
}
pdf.Cell(colWidths[0], 8, "")
pdf.Cell(colWidths[1], 8, "")
pdf.Cell(colWidths[2], 8, "TOTAL")
pdf.Cell(colWidths[3], 8, fmt.Sprintf("%.2f", totalLocal))
pdf.Cell(colWidths[4], 8, "")
pdf.Cell(colWidths[5], 8, fmt.Sprintf("%.2f", totalClaim))
pdf.Cell(colWidths[6], 8, claimTotalCur)
pdf.Cell(colWidths[7], 8, "")
pdf.Cell(colWidths[8], 8, "")
// Write to buffer.
var buf bytes.Buffer

View file

@ -661,11 +661,10 @@ func generateMonthlyPDF(monthName string, events []database.Event, expenses []da
itemNum := 1
// Landscape A4: 297mm wide, 10mm margins → 277mm usable.
colWidths := []float64{8, 28, 55, 22, 16, 50, 98}
headers := []string{"#", "Date", "Merchant", "Amount", "Curr.", "Category", "Description"}
colWidths := []float64{7, 22, 38, 18, 12, 18, 12, 36, 114}
headers := []string{"#", "Date", "Merchant", "Local Amt", "Cur", "Claim Amt", "Claim", "Category", "Description"}
marginBottom := 18.0
var grandTotal float64
for _, evt := range events {
evtExpenses := expensesByEvent[evt.ID]
if len(evtExpenses) == 0 {
@ -687,7 +686,7 @@ func generateMonthlyPDF(monthName string, events []database.Event, expenses []da
}
pdf.Ln(7)
var evtTotal float64
var evtLocal, evtClaim float64
pdf.SetFont("Helvetica", "", 8)
for _, exp := range evtExpenses {
if pdf.GetY() > 210-marginBottom {
@ -699,36 +698,46 @@ func generateMonthlyPDF(monthName string, events []database.Event, expenses []da
pdf.Ln(7)
pdf.SetFont("Helvetica", "", 8)
}
claimAmt := exp.ConvertedAmount
claimCur := exp.BaseCurrency
if claimAmt <= 0 {
claimAmt = exp.Amount
}
if claimCur == "" {
claimCur = exp.Currency
}
pdf.Cell(colWidths[0], 6, fmt.Sprintf("%d", itemNum))
pdf.Cell(colWidths[1], 6, exp.Date)
pdf.Cell(colWidths[2], 6, exp.Merchant)
pdf.Cell(colWidths[3], 6, fmt.Sprintf("%.2f", exp.Amount))
pdf.Cell(colWidths[4], 6, exp.Currency)
pdf.Cell(colWidths[5], 6, exp.Category)
pdf.Cell(colWidths[6], 6, exp.Description)
pdf.Cell(colWidths[5], 6, fmt.Sprintf("%.2f", claimAmt))
pdf.Cell(colWidths[6], 6, claimCur)
pdf.Cell(colWidths[7], 6, exp.Category)
pdf.Cell(colWidths[8], 6, exp.Description)
pdf.Ln(6)
evtTotal += exp.Amount
evtLocal += exp.Amount
evtClaim += claimAmt
itemNum++
}
// Event subtotal.
// Event subtotal in both local and claim currency.
pdf.SetDrawColor(71, 85, 105)
pdf.Line(10, pdf.GetY()+1, 287, pdf.GetY()+1)
pdf.Ln(3)
pdf.SetFont("Helvetica", "B", 9)
pdf.Cell(colWidths[0], 8, "")
pdf.Cell(colWidths[1], 8, "")
pdf.Cell(colWidths[2], 8, "Event Total")
pdf.Cell(colWidths[3], 8, fmt.Sprintf("%.2f", evtTotal))
pdf.Cell(colWidths[2], 8, fmt.Sprintf("%s Total", eventNames[evt.ID]))
pdf.Cell(colWidths[3], 8, fmt.Sprintf("%.2f", evtLocal))
pdf.Cell(colWidths[4], 8, "")
pdf.Cell(colWidths[5], 8, fmt.Sprintf("%.2f", evtClaim))
pdf.Cell(colWidths[6], 8, "")
pdf.Cell(colWidths[7], 8, "")
pdf.Cell(colWidths[8], 8, "")
pdf.Ln(10)
grandTotal += evtTotal
}
// Grand total.
pdf.SetFont("Helvetica", "B", 11)
pdf.Cell(0, 8, fmt.Sprintf("Grand Total: %.2f", grandTotal))
pdf.Ln(10)
var buf bytes.Buffer
if err := pdf.Output(&buf); err != nil {
return nil, fmt.Errorf("PDF output: %w", err)