package routes import ( "bssapp-backend/auth" "bssapp-backend/models" "bssapp-backend/queries" "bytes" "database/sql" "fmt" "net/http" "strconv" "strings" "time" "github.com/jung-kurt/gofpdf" ) type agingScreenPDFRow struct { Cari8 string CariDetay string FaturaCari string OdemeCari string FaturaRef string OdemeRef string FaturaTarihi string OdemeTarihi string OdemeDocDate string EslesenTutar float64 UsdTutar float64 CurrencyTryRate float64 GunSayisi float64 GunSayisiDocDate float64 Aciklama string DocCurrencyCode string } func ExportStatementAgingScreenPDFHandler(_ *sql.DB) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { 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.StatementAgingParams{ AccountCode: strings.TrimSpace(r.URL.Query().Get("accountcode")), EndDate: selectedDate, Parislemler: r.URL.Query()["parislemler"], } if err := queries.RebuildStatementAgingCache(r.Context()); err != nil { http.Error(w, "Error rebuilding aging cache: "+err.Error(), http.StatusInternalServerError) return } rawRows, err := queries.GetStatementAging(params) if err != nil { http.Error(w, "Error fetching aging statement: "+err.Error(), http.StatusInternalServerError) return } rows := make([]agingScreenPDFRow, 0, len(rawRows)) for _, r := range rawRows { rows = append(rows, agingScreenPDFRow{ Cari8: pickString(r, "Cari8", "cari8"), CariDetay: pickString(r, "CariDetay", "cari_detay"), FaturaCari: pickString(r, "FaturaCari", "fatura_cari"), OdemeCari: pickString(r, "OdemeCari", "odeme_cari"), FaturaRef: pickString(r, "FaturaRef", "fatura_ref"), OdemeRef: pickString(r, "OdemeRef", "odeme_ref"), FaturaTarihi: pickString(r, "FaturaTarihi", "fatura_tarihi"), OdemeTarihi: pickString(r, "OdemeTarihi", "odeme_tarihi"), OdemeDocDate: pickString(r, "OdemeDocDate", "odeme_doc_date"), EslesenTutar: pickFloat(r, "EslesenTutar", "eslesen_tutar"), UsdTutar: pickFloat(r, "UsdTutar", "usd_tutar"), CurrencyTryRate: pickFloat(r, "CurrencyTryRate", "currency_try_rate"), GunSayisi: pickFloat(r, "GunSayisi", "gun_sayisi"), GunSayisiDocDate: pickFloat(r, "GunSayisi_DocDate", "gun_sayisi_docdate"), Aciklama: pickString(r, "Aciklama", "aciklama"), DocCurrencyCode: pickString(r, "DocCurrencyCode", "doc_currency_code"), }) } pdf := gofpdf.New("L", "mm", "A3", "") pdf.SetMargins(8, 8, 8) pdf.SetAutoPageBreak(false, 10) if err := registerDejavuFonts(pdf, "dejavu"); err != nil { http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError) return } drawStatementAgingScreenPDF(pdf, selectedDate, params.AccountCode, rows) 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 } w.Header().Set("Content-Type", "application/pdf") w.Header().Set("Content-Disposition", `inline; filename="account-aging-screen.pdf"`) _, _ = w.Write(buf.Bytes()) } } func drawStatementAgingScreenPDF(pdf *gofpdf.Fpdf, selectedDate, accountCode string, rows []agingScreenPDFRow) { pageW, _ := pdf.GetPageSize() marginL, marginT, marginR, marginB := 8.0, 8.0, 8.0, 10.0 tableW := pageW - marginL - marginR cols := []string{ "Ana Cari", "Ana Cari Detay", "Fatura Cari", "Odeme Cari", "Fatura Ref", "Odeme Ref", "Fatura Tarihi", "Odeme Vade", "Odeme DocDate", "Eslesen Tutar", "USD Tutar", "Kur", "Gun", "Gun (DocDate)", "Aciklama", "Doviz", } widths := normalizeWidths([]float64{ 18, 34, 18, 18, 22, 22, 16, 16, 18, 19, 16, 12, 10, 13, 28, 11, }, tableW) header := func() { pdf.AddPage() pdf.SetFont("dejavu", "B", 14) pdf.SetTextColor(149, 113, 22) pdf.SetXY(marginL, marginT) pdf.CellFormat(150, 6, "Cari Yaslandirmali Ekstre", "", 0, "L", false, 0, "") pdf.SetFont("dejavu", "", 8.5) pdf.SetTextColor(30, 30, 30) pdf.SetXY(pageW-marginR-90, marginT+0.5) pdf.CellFormat(90, 4.8, "Son Tarih: "+selectedDate, "", 0, "R", false, 0, "") if strings.TrimSpace(accountCode) != "" { pdf.SetXY(pageW-marginR-90, marginT+5) pdf.CellFormat(90, 4.8, "Cari: "+accountCode, "", 0, "R", false, 0, "") } pdf.SetDrawColor(149, 113, 22) pdf.Line(marginL, marginT+10.5, pageW-marginR, marginT+10.5) pdf.SetDrawColor(200, 200, 200) pdf.SetY(marginT + 13) pdf.SetFont("dejavu", "B", 7.2) pdf.SetTextColor(255, 255, 255) pdf.SetFillColor(149, 113, 22) y := pdf.GetY() x := marginL for i, c := range cols { pdf.Rect(x, y, widths[i], 6.2, "DF") pdf.SetXY(x+0.8, y+1) pdf.CellFormat(widths[i]-1.6, 4.2, c, "", 0, "C", false, 0, "") x += widths[i] } pdf.SetY(y + 6.2) } needPage := func(needH float64) bool { return pdf.GetY()+needH+marginB > 297.0 } header() pdf.SetFont("dejavu", "", 6.8) pdf.SetTextColor(25, 25, 25) for _, r := range rows { if needPage(5.5) { header() pdf.SetFont("dejavu", "", 6.8) pdf.SetTextColor(25, 25, 25) } line := []string{ r.Cari8, r.CariDetay, r.FaturaCari, r.OdemeCari, r.FaturaRef, r.OdemeRef, r.FaturaTarihi, r.OdemeTarihi, r.OdemeDocDate, formatMoneyPDF(r.EslesenTutar), formatMoneyPDF(r.UsdTutar), formatMoneyPDF(r.CurrencyTryRate), fmt.Sprintf("%.0f", r.GunSayisi), fmt.Sprintf("%.0f", r.GunSayisiDocDate), r.Aciklama, r.DocCurrencyCode, } y := pdf.GetY() x := marginL for i, v := range line { pdf.Rect(x, y, widths[i], 5.5, "") align := "L" if i >= 9 && i <= 11 { align = "R" } if i == 12 || i == 13 { align = "C" } pdf.SetXY(x+0.8, y+0.8) pdf.CellFormat(widths[i]-1.6, 3.8, v, "", 0, align, false, 0, "") x += widths[i] } pdf.SetY(y + 5.5) } } func pickString(m map[string]interface{}, keys ...string) string { for _, k := range keys { if v, ok := m[k]; ok { return strings.TrimSpace(toStringValue(v)) } } return "" } func pickFloat(m map[string]interface{}, keys ...string) float64 { for _, k := range keys { if v, ok := m[k]; ok { return toFloat64Value(v) } } return 0 } func toStringValue(v interface{}) string { switch x := v.(type) { case nil: return "" case string: return x case []byte: return string(x) default: return fmt.Sprint(x) } } func toFloat64Value(v interface{}) float64 { switch x := v.(type) { case nil: return 0 case float64: return x case float32: return float64(x) case int: return float64(x) case int64: return float64(x) case int32: return float64(x) case string: return parseFloatValue(x) case []byte: return parseFloatValue(string(x)) default: return parseFloatValue(fmt.Sprint(x)) } } func parseFloatValue(s string) float64 { s = strings.TrimSpace(s) if s == "" { return 0 } hasComma := strings.Contains(s, ",") hasDot := strings.Contains(s, ".") if hasComma && hasDot { if strings.LastIndex(s, ",") > strings.LastIndex(s, ".") { s = strings.ReplaceAll(s, ".", "") s = strings.Replace(s, ",", ".", 1) } else { s = strings.ReplaceAll(s, ",", "") } } else if hasComma { s = strings.ReplaceAll(s, ".", "") s = strings.Replace(s, ",", ".", 1) } n, err := strconv.ParseFloat(s, 64) if err != nil { return 0 } return n }