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") to := r.FormValue("email")
format := r.FormValue("format")
if to == "" { if to == "" {
log.Printf("ERROR [%s] handlers: FileEvent: missing email field", 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.") renderFileError(w, "Email address is required.")
return 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. // 3. Verify the authenticated user owns this event.
userID := getUserID(r) userID := getUserID(r)
@ -139,26 +132,27 @@ func (h *FileHandler) FileEvent(w http.ResponseWriter, r *http.Request) {
userDept = reportUser.Department userDept = reportUser.Department
} }
// 5. Generate the report in the requested format. // 5. Generate both CSV and PDF reports.
var reportAttachment *email.Attachment csvAttachment, err := generateCSV(event.Name, expenses, userName, userDept)
switch format {
case "csv":
reportAttachment, err = generateCSV(event.Name, expenses, userName, userDept)
case "pdf":
reportAttachment, err = generatePDF(event.Name, expenses, userName, userDept)
}
if err != nil { if err != nil {
log.Printf("ERROR [%s] handlers: FileEvent: generate %s report: %v", log.Printf("ERROR [%s] handlers: FileEvent: generate CSV: %v",
time.Now().Format(time.RFC3339), format, err) time.Now().Format(time.RFC3339), err)
renderFileError(w, "Failed to generate report. Please try again.") 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 return
} }
// 6. Create a ZIP of all receipt images. // 6. Create a ZIP of all receipt images.
zipAttachment, zipErr := createReceiptZip(event.Name, expenses) zipAttachment, zipErr := createReceiptZip(event.Name, expenses)
// 7. Build the list of attachments (report + ZIP if available). // 7. Build the list of attachments (CSV + PDF + ZIP if available).
attachments := []*email.Attachment{reportAttachment} attachments := []*email.Attachment{csvAttachment, pdfAttachment}
if zipErr == nil && zipAttachment != nil { if zipErr == nil && zipAttachment != nil {
attachments = append(attachments, zipAttachment) attachments = append(attachments, zipAttachment)
} else if zipErr != nil { } else if zipErr != nil {
@ -219,11 +213,6 @@ func (h *FileHandler) GenerateReport(w http.ResponseWriter, r *http.Request) {
return return
} }
format := strings.ToLower(strings.TrimSpace(r.FormValue("format")))
if format != "csv" && format != "pdf" {
format = "pdf"
}
userID := getUserID(r) userID := getUserID(r)
if userID == "" { if userID == "" {
renderFileError(w, "Session expired. Please log in again.") 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 uDept = repUser.Department
} }
// Generate the report. // Generate both CSV and PDF reports.
var reportAtt *email.Attachment csvAtt, err := generateCSV(event.Name, expenses, uName, uDept)
if format == "csv" {
reportAtt, err = generateCSV(event.Name, expenses, uName, uDept)
} else {
reportAtt, err = generatePDF(event.Name, expenses, uName, uDept)
}
if err != nil { if err != nil {
log.Printf("ERROR [%s] handlers: GenerateReport: generate %s: %v", log.Printf("ERROR [%s] handlers: GenerateReport: generate CSV: %v",
time.Now().Format(time.RFC3339), format, err) time.Now().Format(time.RFC3339), err)
renderFileError(w, "Failed to generate report.") 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 return
} }
@ -277,8 +268,9 @@ func (h *FileHandler) GenerateReport(w http.ResponseWriter, r *http.Request) {
var pkgBuf bytes.Buffer var pkgBuf bytes.Buffer
pkg := zip.NewWriter(&pkgBuf) pkg := zip.NewWriter(&pkgBuf)
// Add report file. // Add both report files.
addToZip(pkg, reportAtt.Filename, reportAtt.Content) addToZip(pkg, csvAtt.Filename, csvAtt.Content)
addToZip(pkg, pdfAtt.Filename, pdfAtt.Content)
// Add receipt images directly (not nested). // Add receipt images directly (not nested).
for i, exp := range expenses { 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) time.Now().Format(time.RFC3339), pkgFilename, eventID)
// Render the download/send fragment. // 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") 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;"> 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> <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;"> <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> <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> </div>
@ -364,7 +354,7 @@ func (h *FileHandler) GenerateReport(w http.ResponseWriter, r *http.Request) {
<div id="send-link-result"></div> <div id="send-link-result"></div>
</div> </div>
</div>`, </div>`,
template.HTMLEscapeString(reportName), len(expenses), len(expenses),
template.HTMLEscapeString(token), template.HTMLEscapeString(dlName), template.HTMLEscapeString(token), template.HTMLEscapeString(dlName),
template.HTMLEscapeString(eventID), template.HTMLEscapeString(token)) template.HTMLEscapeString(eventID), template.HTMLEscapeString(token))
} }

View file

@ -135,13 +135,8 @@
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label>Format</label> <label>Format</label>
<div style="display: flex; gap: 1rem; margin-top: 0.25rem;"> <div style="display: flex; gap: 1rem; margin-top: 0.25rem; color: var(--color-text-muted); font-size: 0.85rem;">
<label style="display: flex; align-items: center; gap: 0.25rem;"> CSV + PDF (both included)
<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> </div>
</div> </div>
<div class="form-group"> <div class="form-group">