package routes import ( "bssapp-backend/auth" "bssapp-backend/models" "bssapp-backend/queries" "bytes" "database/sql" "fmt" "log" "net/http" "runtime/debug" "strings" "time" "github.com/jung-kurt/gofpdf" ) func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { defer func() { if rec := recover(); rec != nil { stack := string(debug.Stack()) log.Printf("❌ ExportStatementAgingPDFHandler panic: %v\n%s", rec, stack) http.Error(w, fmt.Sprintf("pdf panic: %v\n%s", rec, stack), http.StatusInternalServerError) } }() claims, ok := auth.GetClaimsFromContext(r.Context()) if !ok || claims == nil { http.Error(w, "unauthorized", http.StatusUnauthorized) return } selectedDate := strings.TrimSpace(r.URL.Query().Get("enddate")) if selectedDate == "" { selectedDate = strings.TrimSpace(r.URL.Query().Get("selected_date")) } if selectedDate == "" { selectedDate = time.Now().Format("2006-01-02") } params := models.CustomerBalanceListParams{ SelectedDate: selectedDate, CariSearch: strings.TrimSpace(r.URL.Query().Get("cari_search")), CariIlkGrup: strings.TrimSpace(r.URL.Query().Get("cari_ilk_grup")), Piyasa: strings.TrimSpace(r.URL.Query().Get("piyasa")), Temsilci: strings.TrimSpace(r.URL.Query().Get("temsilci")), RiskDurumu: strings.TrimSpace(r.URL.Query().Get("risk_durumu")), IslemTipi: strings.TrimSpace(r.URL.Query().Get("islem_tipi")), Ulke: strings.TrimSpace(r.URL.Query().Get("ulke")), Il: strings.TrimSpace(r.URL.Query().Get("il")), Ilce: strings.TrimSpace(r.URL.Query().Get("ilce")), } if accountCode := strings.TrimSpace(r.URL.Query().Get("accountcode")); accountCode != "" { params.CariSearch = accountCode } if err := queries.RebuildStatementAgingCache(r.Context()); err != nil { http.Error(w, "Error rebuilding aging cache: "+err.Error(), http.StatusInternalServerError) return } detailed := parseBoolQuery(r.URL.Query().Get("detailed")) excludeZero12 := parseBoolQuery(r.URL.Query().Get("exclude_zero_12")) excludeZero13 := parseBoolQuery(r.URL.Query().Get("exclude_zero_13")) rows, err := queries.GetStatementAgingBalanceList(r.Context(), params) if err != nil { http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError) return } rows = filterCustomerBalanceRowsForPDF(rows, excludeZero12, excludeZero13) summaries, detailsByMaster := buildCustomerBalancePDFData(rows) pdf := gofpdf.New("P", "mm", "A4", "") pdf.SetMargins(8, 8, 8) pdf.SetAutoPageBreak(false, 12) if err := registerDejavuFonts(pdf, "dejavu"); err != nil { http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError) return } if err := safeDrawCustomerBalancePDF( pdf, selectedDate, params.CariSearch, detailed, "Cari Yaslandirmali Ekstre", true, summaries, detailsByMaster, ); err != nil { pdf = gofpdf.New("P", "mm", "A4", "") pdf.SetMargins(8, 8, 8) pdf.SetAutoPageBreak(true, 12) if ferr := registerDejavuFonts(pdf, "dejavu"); ferr != nil { http.Error(w, "pdf font error: "+ferr.Error(), http.StatusInternalServerError) return } drawCustomerBalancePDFFallback(pdf, selectedDate, params.CariSearch, "Cari Yaslandirmali Ekstre", summaries, true) } if err := pdf.Error(); err != nil { http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError) return } var buf bytes.Buffer if err := pdf.Output(&buf); err != nil { http.Error(w, "pdf output error: "+err.Error(), http.StatusInternalServerError) return } filename := "account-aging-summary.pdf" if detailed { filename = "account-aging-detailed.pdf" } w.Header().Set("Content-Type", "application/pdf") w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=%q", filename)) _, _ = w.Write(buf.Bytes()) } }