From 8a8f3849278e7cdaf660f9e1955f318f6c1fff7a Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Tue, 17 Mar 2026 10:59:10 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- svc/routes/customer_balance_pdf.go | 72 ++++ svc/routes/statement_aging_screen_pdf.go | 399 +++++++++++++++++++---- 2 files changed, 411 insertions(+), 60 deletions(-) diff --git a/svc/routes/customer_balance_pdf.go b/svc/routes/customer_balance_pdf.go index ffa81e0..ceace52 100644 --- a/svc/routes/customer_balance_pdf.go +++ b/svc/routes/customer_balance_pdf.go @@ -340,6 +340,78 @@ func drawCustomerBalancePDF( pdf.SetFont("dejavu", "", 7.2) pdf.SetTextColor(20, 20, 20) + totalUSD12, totalTRY12 := 0.0, 0.0 + totalUSD13, totalTRY13 := 0.0, 0.0 + totalPrBr12 := map[string]float64{} + totalPrBr13 := map[string]float64{} + totalVadeBase, totalVadeSum, totalVadeBelgeSum := 0.0, 0.0, 0.0 + for _, s := range summaries { + totalUSD12 += s.USDBakiye12 + totalTRY12 += s.TLBakiye12 + totalUSD13 += s.USDBakiye13 + totalTRY13 += s.TLBakiye13 + for k, v := range s.Bakiye12Map { + totalPrBr12[k] += v + } + for k, v := range s.Bakiye13Map { + totalPrBr13[k] += v + } + w := absFloatExcel(s.USDBakiye12) + absFloatExcel(s.TLBakiye12) + absFloatExcel(s.USDBakiye13) + absFloatExcel(s.TLBakiye13) + if w > 0 { + totalVadeBase += w + totalVadeSum += s.VadeGun * w + totalVadeBelgeSum += s.VadeBelge * w + } + } + + totalsRow := []string{ + "TOPLAM", + "", + "", + "", + "", + formatCurrencyMapPDF(totalPrBr12), + formatCurrencyMapPDF(totalPrBr13), + formatMoneyPDF(totalUSD12), + formatMoneyPDF(totalTRY12), + formatMoneyPDF(totalUSD13), + formatMoneyPDF(totalTRY13), + } + if includeVadeColumns { + totalVade, totalVadeBelge := 0.0, 0.0 + if totalVadeBase > 0 { + totalVade = totalVadeSum / totalVadeBase + totalVadeBelge = totalVadeBelgeSum / totalVadeBase + } + totalsRow = append(totalsRow, formatMoneyPDF(totalVade), formatMoneyPDF(totalVadeBelge)) + } + + totalH := calcPDFRowHeight(pdf, totalsRow, summaryW, map[int]bool{0: true, 1: true, 2: true, 3: true}, 6.2, 3.6) + if needPage(totalH) { + header() + drawSummaryHeader() + } + pdf.SetFont("dejavu", "B", 7.2) + pdf.SetFillColor(218, 193, 151) + pdf.SetTextColor(20, 20, 20) + totalY := pdf.GetY() + totalX := marginL + for i, v := range totalsRow { + pdf.Rect(totalX, totalY, summaryW[i], totalH, "FD") + align := "L" + if i >= 7 { + align = "R" + } + if includeVadeColumns && (i == len(totalsRow)-1 || i == len(totalsRow)-2) { + align = "C" + } + drawPDFCellWrapped(pdf, v, totalX, totalY, summaryW[i], totalH, align, 3.6) + totalX += summaryW[i] + } + pdf.SetY(totalY + totalH) + pdf.SetFont("dejavu", "", 7.2) + pdf.SetTextColor(20, 20, 20) + for _, s := range summaries { row := []string{ s.AnaCariKodu, diff --git a/svc/routes/statement_aging_screen_pdf.go b/svc/routes/statement_aging_screen_pdf.go index d04e48e..f5dc1b7 100644 --- a/svc/routes/statement_aging_screen_pdf.go +++ b/svc/routes/statement_aging_screen_pdf.go @@ -8,6 +8,7 @@ import ( "database/sql" "fmt" "net/http" + "sort" "strconv" "strings" "time" @@ -27,7 +28,7 @@ type agingScreenPDFRow struct { OdemeDocDate string EslesenTutar float64 UsdTutar float64 - CurrencyTryRate float64 + CurrencyUsdRate float64 GunSayisi float64 GunSayisiDocDate float64 Aciklama string @@ -81,7 +82,7 @@ func ExportStatementAgingScreenPDFHandler(_ *sql.DB) http.HandlerFunc { 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"), + CurrencyUsdRate: pickFloat(r, "CurrencyUsdRate", "currency_usd_rate", "CurrencyTryRate", "currency_try_rate"), GunSayisi: pickFloat(r, "GunSayisi", "gun_sayisi"), GunSayisiDocDate: pickFloat(r, "GunSayisi_DocDate", "gun_sayisi_docdate"), Aciklama: pickString(r, "Aciklama", "aciklama"), @@ -116,21 +117,54 @@ func ExportStatementAgingScreenPDFHandler(_ *sql.DB) http.HandlerFunc { } } +type agingScreenMasterPDF struct { + GroupKey string + Cari8 string + CariDetay string + SatirSayisi int + ToplamUSD float64 + NormalUSD float64 + AcikKalemUSD float64 + KurWeightedBase float64 + KurWeightedSum float64 + KurFallback float64 + WeightedBase float64 + WeightedGunSum float64 + WeightedGunDocSum float64 +} + +type agingScreenCurrencyPDF struct { + GroupKey string + MasterKey string + DocCurrencyCode string + SatirSayisi int + ToplamTutar float64 + ToplamUSD float64 + NormalTutar float64 + AcikKalemTutar float64 + KurWeightedBase float64 + KurWeightedSum float64 + KurFallback float64 + WeightedBase float64 + WeightedGunSum float64 + WeightedGunDocSum float64 +} + func drawStatementAgingScreenPDF(pdf *gofpdf.Fpdf, selectedDate, accountCode string, rows []agingScreenPDFRow) { - pageW, _ := pdf.GetPageSize() + masters, currenciesByMaster, detailsByCurrency := buildStatementAgingScreenPDFData(rows) + + pageW, pageH := 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) + masterCols := []string{"Ana Cari Kod", "Ana Cari Detay", "Satir", "Toplam USD", "Normal USD", "Acik Kalem USD", "Ort. Gun", "Ort. Gun (DocDate)", "Kur"} + masterW := normalizeWidths([]float64{22, 40, 12, 18, 18, 20, 14, 18, 12}, tableW) + + currencyCols := []string{"Doviz", "Satir", "Toplam Tutar", "Toplam USD", "Normal", "Acik Kalem", "Kur", "Ort. Gun", "Ort. Gun (DocDate)"} + currencyW := normalizeWidths([]float64{16, 12, 20, 18, 18, 18, 12, 14, 18}, tableW) + + detailCols := []string{"Fatura Cari", "Odeme Cari", "Fatura Ref", "Odeme Ref", "Fatura Tarihi", "Odeme Vade", "Odeme DocDate", "Eslesen Tutar", "USD Tutar", "Kur", "Gun", "Gun (DocDate)", "Aciklama", "Doviz"} + detailW := normalizeWidths([]float64{18, 18, 22, 22, 16, 16, 18, 18, 16, 12, 10, 13, 30, 11}, tableW) header := func() { pdf.AddPage() @@ -142,7 +176,7 @@ func drawStatementAgingScreenPDF(pdf *gofpdf.Fpdf, selectedDate, accountCode str 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, "") + pdf.CellFormat(90, 4.8, "Son Tarih: "+formatDateTR(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, "") @@ -152,74 +186,319 @@ func drawStatementAgingScreenPDF(pdf *gofpdf.Fpdf, selectedDate, accountCode str 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 + return pdf.GetY()+needH+marginB > pageH + } + + drawHeaderRow := func(cols []string, widths []float64, h float64, r, g, b int, fontSize float64) { + pdf.SetFont("dejavu", "B", fontSize) + pdf.SetTextColor(255, 255, 255) + pdf.SetFillColor(r, g, b) + y := pdf.GetY() + x := marginL + for i, c := range cols { + pdf.Rect(x, y, widths[i], h, "DF") + pdf.SetXY(x+0.8, y+0.9) + pdf.CellFormat(widths[i]-1.6, h-1.8, c, "", 0, "C", false, 0, "") + x += widths[i] + } + pdf.SetY(y + h) } header() - pdf.SetFont("dejavu", "", 6.8) + drawHeaderRow(masterCols, masterW, 6.2, 149, 113, 22, 7.2) + pdf.SetFont("dejavu", "", 7) pdf.SetTextColor(25, 25, 25) - for _, r := range rows { - if needPage(5.5) { - header() - pdf.SetFont("dejavu", "", 6.8) - pdf.SetTextColor(25, 25, 25) + for _, m := range masters { + masterLine := []string{ + m.Cari8, + m.CariDetay, + strconv.Itoa(m.SatirSayisi), + formatMoneyPDF(m.ToplamUSD), + formatMoneyPDF(m.NormalUSD), + formatMoneyPDF(m.AcikKalemUSD), + fmt.Sprintf("%.0f", statementAgingAvg(m.WeightedGunSum, m.WeightedBase)), + fmt.Sprintf("%.0f", statementAgingAvg(m.WeightedGunDocSum, m.WeightedBase)), + formatMoneyPDF(statementAgingAvg(m.KurWeightedSum, m.KurWeightedBase, m.KurFallback)), } - - 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, + rowH := calcPDFRowHeight(pdf, masterLine, masterW, map[int]bool{1: true}, 5.8, 3.3) + if needPage(rowH) { + header() + drawHeaderRow(masterCols, masterW, 6.2, 149, 113, 22, 7.2) + pdf.SetFont("dejavu", "", 7) + pdf.SetTextColor(25, 25, 25) } y := pdf.GetY() x := marginL - for i, v := range line { - pdf.Rect(x, y, widths[i], 5.5, "") + for i, v := range masterLine { + pdf.Rect(x, y, masterW[i], rowH, "") align := "L" - if i >= 9 && i <= 11 { + if i >= 2 { align = "R" } - if i == 12 || i == 13 { + if i == 6 || i == 7 { 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] + drawPDFCellWrapped(pdf, v, x, y, masterW[i], rowH, align, 3.3) + x += masterW[i] } - pdf.SetY(y + 5.5) + pdf.SetY(y + rowH) + + for _, c := range currenciesByMaster[m.GroupKey] { + if needPage(11.2) { + header() + drawHeaderRow(masterCols, masterW, 6.2, 149, 113, 22, 7.2) + pdf.SetFont("dejavu", "", 7) + pdf.SetTextColor(25, 25, 25) + } + + pdf.SetFont("dejavu", "B", 7) + drawHeaderRow(currencyCols, currencyW, 5.6, 76, 95, 122, 6.8) + + pdf.SetFont("dejavu", "", 6.8) + pdf.SetTextColor(35, 35, 35) + currencyLine := []string{ + c.DocCurrencyCode, + strconv.Itoa(c.SatirSayisi), + formatMoneyPDF(c.ToplamTutar), + formatMoneyPDF(c.ToplamUSD), + formatMoneyPDF(c.NormalTutar), + formatMoneyPDF(c.AcikKalemTutar), + formatMoneyPDF(statementAgingAvg(c.KurWeightedSum, c.KurWeightedBase, c.KurFallback)), + fmt.Sprintf("%.0f", statementAgingAvg(c.WeightedGunSum, c.WeightedBase)), + fmt.Sprintf("%.0f", statementAgingAvg(c.WeightedGunDocSum, c.WeightedBase)), + } + + cRowH := 5.4 + y := pdf.GetY() + x := marginL + for i, v := range currencyLine { + pdf.Rect(x, y, currencyW[i], cRowH, "") + align := "R" + if i == 0 { + align = "L" + } + if i == 7 || i == 8 { + align = "C" + } + drawPDFCellWrapped(pdf, v, x, y, currencyW[i], cRowH, align, 3.2) + x += currencyW[i] + } + pdf.SetY(y + cRowH) + + drawHeaderRow(detailCols, detailW, 5.6, 31, 59, 91, 6.8) + pdf.SetFont("dejavu", "", 6.6) + pdf.SetTextColor(30, 30, 30) + + for _, d := range detailsByCurrency[c.GroupKey] { + line := []string{ + d.FaturaCari, + d.OdemeCari, + d.FaturaRef, + d.OdemeRef, + formatDateTR(d.FaturaTarihi), + formatDateTR(d.OdemeTarihi), + formatDateTR(d.OdemeDocDate), + formatMoneyPDF(d.EslesenTutar), + formatMoneyPDF(d.UsdTutar), + formatMoneyPDF(d.CurrencyUsdRate), + fmt.Sprintf("%.0f", d.GunSayisi), + fmt.Sprintf("%.0f", d.GunSayisiDocDate), + d.Aciklama, + d.DocCurrencyCode, + } + rowH := calcPDFRowHeight(pdf, line, detailW, map[int]bool{0: true, 1: true, 2: true, 3: true, 12: true}, 5.4, 3.1) + if needPage(rowH) { + header() + drawHeaderRow(masterCols, masterW, 6.2, 149, 113, 22, 7.2) + pdf.SetFont("dejavu", "B", 7) + drawHeaderRow(currencyCols, currencyW, 5.6, 76, 95, 122, 6.8) + pdf.SetFont("dejavu", "", 6.8) + pdf.SetTextColor(35, 35, 35) + y = pdf.GetY() + x = marginL + for i, v := range currencyLine { + pdf.Rect(x, y, currencyW[i], cRowH, "") + align := "R" + if i == 0 { + align = "L" + } + if i == 7 || i == 8 { + align = "C" + } + drawPDFCellWrapped(pdf, v, x, y, currencyW[i], cRowH, align, 3.2) + x += currencyW[i] + } + pdf.SetY(y + cRowH) + drawHeaderRow(detailCols, detailW, 5.6, 31, 59, 91, 6.8) + pdf.SetFont("dejavu", "", 6.6) + pdf.SetTextColor(30, 30, 30) + } + + rowY := pdf.GetY() + rowX := marginL + for i, v := range line { + pdf.Rect(rowX, rowY, detailW[i], rowH, "") + align := "L" + if i >= 7 && i <= 9 { + align = "R" + } + if i == 10 || i == 11 { + align = "C" + } + drawPDFCellWrapped(pdf, v, rowX, rowY, detailW[i], rowH, align, 3.1) + rowX += detailW[i] + } + pdf.SetY(rowY + rowH) + } + pdf.Ln(1) + } + pdf.Ln(1.2) } } +func statementAgingAvg(sum, base float64, fallback ...float64) float64 { + if base > 0 { + return sum / base + } + if len(fallback) > 0 { + return fallback[0] + } + return 0 +} + +func buildStatementAgingScreenPDFData(rows []agingScreenPDFRow) ([]agingScreenMasterPDF, map[string][]agingScreenCurrencyPDF, map[string][]agingScreenPDFRow) { + masterMap := map[string]*agingScreenMasterPDF{} + currencyMap := map[string]*agingScreenCurrencyPDF{} + detailsByCurrency := map[string][]agingScreenPDFRow{} + + for _, row := range rows { + masterKey := strings.TrimSpace(row.Cari8) + if masterKey == "" { + continue + } + curr := strings.ToUpper(strings.TrimSpace(row.DocCurrencyCode)) + if curr == "" { + curr = "N/A" + } + currencyKey := masterKey + "|" + curr + aciklama := strings.ToUpper(strings.TrimSpace(row.Aciklama)) + absUsd := absFloatExcel(row.UsdTutar) + + m := masterMap[masterKey] + if m == nil { + m = &agingScreenMasterPDF{ + GroupKey: masterKey, + Cari8: masterKey, + CariDetay: strings.TrimSpace(row.CariDetay), + } + masterMap[masterKey] = m + } + if m.CariDetay == "" { + m.CariDetay = strings.TrimSpace(row.CariDetay) + } + + c := currencyMap[currencyKey] + if c == nil { + c = &agingScreenCurrencyPDF{ + GroupKey: currencyKey, + MasterKey: masterKey, + DocCurrencyCode: curr, + } + currencyMap[currencyKey] = c + } + + m.SatirSayisi++ + m.ToplamUSD += row.UsdTutar + if aciklama == "ACIKKALEM" { + m.AcikKalemUSD += row.UsdTutar + } else { + m.NormalUSD += row.UsdTutar + } + if absUsd > 0 { + m.WeightedBase += absUsd + m.WeightedGunSum += absUsd * row.GunSayisi + m.WeightedGunDocSum += absUsd * row.GunSayisiDocDate + if row.CurrencyUsdRate > 0 { + m.KurWeightedBase += absUsd + m.KurWeightedSum += absUsd * row.CurrencyUsdRate + } + } + if row.CurrencyUsdRate > 0 { + m.KurFallback = row.CurrencyUsdRate + } + + c.SatirSayisi++ + c.ToplamTutar += row.EslesenTutar + c.ToplamUSD += row.UsdTutar + if aciklama == "ACIKKALEM" { + c.AcikKalemTutar += row.EslesenTutar + } else { + c.NormalTutar += row.EslesenTutar + } + if absUsd > 0 { + c.WeightedBase += absUsd + c.WeightedGunSum += absUsd * row.GunSayisi + c.WeightedGunDocSum += absUsd * row.GunSayisiDocDate + if row.CurrencyUsdRate > 0 { + c.KurWeightedBase += absUsd + c.KurWeightedSum += absUsd * row.CurrencyUsdRate + } + } + if row.CurrencyUsdRate > 0 { + c.KurFallback = row.CurrencyUsdRate + } + + detailsByCurrency[currencyKey] = append(detailsByCurrency[currencyKey], row) + } + + masterKeys := make([]string, 0, len(masterMap)) + for k := range masterMap { + masterKeys = append(masterKeys, k) + } + sort.SliceStable(masterKeys, func(i, j int) bool { + return strings.ToUpper(masterKeys[i]) < strings.ToUpper(masterKeys[j]) + }) + + masters := make([]agingScreenMasterPDF, 0, len(masterKeys)) + currenciesByMaster := make(map[string][]agingScreenCurrencyPDF, len(masterKeys)) + for _, mk := range masterKeys { + masters = append(masters, *masterMap[mk]) + } + + for _, c := range currencyMap { + currenciesByMaster[c.MasterKey] = append(currenciesByMaster[c.MasterKey], *c) + } + for mk := range currenciesByMaster { + sort.SliceStable(currenciesByMaster[mk], func(i, j int) bool { + return strings.ToUpper(currenciesByMaster[mk][i].DocCurrencyCode) < strings.ToUpper(currenciesByMaster[mk][j].DocCurrencyCode) + }) + } + + for k := range detailsByCurrency { + sort.SliceStable(detailsByCurrency[k], func(i, j int) bool { + a := detailsByCurrency[k][i] + b := detailsByCurrency[k][j] + if strings.TrimSpace(a.FaturaCari) == strings.TrimSpace(b.FaturaCari) { + if strings.TrimSpace(a.OdemeCari) == strings.TrimSpace(b.OdemeCari) { + if strings.TrimSpace(a.FaturaRef) == strings.TrimSpace(b.FaturaRef) { + return strings.TrimSpace(a.OdemeRef) < strings.TrimSpace(b.OdemeRef) + } + return strings.TrimSpace(a.FaturaRef) < strings.TrimSpace(b.FaturaRef) + } + return strings.TrimSpace(a.OdemeCari) < strings.TrimSpace(b.OdemeCari) + } + return strings.TrimSpace(a.FaturaCari) < strings.TrimSpace(b.FaturaCari) + }) + } + + return masters, currenciesByMaster, detailsByCurrency +} + func pickString(m map[string]interface{}, keys ...string) string { for _, k := range keys { if v, ok := m[k]; ok {