diff --git a/internal/handlers/file.go b/internal/handlers/file.go index a27e3d4..2ae71ba 100644 --- a/internal/handlers/file.go +++ b/internal/handlers/file.go @@ -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 diff --git a/internal/handlers/months.go b/internal/handlers/months.go index be25237..5ba797a 100644 --- a/internal/handlers/months.go +++ b/internal/handlers/months.go @@ -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)