fix: CSV reports now include claim currency column, claim totals, and total claim header

This commit is contained in:
Claus Lohmar 2026-07-14 14:33:45 +00:00
parent f1b029949e
commit 22d2507d52
2 changed files with 76 additions and 73 deletions

View file

@ -568,85 +568,64 @@ func generateCSV(eventName string, expenses []database.Expense, userName, userDe
var buf bytes.Buffer
writer := csv.NewWriter(&buf)
// Write user metadata row (prepared by).
// Write user metadata row.
if userName != "" {
metaLine := fmt.Sprintf("Prepared by: %s", userName)
if userDept != "" && userDept != "-" {
metaLine += fmt.Sprintf(" | Department: %s", userDept)
}
writer.Write([]string{metaLine})
writer.Write([]string{""}) // blank separator
writer.Write([]string{""})
}
// Determine if we need conversion columns.
hasConversion := false
// Total claim summary.
var totalClaim float64
claimCur := ""
for _, exp := range expenses {
if exp.ConvertedAmount > 0 && exp.BaseCurrency != "" && exp.BaseCurrency != exp.Currency {
hasConversion = true
break
cAmt := exp.ConvertedAmount
if cAmt <= 0 {
cAmt = exp.Amount
}
totalClaim += cAmt
if claimCur == "" && exp.BaseCurrency != "" {
claimCur = exp.BaseCurrency
}
}
if claimCur == "" && len(expenses) > 0 {
claimCur = expenses[0].Currency
}
writer.Write([]string{fmt.Sprintf("Total Claim: %.2f %s", totalClaim, claimCur)})
writer.Write([]string{""})
// Write header row.
var header []string
if hasConversion {
header = []string{"#", "Date", "Merchant", "Amount", "Currency", "Converted", "Claim Currency", "Category", "Description"}
} else {
header = []string{"#", "Date", "Merchant", "Amount", "Currency", "Category", "Description"}
}
if err := writer.Write(header); err != nil {
return nil, fmt.Errorf("write CSV header: %w", err)
}
// Write header row with both local and claim columns.
header := []string{"#", "Date", "Merchant", "Local Amt", "Currency", "Claim Amt", "Claim Curr", "Category", "Description"}
writer.Write(header)
// Write one data row per expense with item number.
totalOrig := 0.0
totalConv := 0.0
// Write data rows.
var tableLocal, tableClaim float64
for i, exp := range expenses {
itemNum := i + 1
var row []string
if hasConversion {
row = []string{
fmt.Sprintf("%d", itemNum),
exp.Date,
exp.Merchant,
fmt.Sprintf("%.2f", exp.Amount),
exp.Currency,
fmt.Sprintf("%.2f", exp.ConvertedAmount),
exp.BaseCurrency,
exp.Category,
exp.Description,
claimAmt := exp.ConvertedAmount
claimCur := exp.BaseCurrency
if claimAmt <= 0 {
claimAmt = exp.Amount
}
} else {
row = []string{
fmt.Sprintf("%d", itemNum),
exp.Date,
exp.Merchant,
fmt.Sprintf("%.2f", exp.Amount),
exp.Currency,
exp.Category,
exp.Description,
if claimCur == "" {
claimCur = exp.Currency
}
row := []string{
fmt.Sprintf("%d", i+1),
exp.Date, exp.Merchant,
fmt.Sprintf("%.2f", exp.Amount), exp.Currency,
fmt.Sprintf("%.2f", claimAmt), claimCur,
exp.Category, exp.Description,
}
if err := writer.Write(row); err != nil {
return nil, fmt.Errorf("write CSV row: %w", err)
}
totalOrig += exp.Amount
totalConv += exp.ConvertedAmount
writer.Write(row)
tableLocal += exp.Amount
tableClaim += claimAmt
}
// Write totals row.
if hasConversion {
baseCur := ""
for _, exp := range expenses {
if exp.BaseCurrency != "" {
baseCur = exp.BaseCurrency
break
}
}
writer.Write([]string{"TOTAL", "", "", fmt.Sprintf("%.2f", totalOrig), "", fmt.Sprintf("%.2f", totalConv), baseCur, "", ""})
} else {
writer.Write([]string{"TOTAL", "", "", fmt.Sprintf("%.2f", totalOrig), "", "", "", ""})
}
writer.Write([]string{"TOTAL", "", "", fmt.Sprintf("%.2f", tableLocal), "", fmt.Sprintf("%.2f", tableClaim), "", "", ""})
writer.Flush()
if err := writer.Error(); err != nil {

View file

@ -581,6 +581,25 @@ func generateMonthlyCSV(monthName string, events []database.Event, expenses []da
}
buf.WriteString("\r\n")
// Total claim summary.
var totalClaim float64
claimCur := ""
for _, exp := range expenses {
cAmt := exp.ConvertedAmount
if cAmt <= 0 {
cAmt = exp.Amount
}
totalClaim += cAmt
if claimCur == "" && exp.BaseCurrency != "" {
claimCur = exp.BaseCurrency
}
}
if claimCur == "" && len(expenses) > 0 {
claimCur = expenses[0].Currency
}
buf.WriteString(fmt.Sprintf("Total Claim: %.2f %s\r\n", totalClaim, claimCur))
buf.WriteString("\r\n")
// Group expenses by event.
expensesByEvent := make(map[string][]database.Expense)
eventNames := make(map[string]string)
@ -592,7 +611,7 @@ func generateMonthlyCSV(monthName string, events []database.Event, expenses []da
}
itemNum := 1
var grandTotalOrig, grandTotalConv float64
var grandLocal, grandClaim float64
for _, evt := range events {
evtExpenses := expensesByEvent[evt.ID]
@ -601,24 +620,29 @@ func generateMonthlyCSV(monthName string, events []database.Event, expenses []da
}
buf.WriteString(fmt.Sprintf("\r\n--- %s ---\r\n", eventNames[evt.ID]))
buf.WriteString("#,Date,Merchant,Amount,Currency,Category,Description\r\n")
buf.WriteString("#,Date,Merchant,Local Amt,Currency,Claim Amt,Claim Curr,Category,Description\r\n")
var evtTotal float64
var evtLocal, evtClaim float64
for _, exp := range evtExpenses {
buf.WriteString(fmt.Sprintf("%d,%s,%s,%.2f,%s,%s,%s\r\n",
itemNum, exp.Date, exp.Merchant, exp.Amount, exp.Currency, exp.Category, exp.Description))
evtTotal += exp.Amount
if exp.ConvertedAmount > 0 {
grandTotalConv += exp.ConvertedAmount
} else {
grandTotalConv += exp.Amount
claimAmt := exp.ConvertedAmount
claimCur := exp.BaseCurrency
if claimAmt <= 0 {
claimAmt = exp.Amount
}
if claimCur == "" {
claimCur = exp.Currency
}
buf.WriteString(fmt.Sprintf("%d,%s,%s,%.2f,%s,%.2f,%s,%s,%s\r\n",
itemNum, exp.Date, exp.Merchant, exp.Amount, exp.Currency, claimAmt, claimCur, exp.Category, exp.Description))
evtLocal += exp.Amount
evtClaim += claimAmt
itemNum++
}
buf.WriteString(fmt.Sprintf("Event Total,,,,%.2f,,\r\n", evtTotal))
grandTotalOrig += evtTotal
buf.WriteString(fmt.Sprintf("Event Total,,,%.2f,,%.2f,,\r\n", evtLocal, evtClaim))
grandLocal += evtLocal
grandClaim += evtClaim
}
buf.WriteString(fmt.Sprintf("\r\nGrand Total,,,,%.2f,,\r\n", grandTotalOrig))
buf.WriteString(fmt.Sprintf("\r\nGrand Total,,,%.2f,,%.2f,,\r\n", grandLocal, grandClaim))
filename := fmt.Sprintf("monthly-%s-report.csv", sanitiseFilename(monthName))
return &email.Attachment{