ilk
This commit is contained in:
1387
svc/routes/order_pdf.go
Normal file
1387
svc/routes/order_pdf.go
Normal file
@@ -0,0 +1,1387 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/jung-kurt/gofpdf"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
/* ===========================================================
|
||||
1) SABİTLER / RENKLER / TİPLER
|
||||
=========================================================== */
|
||||
|
||||
// Baggi renkleri
|
||||
var (
|
||||
baggiGoldR, baggiGoldG, baggiGoldB = 201, 162, 39
|
||||
baggiCreamR, baggiCreamG, baggiCreamB = 255, 254, 249
|
||||
baggiGrayBorderR, baggiGrayBorderG, baggiGrayBorderB = 187, 187, 187
|
||||
)
|
||||
|
||||
// Beden kategorileri (frontend birebir)
|
||||
const (
|
||||
catAyk = "ayk"
|
||||
catYas = "yas"
|
||||
catPan = "pan"
|
||||
catGom = "gom"
|
||||
catTak = "tak"
|
||||
catAksbir = "aksbir"
|
||||
)
|
||||
|
||||
var categoryOrder = []string{catAyk, catYas, catPan, catGom, catTak, catAksbir}
|
||||
|
||||
var categoryTitle = map[string]string{
|
||||
catAyk: " AYAKKABI",
|
||||
catYas: " YAŞ",
|
||||
catPan: " PANTOLON",
|
||||
catGom: " GÖMLEK",
|
||||
catTak: " TAKIM ELBİSE",
|
||||
catAksbir: " AKSESUAR",
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
HEADER MODEL
|
||||
=========================================================== */
|
||||
|
||||
type OrderHeader struct {
|
||||
OrderHeaderID string
|
||||
OrderNumber string
|
||||
CurrAccCode string
|
||||
CurrAccName string
|
||||
DocCurrency string
|
||||
OrderDate time.Time
|
||||
Description string
|
||||
InternalDesc string
|
||||
OfficeCode string
|
||||
CreatedUser string
|
||||
CustomerRep string // 🆕 Müşteri Temsilcisi
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
RAW LINE MODEL
|
||||
=========================================================== */
|
||||
|
||||
type OrderLineRaw struct {
|
||||
OrderLineID sql.NullString
|
||||
ItemCode string
|
||||
ColorCode string
|
||||
ItemDim1Code sql.NullString
|
||||
ItemDim2Code sql.NullString
|
||||
Qty1 sql.NullFloat64
|
||||
Price sql.NullFloat64
|
||||
DocCurrencyCode sql.NullString
|
||||
DeliveryDate sql.NullTime
|
||||
LineDescription sql.NullString
|
||||
UrunAnaGrubu sql.NullString
|
||||
UrunAltGrubu sql.NullString
|
||||
IsClosed sql.NullBool
|
||||
WithHoldingTaxType sql.NullString
|
||||
DOVCode sql.NullString
|
||||
PlannedDateOfLading sql.NullTime
|
||||
CostCenterCode sql.NullString
|
||||
VatCode sql.NullString
|
||||
VatRate sql.NullFloat64
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
PDF SATIR MODELİ
|
||||
=========================================================== */
|
||||
|
||||
type PdfRow struct {
|
||||
Model string
|
||||
Color string
|
||||
GroupMain string
|
||||
GroupSub string
|
||||
Description string
|
||||
Category string
|
||||
SizeQty map[string]int
|
||||
TotalQty int
|
||||
Price float64
|
||||
Currency string
|
||||
Amount float64
|
||||
Termin string
|
||||
IsClosed bool
|
||||
OrderLineIDs map[string]string
|
||||
|
||||
ClosedSizes map[string]bool // 🆕 her beden için IsClosed bilgisi
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
PDF LAYOUT STRUCT
|
||||
=========================================================== */
|
||||
|
||||
type pdfLayout struct {
|
||||
PageW, PageH float64
|
||||
MarginL float64
|
||||
MarginR float64
|
||||
MarginT float64
|
||||
MarginB float64
|
||||
|
||||
ColModelW float64
|
||||
ColRenkW float64
|
||||
ColGroupW float64
|
||||
ColGroupW2 float64
|
||||
ColDescLeft float64
|
||||
ColDescRight float64
|
||||
ColDescW float64
|
||||
ColQtyW float64
|
||||
ColPriceW float64
|
||||
ColCurW float64
|
||||
ColAmountW float64
|
||||
ColTerminW float64
|
||||
|
||||
CentralW float64
|
||||
HeaderMainH float64
|
||||
HeaderSizeH float64
|
||||
RowH float64
|
||||
}
|
||||
|
||||
/* genel cell padding */
|
||||
const OcellPadX = 2
|
||||
|
||||
/*
|
||||
===========================================================
|
||||
|
||||
PDF LAYOUT OLUŞTURUCU
|
||||
===========================================================
|
||||
*/
|
||||
|
||||
/*
|
||||
===========================================================
|
||||
|
||||
PDF LAYOUT OLUŞTURUCU — AÇIKLAMA TEK KOLON
|
||||
===========================================================
|
||||
*/
|
||||
func newPdfLayout(pdf *gofpdf.Fpdf) pdfLayout {
|
||||
pageW, pageH := pdf.GetPageSize()
|
||||
|
||||
l := pdfLayout{
|
||||
PageW: pageW,
|
||||
PageH: pageH,
|
||||
MarginL: 10,
|
||||
MarginR: 10,
|
||||
MarginT: 10,
|
||||
MarginB: 12,
|
||||
|
||||
RowH: 7,
|
||||
HeaderMainH: 8,
|
||||
HeaderSizeH: 6,
|
||||
}
|
||||
|
||||
totalW := pageW - l.MarginL - l.MarginR
|
||||
|
||||
/* --------------------------------------------------------
|
||||
SOL BLOK GÜNCEL – MODEL/RENK ÇAKIŞMASI GİDERİLDİ
|
||||
-------------------------------------------------------- */
|
||||
l.ColModelW = 24 // eski 18 → genişletildi
|
||||
l.ColRenkW = 24 // eski 14 → biraz geniş
|
||||
l.ColGroupW = 20
|
||||
l.ColGroupW2 = 20
|
||||
|
||||
/* --------------------------------------------------------
|
||||
AÇIKLAMA = TEK GENİŞ KOLON (kategori listesi + açıklama içerir)
|
||||
-------------------------------------------------------- */
|
||||
l.ColDescLeft = 50 // açıklama başlığı + kategori alanı (artık tek kolon)
|
||||
l.ColDescRight = 0 // kullanılmıyor (0 bırakılmalı!)
|
||||
|
||||
left := l.ColModelW + l.ColRenkW + l.ColGroupW + l.ColGroupW2 + l.ColDescLeft
|
||||
|
||||
/* --------------------------------------------------------
|
||||
SAĞ BLOK
|
||||
-------------------------------------------------------- */
|
||||
l.ColQtyW = 12
|
||||
l.ColPriceW = 16
|
||||
l.ColCurW = 10
|
||||
l.ColAmountW = 20
|
||||
l.ColTerminW = 20
|
||||
|
||||
right := l.ColQtyW + l.ColPriceW + l.ColCurW + l.ColAmountW + l.ColTerminW
|
||||
|
||||
/* --------------------------------------------------------
|
||||
ORTA BLOK (BEDEN 16 KOLON)
|
||||
-------------------------------------------------------- */
|
||||
l.CentralW = totalW - left - right
|
||||
if l.CentralW < 70 {
|
||||
l.CentralW = 70
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
HELPER FONKSİYONLAR
|
||||
=========================================================== */
|
||||
|
||||
func safeTrimUpper(s string) string {
|
||||
return strings.ToUpper(strings.TrimSpace(s))
|
||||
}
|
||||
|
||||
func f64(v sql.NullFloat64) float64 {
|
||||
if !v.Valid {
|
||||
return 0
|
||||
}
|
||||
return v.Float64
|
||||
}
|
||||
|
||||
func s64(v sql.NullString) string {
|
||||
if !v.Valid {
|
||||
return ""
|
||||
}
|
||||
return v.String
|
||||
}
|
||||
|
||||
func normalizeBedenLabelGo(v string) string {
|
||||
// 1️⃣ NULL / boş / whitespace → " " (aksbir null kolonu)
|
||||
s := strings.TrimSpace(v)
|
||||
if s == "" {
|
||||
return " " // 🔥 NULL BEDEN → boş kolon
|
||||
}
|
||||
|
||||
// 2️⃣ Uppercase
|
||||
s = strings.ToUpper(s)
|
||||
|
||||
/* --------------------------------------------------
|
||||
🔥 AKSBİR ÖZEL (STD eş anlamlıları)
|
||||
-------------------------------------------------- */
|
||||
switch s {
|
||||
case "STD", "STANDART", "STANDARD", "ONE SIZE", "ONESIZE":
|
||||
return "STD"
|
||||
}
|
||||
|
||||
/* --------------------------------------------------
|
||||
🔢 SADECE "CM" VARSA → NUMERİK KISMI AL
|
||||
120CM / 120 CM → 120
|
||||
❌ 105 / 110 / 120 → DOKUNMA
|
||||
-------------------------------------------------- */
|
||||
if strings.HasSuffix(s, "CM") {
|
||||
num := strings.TrimSpace(strings.TrimSuffix(s, "CM"))
|
||||
if num != "" {
|
||||
return num
|
||||
}
|
||||
}
|
||||
|
||||
/* --------------------------------------------------
|
||||
HARF BEDENLER (DOKUNMA)
|
||||
-------------------------------------------------- */
|
||||
switch s {
|
||||
case "XS", "S", "M", "L", "XL",
|
||||
"2XL", "3XL", "4XL", "5XL", "6XL", "7XL":
|
||||
return s
|
||||
}
|
||||
|
||||
// 4️⃣ Sayısal veya başka değerler → olduğu gibi
|
||||
return s
|
||||
}
|
||||
|
||||
func detectBedenGroupGo(bedenList []string, ana, alt string) string {
|
||||
ana = safeTrimUpper(ana)
|
||||
alt = safeTrimUpper(alt)
|
||||
|
||||
for _, b := range bedenList {
|
||||
switch b {
|
||||
case "XS", "S", "M", "L", "XL":
|
||||
return catGom
|
||||
}
|
||||
}
|
||||
|
||||
if strings.Contains(ana, "PANTOLON") {
|
||||
return catPan
|
||||
}
|
||||
if strings.Contains(alt, "ÇOCUK") || strings.Contains(alt, "GARSON") {
|
||||
return catYas
|
||||
}
|
||||
return catTak
|
||||
}
|
||||
func defaultSizeListFor(cat string) []string {
|
||||
switch cat {
|
||||
case catAyk:
|
||||
return []string{"39", "40", "41", "42", "43", "44", "45"}
|
||||
case catYas:
|
||||
return []string{"2", "4", "6", "8", "10", "12", "14"}
|
||||
case catPan:
|
||||
return []string{"38", "40", "42", "44", "46", "48", "50", "52", "54", "56", "58", "60", "62", "64", "66", "68"}
|
||||
case catGom:
|
||||
return []string{"XS", "S", "M", "L", "XL", "2XL", "3XL", "4XL", "5XL", "6XL", "7XL"}
|
||||
case catTak:
|
||||
return []string{"44", "46", "48", "50", "52", "54", "56", "58", "60", "62", "64", "66", "68", "70", "72", "74"}
|
||||
case catAksbir:
|
||||
return []string{"", "44", "STD", "110", "115", "120", "125", "130", "135"}
|
||||
}
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func contains(list []string, v string) bool {
|
||||
for _, x := range list {
|
||||
if x == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
2) PDF OLUŞTURUCU (A4 YATAY + FOOTER)
|
||||
=========================================================== */
|
||||
|
||||
func newOrderPdf() *gofpdf.Fpdf {
|
||||
pdf := gofpdf.New("L", "mm", "A4", "")
|
||||
pdf.SetMargins(10, 10, 10)
|
||||
pdf.SetAutoPageBreak(false, 12)
|
||||
|
||||
// UTF8 fontlar
|
||||
pdf.AddUTF8Font("dejavu", "", "fonts/DejaVuSans.ttf")
|
||||
pdf.AddUTF8Font("dejavu-b", "", "fonts/DejaVuSans-Bold.ttf")
|
||||
|
||||
// Footer: sayfa numarası
|
||||
pdf.AliasNbPages("")
|
||||
pdf.SetFooterFunc(func() {
|
||||
pdf.SetY(-10)
|
||||
pdf.SetFont("dejavu", "", 8)
|
||||
txt := fmt.Sprintf("Sayfa %d/{nb}", pdf.PageNo())
|
||||
pdf.CellFormat(0, 10, txt, "", 0, "R", false, 0, "")
|
||||
})
|
||||
|
||||
return pdf
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
3) DB FONKSİYONLARI (HEADER + LINES)
|
||||
=========================================================== */
|
||||
|
||||
// HEADER
|
||||
func getOrderHeaderFromDB(db *sql.DB, orderID string) (*OrderHeader, error) {
|
||||
row := db.QueryRow(`
|
||||
SELECT
|
||||
CAST(h.OrderHeaderID AS varchar(36)),
|
||||
h.OrderNumber,
|
||||
h.CurrAccCode,
|
||||
d.CurrAccDescription,
|
||||
h.DocCurrencyCode,
|
||||
h.OrderDate,
|
||||
h.Description,
|
||||
h.InternalDescription,
|
||||
h.OfficeCode,
|
||||
h.CreatedUserName,
|
||||
ISNULL((
|
||||
SELECT TOP (1) ca.AttributeDescription
|
||||
FROM BAGGI_V3.dbo.cdCurrAccAttributeDesc AS ca WITH (NOLOCK)
|
||||
WHERE ca.CurrAccTypeCode = 3
|
||||
AND ca.AttributeTypeCode = 2 -- 🟡 Müşteri Temsilcisi
|
||||
AND ca.AttributeCode = f.CustomerAtt02
|
||||
AND ca.LangCode = 'TR'
|
||||
), '') AS CustomerRep
|
||||
FROM BAGGI_V3.dbo.trOrderHeader AS h
|
||||
LEFT JOIN BAGGI_V3.dbo.cdCurrAccDesc AS d
|
||||
ON h.CurrAccCode = d.CurrAccCode
|
||||
LEFT JOIN BAGGI_V3.dbo.CustomerAttributesFilter AS f
|
||||
ON h.CurrAccCode = f.CurrAccCode
|
||||
WHERE h.OrderHeaderID = @p1
|
||||
`, orderID)
|
||||
|
||||
var h OrderHeader
|
||||
var orderDate sql.NullTime
|
||||
|
||||
err := row.Scan(
|
||||
&h.OrderHeaderID,
|
||||
&h.OrderNumber,
|
||||
&h.CurrAccCode,
|
||||
&h.CurrAccName,
|
||||
&h.DocCurrency,
|
||||
&orderDate,
|
||||
&h.Description,
|
||||
&h.InternalDesc,
|
||||
&h.OfficeCode,
|
||||
&h.CreatedUser,
|
||||
&h.CustomerRep, // 🆕 buradan geliyor
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if orderDate.Valid {
|
||||
h.OrderDate = orderDate.Time
|
||||
}
|
||||
return &h, nil
|
||||
}
|
||||
|
||||
// LINES
|
||||
func getOrderLinesFromDB(db *sql.DB, orderID string) ([]OrderLineRaw, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT
|
||||
CAST(L.OrderLineID AS varchar(36)),
|
||||
L.ItemCode,
|
||||
L.ColorCode,
|
||||
L.ItemDim1Code,
|
||||
L.ItemDim2Code,
|
||||
L.Qty1,
|
||||
L.Price,
|
||||
L.DocCurrencyCode,
|
||||
L.DeliveryDate,
|
||||
L.LineDescription,
|
||||
P.ProductAtt01Desc,
|
||||
P.ProductAtt02Desc,
|
||||
L.IsClosed,
|
||||
L.WithHoldingTaxTypeCode,
|
||||
L.DOVCode,
|
||||
L.PlannedDateOfLading,
|
||||
L.CostCenterCode,
|
||||
L.VatCode,
|
||||
L.VatRate
|
||||
FROM BAGGI_V3.dbo.trOrderLine AS L
|
||||
LEFT JOIN ProductFilterWithDescription('TR') AS P
|
||||
ON LTRIM(RTRIM(P.ProductCode)) = LTRIM(RTRIM(L.ItemCode))
|
||||
WHERE L.OrderHeaderID = @p1
|
||||
ORDER BY L.SortOrder, L.OrderLineID
|
||||
`, orderID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var out []OrderLineRaw
|
||||
for rows.Next() {
|
||||
var l OrderLineRaw
|
||||
if err := rows.Scan(
|
||||
&l.OrderLineID,
|
||||
&l.ItemCode,
|
||||
&l.ColorCode,
|
||||
&l.ItemDim1Code,
|
||||
&l.ItemDim2Code,
|
||||
&l.Qty1,
|
||||
&l.Price,
|
||||
&l.DocCurrencyCode,
|
||||
&l.DeliveryDate,
|
||||
&l.LineDescription,
|
||||
&l.UrunAnaGrubu,
|
||||
&l.UrunAltGrubu,
|
||||
&l.IsClosed,
|
||||
&l.WithHoldingTaxType,
|
||||
&l.DOVCode,
|
||||
&l.PlannedDateOfLading,
|
||||
&l.CostCenterCode,
|
||||
&l.VatCode,
|
||||
&l.VatRate,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, l)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
4) NORMALIZE + CATEGORY MAP
|
||||
=========================================================== */
|
||||
|
||||
func normalizeOrderLinesForPdf(lines []OrderLineRaw) []PdfRow {
|
||||
type comboKey struct {
|
||||
Model, Color, Color2 string
|
||||
}
|
||||
|
||||
merged := make(map[comboKey]*PdfRow)
|
||||
|
||||
for _, raw := range lines {
|
||||
|
||||
// ❌ ARTIK KAPALI SATIRLARI ATMAYACAĞIZ
|
||||
// if raw.IsClosed.Valid && raw.IsClosed.Bool {
|
||||
// continue
|
||||
// }
|
||||
|
||||
model := safeTrimUpper(raw.ItemCode)
|
||||
color := safeTrimUpper(raw.ColorCode)
|
||||
color2 := safeTrimUpper(s64(raw.ItemDim2Code))
|
||||
displayColor := color
|
||||
if color2 != "" {
|
||||
displayColor = fmt.Sprintf("%s-%s", color, color2)
|
||||
}
|
||||
|
||||
key := comboKey{model, color, color2}
|
||||
|
||||
if _, ok := merged[key]; !ok {
|
||||
merged[key] = &PdfRow{
|
||||
Model: model,
|
||||
Color: displayColor,
|
||||
GroupMain: s64(raw.UrunAnaGrubu),
|
||||
GroupSub: s64(raw.UrunAltGrubu),
|
||||
Description: s64(raw.LineDescription),
|
||||
SizeQty: make(map[string]int),
|
||||
Currency: s64(raw.DocCurrencyCode),
|
||||
Price: f64(raw.Price),
|
||||
OrderLineIDs: make(map[string]string),
|
||||
ClosedSizes: make(map[string]bool), // 🆕
|
||||
}
|
||||
}
|
||||
row := merged[key]
|
||||
|
||||
// beden
|
||||
rawBeden := s64(raw.ItemDim1Code)
|
||||
if raw.ItemDim1Code.Valid {
|
||||
rawBeden = raw.ItemDim1Code.String
|
||||
}
|
||||
normalized := normalizeBedenLabelGo(rawBeden)
|
||||
|
||||
qty := int(math.Round(f64(raw.Qty1)))
|
||||
if qty > 0 {
|
||||
row.SizeQty[normalized] += qty
|
||||
row.TotalQty += qty
|
||||
|
||||
// 🆕 Bu beden kapalı satırdan geldiyse işaretle
|
||||
if raw.IsClosed.Valid && raw.IsClosed.Bool {
|
||||
row.ClosedSizes[normalized] = true
|
||||
}
|
||||
}
|
||||
|
||||
// OrderLineID eşleştirmesi
|
||||
if raw.OrderLineID.Valid {
|
||||
row.OrderLineIDs[normalized] = raw.OrderLineID.String
|
||||
}
|
||||
|
||||
// Termin
|
||||
if row.Termin == "" && raw.DeliveryDate.Valid {
|
||||
row.Termin = raw.DeliveryDate.Time.Format("02.01.2006")
|
||||
}
|
||||
}
|
||||
|
||||
// finalize
|
||||
out := make([]PdfRow, 0, len(merged))
|
||||
for _, r := range merged {
|
||||
var sizes []string
|
||||
for s := range r.SizeQty {
|
||||
sizes = append(sizes, s)
|
||||
}
|
||||
r.Category = detectBedenGroupGo(sizes, r.GroupMain, r.GroupSub)
|
||||
r.Amount = float64(r.TotalQty) * r.Price
|
||||
out = append(out, *r)
|
||||
}
|
||||
|
||||
// Sıralama: Model → Renk → Category
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
if out[i].Model != out[j].Model {
|
||||
return out[i].Model < out[j].Model
|
||||
}
|
||||
if out[i].Color != out[j].Color {
|
||||
return out[i].Color < out[j].Color
|
||||
}
|
||||
return out[i].Category < out[j].Category
|
||||
})
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
5) CATEGORY → SIZE MAP (HEADER İÇİN)
|
||||
=========================================================== */
|
||||
|
||||
type CategorySizeMap map[string][]string
|
||||
|
||||
// kategori beden map (global – TÜM GRİD İÇİN TEK HEADER)
|
||||
func buildCategorySizeMap(rows []PdfRow) CategorySizeMap {
|
||||
cm := make(CategorySizeMap)
|
||||
|
||||
// Her kategori için sabit default listeleri kullan
|
||||
for _, cat := range categoryOrder {
|
||||
base := defaultSizeListFor(cat)
|
||||
if len(base) > 0 {
|
||||
cm[cat] = append([]string{}, base...)
|
||||
}
|
||||
}
|
||||
|
||||
// İstersen ekstra bedenler varsa ekle (opsiyonel)
|
||||
for _, r := range rows {
|
||||
c := r.Category
|
||||
if c == "" {
|
||||
c = catTak
|
||||
}
|
||||
if _, ok := cm[c]; !ok {
|
||||
cm[c] = []string{}
|
||||
}
|
||||
for size := range r.SizeQty {
|
||||
if !contains(cm[c], size) {
|
||||
cm[c] = append(cm[c], size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cm
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
ORDER HEADER (Logo + Gold Label + Sağ Bilgi Kutusu)
|
||||
=========================================================== */
|
||||
|
||||
func drawOrderHeader(pdf *gofpdf.Fpdf, h *OrderHeader, showDesc bool) float64 {
|
||||
pageW, _ := pdf.GetPageSize()
|
||||
marginL := 10.0
|
||||
y := 8.0
|
||||
|
||||
/* ----------------------------------------------------
|
||||
1) LOGO
|
||||
---------------------------------------------------- */
|
||||
logo := "./public/Baggi-Tekstil-A.s-Logolu.jpeg"
|
||||
if _, err := os.Stat(logo); err == nil {
|
||||
pdf.ImageOptions(logo, marginL, y, 32, 0, false, gofpdf.ImageOptions{}, 0, "")
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
2) ALTIN BAŞLIK BAR
|
||||
---------------------------------------------------- */
|
||||
titleW := 150.0
|
||||
titleX := marginL + 40
|
||||
titleY := y + 2
|
||||
|
||||
pdf.SetFillColor(149, 113, 22) // Baggi altın
|
||||
pdf.Rect(titleX, titleY, titleW, 10, "F")
|
||||
|
||||
pdf.SetFont("dejavu-b", "", 13)
|
||||
pdf.SetTextColor(255, 255, 255)
|
||||
pdf.SetXY(titleX+4, titleY+2)
|
||||
pdf.CellFormat(titleW-8, 6, "BAGGI TEKSTİL - SİPARİŞ FORMU", "", 0, "L", false, 0, "")
|
||||
|
||||
/* ----------------------------------------------------
|
||||
3) SAĞ TARAF BİLGİ KUTUSU
|
||||
---------------------------------------------------- */
|
||||
boxW := 78.0
|
||||
boxH := 30.0
|
||||
boxX := pageW - marginL - boxW
|
||||
boxY := y - 2
|
||||
|
||||
pdf.SetDrawColor(180, 180, 180)
|
||||
pdf.Rect(boxX, boxY, boxW, boxH, "")
|
||||
|
||||
pdf.SetFont("dejavu-b", "", 9)
|
||||
pdf.SetTextColor(149, 113, 22)
|
||||
rep := strings.TrimSpace(h.CustomerRep)
|
||||
if rep == "" {
|
||||
rep = strings.TrimSpace(h.CreatedUser)
|
||||
}
|
||||
|
||||
info := []string{
|
||||
"Formun Basılma Tarihi: " + time.Now().Format("02.01.2006"),
|
||||
"Sipariş Tarihi: " + h.OrderDate.Format("02.01.2006"),
|
||||
"Sipariş No: " + h.OrderNumber,
|
||||
"Müşteri Temsilcisi: " + rep, // 🔥 YENİ EKLENDİ
|
||||
"Cari Kod: " + h.CurrAccCode,
|
||||
"Müşteri: " + h.CurrAccName,
|
||||
}
|
||||
|
||||
iy := boxY + 3
|
||||
for _, line := range info {
|
||||
pdf.SetXY(boxX+3, iy)
|
||||
pdf.CellFormat(boxW-6, 4.5, line, "", 0, "L", false, 0, "")
|
||||
iy += 4.5
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
4) ALT AYIRICI ÇİZGİ
|
||||
---------------------------------------------------- */
|
||||
lineY := boxY + boxH + 3
|
||||
pdf.SetDrawColor(120, 120, 120)
|
||||
pdf.Line(marginL, lineY, pageW-marginL, lineY)
|
||||
pdf.SetDrawColor(200, 200, 200)
|
||||
|
||||
y = lineY + 4
|
||||
|
||||
/* ----------------------------------------------------
|
||||
5) AÇIKLAMA (Varsa)
|
||||
---------------------------------------------------- */
|
||||
if showDesc && strings.TrimSpace(h.Description) != "" {
|
||||
text := strings.TrimSpace(h.Description)
|
||||
|
||||
pdf.SetFont("dejavu", "", 8) // wrap’te kullanılacak font
|
||||
lineH := 4.0
|
||||
textW := pageW - marginL*2 - 52
|
||||
|
||||
// Kaç satır olacağını hesapla
|
||||
lines := pdf.SplitLines([]byte(text), textW)
|
||||
descBoxH := float64(len(lines))*lineH + 4 // min boşluk
|
||||
|
||||
if descBoxH < 10 {
|
||||
descBoxH = 10
|
||||
}
|
||||
|
||||
pdf.SetDrawColor(210, 210, 210)
|
||||
pdf.Rect(marginL, y, pageW-marginL*2, descBoxH, "")
|
||||
|
||||
// Başlık
|
||||
pdf.SetFont("dejavu-b", "", 8)
|
||||
pdf.SetTextColor(149, 113, 22)
|
||||
pdf.SetXY(marginL+3, y+2)
|
||||
pdf.CellFormat(40, 4, "Sipariş Genel Açıklaması:", "", 0, "L", false, 0, "")
|
||||
|
||||
// Metin
|
||||
pdf.SetFont("dejavu", "", 8)
|
||||
pdf.SetTextColor(30, 30, 30)
|
||||
pdf.SetXY(marginL+48, y+2)
|
||||
pdf.MultiCell(textW, lineH, text, "", "L", false)
|
||||
|
||||
y += descBoxH + 3
|
||||
}
|
||||
|
||||
return y
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
GRID HEADER — 2 katmanlı + 16 beden kolonlu
|
||||
=========================================================== */
|
||||
|
||||
/*
|
||||
===========================================================
|
||||
|
||||
GRID HEADER — AÇIKLAMA içinde kategori listesi + 16 beden kolonu
|
||||
===========================================================
|
||||
*/
|
||||
func drawGridHeader(pdf *gofpdf.Fpdf, layout pdfLayout, startY float64, catSizes CategorySizeMap) float64 {
|
||||
pdf.SetFont("dejavu-b", "", 6)
|
||||
pdf.SetDrawColor(baggiGrayBorderR, baggiGrayBorderG, baggiGrayBorderB)
|
||||
pdf.SetFillColor(baggiCreamR, baggiCreamG, baggiCreamB)
|
||||
pdf.SetTextColor(20, 20, 20) // 🟣 TÜM HEADER YAZILARI SİYAH
|
||||
|
||||
y := startY
|
||||
x := layout.MarginL
|
||||
|
||||
totalHeaderH := float64(len(categoryOrder)) * layout.HeaderSizeH
|
||||
|
||||
centerLabel := func(h float64) float64 {
|
||||
return y + (h/2.0 - 3)
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
SOL BLOK (Model – Renk – Grup1 – Grup2)
|
||||
---------------------------------------------------- */
|
||||
cols := []struct {
|
||||
w float64
|
||||
t string
|
||||
}{
|
||||
{layout.ColModelW, "MODEL"},
|
||||
{layout.ColRenkW, "RENK"},
|
||||
{layout.ColGroupW, "ÜRÜN ANA"},
|
||||
{layout.ColGroupW2, "ÜRÜN ALT"},
|
||||
}
|
||||
|
||||
for _, c := range cols {
|
||||
pdf.Rect(x, y, c.w, totalHeaderH, "DF")
|
||||
pdf.SetXY(x, centerLabel(totalHeaderH))
|
||||
pdf.CellFormat(c.w, 6, c.t, "", 0, "C", false, 0, "")
|
||||
x += c.w
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
AÇIKLAMA BAŞLIĞI — TEK HÜCRE & DİKEY ORTALAMA
|
||||
---------------------------------------------------- */
|
||||
descX := x
|
||||
descW := layout.ColDescLeft
|
||||
|
||||
pdf.Rect(descX, y, descW, totalHeaderH, "DF")
|
||||
|
||||
// AÇIKLAMA yazısı ortalanacak
|
||||
pdf.SetXY(descX, y+(totalHeaderH/2-3))
|
||||
pdf.CellFormat(descW, 6, "AÇIKLAMA", "", 0, "C", false, 0, "")
|
||||
|
||||
/* ----------------------------------------------------
|
||||
AÇIKLAMA sağında kategori listesi dikey şekilde
|
||||
---------------------------------------------------- */
|
||||
catX := descX + 1
|
||||
catY := y + 2
|
||||
|
||||
pdf.SetFont("dejavu", "", 6.2)
|
||||
for _, cat := range categoryOrder {
|
||||
label := categoryTitle[cat]
|
||||
pdf.SetXY(catX+2, catY)
|
||||
pdf.CellFormat(descW-4, 4.8, label, "", 0, "L", false, 0, "")
|
||||
catY += layout.HeaderSizeH
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
16’lı BEDEN BLOĞU
|
||||
---------------------------------------------------- */
|
||||
x = descX + descW
|
||||
colW := layout.CentralW / 16.0
|
||||
|
||||
cy := y
|
||||
|
||||
for _, cat := range categoryOrder {
|
||||
|
||||
// Arka plan
|
||||
pdf.SetFillColor(baggiCreamR, baggiCreamG, baggiCreamB)
|
||||
pdf.Rect(x, cy, layout.CentralW, layout.HeaderSizeH, "DF")
|
||||
|
||||
sizes := catSizes[cat]
|
||||
if len(sizes) == 0 {
|
||||
sizes = defaultSizeListFor(cat)
|
||||
}
|
||||
if cat == catAksbir {
|
||||
pdf.SetFont("dejavu", "", 5) // sadece aksesuar için küçük font
|
||||
} else {
|
||||
pdf.SetFont("dejavu", "", 6) // diğer tüm kategoriler normal font
|
||||
}
|
||||
|
||||
xx := x
|
||||
for i := 0; i < 16; i++ {
|
||||
pdf.Rect(xx, cy, colW, layout.HeaderSizeH, "")
|
||||
if i < len(sizes) {
|
||||
pdf.SetXY(xx, cy+1)
|
||||
pdf.CellFormat(colW, layout.HeaderSizeH-2, sizes[i], "", 0, "C", false, 0, "")
|
||||
}
|
||||
xx += colW
|
||||
}
|
||||
|
||||
cy += layout.HeaderSizeH
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
SAĞ BLOK (ADET – FİYAT – PB – TUTAR – TERMİN)
|
||||
---------------------------------------------------- */
|
||||
rightX := x + 16*colW
|
||||
|
||||
rightCols := []struct {
|
||||
w float64
|
||||
t string
|
||||
}{
|
||||
{layout.ColQtyW, "ADET"},
|
||||
{layout.ColPriceW, "FİYAT"},
|
||||
{layout.ColCurW, "PB"},
|
||||
{layout.ColAmountW, "TUTAR"},
|
||||
{layout.ColTerminW, "TERMİN"},
|
||||
}
|
||||
|
||||
for _, c := range rightCols {
|
||||
pdf.Rect(rightX, y, c.w, totalHeaderH, "DF")
|
||||
pdf.SetXY(rightX, centerLabel(totalHeaderH))
|
||||
pdf.CellFormat(c.w, 6, c.t, "", 0, "C", false, 0, "")
|
||||
rightX += c.w
|
||||
}
|
||||
|
||||
return y + totalHeaderH
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
AÇIKLAMA WRAP + DİNAMİK SATIR YÜKSEKLİĞİ
|
||||
=========================================================== */
|
||||
|
||||
// Açıklama kolonunu çok satırlı yazan helper
|
||||
func drawWrappedCell(pdf *gofpdf.Fpdf, text string, x, y, w, h float64) {
|
||||
txt := strings.TrimSpace(text)
|
||||
if txt == "" {
|
||||
return
|
||||
}
|
||||
|
||||
lineH := 3.2
|
||||
lines := pdf.SplitLines([]byte(txt), w-2*OcellPadX)
|
||||
cy := y + 1
|
||||
|
||||
for _, ln := range lines {
|
||||
if cy+lineH > y+h {
|
||||
break
|
||||
}
|
||||
pdf.SetXY(x+OcellPadX, cy)
|
||||
pdf.CellFormat(w-2*OcellPadX, lineH, string(ln), "", 0, "L", false, 0, "")
|
||||
cy += lineH
|
||||
}
|
||||
}
|
||||
|
||||
// Açıklamaya göre satır yüksekliği hesaplar
|
||||
func calcRowHeight(pdf *gofpdf.Fpdf, layout pdfLayout, row PdfRow) float64 {
|
||||
base := layout.RowH
|
||||
desc := strings.TrimSpace(row.Description)
|
||||
if desc == "" {
|
||||
return base
|
||||
}
|
||||
|
||||
// Yeni: açıklama genişliği = sol + sağ
|
||||
descW := layout.ColDescW
|
||||
|
||||
lines := pdf.SplitLines([]byte(desc), descW-2*OcellPadX)
|
||||
lineH := 3.2
|
||||
h := float64(len(lines))*lineH + 2
|
||||
|
||||
if h < base {
|
||||
h = base
|
||||
}
|
||||
return h
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
SATIR ÇİZİMİ — 16 kolonlu beden dizilimi (sola yaslı)
|
||||
=========================================================== */
|
||||
|
||||
/*
|
||||
===========================================================
|
||||
|
||||
SATIR ÇİZİMİ — Tek açıklama sütunu + 16 beden kolonu
|
||||
===========================================================
|
||||
*/
|
||||
func drawPdfRow(pdf *gofpdf.Fpdf, layout pdfLayout, y float64, row PdfRow, catSizes CategorySizeMap, rowH float64) float64 {
|
||||
|
||||
pdf.SetFont("dejavu", "", 7)
|
||||
pdf.SetDrawColor(200, 200, 200)
|
||||
pdf.SetLineWidth(0.15)
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
|
||||
x := layout.MarginL
|
||||
h := rowH
|
||||
|
||||
centerY := func() float64 {
|
||||
return y + (h-3.5)/2
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
1) SOL BLOK
|
||||
MODEL – RENK – ÜRÜN ANA – ÜRÜN ALT
|
||||
---------------------------------------------------- */
|
||||
|
||||
cols := []struct {
|
||||
w float64
|
||||
v string
|
||||
}{
|
||||
{layout.ColModelW, row.Model},
|
||||
{layout.ColRenkW, row.Color},
|
||||
{layout.ColGroupW, row.GroupMain},
|
||||
{layout.ColGroupW2, row.GroupSub},
|
||||
}
|
||||
|
||||
for _, c := range cols {
|
||||
pdf.Rect(x, y, c.w, h, "")
|
||||
pdf.SetXY(x+1.3, centerY())
|
||||
pdf.CellFormat(c.w-2.6, 3.5, c.v, "", 0, "L", false, 0, "")
|
||||
x += c.w
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
2) AÇIKLAMA (TEK BÜYÜK KOLON)
|
||||
---------------------------------------------------- */
|
||||
|
||||
descW := layout.ColDescLeft // açıklama = sadece sol kolon
|
||||
|
||||
pdf.Rect(x, y, descW, h, "")
|
||||
drawWrappedCell(pdf, row.Description, x, y, descW, h)
|
||||
|
||||
x += descW
|
||||
|
||||
/* ----------------------------------------------------
|
||||
3) 16 BEDEN KOLONU
|
||||
---------------------------------------------------- */
|
||||
|
||||
colW := layout.CentralW / 16.0
|
||||
|
||||
// kategorinin beden listesi
|
||||
sizes := catSizes[row.Category]
|
||||
|
||||
if len(sizes) == 0 {
|
||||
tmp := make([]string, 0, len(row.SizeQty))
|
||||
for s := range row.SizeQty {
|
||||
tmp = append(tmp, s)
|
||||
}
|
||||
sort.Strings(tmp)
|
||||
sizes = tmp
|
||||
}
|
||||
|
||||
xx := x
|
||||
for i := 0; i < 16; i++ {
|
||||
if i < len(sizes) {
|
||||
lbl := sizes[i]
|
||||
|
||||
if q, ok := row.SizeQty[lbl]; ok && q > 0 {
|
||||
// 🔍 Bu beden kapalı mı?
|
||||
isClosedSize := row.ClosedSizes != nil && row.ClosedSizes[lbl]
|
||||
|
||||
if isClosedSize {
|
||||
// Gri dolgu + beyaz yazı
|
||||
pdf.SetFillColor(220, 220, 220) // açık gri zemin
|
||||
pdf.Rect(xx, y, colW, h, "DF") // doldurulmuş hücre
|
||||
pdf.SetTextColor(255, 255, 255) // beyaz text
|
||||
} else {
|
||||
// Normal hücre: sadece border
|
||||
pdf.Rect(xx, y, colW, h, "")
|
||||
pdf.SetTextColor(0, 0, 0) // siyah text
|
||||
}
|
||||
|
||||
pdf.SetXY(xx, centerY())
|
||||
pdf.CellFormat(colW, 3.5, fmt.Sprintf("%d", q), "", 0, "C", false, 0, "")
|
||||
|
||||
// Sonraki işler için text rengini resetle
|
||||
if isClosedSize {
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
}
|
||||
} else {
|
||||
// Bu beden kolonunda quantity yoksa normal boş hücre
|
||||
pdf.Rect(xx, y, colW, h, "")
|
||||
}
|
||||
} else {
|
||||
// 16 kolonun kalanları (header'da var ama bu kategoride kullanılmayan bedenler)
|
||||
pdf.Rect(xx, y, colW, h, "")
|
||||
}
|
||||
|
||||
xx += colW
|
||||
}
|
||||
x = xx
|
||||
|
||||
/* ----------------------------------------------------
|
||||
4) SAĞ BLOK: ADET – FİYAT – PB – TUTAR – TERMİN
|
||||
---------------------------------------------------- */
|
||||
|
||||
rightCols := []struct {
|
||||
w float64
|
||||
v string
|
||||
alg string
|
||||
}{
|
||||
{layout.ColQtyW, fmt.Sprintf("%d", row.TotalQty), "C"},
|
||||
{layout.ColPriceW, fmt.Sprintf("%.2f", row.Price), "R"},
|
||||
{layout.ColCurW, row.Currency, "C"},
|
||||
{layout.ColAmountW, fmt.Sprintf("%.2f", row.Amount), "R"},
|
||||
{layout.ColTerminW, row.Termin, "C"},
|
||||
}
|
||||
|
||||
for _, c := range rightCols {
|
||||
pdf.Rect(x, y, c.w, h, "")
|
||||
pdf.SetXY(x+0.6, centerY())
|
||||
pdf.CellFormat(c.w-1.2, 3.5, c.v, "", 0, c.alg, false, 0, "")
|
||||
x += c.w
|
||||
}
|
||||
|
||||
return y + h
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
FORMATLAYICI: TR PARA BİÇİMİ (1.234.567,89)
|
||||
=========================================================== */
|
||||
/* ===========================================================
|
||||
TOPLAM TUTAR / KDV / KDV DAHİL TOPLAM KUTUSU
|
||||
(Sipariş Genel Açıklaması ile grid header arasında)
|
||||
=========================================================== */
|
||||
|
||||
/* ===========================================================
|
||||
PREMIUM BAGGI GOLD TOTALS BOX
|
||||
(Sipariş Açıklaması ile Grid Header arasındaki kutu)
|
||||
=========================================================== */
|
||||
|
||||
func drawTotalsBox(
|
||||
pdf *gofpdf.Fpdf,
|
||||
layout pdfLayout,
|
||||
startY float64,
|
||||
subtotal float64,
|
||||
hasVat bool,
|
||||
vatRate float64,
|
||||
vatAmount float64,
|
||||
totalWithVat float64,
|
||||
currency string,
|
||||
) float64 {
|
||||
|
||||
x := layout.MarginL
|
||||
w := layout.PageW - layout.MarginL - layout.MarginR
|
||||
|
||||
lineH := 6.0
|
||||
rows := 1
|
||||
if hasVat {
|
||||
rows = 3
|
||||
}
|
||||
|
||||
boxH := float64(rows)*lineH + 5
|
||||
|
||||
/* ----------------------------------------------------
|
||||
ARKA PLAN + ALTIN ÇERÇEVE
|
||||
---------------------------------------------------- */
|
||||
pdf.SetFillColor(255, 253, 245) // yumuşak krem
|
||||
pdf.SetDrawColor(149, 113, 22) // Baggi gold
|
||||
pdf.SetLineWidth(0.4)
|
||||
pdf.Rect(x, startY, w, boxH, "DF")
|
||||
|
||||
/* ----------------------------------------------------
|
||||
Genel metin stilleri
|
||||
---------------------------------------------------- */
|
||||
labelX := x + 4
|
||||
valueX := x + w - 70 // değerlerin sağda hizalanacağı kolon
|
||||
|
||||
pdf.SetTextColor(149, 113, 22) // Sol başlık gold
|
||||
pdf.SetFont("dejavu-b", "", 8.5)
|
||||
|
||||
y := startY + 2
|
||||
|
||||
/* ----------------------------------------------------
|
||||
1️⃣ TOPLAM TUTAR
|
||||
---------------------------------------------------- */
|
||||
pdf.SetXY(labelX, y)
|
||||
pdf.CellFormat(80, lineH, "TOPLAM TUTAR", "", 0, "L", false, 0, "")
|
||||
|
||||
pdf.SetTextColor(201, 162, 39)
|
||||
pdf.SetFont("dejavu-b", "", 9)
|
||||
|
||||
pdf.SetXY(valueX, y)
|
||||
pdf.CellFormat(65, lineH,
|
||||
fmt.Sprintf("%s %s", formatCurrencyTR(subtotal), currency),
|
||||
"", 0, "R", false, 0, "")
|
||||
|
||||
y += lineH
|
||||
|
||||
/* ----------------------------------------------------
|
||||
2️⃣ KDV (opsiyonel)
|
||||
---------------------------------------------------- */
|
||||
if hasVat {
|
||||
|
||||
pdf.SetTextColor(149, 113, 22) // gold başlık
|
||||
pdf.SetFont("dejavu-b", "", 8.5)
|
||||
|
||||
pdf.SetXY(labelX, y)
|
||||
pdf.CellFormat(80, lineH,
|
||||
fmt.Sprintf("KDV (%%%g)", vatRate),
|
||||
"", 0, "L", false, 0, "")
|
||||
|
||||
pdf.SetTextColor(20, 20, 20)
|
||||
pdf.SetFont("dejavu-b", "", 9)
|
||||
|
||||
pdf.SetXY(valueX, y)
|
||||
pdf.CellFormat(65, lineH,
|
||||
fmt.Sprintf("%s %s", formatCurrencyTR(vatAmount), currency),
|
||||
"", 0, "R", false, 0, "")
|
||||
|
||||
y += lineH
|
||||
|
||||
/* ----------------------------------------------------
|
||||
3️⃣ KDV DAHİL TOPLAM
|
||||
---------------------------------------------------- */
|
||||
pdf.SetTextColor(201, 162, 39)
|
||||
pdf.SetFont("dejavu-b", "", 8.5)
|
||||
|
||||
pdf.SetXY(labelX, y)
|
||||
pdf.CellFormat(80, lineH, "KDV DAHİL TOPLAM TUTAR", "", 0, "L", false, 0, "")
|
||||
|
||||
pdf.SetTextColor(20, 20, 20)
|
||||
pdf.SetFont("dejavu-b", "", 9)
|
||||
|
||||
pdf.SetXY(valueX, y)
|
||||
pdf.CellFormat(65, lineH,
|
||||
fmt.Sprintf("%s %s", formatCurrencyTR(totalWithVat), currency),
|
||||
"", 0, "R", false, 0, "")
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------
|
||||
Kutu altı boşluk
|
||||
---------------------------------------------------- */
|
||||
return startY + boxH + 4
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
GRUP TOPLAM BAR (Frontend tarzı sarı bar)
|
||||
=========================================================== */
|
||||
|
||||
func drawGroupSummaryBar(pdf *gofpdf.Fpdf, layout pdfLayout, groupName string, totalQty int, totalAmount float64, currency string) float64 {
|
||||
y := pdf.GetY()
|
||||
x := layout.MarginL
|
||||
w := layout.PageW - layout.MarginL - layout.MarginR
|
||||
h := 7.0
|
||||
|
||||
// Açık sarı zemin
|
||||
pdf.SetFillColor(255, 249, 205) // #fff9cd benzeri
|
||||
pdf.SetDrawColor(214, 192, 106)
|
||||
pdf.Rect(x, y, w, h, "DF")
|
||||
|
||||
pdf.SetFont("dejavu-b", "", 8.5)
|
||||
pdf.SetTextColor(20, 20, 20)
|
||||
|
||||
leftTxt := strings.ToUpper(strings.TrimSpace(groupName))
|
||||
if leftTxt == "" {
|
||||
leftTxt = "GENEL"
|
||||
}
|
||||
pdf.SetXY(x+2, y+1.2)
|
||||
pdf.CellFormat(w*0.40, h-2.4, leftTxt, "", 0, "L", false, 0, "")
|
||||
|
||||
rightTxt := fmt.Sprintf(
|
||||
"TOPLAM %s ADET: %d TOPLAM %s TUTAR: %s %s",
|
||||
leftTxt, totalQty,
|
||||
leftTxt, formatCurrencyTR(totalAmount), currency,
|
||||
)
|
||||
|
||||
pdf.SetXY(x+w*0.35, y+1.2)
|
||||
pdf.CellFormat(w*0.60-2, h-2.4, rightTxt, "", 0, "R", false, 0, "")
|
||||
|
||||
// reset
|
||||
pdf.SetTextColor(0, 0, 0)
|
||||
pdf.SetDrawColor(201, 162, 39)
|
||||
|
||||
pdf.SetY(y + h)
|
||||
return y + h
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
MULTIPAGE RENDER ENGINE (Header + GridHeader + Rows)
|
||||
=========================================================== */
|
||||
|
||||
func renderOrderGrid(pdf *gofpdf.Fpdf, header *OrderHeader, rows []PdfRow, hasVat bool, vatRate float64) {
|
||||
layout := newPdfLayout(pdf)
|
||||
catSizes := buildCategorySizeMap(rows)
|
||||
|
||||
// Grup: ÜRÜN ANA GRUBU
|
||||
type group struct {
|
||||
Name string
|
||||
Rows []PdfRow
|
||||
Adet int
|
||||
Tutar float64
|
||||
}
|
||||
|
||||
groups := map[string]*group{}
|
||||
var order []string
|
||||
|
||||
for _, r := range rows {
|
||||
name := strings.TrimSpace(r.GroupMain)
|
||||
if name == "" {
|
||||
name = "GENEL"
|
||||
}
|
||||
g, ok := groups[name]
|
||||
if !ok {
|
||||
g = &group{Name: name}
|
||||
groups[name] = g
|
||||
order = append(order, name)
|
||||
}
|
||||
g.Rows = append(g.Rows, r)
|
||||
g.Adet += r.TotalQty
|
||||
g.Tutar += r.Amount
|
||||
}
|
||||
|
||||
groupSummaryH := 7.0
|
||||
|
||||
// 🔹 Genel toplam (grid içindeki satırlardan)
|
||||
var subtotal float64
|
||||
for _, r := range rows {
|
||||
subtotal += r.Amount
|
||||
}
|
||||
|
||||
// 🔹 KDV hesapla (hasVat ve vatRate, OrderPDFHandler'dan geliyor)
|
||||
var vatAmount float64
|
||||
totalWithVat := subtotal
|
||||
|
||||
if hasVat && vatRate > 0 {
|
||||
vatAmount = subtotal * vatRate / 100.0
|
||||
totalWithVat = subtotal + vatAmount
|
||||
}
|
||||
|
||||
var y float64
|
||||
firstPage := true
|
||||
|
||||
newPage := func(showDesc bool, showTotals bool) {
|
||||
pdf.AddPage()
|
||||
// Üst header (logo + sağ kutu + genel açıklama)
|
||||
y = drawOrderHeader(pdf, header, showDesc)
|
||||
|
||||
// 🔸 İlk sayfada, header ile grid arasında TOPLAM kutusu
|
||||
if showTotals {
|
||||
y = drawTotalsBox(
|
||||
pdf,
|
||||
layout,
|
||||
y,
|
||||
subtotal,
|
||||
hasVat,
|
||||
vatRate,
|
||||
vatAmount,
|
||||
totalWithVat,
|
||||
header.DocCurrency,
|
||||
)
|
||||
}
|
||||
|
||||
// Grid header
|
||||
y = drawGridHeader(pdf, layout, y, catSizes)
|
||||
y += 1
|
||||
}
|
||||
|
||||
// İlk sayfa: açıklama + toplam kutusu
|
||||
newPage(firstPage, true)
|
||||
firstPage = false
|
||||
|
||||
for _, name := range order {
|
||||
g := groups[name]
|
||||
|
||||
for _, row := range g.Rows {
|
||||
rh := calcRowHeight(pdf, layout, row)
|
||||
|
||||
if y+rh+groupSummaryH+2 > layout.PageH-layout.MarginB {
|
||||
// Sonraki sayfalarda: açıklama yok, toplam kutusu yok
|
||||
newPage(false, false)
|
||||
}
|
||||
y = drawPdfRow(pdf, layout, y, row, catSizes, rh)
|
||||
pdf.SetY(y) // 🔹 satırın altına imleci getir
|
||||
}
|
||||
|
||||
// Grup toplam barı için yer kontrolü
|
||||
if y+groupSummaryH+2 > layout.PageH-layout.MarginB {
|
||||
newPage(false, false)
|
||||
}
|
||||
y = drawGroupSummaryBar(pdf, layout, g.Name, g.Adet, g.Tutar, header.DocCurrency)
|
||||
y += 1
|
||||
}
|
||||
|
||||
// ⚠️ Eski alttaki "Genel Toplam" yazdırma kaldırıldı; toplam kutusu artık üstte.
|
||||
}
|
||||
|
||||
/* ===========================================================
|
||||
HTTP HANDLER → /api/order/pdf/{id}
|
||||
=========================================================== */
|
||||
|
||||
func OrderPDFHandler(db *sql.DB) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
orderID := mux.Vars(r)["id"]
|
||||
if orderID == "" {
|
||||
http.Error(w, "missing order id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if db == nil {
|
||||
http.Error(w, "db not initialized", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Header
|
||||
header, err := getOrderHeaderFromDB(db, orderID)
|
||||
if err != nil {
|
||||
log.Println("header error:", err)
|
||||
http.Error(w, "header not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Lines
|
||||
lines, err := getOrderLinesFromDB(db, orderID)
|
||||
if err != nil {
|
||||
log.Println("lines error:", err)
|
||||
http.Error(w, "lines not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// 🔹 Satırlardan KDV bilgisi yakala (ilk pozitif orana göre)
|
||||
hasVat := false
|
||||
var vatRate float64
|
||||
|
||||
for _, l := range lines {
|
||||
if l.VatRate.Valid && l.VatRate.Float64 > 0 {
|
||||
hasVat = true
|
||||
vatRate = l.VatRate.Float64
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize
|
||||
rows := normalizeOrderLinesForPdf(lines)
|
||||
|
||||
// PDF
|
||||
pdf := newOrderPdf()
|
||||
renderOrderGrid(pdf, header, rows, hasVat, vatRate)
|
||||
|
||||
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",
|
||||
fmt.Sprintf("inline; filename=\"ORDER_%s.pdf\"", header.OrderNumber),
|
||||
)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(buf.Bytes())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user