Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-03-06 13:59:17 +03:00
parent 46f4d15ac7
commit 807bbad0e7
6 changed files with 340 additions and 64 deletions

View File

@@ -453,6 +453,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
"finance", "export",
wrapV3(routes.ExportStatementAgingPDFHandler(mssql)),
)
bindV3(r, pgDB,
"/api/finance/account-aging-statement/export-screen-pdf", "GET",
"finance", "export",
wrapV3(routes.ExportStatementAgingScreenPDFHandler(mssql)),
)
bindV3(r, pgDB,
"/api/finance/account-aging-statement/export-excel", "GET",
"finance", "export",

View File

@@ -85,6 +85,7 @@ func ExportCustomerBalancePDFHandler(_ *sql.DB) http.HandlerFunc {
selectedDate,
params.CariSearch,
detailed,
"Cari Bakiye Listesi",
summaries,
detailsByMaster,
)
@@ -233,6 +234,7 @@ func drawCustomerBalancePDF(
selectedDate string,
searchText string,
detailed bool,
reportTitle string,
summaries []balanceSummaryPDF,
detailsByMaster map[string][]models.CustomerBalanceListRow,
) {
@@ -251,7 +253,11 @@ func drawCustomerBalancePDF(
pdf.SetFont("dejavu", "B", 15)
pdf.SetTextColor(149, 113, 22)
pdf.SetXY(marginL, marginT)
pdf.CellFormat(120, 7, "Cari Bakiye Listesi", "", 0, "L", false, 0, "")
title := strings.TrimSpace(reportTitle)
if title == "" {
title = "Cari Bakiye Listesi"
}
pdf.CellFormat(120, 7, title, "", 0, "L", false, 0, "")
pdf.SetFont("dejavu", "", 9)
pdf.SetTextColor(20, 20, 20)

View File

@@ -77,6 +77,7 @@ func ExportStatementAgingPDFHandler(_ *sql.DB) http.HandlerFunc {
selectedDate,
params.CariSearch,
detailed,
"Cari Yaslandirmali Ekstre",
summaries,
detailsByMaster,
)

View File

@@ -0,0 +1,302 @@
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
}