Files
bssapp/svc/routes/statement_header_pdf.go

403 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routes
import (
"bssapp-backend/auth"
"bssapp-backend/models"
"bssapp-backend/queries"
"bytes"
"database/sql"
"fmt"
"log"
"net/http"
"runtime/debug"
"sort"
"strings"
"time"
"github.com/jung-kurt/gofpdf"
)
/* ============================ SABİTLER ============================ */
// A4 Landscape (mm)
const (
hPageWidth = 297.0
hPageHeight = 210.0
hMarginL = 8.0
hMarginT = 8.0
hMarginR = 8.0
hMarginB = 15.0
hLineHMain = 5.2
hCellPadX = 2.0
hHeaderRowH = 8.4
hGroupBarH = 9.0
hGridLineWidth = 0.3
hLogoW = 42.0
)
// Kolonlar
var hMainCols = []string{
"Belge No", "Tarih", "Vade", "İşlem",
"Açıklama", "Para", "Borç", "Alacak", "Bakiye",
}
var hMainWbase = []float64{
34, 30, 28, 24, 60, 20, 36, 36, 36,
}
// Font dosyaları
const (
hFontFamilyReg = "dejavu"
hFontFamilyBold = "dejavu"
)
// Renkler
var (
hColorPrimary = [3]int{149, 113, 22} // #957116
hColorSecondary = [3]int{218, 193, 151} // #dac197 (Baggi secondary)
)
/* ============================ FONT / FORMAT ============================ */
func hEnsureFonts(pdf *gofpdf.Fpdf) error {
return registerDejavuFonts(pdf, hFontFamilyReg, hFontFamilyBold)
}
func hNormalizeWidths(base []float64, targetTotal float64) []float64 {
sum := 0.0
for _, v := range base {
sum += v
}
scale := targetTotal / sum
out := make([]float64, len(base))
for i, v := range base {
out[i] = v * scale
}
return out
}
func hFormatCurrencyTR(val float64) string {
s := fmt.Sprintf("%.2f", val)
parts := strings.Split(s, ".")
intPart, decPart := parts[0], parts[1]
var out []string
for len(intPart) > 3 {
out = append([]string{intPart[len(intPart)-3:]}, out...)
intPart = intPart[:len(intPart)-3]
}
if intPart != "" {
out = append([]string{intPart}, out...)
}
return strings.Join(out, ".") + "," + decPart
}
func hNeedNewPage(pdf *gofpdf.Fpdf, needH float64) bool {
return pdf.GetY()+needH+hMarginB > hPageHeight
}
/* ============================ YARDIMCI FONKSİYONLAR ============================ */
func hSplitLinesSafe(pdf *gofpdf.Fpdf, text string, width float64) [][]byte {
if width <= 0 {
width = 1
}
return pdf.SplitLines([]byte(text), width)
}
func hDrawWrapText(pdf *gofpdf.Fpdf, text string, x, y, width, lineH float64) {
if text == "" {
return
}
lines := hSplitLinesSafe(pdf, text, width)
cy := y
for _, ln := range lines {
pdf.SetXY(x, cy)
pdf.CellFormat(width, lineH, string(ln), "", 0, "L", false, 0, "")
cy += lineH
}
}
func hCalcRowHeightForText(pdf *gofpdf.Fpdf, text string, colWidth, lineHeight, padX float64) float64 {
if text == "" {
return lineHeight
}
lines := hSplitLinesSafe(pdf, text, colWidth-(2*padX))
h := float64(len(lines)) * lineHeight
if h < lineHeight {
h = lineHeight
}
return h
}
/* ============================ HEADER ============================ */
func hDrawPageHeader(pdf *gofpdf.Fpdf, cariKod, cariIsim, start, end string) float64 {
if logoPath, err := resolvePdfImagePath("Baggi-Tekstil-A.s-Logolu.jpeg"); err == nil {
pdf.ImageOptions(logoPath, hMarginL, 2, hLogoW, 0, false, gofpdf.ImageOptions{}, 0, "")
}
// Başlıklar
pdf.SetFont(hFontFamilyBold, "", 16)
pdf.SetTextColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
pdf.SetXY(hMarginL+hLogoW+8, hMarginT+2)
pdf.CellFormat(120, 7, "Baggi Software System", "", 0, "L", false, 0, "")
pdf.SetFont(hFontFamilyBold, "", 12)
pdf.SetXY(hMarginL+hLogoW+8, hMarginT+10)
pdf.CellFormat(120, 6, "Cari Hesap Raporu", "", 0, "L", false, 0, "")
// Bugünün tarihi (sağ üst)
today := time.Now().Format("02.01.2006")
pdf.SetFont(hFontFamilyReg, "", 9)
pdf.SetXY(hPageWidth-hMarginR-40, hMarginT+3)
pdf.CellFormat(40, 6, "Tarih: "+today, "", 0, "R", false, 0, "")
// Cari & Tarih kutuları (daha yukarı taşındı)
boxY := hMarginT + hLogoW - 6
pdf.SetFont(hFontFamilyBold, "", 10)
pdf.Rect(hMarginL, boxY, 140, 11, "")
pdf.SetXY(hMarginL+2, boxY+3)
pdf.CellFormat(136, 5, fmt.Sprintf("Cari: %s — %s", cariKod, cariIsim), "", 0, "L", false, 0, "")
pdf.Rect(hPageWidth-hMarginR-140, boxY, 140, 11, "")
pdf.SetXY(hPageWidth-hMarginR-138, boxY+3)
pdf.CellFormat(136, 5, fmt.Sprintf("Tarih Aralığı: %s → %s", start, end), "", 0, "R", false, 0, "")
// Alt çizgi
y := boxY + 13
pdf.SetDrawColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
pdf.Line(hMarginL, y, hPageWidth-hMarginR, y)
pdf.SetDrawColor(200, 200, 200)
return y + 4
}
/* ============================ TABLO ============================ */
func hDrawGroupBar(pdf *gofpdf.Fpdf, currency string, sonBakiye float64) {
x := hMarginL
y := pdf.GetY()
w := hPageWidth - hMarginL - hMarginR
h := hGroupBarH
pdf.SetDrawColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
pdf.SetLineWidth(0.6)
pdf.Rect(x, y, w, h, "")
pdf.SetFont(hFontFamilyBold, "", 11.3)
pdf.SetTextColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
pdf.SetXY(x+hCellPadX+1.0, y+(h-5.0)/2)
pdf.CellFormat(w*0.6, 5.0, currency, "", 0, "L", false, 0, "")
txt := "Son Bakiye = " + hFormatCurrencyTR(sonBakiye)
pdf.SetXY(x+w*0.4, y+(h-5.0)/2)
pdf.CellFormat(w*0.6-2.0, 5.0, txt, "", 0, "R", false, 0, "")
pdf.SetLineWidth(hGridLineWidth)
pdf.SetDrawColor(200, 200, 200)
pdf.SetTextColor(0, 0, 0)
pdf.Ln(h)
}
func hDrawMainHeaderRow(pdf *gofpdf.Fpdf, cols []string, widths []float64) {
pdf.SetFont(hFontFamilyBold, "", 10.2)
pdf.SetTextColor(255, 255, 255)
pdf.SetFillColor(hColorPrimary[0], hColorPrimary[1], hColorPrimary[2])
pdf.SetDrawColor(120, 90, 20)
x := hMarginL
y := pdf.GetY()
for i, c := range cols {
pdf.Rect(x, y, widths[i], hHeaderRowH, "DF")
pdf.SetXY(x+hCellPadX, y+1.6)
pdf.CellFormat(widths[i]-2*hCellPadX, hHeaderRowH-3.2, c, "", 0, "C", false, 0, "")
x += widths[i]
}
pdf.Ln(hHeaderRowH)
}
func hDrawMainDataRow(pdf *gofpdf.Fpdf, row []string, widths []float64, rowH float64, rowIndex int) {
x := hMarginL
y := pdf.GetY()
// ✅ Zebra efekti
wTotal := hPageWidth - hMarginL - hMarginR
if rowIndex%2 == 1 {
pdf.SetFillColor(hColorSecondary[0], hColorSecondary[1], hColorSecondary[2])
pdf.Rect(x, y, wTotal, rowH, "F")
}
pdf.SetFont(hFontFamilyReg, "", 8.2)
pdf.SetDrawColor(242, 235, 222)
pdf.SetTextColor(30, 30, 30)
for i, val := range row {
pdf.Rect(x, y, widths[i], rowH, "")
switch {
case i == 4: // Açıklama wrap
hDrawWrapText(pdf, val, x+hCellPadX, y+0.5, widths[i]-2*hCellPadX, hLineHMain)
case i >= 6:
pdf.SetXY(x+hCellPadX, y+(rowH-hLineHMain)/2)
pdf.CellFormat(widths[i]-2*hCellPadX, hLineHMain, val, "", 0, "R", false, 0, "")
default:
pdf.SetXY(x+hCellPadX, y+(rowH-hLineHMain)/2)
pdf.CellFormat(widths[i]-2*hCellPadX, hLineHMain, val, "", 0, "L", false, 0, "")
}
x += widths[i]
}
// ❌ pdf.Ln(rowH) YOK
// ✅ Manuel olarak Y'yi arttırıyoruz
pdf.SetY(y + rowH)
}
/* ============================ MAIN HANDLER ============================ */
func ExportStatementHeaderReportPDFHandler(mssql *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
log.Printf("❌ PANIC ExportStatementHeaderReportPDFHandler: %v", rec)
debug.PrintStack()
http.Error(w, fmt.Sprintf("header PDF panic: %v", rec), http.StatusInternalServerError)
}
}()
claims, ok := auth.GetClaimsFromContext(r.Context())
if !ok || claims == nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
started := time.Now()
accountCode := r.URL.Query().Get("accountcode")
startDate := r.URL.Query().Get("startdate")
endDate := r.URL.Query().Get("enddate")
rawParis := r.URL.Query()["parislemler"]
var parislemler []string
for _, v := range rawParis {
v = strings.TrimSpace(v)
if v != "" && v != "undefined" && v != "null" {
parislemler = append(parislemler, v)
}
}
headers, _, err := queries.GetStatementsHPDF(accountCode, startDate, endDate, parislemler)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
type grp struct {
code string
rows []models.StatementHeader
sonBakiye float64
}
order := []string{}
groups := map[string]*grp{}
for _, h := range headers {
g, ok := groups[h.ParaBirimi]
if !ok {
g = &grp{code: h.ParaBirimi}
groups[h.ParaBirimi] = g
order = append(order, h.ParaBirimi)
}
g.rows = append(g.rows, h)
}
for _, k := range order {
sort.SliceStable(groups[k].rows, func(i, j int) bool {
ri, rj := groups[k].rows[i], groups[k].rows[j]
if ri.BelgeTarihi == rj.BelgeTarihi {
return ri.BelgeNo < rj.BelgeNo
}
return ri.BelgeTarihi < rj.BelgeTarihi
})
if n := len(groups[k].rows); n > 0 {
groups[k].sonBakiye = groups[k].rows[n-1].Bakiye
}
}
pdf := gofpdf.New("L", "mm", "A4", "")
pdf.SetMargins(hMarginL, hMarginT, hMarginR)
pdf.SetAutoPageBreak(false, hMarginB)
if err := hEnsureFonts(pdf); err != nil {
http.Error(w, "PDF font yükleme hatası: "+err.Error(), http.StatusInternalServerError)
return
}
wAvail := hPageWidth - hMarginL - hMarginR
mainWn := hNormalizeWidths(hMainWbase, wAvail)
cariIsim := ""
if len(headers) > 0 {
cariIsim = headers[0].CariIsim
}
pageNum := 0
newPage := func() {
pageNum++
pdf.AddPage()
tableTop := hDrawPageHeader(pdf, accountCode, cariIsim, startDate, endDate)
pdf.SetY(tableTop)
}
newPage()
for _, cur := range order {
g := groups[cur]
hDrawGroupBar(pdf, cur, g.sonBakiye)
hDrawMainHeaderRow(pdf, hMainCols, mainWn)
rowIndex := 0
for _, h := range g.rows {
row := []string{
h.BelgeNo, h.BelgeTarihi, h.VadeTarihi, h.IslemTipi,
h.Aciklama, h.ParaBirimi,
hFormatCurrencyTR(h.Borc),
hFormatCurrencyTR(h.Alacak),
hFormatCurrencyTR(h.Bakiye),
}
rh := hCalcRowHeightForText(pdf, row[4], mainWn[4], hLineHMain, hCellPadX)
if hNeedNewPage(pdf, rh+hHeaderRowH) {
newPage()
hDrawGroupBar(pdf, cur, g.sonBakiye)
hDrawMainHeaderRow(pdf, hMainCols, mainWn)
}
hDrawMainDataRow(pdf, row, mainWn, rh, rowIndex)
rowIndex++
}
pdf.Ln(1)
}
if err := pdf.Error(); err != nil {
http.Error(w, "PDF render hatası: "+err.Error(), http.StatusInternalServerError)
return
}
var buf bytes.Buffer
if err := pdf.Output(&buf); err != nil {
http.Error(w, "PDF oluşturulamadı: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Disposition", "inline; filename=statement-header-report.pdf")
_, _ = w.Write(buf.Bytes())
log.Printf("✅ Header-only PDF üretimi tamamlandı (%s)", time.Since(started))
}
}