Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-03-05 10:45:12 +03:00
parent 5564dbfbd3
commit 4a6ca5a4d2
9 changed files with 334 additions and 99 deletions

View File

@@ -508,6 +508,7 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
{"/api/orders/close-ready", "GET", "update", routes.OrderCloseReadyListRoute(mssql)},
{"/api/orders/bulk-close", "POST", "update", routes.OrderBulkCloseRoute(mssql)},
{"/api/orders/export", "GET", "export", routes.OrderListExcelRoute(mssql)},
{"/api/orders/export-pdf", "GET", "export", routes.OrderListPDFRoute(mssql)},
{"/api/order/check/{id}", "GET", "view", routes.OrderExistsHandler(mssql)},
{"/api/order/validate", "POST", "insert", routes.ValidateOrderHandler(mssql)},
{"/api/order/pdf/{id}", "GET", "export", routes.OrderPDFHandler(mssql)},

View File

@@ -9,6 +9,7 @@ type OrderList struct {
OrderHeaderID string `json:"OrderHeaderID"`
OrderNumber string `json:"OrderNumber"`
OrderDate string `json:"OrderDate"`
TerminTarihi string `json:"TerminTarihi"`
// 🧾 Cari Bilgileri
CurrAccCode string `json:"CurrAccCode"`

View File

@@ -17,6 +17,7 @@ SELECT
CAST(h.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
ISNULL(h.OrderNumber,'') AS OrderNumber,
CONVERT(varchar,h.OrderDate,23) AS OrderDate,
CONVERT(varchar,h.AverageDueDate,23) AS TerminTarihi,
ISNULL(h.CurrAccCode,'') AS CurrAccCode,
ISNULL(ca.CurrAccDescription,'') AS CurrAccDescription,
@@ -73,6 +74,17 @@ SELECT
ELSE 0
END AS PackedRatePct,
CASE
WHEN EXISTS (
SELECT 1
FROM dbo.trOrderLine l2
WHERE l2.OrderHeaderID = h.OrderHeaderID
AND ISNULL(l2.ItemCode,'') LIKE 'U%%'
)
THEN CAST(1 AS bit)
ELSE CAST(0 AS bit)
END AS HasUretimUrunu,
ISNULL(h.Description,'') AS Description,
usd.Rate AS ExchangeRateUSD

View File

@@ -57,6 +57,7 @@ SELECT
CAST(h.OrderHeaderID AS NVARCHAR(50)) AS OrderHeaderID,
ISNULL(h.OrderNumber, '') AS OrderNumber,
CONVERT(varchar, h.OrderDate, 23) AS OrderDate,
CONVERT(varchar, h.AverageDueDate, 23) AS TerminTarihi,
ISNULL(h.CurrAccCode, '') AS CurrAccCode,
ISNULL(ca.CurrAccDescription, '') AS CurrAccDescription,

View File

@@ -12,41 +12,29 @@ import (
func OrderListExcelRoute(db *sql.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
// ======================
// PARAMS
// ======================
search := r.URL.Query().Get("search")
currAcc := r.URL.Query().Get("CurrAccCode")
orderDate := r.URL.Query().Get("OrderDate")
// ======================
// QUERY
// ======================
rows, err := queries.GetOrderListExcel(db, search, currAcc, orderDate)
if err != nil {
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
// ======================
// EXCEL INIT
// ======================
f := excelize.NewFile()
sheet := "Orders"
f.SetSheetName("Sheet1", sheet)
// ======================
// HEADERS
// ======================
headers := []string{
"Sipariş No",
"Siparis No",
"Tarih",
"Termin Tarihi",
"Cari Kod",
"Cari Adı",
"Cari Adi",
"Temsilci",
"Piyasa",
"PB",
@@ -55,8 +43,9 @@ func OrderListExcelRoute(db *sql.DB) http.Handler {
"Paketlenen Tutar",
"Paketlenen (USD)",
"Paketlenme (%)",
"Uretim",
"USD Kur",
"Açıklama",
"Aciklama",
}
for i, h := range headers {
@@ -64,16 +53,10 @@ func OrderListExcelRoute(db *sql.DB) http.Handler {
f.SetCellValue(sheet, cell, h)
}
// ======================
// ROWS
// ======================
row := 2
for rows.Next() {
// 🔴 15 KOLON = 15 DEĞİŞKEN
var (
id, no, date, code, name string
id, no, date, termin, code, name string
rep, piyasa, cur string
total float64
@@ -81,41 +64,44 @@ func OrderListExcelRoute(db *sql.DB) http.Handler {
packedAmount float64
packedUSD float64
packedRatePct float64
usdRate float64
hasUretim bool
desc string
usdRate float64
)
// 🔴 SELECT SIRASIYLA BİREBİR
err := rows.Scan(
&id, // 1
&no, // 2
&date, // 3
&code, // 4
&name, // 5
&rep, // 6
&piyasa, // 7
&cur, // 8
&total, // 9
&totalUSD, // 10
&packedAmount, // 11
&packedUSD, // 12
&packedRatePct, // 13
&desc, // 14
&usdRate, // 15
&termin, // 4
&code, // 5
&name, // 6
&rep, // 7
&piyasa, // 8
&cur, // 9
&total, // 10
&totalUSD, // 11
&packedAmount, // 12
&packedUSD, // 13
&packedRatePct, // 14
&hasUretim, // 15
&desc, // 16
&usdRate, // 17
)
if err != nil {
http.Error(w, "Scan error: "+err.Error(), 500)
http.Error(w, "Scan error: "+err.Error(), http.StatusInternalServerError)
return
}
// ======================
// WRITE ROW
// ======================
uretim := ""
if hasUretim {
uretim = "VAR"
}
f.SetSheetRow(sheet, fmt.Sprintf("A%d", row), &[]any{
no,
date,
termin,
code,
name,
rep,
@@ -126,6 +112,7 @@ func OrderListExcelRoute(db *sql.DB) http.Handler {
packedAmount,
packedUSD,
packedRatePct,
uretim,
usdRate,
desc,
})
@@ -133,38 +120,17 @@ func OrderListExcelRoute(db *sql.DB) http.Handler {
row++
}
// ======================
// BUFFER WRITE
// ======================
buf, err := f.WriteToBuffer()
if err != nil {
http.Error(w, err.Error(), 500)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
filename := fmt.Sprintf(
"siparis_listesi_%s.xlsx",
time.Now().Format("20060102_150405"),
)
// ======================
// RESPONSE
// ======================
w.Header().Set(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
w.Header().Set(
"Content-Disposition",
"attachment; filename=\""+filename+"\"",
)
w.Header().Set(
"Content-Length",
fmt.Sprint(len(buf.Bytes())),
)
filename := fmt.Sprintf("siparis_listesi_%s.xlsx", time.Now().Format("20060102_150405"))
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=\""+filename+"\"")
w.Header().Set("Content-Length", fmt.Sprint(len(buf.Bytes())))
w.WriteHeader(http.StatusOK)
_, _ = w.Write(buf.Bytes())
})

View File

@@ -0,0 +1,211 @@
package routes
import (
"bssapp-backend/queries"
"bytes"
"database/sql"
"fmt"
"net/http"
"strings"
"time"
"github.com/jung-kurt/gofpdf"
)
type orderListPDFRow struct {
OrderNumber string
OrderDate string
TerminTarihi string
CurrAccCode string
CurrAccDescription string
MusteriTemsilcisi string
Piyasa string
DocCurrencyCode string
TotalAmount float64
TotalAmountUSD float64
PackedUSD float64
PackedRatePct float64
HasUretimUrunu bool
Description string
}
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"))
rows, err := queries.GetOrderListExcel(db, search, currAcc, orderDate)
if err != nil {
http.Error(w, "db error: "+err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
out := make([]orderListPDFRow, 0, 256)
for rows.Next() {
var (
id, no, date, termin, code, name string
rep, piyasa, cur string
total float64
totalUSD float64
packedAmount float64
packedUSD float64
packedRatePct float64
hasUretim bool
desc string
usdRate float64
)
if err := rows.Scan(
&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
}
_ = packedAmount
_ = usdRate
out = append(out, orderListPDFRow{
OrderNumber: no,
OrderDate: date,
TerminTarihi: termin,
CurrAccCode: code,
CurrAccDescription: name,
MusteriTemsilcisi: rep,
Piyasa: piyasa,
DocCurrencyCode: cur,
TotalAmount: total,
TotalAmountUSD: totalUSD,
PackedUSD: packedUSD,
PackedRatePct: packedRatePct,
HasUretimUrunu: hasUretim,
Description: desc,
})
}
pdf := gofpdf.New("L", "mm", "A4", "")
pdf.SetMargins(8, 8, 8)
pdf.SetAutoPageBreak(true, 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)
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=\"siparis-listesi.pdf\"")
_, _ = w.Write(buf.Bytes())
})
}
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",
}
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, "")
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)
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.Ln(-1)
pdf.SetFont("dejavu", "", 7)
for _, row := range rows {
uretim := ""
if row.HasUretimUrunu {
uretim = "VAR"
}
cells := []string{
row.OrderNumber,
row.OrderDate,
row.TerminTarihi,
row.CurrAccCode,
shorten(row.CurrAccDescription, 24),
shorten(row.MusteriTemsilcisi, 12),
shorten(row.Piyasa, 10),
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),
}
for i, v := range cells {
align := "L"
if i >= 8 && i <= 11 {
align = "R"
}
if i == 1 || i == 2 || i == 7 || i == 12 {
align = "C"
}
pdf.CellFormat(widths[i], 5, v, "1", 0, align, false, 0, "")
}
pdf.Ln(-1)
}
}
func emptyDash(v string) string {
if strings.TrimSpace(v) == "" {
return "-"
}
return v
}
func shorten(v string, max int) string {
s := strings.TrimSpace(v)
if len([]rune(s)) <= max {
return s
}
r := []rune(s)
if max <= 1 {
return string(r[:1])
}
return string(r[:max-1]) + "…"
}

View File

@@ -68,27 +68,28 @@ func OrderListRoute(mssql *sql.DB) http.Handler {
&o.OrderHeaderID, // 1
&o.OrderNumber, // 2
&o.OrderDate, // 3
&o.TerminTarihi, // 4
&o.CurrAccCode, // 4
&o.CurrAccDescription, // 5
&o.CurrAccCode, // 5
&o.CurrAccDescription, // 6
&o.MusteriTemsilcisi, // 6
&o.Piyasa, // 7
&o.MusteriTemsilcisi, // 7
&o.Piyasa, // 8
&o.CreditableConfirmedDate, // 8
&o.DocCurrencyCode, // 9
&o.CreditableConfirmedDate, // 9
&o.DocCurrencyCode, // 10
&o.TotalAmount, // 10
&o.TotalAmountUSD, // 11
&o.PackedAmount, // 12
&o.PackedUSD, // 13
&o.PackedRatePct, // 14
&o.TotalAmount, // 11
&o.TotalAmountUSD, // 12
&o.PackedAmount, // 13
&o.PackedUSD, // 14
&o.PackedRatePct, // 15
&o.IsCreditableConfirmed, // 15
&o.HasUretimUrunu, // 16
&o.Description, // 17
&o.IsCreditableConfirmed, // 16
&o.HasUretimUrunu, // 17
&o.Description, // 18
&o.ExchangeRateUSD, // 18
&o.ExchangeRateUSD, // 19
)
if err != nil {

View File

@@ -62,6 +62,15 @@
:disable="store.loading || store.filteredOrders.length === 0"
@click="exportExcel"
/>
<q-btn
label="PDF Yazdır"
icon="picture_as_pdf"
color="red"
outline
:disable="store.loading || store.filteredOrders.length === 0"
@click="exportListPdf"
/>
</div>
<div class="ol-filter-total">
@@ -103,7 +112,20 @@
hide-bottom
>
<template #body-cell-IsCreditableConfirmed="props">
<q-td :props="props" class="text-center q-gutter-sm">
<q-td :props="props" class="text-center">
<q-icon
:name="props.row.IsCreditableConfirmed ? 'check_circle' : 'cancel'"
:color="props.row.IsCreditableConfirmed ? 'green' : 'red'"
size="20px"
>
<q-tooltip>
{{ props.row.IsCreditableConfirmed ? 'Onaylı' : 'Onaysız' }}
</q-tooltip>
</q-icon>
</q-td>
</template>
<template #body-cell-pdf="props">
<q-td :props="props" class="text-center">
<q-btn
icon="picture_as_pdf"
color="red"
@@ -114,16 +136,6 @@
>
<q-tooltip>Siparişi PDF olarak </q-tooltip>
</q-btn>
<q-icon
:name="props.row.IsCreditableConfirmed ? 'check_circle' : 'cancel'"
:color="props.row.IsCreditableConfirmed ? 'green' : 'red'"
size="20px"
>
<q-tooltip>
{{ props.row.IsCreditableConfirmed ? 'Onaylı' : 'Onaysız' }}
</q-tooltip>
</q-icon>
</q-td>
</template>
<template #body-cell-HasUretimUrunu="props">
@@ -140,6 +152,12 @@
</q-td>
</template>
<template #body-cell-TerminTarihi="props">
<q-td :props="props" class="text-center">
{{ formatDate(props.row.TerminTarihi) }}
</q-td>
</template>
<template #body-cell-CreditableConfirmedDate="props">
<q-td :props="props" class="text-center">
{{ formatDate(props.row.CreditableConfirmedDate) }}
@@ -240,7 +258,7 @@ import { useQuasar } from 'quasar'
import { useOrderListStore } from 'src/stores/OrdernewListStore'
import { useAuthStore } from 'src/stores/authStore'
import { usePermission } from 'src/composables/usePermission'
import api, { extractApiErrorDetail } from 'src/services/api'
import api, { download, extractApiErrorDetail } from 'src/services/api'
const { canRead } = usePermission()
const canReadOrder = canRead('order')
@@ -296,6 +314,28 @@ function exportExcel () {
})
})
}
async function exportListPdf () {
try {
const blob = await download('/orders/export-pdf', {
search: store.filters.search || '',
CurrAccCode: store.filters.CurrAccCode || '',
OrderDate: store.filters.OrderDate || ''
})
const blobUrl = URL.createObjectURL(blob)
window.open(blobUrl, '_blank', 'noopener,noreferrer')
setTimeout(() => URL.revokeObjectURL(blobUrl), 15000)
} catch (err) {
const detail = err?.message || 'PDF alınamadı'
$q.notify({
type: 'negative',
message: detail,
position: 'top-right'
})
}
}
function formatDate (s) {
if (!s) return ''
const [y, m, d] = String(s).split('-')
@@ -314,6 +354,7 @@ const columns = [
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false },
{ name: 'OrderNumber', label: 'Sipariş No', field: 'OrderNumber', align: 'left', sortable: true, style: 'min-width:108px;white-space:nowrap', headerStyle: 'min-width:108px;white-space:nowrap' },
{ name: 'OrderDate', label: 'Tarih', field: 'OrderDate', align: 'center', sortable: true, style: 'min-width:82px;white-space:nowrap', headerStyle: 'min-width:82px;white-space:nowrap' },
{ name: 'TerminTarihi', label: 'Termin Tarihi', field: 'TerminTarihi', align: 'center', sortable: true, style: 'min-width:95px;white-space:nowrap', headerStyle: 'min-width:95px;white-space:nowrap' },
{ name: 'CurrAccCode', label: 'Cari Kod', field: 'CurrAccCode', align: 'left', sortable: true, style: 'min-width:82px;white-space:nowrap', headerStyle: 'min-width:82px;white-space:nowrap' },
{ name: 'CurrAccDescription', label: 'Cari Adı', field: 'CurrAccDescription', align: 'left', sortable: true, classes: 'ol-col-cari', headerClasses: 'ol-col-cari', style: 'width:160px;max-width:160px', headerStyle: 'width:160px;max-width:160px' },
{ name: 'MusteriTemsilcisi', label: 'Temsilci', field: 'MusteriTemsilcisi', align: 'left', sortable: true, classes: 'ol-col-short', headerClasses: 'ol-col-short', style: 'width:88px;max-width:88px', headerStyle: 'width:88px;max-width:88px' },
@@ -570,3 +611,4 @@ onMounted(() => {
}
</style>