From 9097b5af2dae6e2256cc9cb7093d6ae7401150d9 Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Thu, 5 Mar 2026 11:21:56 +0300 Subject: [PATCH] Merge remote-tracking branch 'origin/master' --- svc/routes/order_list_pdf.go | 299 +++++++++++++++++++++++++++-------- ui/src/pages/OrderList.vue | 12 +- 2 files changed, 243 insertions(+), 68 deletions(-) diff --git a/svc/routes/order_list_pdf.go b/svc/routes/order_list_pdf.go index de45dd1..4e3e4b4 100644 --- a/svc/routes/order_list_pdf.go +++ b/svc/routes/order_list_pdf.go @@ -6,6 +6,8 @@ import ( "database/sql" "fmt" "net/http" + "sort" + "strconv" "strings" "time" @@ -29,11 +31,21 @@ type orderListPDFRow struct { Description string } +type pdfColumn struct { + Header string + Width float64 + Align string + Wrap bool + MaxLines int +} + func OrderListPDFRoute(db *sql.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { search := strings.TrimSpace(r.URL.Query().Get("search")) currAcc := strings.TrimSpace(r.URL.Query().Get("CurrAccCode")) orderDate := strings.TrimSpace(r.URL.Query().Get("OrderDate")) + sortBy := strings.TrimSpace(r.URL.Query().Get("sort_by")) + descending := parseBool(r.URL.Query().Get("descending")) rows, err := queries.GetOrderListExcel(db, search, currAcc, orderDate) if err != nil { @@ -58,28 +70,14 @@ func OrderListPDFRoute(db *sql.DB) http.Handler { ) if err := rows.Scan( - &id, - &no, - &date, - &termin, - &code, - &name, - &rep, - &piyasa, - &cur, - &total, - &totalUSD, - &packedAmount, - &packedUSD, - &packedRatePct, - &hasUretim, - &desc, - &usdRate, + &id, &no, &date, &termin, &code, &name, &rep, &piyasa, &cur, + &total, &totalUSD, &packedAmount, &packedUSD, &packedRatePct, + &hasUretim, &desc, &usdRate, ); err != nil { http.Error(w, "scan error: "+err.Error(), http.StatusInternalServerError) return } - + _ = id _ = packedAmount _ = usdRate @@ -101,15 +99,17 @@ func OrderListPDFRoute(db *sql.DB) http.Handler { }) } + applyOrderListSort(out, sortBy, descending) + pdf := gofpdf.New("L", "mm", "A4", "") pdf.SetMargins(8, 8, 8) - pdf.SetAutoPageBreak(true, 10) + pdf.SetAutoPageBreak(false, 10) if err := registerDejavuFonts(pdf, "dejavu"); err != nil { http.Error(w, "pdf font error: "+err.Error(), http.StatusInternalServerError) return } - drawOrderListPDF(pdf, out, search, currAcc, orderDate) + drawOrderListPDF(pdf, out, search, currAcc, orderDate, sortBy, descending) if err := pdf.Error(); err != nil { http.Error(w, "pdf render error: "+err.Error(), http.StatusInternalServerError) return @@ -127,68 +127,234 @@ func OrderListPDFRoute(db *sql.DB) http.Handler { }) } -func drawOrderListPDF(pdf *gofpdf.Fpdf, rows []orderListPDFRow, search, currAcc, orderDate string) { - headers := []string{ - "Siparis No", "Tarih", "Termin", "Cari Kod", "Cari Adi", "Temsilci", "Piyasa", - "PB", "Tutar", "USD", "Paket USD", "%", "Uretim", "Aciklama", +func drawOrderListPDF( + pdf *gofpdf.Fpdf, + rows []orderListPDFRow, + search, currAcc, orderDate, sortBy string, + desc bool, +) { + cols := []pdfColumn{ + {Header: "Siparis No", Width: 20, Align: "L", Wrap: false, MaxLines: 1}, + {Header: "Tarih", Width: 12, Align: "C", Wrap: false, MaxLines: 1}, + {Header: "Termin", Width: 12, Align: "C", Wrap: false, MaxLines: 1}, + {Header: "Cari Kod", Width: 15, Align: "L", Wrap: true, MaxLines: 2}, + {Header: "Cari Adi", Width: 27, Align: "L", Wrap: true, MaxLines: 3}, + {Header: "Temsilci", Width: 18, Align: "L", Wrap: true, MaxLines: 2}, + {Header: "Piyasa", Width: 13, Align: "L", Wrap: true, MaxLines: 2}, + {Header: "PB", Width: 8, Align: "C", Wrap: false, MaxLines: 1}, + {Header: "Tutar", Width: 15, Align: "R", Wrap: false, MaxLines: 1}, + {Header: "USD", Width: 15, Align: "R", Wrap: false, MaxLines: 1}, + {Header: "Paket USD", Width: 15, Align: "R", Wrap: false, MaxLines: 1}, + {Header: "%", Width: 8, Align: "R", Wrap: false, MaxLines: 1}, + {Header: "Uretim", Width: 24, Align: "L", Wrap: true, MaxLines: 3}, + {Header: "Aciklama", Width: 52, Align: "L", Wrap: true, MaxLines: 3}, } - widths := []float64{24, 14, 14, 18, 34, 16, 12, 8, 18, 18, 18, 10, 14, 43} - pdf.AddPage() - pdf.SetFont("dejavu", "B", 12) - pdf.CellFormat(0, 7, "Siparis Listesi", "", 1, "L", false, 0, "") + pageW, pageH := pdf.GetPageSize() + marginL, marginT, marginR := 8.0, 8.0, 8.0 + bottomLimit := pageH - 10.0 + lineH := 3.8 + cellPadX := 1.1 + cellPadY := 0.6 - pdf.SetFont("dejavu", "", 8) - filterLine := fmt.Sprintf( - "Filtreler -> Arama: %s | Cari: %s | Siparis Tarihi: %s | Uretim: %s", - emptyDash(search), emptyDash(currAcc), emptyDash(orderDate), time.Now().Format("2006-01-02 15:04"), - ) - pdf.CellFormat(0, 5, filterLine, "", 1, "L", false, 0, "") - pdf.Ln(1) + drawHeader := func() { + pdf.AddPage() + pdf.SetFont("dejavu", "B", 12) + pdf.CellFormat(0, 7, "Siparis Listesi", "", 1, "L", false, 0, "") - pdf.SetFont("dejavu", "B", 8) - pdf.SetFillColor(240, 240, 240) - for i, h := range headers { - pdf.CellFormat(widths[i], 6, h, "1", 0, "C", true, 0, "") + pdf.SetFont("dejavu", "", 8) + sortText := "TotalAmountUSD DESC (varsayilan)" + if sortBy != "" { + dir := "ASC" + if desc { + dir = "DESC" + } + sortText = fmt.Sprintf("%s %s", sortBy, dir) + } + filterLine := fmt.Sprintf( + "Filtreler -> Arama: %s | Cari: %s | Siparis Tarihi: %s | Siralama: %s | Olusturma: %s", + emptyDash(search), + emptyDash(currAcc), + emptyDash(orderDate), + sortText, + time.Now().Format("2006-01-02 15:04"), + ) + pdf.CellFormat(0, 5, filterLine, "", 1, "L", false, 0, "") + pdf.Ln(1) + + pdf.SetFont("dejavu", "B", 8) + pdf.SetFillColor(240, 240, 240) + for _, c := range cols { + pdf.CellFormat(c.Width, 6, c.Header, "1", 0, "C", true, 0, "") + } + pdf.Ln(-1) + pdf.SetFont("dejavu", "", 7) } - pdf.Ln(-1) - pdf.SetFont("dejavu", "", 7) - for _, row := range rows { + buildCells := func(row orderListPDFRow) []string { uretim := "" if row.HasUretimUrunu { - uretim = "VAR" + uretim = "Uretime Verilecek Urunu Var" } - - cells := []string{ + return []string{ row.OrderNumber, row.OrderDate, row.TerminTarihi, row.CurrAccCode, - shorten(row.CurrAccDescription, 24), - shorten(row.MusteriTemsilcisi, 12), - shorten(row.Piyasa, 10), + row.CurrAccDescription, + row.MusteriTemsilcisi, + row.Piyasa, row.DocCurrencyCode, fmt.Sprintf("%.2f", row.TotalAmount), fmt.Sprintf("%.2f", row.TotalAmountUSD), fmt.Sprintf("%.2f", row.PackedUSD), fmt.Sprintf("%.2f", row.PackedRatePct), uretim, - shorten(row.Description, 34), + row.Description, + } + } + + calcLineCount := func(col pdfColumn, text string) int { + if !col.Wrap { + return 1 + } + lines := pdf.SplitLines([]byte(strings.TrimSpace(text)), col.Width-(cellPadX*2)) + n := len(lines) + if n < 1 { + n = 1 + } + if col.MaxLines > 0 && n > col.MaxLines { + n = col.MaxLines + } + return n + } + + drawWrappedCell := func(x, y, w, h float64, text, align string, maxLines int) { + pdf.Rect(x, y, w, h, "D") + lines := pdf.SplitLines([]byte(strings.TrimSpace(text)), w-(cellPadX*2)) + if len(lines) == 0 { + lines = [][]byte{[]byte("")} + } + if maxLines > 0 && len(lines) > maxLines { + lines = lines[:maxLines] + } + ty := y + cellPadY + 2.8 + for _, ln := range lines { + pdf.SetXY(x+cellPadX, ty-2.8) + pdf.CellFormat(w-(cellPadX*2), 3.8, string(ln), "", 0, align, false, 0, "") + ty += 3.8 + } + } + + drawHeader() + + for _, row := range rows { + cells := buildCells(row) + maxLines := 1 + for i, c := range cols { + n := calcLineCount(c, cells[i]) + if n > maxLines { + maxLines = n + } + } + rowH := (float64(maxLines) * lineH) + (cellPadY * 2) + + if pdf.GetY()+rowH > bottomLimit { + drawHeader() } - for i, v := range cells { - align := "L" - if i >= 8 && i <= 11 { - align = "R" + x := marginL + y := pdf.GetY() + for i, c := range cols { + if c.Wrap { + drawWrappedCell(x, y, c.Width, rowH, cells[i], c.Align, c.MaxLines) + } else { + pdf.SetXY(x, y) + pdf.CellFormat(c.Width, rowH, cells[i], "1", 0, c.Align, false, 0, "") } - if i == 1 || i == 2 || i == 7 || i == 12 { - align = "C" - } - pdf.CellFormat(widths[i], 5, v, "1", 0, align, false, 0, "") + x += c.Width } - pdf.Ln(-1) + pdf.SetXY(marginL, y+rowH) } + + _ = pageW + _ = marginT + _ = marginR +} + +func applyOrderListSort(rows []orderListPDFRow, sortBy string, desc bool) { + if len(rows) <= 1 { + return + } + + field := strings.TrimSpace(sortBy) + if field == "" { + field = "TotalAmountUSD" + desc = true + } + + lessText := func(a, b string) bool { + aa := strings.ToLower(strings.TrimSpace(a)) + bb := strings.ToLower(strings.TrimSpace(b)) + if desc { + return aa > bb + } + return aa < bb + } + lessNum := func(a, b float64) bool { + if desc { + return a > b + } + return a < b + } + lessBool := func(a, b bool) bool { + av, bv := 0, 0 + if a { + av = 1 + } + if b { + bv = 1 + } + if desc { + return av > bv + } + return av < bv + } + + sort.SliceStable(rows, func(i, j int) bool { + a := rows[i] + b := rows[j] + switch field { + case "OrderNumber": + return lessText(a.OrderNumber, b.OrderNumber) + case "OrderDate": + return lessText(a.OrderDate, b.OrderDate) + case "TerminTarihi": + return lessText(a.TerminTarihi, b.TerminTarihi) + case "CurrAccCode": + return lessText(a.CurrAccCode, b.CurrAccCode) + case "CurrAccDescription": + return lessText(a.CurrAccDescription, b.CurrAccDescription) + case "MusteriTemsilcisi": + return lessText(a.MusteriTemsilcisi, b.MusteriTemsilcisi) + case "Piyasa": + return lessText(a.Piyasa, b.Piyasa) + case "DocCurrencyCode": + return lessText(a.DocCurrencyCode, b.DocCurrencyCode) + case "TotalAmount": + return lessNum(a.TotalAmount, b.TotalAmount) + case "PackedUSD": + return lessNum(a.PackedUSD, b.PackedUSD) + case "PackedRatePct": + return lessNum(a.PackedRatePct, b.PackedRatePct) + case "HasUretimUrunu": + return lessBool(a.HasUretimUrunu, b.HasUretimUrunu) + case "Description": + return lessText(a.Description, b.Description) + default: + return lessNum(a.TotalAmountUSD, b.TotalAmountUSD) + } + }) } func emptyDash(v string) string { @@ -198,14 +364,15 @@ func emptyDash(v string) string { return v } -func shorten(v string, max int) string { - s := strings.TrimSpace(v) - if len([]rune(s)) <= max { - return s +func parseBool(v string) bool { + b, err := strconv.ParseBool(strings.TrimSpace(v)) + if err == nil { + return b } - r := []rune(s) - if max <= 1 { - return string(r[:1]) + switch strings.TrimSpace(strings.ToLower(v)) { + case "1", "yes", "on": + return true + default: + return false } - return string(r[:max-1]) + "…" } diff --git a/ui/src/pages/OrderList.vue b/ui/src/pages/OrderList.vue index 584bd11..de45796 100644 --- a/ui/src/pages/OrderList.vue +++ b/ui/src/pages/OrderList.vue @@ -109,6 +109,7 @@ :loading="store.loading" no-data-label="Sipariş bulunamadı" :rows-per-page-options="[0]" + v-model:pagination="pagination" hide-bottom >