ilk
This commit is contained in:
642
svc/routes/statements_pdf.go
Normal file
642
svc/routes/statements_pdf.go
Normal file
@@ -0,0 +1,642 @@
|
||||
// routes/statements_pdf.go
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/auth"
|
||||
"bssapp-backend/models"
|
||||
"bssapp-backend/queries"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
)
|
||||
|
||||
/* ============================ SABİTLER ============================ */
|
||||
|
||||
// A4 Landscape (mm)
|
||||
const (
|
||||
pageWidth = 297.0
|
||||
pageHeight = 210.0
|
||||
|
||||
// Kenar boşlukları (mm)
|
||||
marginL = 8.0
|
||||
marginT = 8.0
|
||||
marginR = 8.0
|
||||
marginB = 15.0
|
||||
|
||||
// Satır aralıkları (mm)
|
||||
lineHMain = 6.0 // ana satır biraz iri
|
||||
lineHDetail = 5.2
|
||||
cellPadX = 2.2
|
||||
|
||||
// Satır yükseklikleri (mm)
|
||||
headerRowH = 8.4
|
||||
subHeaderRowH = 6.2
|
||||
groupBarH = 9.0
|
||||
|
||||
// Çizgi kalınlığı
|
||||
gridLineWidth = 0.3
|
||||
|
||||
// Logo genişliği (yükseklik oranlanır)
|
||||
logoW = 42.0
|
||||
)
|
||||
|
||||
// Ana tablo kolonları
|
||||
var mainCols = []string{
|
||||
"Belge No", "Tarih", "Vade", "İşlem",
|
||||
"Açıklama", "Para", "Borç", "Alacak", "Bakiye",
|
||||
}
|
||||
|
||||
// Ana tablo kolon genişlikleri (ilk 3 geniş)
|
||||
var mainWbase = []float64{
|
||||
34, // Belge No
|
||||
30, // Tarih
|
||||
28, // Vade
|
||||
24, // İşlem
|
||||
60, // Açıklama (biraz daraltıldı)
|
||||
20, // Para
|
||||
36, // Borç (genişletildi)
|
||||
36, // Alacak (genişletildi)
|
||||
36, // Bakiye (genişletildi)
|
||||
}
|
||||
|
||||
// Detay tablo kolonları ve genişlikleri
|
||||
var dCols = []string{
|
||||
"Ana Grup", "Alt Grup", "Garson", "Fit", "İçerik",
|
||||
"Ürün", "Renk", "Adet", "Fiyat", "Tutar",
|
||||
}
|
||||
var dWbase = []float64{
|
||||
30, 28, 22, 20, 56, 30, 22, 20, 20, 26}
|
||||
|
||||
// Font dosyaları
|
||||
const (
|
||||
fontFamilyReg = "dejavu"
|
||||
fontFamilyBold = "dejavu-b"
|
||||
fontPathReg = "fonts/DejaVuSans.ttf"
|
||||
fontPathBold = "fonts/DejaVuSans-Bold.ttf"
|
||||
)
|
||||
|
||||
// Kurumsal renkler
|
||||
var (
|
||||
colorPrimary = [3]int{149, 113, 22} // #957116 (altın)
|
||||
colorSecondary = [3]int{218, 193, 151} // #dac197 (bej)
|
||||
colorDetailFill = [3]int{242, 235, 222} // secondary’den daha açık (detay satır zemin)
|
||||
)
|
||||
|
||||
/* ============================ HELPERS ============================ */
|
||||
|
||||
// Genişlikleri normalize et
|
||||
func normalizeWidths(base []float64, targetTotal float64) []float64 {
|
||||
sum := 0.0
|
||||
for _, v := range base {
|
||||
sum += v
|
||||
}
|
||||
out := make([]float64, len(base))
|
||||
if sum <= 0 {
|
||||
each := targetTotal / float64(len(base))
|
||||
for i := range out {
|
||||
out[i] = each
|
||||
}
|
||||
return out
|
||||
}
|
||||
scale := targetTotal / sum
|
||||
for i, v := range base {
|
||||
out[i] = v * scale
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Türkçe formatlı sayı
|
||||
func formatCurrencyTR(n float64) string {
|
||||
s := fmt.Sprintf("%.2f", n) // "864000.00"
|
||||
parts := strings.Split(s, ".")
|
||||
intPart := parts[0]
|
||||
decPart := parts[1]
|
||||
|
||||
// Binlik ayırıcı (.)
|
||||
var out strings.Builder
|
||||
for i, c := range intPart {
|
||||
if (len(intPart)-i)%3 == 0 && i != 0 {
|
||||
out.WriteRune('.')
|
||||
}
|
||||
out.WriteRune(c)
|
||||
}
|
||||
|
||||
return out.String() + "," + decPart
|
||||
}
|
||||
|
||||
// Fontları yükle
|
||||
func ensureFonts(pdf *gofpdf.Fpdf) {
|
||||
if _, err := os.Stat(fontPathReg); err == nil {
|
||||
pdf.AddUTF8Font(fontFamilyReg, "", fontPathReg)
|
||||
} else {
|
||||
log.Printf("⚠️ Font bulunamadı: %s", fontPathReg)
|
||||
}
|
||||
if _, err := os.Stat(fontPathBold); err == nil {
|
||||
pdf.AddUTF8Font(fontFamilyBold, "", fontPathBold)
|
||||
} else {
|
||||
log.Printf("⚠️ Font bulunamadı: %s", fontPathBold)
|
||||
}
|
||||
}
|
||||
|
||||
// Güvenli satır kırma
|
||||
func splitLinesSafe(pdf *gofpdf.Fpdf, text string, width float64) [][]byte {
|
||||
if width <= 0 {
|
||||
width = 1
|
||||
}
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
log.Printf("⚠️ splitLinesSafe recover: %v", rec)
|
||||
}
|
||||
}()
|
||||
return pdf.SplitLines([]byte(text), width)
|
||||
}
|
||||
|
||||
// Metin wrap çizimi
|
||||
func drawWrapText(pdf *gofpdf.Fpdf, text string, x, y, width, lineH float64) {
|
||||
if text == "" {
|
||||
return
|
||||
}
|
||||
lines := splitLinesSafe(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
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap satır yüksekliği
|
||||
func calcRowHeightForText(pdf *gofpdf.Fpdf, text string, colWidth, lineHeight, padX float64) float64 {
|
||||
if colWidth <= 2*padX {
|
||||
colWidth = 2*padX + 1
|
||||
}
|
||||
if text == "" {
|
||||
return lineHeight
|
||||
}
|
||||
lines := splitLinesSafe(pdf, text, colWidth-(2*padX))
|
||||
if len(lines) == 0 {
|
||||
return lineHeight
|
||||
}
|
||||
h := float64(len(lines)) * lineHeight
|
||||
if h < lineHeight {
|
||||
h = lineHeight
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
// Alt tarafa sığmıyor mu?
|
||||
func needNewPage(pdf *gofpdf.Fpdf, needH float64) bool {
|
||||
return pdf.GetY()+needH+marginB > pageHeight
|
||||
}
|
||||
|
||||
// NULL/boş’ları uzun çizgiye çevir
|
||||
func nullToDash(s string) string {
|
||||
if strings.TrimSpace(s) == "" {
|
||||
return "—"
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
/* ============================ HEADER DRAW ============================ */
|
||||
|
||||
// Küçük yardımcı: kutu içine otomatik 1-2 satır metin (taşarsa 2)
|
||||
func drawLabeledBox(pdf *gofpdf.Fpdf, x, y, w, h float64, label, value string, align string) {
|
||||
// Çerçeve
|
||||
pdf.Rect(x, y, w, h, "")
|
||||
// İç marj
|
||||
ix := x + 2
|
||||
iy := y + 1.8
|
||||
iw := w - 4
|
||||
// Etiket (kalın, küçük)
|
||||
pdf.SetFont(fontFamilyBold, "", 8)
|
||||
pdf.SetXY(ix, iy)
|
||||
pdf.CellFormat(iw, 4, label, "", 0, "L", false, 0, "")
|
||||
|
||||
// Değer (normal, 1-2 satır)
|
||||
pdf.SetFont(fontFamilyReg, "", 8)
|
||||
vy := iy + 4.2
|
||||
lineH := 4.2
|
||||
lines := splitLinesSafe(pdf, value, iw)
|
||||
if len(lines) > 2 {
|
||||
lines = lines[:2] // en fazla 2 satır göster
|
||||
}
|
||||
for _, ln := range lines {
|
||||
pdf.SetXY(ix, vy)
|
||||
pdf.CellFormat(iw, lineH, string(ln), "", 0, align, false, 0, "")
|
||||
vy += lineH
|
||||
}
|
||||
}
|
||||
|
||||
func drawPageHeader(pdf *gofpdf.Fpdf, cariKod, cariIsim, start, end string) float64 {
|
||||
logoPath, _ := filepath.Abs("./public/Baggi-Tekstil-A.s-Logolu.jpeg")
|
||||
|
||||
// Logo
|
||||
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
|
||||
}
|
||||
|
||||
/* ============================ GROUP BAR ============================ */
|
||||
|
||||
func drawGroupBar(pdf *gofpdf.Fpdf, currency string, sonBakiye float64) {
|
||||
// Kutu alanı (tam genişlik)
|
||||
x := marginL
|
||||
y := pdf.GetY()
|
||||
w := pageWidth - marginL - marginR
|
||||
h := groupBarH
|
||||
|
||||
// Çerçeve
|
||||
pdf.SetDrawColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
|
||||
pdf.SetLineWidth(0.6)
|
||||
pdf.Rect(x, y, w, h, "")
|
||||
|
||||
// Metinler
|
||||
pdf.SetFont(fontFamilyBold, "", 11.3) // bir tık büyük
|
||||
pdf.SetTextColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
|
||||
|
||||
pdf.SetXY(x+cellPadX+1.0, y+(h-5.0)/2)
|
||||
pdf.CellFormat(w*0.6, 5.0, fmt.Sprintf("%s", currency), "", 0, "L", false, 0, "")
|
||||
|
||||
txt := "Son Bakiye = " + formatCurrencyTR(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, "")
|
||||
|
||||
// Renk/kalınlık geri
|
||||
pdf.SetLineWidth(gridLineWidth)
|
||||
pdf.SetDrawColor(200, 200, 200)
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
|
||||
pdf.Ln(h)
|
||||
}
|
||||
|
||||
/* ============================ HEADER ROWS ============================ */
|
||||
|
||||
func drawMainHeaderRow(pdf *gofpdf.Fpdf, cols []string, widths []float64) {
|
||||
// Ana başlık: Primary arkaplan, beyaz yazı
|
||||
pdf.SetFont(fontFamilyBold, "", 10.2)
|
||||
pdf.SetTextColor(255, 255, 255)
|
||||
pdf.SetFillColor(colorPrimary[0], colorPrimary[1], colorPrimary[2])
|
||||
pdf.SetDrawColor(120, 90, 20)
|
||||
|
||||
x := marginL
|
||||
y := pdf.GetY()
|
||||
for i, c := range cols {
|
||||
pdf.Rect(x, y, widths[i], headerRowH, "DF")
|
||||
pdf.SetXY(x+cellPadX, y+1.6)
|
||||
pdf.CellFormat(widths[i]-2*cellPadX, headerRowH-3.2, c, "", 0, "C", false, 0, "")
|
||||
x += widths[i]
|
||||
}
|
||||
pdf.SetY(y + headerRowH)
|
||||
|
||||
}
|
||||
|
||||
func drawDetailHeaderRow(pdf *gofpdf.Fpdf, cols []string, widths []float64) {
|
||||
// Detay başlık: Secondary (daha açık)
|
||||
pdf.SetFont(fontFamilyBold, "", 9.2)
|
||||
pdf.SetFillColor(colorSecondary[0], colorSecondary[1], colorSecondary[2])
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.SetDrawColor(160, 140, 100)
|
||||
|
||||
x := marginL
|
||||
y := pdf.GetY()
|
||||
for i, c := range cols {
|
||||
pdf.Rect(x, y, widths[i], subHeaderRowH, "DF")
|
||||
pdf.SetXY(x+cellPadX, y+1.2)
|
||||
pdf.CellFormat(widths[i]-2*cellPadX, subHeaderRowH-2.4, c, "", 0, "C", false, 0, "")
|
||||
x += widths[i]
|
||||
}
|
||||
pdf.Ln(subHeaderRowH)
|
||||
}
|
||||
|
||||
/* ============================ DATA ROWS ============================ */
|
||||
|
||||
// Ana veri satırı: daha büyük ve kalın görünsün
|
||||
// Ana veri satırı (zebrasız, sıkı grid – satırlar yapışık)
|
||||
func drawMainDataRow(pdf *gofpdf.Fpdf, row []string, widths []float64, rowH float64) {
|
||||
x := marginL
|
||||
y := pdf.GetY()
|
||||
|
||||
// Arka plan beyaz, çizgiler gri
|
||||
pdf.SetFont(fontFamilyBold, "", 8.5)
|
||||
pdf.SetDrawColor(210, 210, 210)
|
||||
pdf.SetTextColor(30, 30, 30)
|
||||
pdf.SetFillColor(255, 255, 255)
|
||||
|
||||
for i, val := range row {
|
||||
// Hücre çerçevesi
|
||||
pdf.Rect(x, y, widths[i], rowH, "")
|
||||
switch {
|
||||
case i == 4: // Açıklama wrap
|
||||
drawWrapText(pdf, val, x+cellPadX, y+0.8, widths[i]-2*cellPadX, lineHMain)
|
||||
case i >= 6: // Sayısal sağ
|
||||
pdf.SetXY(x+cellPadX, y+(rowH-lineHMain)/2)
|
||||
pdf.CellFormat(widths[i]-2*cellPadX, lineHMain, val, "", 0, "R", false, 0, "")
|
||||
default: // Normal sol
|
||||
pdf.SetXY(x+cellPadX, y+(rowH-lineHMain)/2)
|
||||
pdf.CellFormat(widths[i]-2*cellPadX, lineHMain, val, "", 0, "L", false, 0, "")
|
||||
}
|
||||
x += widths[i]
|
||||
}
|
||||
|
||||
// ❌ pdf.Ln(rowH) yerine:
|
||||
// ✅ bir alt satıra tam yapışık in
|
||||
pdf.SetY(y + rowH)
|
||||
}
|
||||
|
||||
// Detay veri satırı: açık zemin, biraz küçük; zebra opsiyonel
|
||||
func drawDetailDataRow(pdf *gofpdf.Fpdf, row []string, widths []float64, rowH float64, fillBg bool) {
|
||||
pdf.SetFont(fontFamilyReg, "", 8.0)
|
||||
pdf.SetTextColor(60, 60, 60)
|
||||
pdf.SetDrawColor(220, 220, 220)
|
||||
|
||||
x := marginL
|
||||
y := pdf.GetY()
|
||||
|
||||
for i, val := range row {
|
||||
// Zemin
|
||||
if fillBg {
|
||||
pdf.SetFillColor(colorDetailFill[0], colorDetailFill[1], colorDetailFill[2])
|
||||
pdf.Rect(x, y, widths[i], rowH, "DF")
|
||||
pdf.SetFillColor(255, 255, 255) // geri
|
||||
} else {
|
||||
pdf.Rect(x, y, widths[i], rowH, "")
|
||||
}
|
||||
|
||||
switch {
|
||||
case i == 4: // İçerik wrap
|
||||
drawWrapText(pdf, val, x+cellPadX, y+1.4, widths[i]-2*cellPadX, lineHDetail)
|
||||
case i >= 7: // Sayısal sağ
|
||||
pdf.SetXY(x+cellPadX, y+(rowH-lineHDetail)/2)
|
||||
pdf.CellFormat(widths[i]-2*cellPadX, lineHDetail, val, "", 0, "R", false, 0, "")
|
||||
default: // Sol
|
||||
pdf.SetXY(x+cellPadX, y+(rowH-lineHDetail)/2)
|
||||
pdf.CellFormat(widths[i]-2*cellPadX, lineHDetail, val, "", 0, "L", false, 0, "")
|
||||
}
|
||||
x += widths[i]
|
||||
}
|
||||
pdf.Ln(rowH)
|
||||
}
|
||||
|
||||
/* ============================ HANDLER ============================ */
|
||||
|
||||
func ExportPDFHandler(mssql *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
|
||||
}
|
||||
|
||||
started := time.Now()
|
||||
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
log.Printf("❌ PANIC ExportPDFHandler: %v", rec)
|
||||
http.Error(w, "PDF oluşturulurken hata oluştu", http.StatusInternalServerError)
|
||||
}
|
||||
}()
|
||||
|
||||
accountCode := r.URL.Query().Get("accountcode")
|
||||
startDate := r.URL.Query().Get("startdate")
|
||||
endDate := r.URL.Query().Get("enddate")
|
||||
|
||||
// parislemler sanitize
|
||||
rawParis := r.URL.Query()["parislemler"]
|
||||
parislemler := make([]string, 0, len(rawParis))
|
||||
for _, v := range rawParis {
|
||||
v = strings.TrimSpace(v)
|
||||
if v == "" || strings.EqualFold(v, "undefined") || strings.EqualFold(v, "null") {
|
||||
continue
|
||||
}
|
||||
parislemler = append(parislemler, v)
|
||||
}
|
||||
log.Printf("▶️ ExportPDFHandler: account=%s start=%s end=%s parislemler=%v",
|
||||
accountCode, startDate, endDate, parislemler)
|
||||
|
||||
// 1) Header verileri
|
||||
headers, belgeNos, err := queries.GetStatementsPDF(accountCode, startDate, endDate, parislemler)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("✅ Header verileri alındı: %d kayıt, %d belge no", len(headers), len(belgeNos))
|
||||
|
||||
// 2) Detay verileri
|
||||
detailMap, err := queries.GetDetailsMapPDF(belgeNos, startDate, endDate)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
log.Printf("✅ Detay verileri alındı: %d belge için detay var", len(detailMap))
|
||||
|
||||
// 3) Gruplama
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 4) Kolon genişlikleri
|
||||
wAvail := pageWidth - marginL - marginR
|
||||
mainWn := normalizeWidths(mainWbase, wAvail)
|
||||
dWn := normalizeWidths(dWbase, wAvail)
|
||||
|
||||
// 5) PDF init
|
||||
pdf := gofpdf.New("L", "mm", "A4", "")
|
||||
pdf.SetMargins(marginL, marginT, marginR)
|
||||
pdf.SetAutoPageBreak(false, marginB)
|
||||
ensureFonts(pdf)
|
||||
pdf.SetFont(fontFamilyReg, "", 8.5)
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
|
||||
pageNum := 0
|
||||
cariIsim := ""
|
||||
if len(headers) > 0 {
|
||||
cariIsim = headers[0].CariIsim
|
||||
}
|
||||
|
||||
// Sayfa başlatıcı (header yüksekliği dinamik)
|
||||
newPage := func() {
|
||||
pageNum++
|
||||
pdf.AddPage()
|
||||
|
||||
// drawPageHeader tablo başlangıç yüksekliğini döndürüyor
|
||||
tableTop := drawPageHeader(pdf, accountCode, cariIsim, startDate, endDate)
|
||||
|
||||
// Sayfa numarası
|
||||
pdf.SetFont(fontFamilyReg, "", 6)
|
||||
pdf.SetXY(pageWidth-marginR-28, pageHeight-marginB+3)
|
||||
pdf.CellFormat(28, 5, fmt.Sprintf("Sayfa %d", pageNum), "", 0, "R", false, 0, "")
|
||||
|
||||
// Tablo Y konumunu ayarla
|
||||
pdf.SetY(tableTop)
|
||||
}
|
||||
|
||||
newPage()
|
||||
|
||||
// 6) Gruplar yaz
|
||||
for _, cur := range order {
|
||||
g := groups[cur]
|
||||
|
||||
if needNewPage(pdf, groupBarH+headerRowH) {
|
||||
newPage()
|
||||
}
|
||||
drawGroupBar(pdf, cur, g.sonBakiye)
|
||||
drawMainHeaderRow(pdf, mainCols, mainWn)
|
||||
|
||||
for _, h := range g.rows {
|
||||
row := []string{
|
||||
h.BelgeNo, h.BelgeTarihi, h.VadeTarihi, h.IslemTipi,
|
||||
h.Aciklama, h.ParaBirimi,
|
||||
formatCurrencyTR(h.Borc),
|
||||
formatCurrencyTR(h.Alacak),
|
||||
formatCurrencyTR(h.Bakiye),
|
||||
}
|
||||
|
||||
pdf.SetFont(fontFamilyBold, "", 9.6)
|
||||
rh := calcRowHeightForText(pdf, row[4], mainWn[4], lineHMain, cellPadX)
|
||||
|
||||
if needNewPage(pdf, rh+headerRowH) {
|
||||
newPage()
|
||||
drawGroupBar(pdf, cur, g.sonBakiye)
|
||||
drawMainHeaderRow(pdf, mainCols, mainWn)
|
||||
}
|
||||
drawMainDataRow(pdf, row, mainWn, rh)
|
||||
|
||||
// detaylar
|
||||
details := detailMap[h.BelgeNo]
|
||||
if len(details) > 0 {
|
||||
if needNewPage(pdf, subHeaderRowH) {
|
||||
newPage()
|
||||
drawGroupBar(pdf, cur, g.sonBakiye)
|
||||
drawMainHeaderRow(pdf, mainCols, mainWn)
|
||||
}
|
||||
drawDetailHeaderRow(pdf, dCols, dWn)
|
||||
|
||||
for i, d := range details {
|
||||
drow := []string{
|
||||
nullToDash(d.UrunAnaGrubu),
|
||||
nullToDash(d.UrunAltGrubu),
|
||||
nullToDash(d.YetiskinGarson),
|
||||
nullToDash(d.Fit),
|
||||
nullToDash(d.Icerik),
|
||||
nullToDash(d.UrunKodu),
|
||||
nullToDash(d.UrunRengi),
|
||||
formatCurrencyTR(d.ToplamAdet),
|
||||
formatCurrencyTR(d.ToplamFiyat),
|
||||
formatCurrencyTR(d.ToplamTutar),
|
||||
}
|
||||
|
||||
pdf.SetFont(fontFamilyReg, "", 8)
|
||||
rh2 := calcRowHeightForText(pdf, drow[4], dWn[4], lineHDetail, cellPadX)
|
||||
|
||||
if needNewPage(pdf, rh2) {
|
||||
newPage()
|
||||
drawGroupBar(pdf, cur, g.sonBakiye)
|
||||
drawMainHeaderRow(pdf, mainCols, mainWn)
|
||||
drawDetailHeaderRow(pdf, dCols, dWn)
|
||||
}
|
||||
// zebra: çift indekslerde açık zemin
|
||||
fill := (i%2 == 0)
|
||||
drawDetailDataRow(pdf, drow, dWn, rh2, fill)
|
||||
}
|
||||
}
|
||||
}
|
||||
pdf.Ln(3)
|
||||
}
|
||||
|
||||
// 7) Çıktı
|
||||
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.pdf")
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
|
||||
log.Printf("✅ PDF üretimi tamam: %s", time.Since(started))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
NOTLAR:
|
||||
- Header artık dinamik yüksekliğe sahip (drawPageHeader -> contentTopY döner).
|
||||
- Logo sol üst köşe, başlık “toolbar” gibi; “Cari / Tarih” kutuları
|
||||
1–2 satır metni çerçeve içinde otomatik kırar.
|
||||
- Grup barı primary renkte çerçeveli ve yazı biraz büyük.
|
||||
- Ana satırlar kalın & bir tık büyük (detaydan farklı).
|
||||
- Detay satırlar açık (bej) zeminle zebra (i%2==0) uygulanır.
|
||||
- SplitLines her yerde splitLinesSafe ile korunuyor (panic yok).
|
||||
- AddPage sadece newPage()’de.
|
||||
- Font bulunamazsa gömme fontlarla devam edilir (log yazar).
|
||||
*/
|
||||
Reference in New Issue
Block a user