diff --git a/svc/routes/order_pdf.go b/svc/routes/order_pdf.go index 83da361..9550058 100644 --- a/svc/routes/order_pdf.go +++ b/svc/routes/order_pdf.go @@ -3,6 +3,7 @@ package routes import ( "bytes" "database/sql" + "errors" "fmt" "github.com/gorilla/mux" "github.com/jung-kurt/gofpdf" @@ -331,14 +332,14 @@ func contains(list []string, v string) bool { 2) PDF OLUŞTURUCU (A4 YATAY + FOOTER) =========================================================== */ -func newOrderPdf() *gofpdf.Fpdf { +func newOrderPdf() (*gofpdf.Fpdf, error) { 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") + if err := registerDejavuFonts(pdf, "dejavu", "dejavu-b"); err != nil { + return nil, err + } // Footer: sayfa numarası pdf.AliasNbPages("") @@ -349,7 +350,7 @@ func newOrderPdf() *gofpdf.Fpdf { pdf.CellFormat(0, 10, txt, "", 0, "R", false, 0, "") }) - return pdf + return pdf, nil } /* =========================================================== @@ -1340,6 +1341,10 @@ func OrderPDFHandler(db *sql.DB) http.Handler { header, err := getOrderHeaderFromDB(db, orderID) if err != nil { log.Println("header error:", err) + if errors.Is(err, sql.ErrNoRows) { + http.Error(w, "order not found", http.StatusNotFound) + return + } http.Error(w, "header not found", http.StatusInternalServerError) return } @@ -1367,7 +1372,12 @@ func OrderPDFHandler(db *sql.DB) http.Handler { rows := normalizeOrderLinesForPdf(lines) // PDF - pdf := newOrderPdf() + pdf, err := newOrderPdf() + if err != nil { + log.Println("pdf init error:", err) + http.Error(w, "pdf init error: "+err.Error(), http.StatusInternalServerError) + return + } renderOrderGrid(pdf, header, rows, hasVat, vatRate) var buf bytes.Buffer diff --git a/ui/src/stores/orderentryStore.js b/ui/src/stores/orderentryStore.js index d8cb8c9..8bf190f 100644 --- a/ui/src/stores/orderentryStore.js +++ b/ui/src/stores/orderentryStore.js @@ -268,7 +268,7 @@ export const useOrderEntryStore = defineStore('orderentry', { if (!Array.isArray(invalidList) || invalidList.length === 0) return return new Promise(resolve => { - $q.dialog({ + const dlg = $q.dialog({ title: '🚨 Tanımsız Ürün Kombinasyonları', message: `
@@ -309,16 +309,18 @@ export const useOrderEntryStore = defineStore('orderentry', { }) .onOk(() => resolve()) .onDismiss(() => resolve()) - .onShown(() => { - // Satıra tıklama → scroll + highlight - const nodes = document.querySelectorAll('.invalid-row') - nodes.forEach(n => { - n.addEventListener('click', () => { - const ck = n.getAttribute('data-clientkey') - this.scrollToInvalidRow?.(ck) - }) + + // Quasar v2 chain API'de onShown yok; dialog DOM'u render olduktan sonra bağla. + setTimeout(() => { + if (!dlg) return + const nodes = document.querySelectorAll('.invalid-row') + nodes.forEach(n => { + n.addEventListener('click', () => { + const ck = n.getAttribute('data-clientkey') + this.scrollToInvalidRow?.(ck) }) }) + }, 0) }) } , @@ -415,7 +417,22 @@ export const useOrderEntryStore = defineStore('orderentry', { } catch (err) { console.error('❌ PDF açma hatası:', err) - throw err + let detail = + err?.response?.data?.detail || + err?.response?.data?.message || + err?.message || + 'PDF açılamadı' + + // responseType=blob olduğunda backend metni Blob içine gelir. + const blob = err?.response?.data + if (blob instanceof Blob) { + try { + const text = (await blob.text())?.trim() + if (text) detail = text + } catch (_) {} + } + + throw new Error(detail) } } @@ -1817,7 +1834,9 @@ export const useOrderEntryStore = defineStore('orderentry', { // MERGE: bedenleri topluyoruz (override değil) const merged = { ...(prevMap || {}) } for (const [k, v] of Object.entries(newMap || {})) { - const beden = (k == null || String(k).trim() === '') ? ' ' : String(k).trim() + const beden = (k == null || String(k).trim() === '') + ? ' ' + : normalizeBeden(String(k)) merged[beden] = Number(merged[beden] || 0) + Number(v || 0) } @@ -2231,11 +2250,11 @@ export const useOrderEntryStore = defineStore('orderentry', { merged[modelKey] ??= [] - const beden = ( - raw.ItemDim1Code == null || String(raw.ItemDim1Code).trim() === '' - ? ' ' - : String(raw.ItemDim1Code).trim().toUpperCase() - ) + const bedenRaw = + raw.ItemDim1Code == null + ? '' + : String(raw.ItemDim1Code).trim() + const beden = bedenRaw === '' ? ' ' : normalizeBeden(bedenRaw) const qty = Number(raw.Qty1 || raw.Qty || 0) @@ -2310,8 +2329,25 @@ export const useOrderEntryStore = defineStore('orderentry', { row.kategori ) + const cleanedMap = { ...row.__tmpMap } + const hasNonBlankBeden = Object.keys(cleanedMap) + .some(k => String(k).trim() !== '') + + // Gomlek/takim/pantolon gibi gruplarda bos beden sadece legacy kirli veri olabilir. + // Gecerli bedenler varsa bos bedeni payload/UI'dan temizliyoruz. + if (grpKey !== 'aksbir' && hasNonBlankBeden) { + delete cleanedMap[' '] + delete cleanedMap[''] + if (row.lineIdMap && typeof row.lineIdMap === 'object') { + delete row.lineIdMap[' '] + delete row.lineIdMap[''] + } + } + row.grpKey = grpKey - row.bedenMap = { [grpKey]: { ...row.__tmpMap } } + row.bedenMap = { [grpKey]: { ...cleanedMap } } + row.adet = Object.values(cleanedMap).reduce((a, b) => a + (Number(b) || 0), 0) + row.tutar = Number((row.adet * Number(row.fiyat || 0)).toFixed(2)) /* =================================================== 🔒 AKSBİR — BOŞLUK BEDEN GERÇEK ADETİ ALIR @@ -2725,7 +2761,19 @@ export const useOrderEntryStore = defineStore('orderentry', { ======================================================= */ if (choice === 'print') { const id = this.header?.OrderHeaderID || serverOrderId - if (id) await this.downloadOrderPdf(id) + if (id) { + try { + await this.downloadOrderPdf(id) + } catch (pdfErr) { + console.error('⚠️ PDF açılamadı, kayıt başarılı:', pdfErr) + $q.notify({ + type: 'warning', + message: + pdfErr?.message || + 'Sipariş kaydedildi fakat PDF açılamadı.' + }) + } + } return } @@ -3055,6 +3103,12 @@ export const useOrderEntryStore = defineStore('orderentry', { /* 🔹 BEDENSİZ / AKSBİR */ if (!hasAnyBeden) { + const allowBlankPayload = + grpKey === 'aksbir' || row._deleteSignal === true + if (!allowBlankPayload) { + continue + } + const qty = toNum(row.qty ?? row.Qty1 ?? row.miktar ?? 0) // ✅ ComboKey stabil: bedenKey = '_' @@ -3085,6 +3139,15 @@ export const useOrderEntryStore = defineStore('orderentry', { /* 🔹 BEDENLİ */ for (const [bedenRaw, qtyRaw] of Object.entries(map)) { + const isBlankBeden = safeStr(bedenRaw) === '' + if ( + isBlankBeden && + grpKey !== 'aksbir' && + row._deleteSignal !== true + ) { + continue + } + const qty = toNum(qtyRaw) // ✅ payload beden: '' / 'S' / 'M' ... @@ -3295,7 +3358,7 @@ export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '' if (kat.includes('GARSON') || kat.includes('ÇOCUK')) return 'yas' // 🔸 Harfli beden varsa doğrudan "gom" (gömlek, üst giyim) - const harfliBedenler = ['XS','S','M','L','XL','XXL','3XL','4XL'] + const harfliBedenler = ['XS','S','M','L','XL','XXL','2XL','3XL','4XL','5XL','6XL','7XL'] if (list.some(b => harfliBedenler.includes(b))) return 'gom' @@ -3319,7 +3382,7 @@ export function toSummaryRowFromForm(form) { const lbl = rawLbl == null || String(rawLbl).trim() === '' ? ' ' - : String(rawLbl).trim() + : normalizeBeden(String(rawLbl)) const val = Number(values[i] || 0) if (val > 0) {