chore: always include both CSV and PDF in report ZIP instead of picking one format

This commit is contained in:
Claus Lohmar 2026-07-13 09:10:12 +00:00
parent 517e95adc2
commit 1c9ab4554a
2 changed files with 33 additions and 48 deletions

View file

@ -77,7 +77,6 @@ func (h *FileHandler) FileEvent(w http.ResponseWriter, r *http.Request) {
}
to := r.FormValue("email")
format := r.FormValue("format")
if to == "" {
log.Printf("ERROR [%s] handlers: FileEvent: missing email field",
@ -85,12 +84,6 @@ func (h *FileHandler) FileEvent(w http.ResponseWriter, r *http.Request) {
renderFileError(w, "Email address is required.")
return
}
if format != "csv" && format != "pdf" {
log.Printf("ERROR [%s] handlers: FileEvent: invalid format %q",
time.Now().Format(time.RFC3339), format)
renderFileError(w, "Format must be 'csv' or 'pdf'.")
return
}
// 3. Verify the authenticated user owns this event.
userID := getUserID(r)
@ -139,26 +132,27 @@ func (h *FileHandler) FileEvent(w http.ResponseWriter, r *http.Request) {
userDept = reportUser.Department
}
// 5. Generate the report in the requested format.
var reportAttachment *email.Attachment
switch format {
case "csv":
reportAttachment, err = generateCSV(event.Name, expenses, userName, userDept)
case "pdf":
reportAttachment, err = generatePDF(event.Name, expenses, userName, userDept)
}
// 5. Generate both CSV and PDF reports.
csvAttachment, err := generateCSV(event.Name, expenses, userName, userDept)
if err != nil {
log.Printf("ERROR [%s] handlers: FileEvent: generate %s report: %v",
time.Now().Format(time.RFC3339), format, err)
renderFileError(w, "Failed to generate report. Please try again.")
log.Printf("ERROR [%s] handlers: FileEvent: generate CSV: %v",
time.Now().Format(time.RFC3339), err)
renderFileError(w, "Failed to generate CSV report. Please try again.")
return
}
pdfAttachment, err := generatePDF(event.Name, expenses, userName, userDept)
if err != nil {
log.Printf("ERROR [%s] handlers: FileEvent: generate PDF: %v",
time.Now().Format(time.RFC3339), err)
renderFileError(w, "Failed to generate PDF report. Please try again.")
return
}
// 6. Create a ZIP of all receipt images.
zipAttachment, zipErr := createReceiptZip(event.Name, expenses)
// 7. Build the list of attachments (report + ZIP if available).
attachments := []*email.Attachment{reportAttachment}
// 7. Build the list of attachments (CSV + PDF + ZIP if available).
attachments := []*email.Attachment{csvAttachment, pdfAttachment}
if zipErr == nil && zipAttachment != nil {
attachments = append(attachments, zipAttachment)
} else if zipErr != nil {
@ -219,11 +213,6 @@ func (h *FileHandler) GenerateReport(w http.ResponseWriter, r *http.Request) {
return
}
format := strings.ToLower(strings.TrimSpace(r.FormValue("format")))
if format != "csv" && format != "pdf" {
format = "pdf"
}
userID := getUserID(r)
if userID == "" {
renderFileError(w, "Session expired. Please log in again.")
@ -259,17 +248,19 @@ func (h *FileHandler) GenerateReport(w http.ResponseWriter, r *http.Request) {
uDept = repUser.Department
}
// Generate the report.
var reportAtt *email.Attachment
if format == "csv" {
reportAtt, err = generateCSV(event.Name, expenses, uName, uDept)
} else {
reportAtt, err = generatePDF(event.Name, expenses, uName, uDept)
}
// Generate both CSV and PDF reports.
csvAtt, err := generateCSV(event.Name, expenses, uName, uDept)
if err != nil {
log.Printf("ERROR [%s] handlers: GenerateReport: generate %s: %v",
time.Now().Format(time.RFC3339), format, err)
renderFileError(w, "Failed to generate report.")
log.Printf("ERROR [%s] handlers: GenerateReport: generate CSV: %v",
time.Now().Format(time.RFC3339), err)
renderFileError(w, "Failed to generate CSV report.")
return
}
pdfAtt, err := generatePDF(event.Name, expenses, uName, uDept)
if err != nil {
log.Printf("ERROR [%s] handlers: GenerateReport: generate PDF: %v",
time.Now().Format(time.RFC3339), err)
renderFileError(w, "Failed to generate PDF report.")
return
}
@ -277,8 +268,9 @@ func (h *FileHandler) GenerateReport(w http.ResponseWriter, r *http.Request) {
var pkgBuf bytes.Buffer
pkg := zip.NewWriter(&pkgBuf)
// Add report file.
addToZip(pkg, reportAtt.Filename, reportAtt.Content)
// Add both report files.
addToZip(pkg, csvAtt.Filename, csvAtt.Content)
addToZip(pkg, pdfAtt.Filename, pdfAtt.Content)
// Add receipt images directly (not nested).
for i, exp := range expenses {
@ -344,12 +336,10 @@ func (h *FileHandler) GenerateReport(w http.ResponseWriter, r *http.Request) {
time.Now().Format(time.RFC3339), pkgFilename, eventID)
// Render the download/send fragment.
ext := format
reportName := fmt.Sprintf("%s-report.%s", sanitiseFilename(event.Name), ext)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprintf(w, `<div id="report-package" style="background: #064e3b; border: 1px solid #065f46; border-radius: 0.5rem; padding: 1rem; margin-top: 1rem;">
<div style="font-weight: 600; color: #6ee7b7; margin-bottom: 0.5rem;">Report Ready</div>
<p style="font-size: 0.8rem; color: var(--color-text-muted); margin-bottom: 0.75rem;">%s &amp; %d receipt images packaged.</p>
<p style="font-size: 0.8rem; color: var(--color-text-muted); margin-bottom: 0.75rem;">CSV + PDF report &amp; %d receipt images packaged.</p>
<div style="display: flex; gap: 0.5rem; margin-bottom: 0.75rem;">
<a href="/dl/%s/%s" class="btn btn-primary" style="flex:1; text-align:center; text-decoration:none; font-size:0.85rem;" download> Download Now</a>
</div>
@ -364,7 +354,7 @@ func (h *FileHandler) GenerateReport(w http.ResponseWriter, r *http.Request) {
<div id="send-link-result"></div>
</div>
</div>`,
template.HTMLEscapeString(reportName), len(expenses),
len(expenses),
template.HTMLEscapeString(token), template.HTMLEscapeString(dlName),
template.HTMLEscapeString(eventID), template.HTMLEscapeString(token))
}

View file

@ -135,13 +135,8 @@
<div class="form-row">
<div class="form-group">
<label>Format</label>
<div style="display: flex; gap: 1rem; margin-top: 0.25rem;">
<label style="display: flex; align-items: center; gap: 0.25rem;">
<input type="radio" name="format" value="csv"> CSV
</label>
<label style="display: flex; align-items: center; gap: 0.25rem;">
<input type="radio" name="format" value="pdf" checked> PDF
</label>
<div style="display: flex; gap: 1rem; margin-top: 0.25rem; color: var(--color-text-muted); font-size: 0.85rem;">
CSV + PDF (both included)
</div>
</div>
<div class="form-group">