// src/stores/orderentryStore.js import { defineStore } from 'pinia' import axios from 'axios' import qs from 'qs' import { useAuthStore } from 'src/stores/authStore' import dayjs from 'src/boot/dayjs' import { ref, watch } from 'vue' /* ========================================================== Reaktif shared referanslar (bazı UI yardımcıları) ========================================================== */ const stockMap = ref({}) // { "48": 12, "50": 7, ... } const bedenStock = ref([]) // [{beden:'48', stok:12}, ...] const sizeCache = ref({}) // beden/stok cache (component tarafı çağırıyor) /* ========================================================== STORE ========================================================== */ export const useOrderentryStore = defineStore('orderentry', { state: () => ({ /* 🔹 Ana durumlar */ orders: [], // grid kaynak array’i (summaryRows ile senkron) loading: false, selected: null, // UI’de seçili satır error: null, /* 🔹 Cari */ customers: [], selectedCustomer: null, /* 🔹 Ürün zinciri */ products: [], colors: [], secondColors: [], inventory: [], selectedProduct: null, selectedColor: null, selectedColor2: null, /* 🔹 Transaction & Storage */ activeTransactionId: null, persistKey: 'bss_orderentry_data', // ♻️ kalıcı depolama key’i lastSnapshotKey: 'bss_orderentry_snapshot', // son-kaydedilen-sipariş /* 🔹 Düzenleme durumu */ editingIndex: -1, currentOrderId: null, // edit modunda header ID header: {}, // backend header modeli mode: 'new' // 'new' | 'edit' }), getters: { /* 🔹 Toplam adet */ totalQty(state) { return state.orders.reduce((sum, r) => sum + (Number(r.adet) || 0), 0) }, /* 🔹 Toplam tutar (string fix2) */ totalAmount(state) { const n = state.orders.reduce((s, r) => s + (Number(r.tutar) || 0), 0) return isNaN(n) ? '0.00' : n.toFixed(2) }, /* 🔹 Müşteri bazlı gruplanmış (opsiyonel) */ groupedByCustomer(state) { const out = {} for (const row of state.orders) { const k = row.musteri || '—' if (!out[k]) out[k] = [] out[k].push(row) } return out }, /* 🔹 2. renk var mı? */ hasSecondColor(state) { return Array.isArray(state.secondColors) && state.secondColors.length > 0 }, /* 🔹 Envanter toplamı */ totalInventoryQty(state) { return state.inventory.reduce((s, r) => s + (Number(r.kullanilabilir) || 0), 0) } }, actions: { /* ========================================================== STORAGE — Kalıcı kayıt yardımcıları ========================================================== */ saveToStorage() { try { const payload = { orders: this.orders, header: this.header, currentOrderId: this.currentOrderId, selectedCustomer: this.selectedCustomer, activeTransactionId: this.activeTransactionId, mode: this.mode, savedAt: dayjs().toISOString() } localStorage.setItem(this.persistKey, JSON.stringify(payload)) } catch (err) { console.warn('⚠️ localStorage kaydı başarısız:', err) } }, /* Kayıt sonrası görüntülenecek "snapshot". UI’yi temizlesen bile sayfa yenilenince bu snapshot geri yüklenebilir. */ saveSnapshot(tag = 'post-submit') { try { const snap = { tag, orders: this.orders, header: this.header, currentOrderId: this.currentOrderId, selectedCustomer: this.selectedCustomer, mode: this.mode, savedAt: dayjs().toISOString() } localStorage.setItem(this.lastSnapshotKey, JSON.stringify(snap)) } catch (e) { console.warn('⚠️ saveSnapshot hatası:', e) } }, loadFromStorage() { try { const raw = localStorage.getItem(this.persistKey) if (!raw) return const data = JSON.parse(raw) if (Array.isArray(data.orders)) this.orders = data.orders this.header = data.header || {} this.currentOrderId = data.currentOrderId || null this.selectedCustomer = data.selectedCustomer || null this.activeTransactionId = data.activeTransactionId || null this.mode = data.mode || 'new' console.log(`♻️ Storage yüklendi • mode:${this.mode} • rows:${this.orders.length}`) } catch (err) { console.warn('⚠️ localStorage okuma hatası:', err) } }, loadSnapshot() { try { const raw = localStorage.getItem(this.lastSnapshotKey) if (!raw) return null return JSON.parse(raw) } catch (e) { console.warn('⚠️ loadSnapshot hatası:', e) return null } }, clearStorage() { localStorage.removeItem(this.persistKey) // snapshot’ı silmiyoruz → kullanıcı isterse elle siler }, /* ========================================================== TRANSACTION STATE ========================================================== */ setTransaction(id) { this.activeTransactionId = id this.saveToStorage() }, async initTransaction() { if (this.activeTransactionId) { console.log('🔹 Aktif transaction:', this.activeTransactionId) return this.activeTransactionId } try { const dummyId = Math.floor(100000 + Math.random() * 900000) this.activeTransactionId = dummyId this.saveToStorage() console.log('🧩 Dummy Transaction başlatıldı:', dummyId) return dummyId } catch (err) { console.error('❌ Dummy transaction başlatılamadı:', err) return null } }, clearTransaction() { this.activeTransactionId = null this.saveToStorage() }, /* Orders’ı otomatik kaydeden watcher (component’ten çağrılır) */ watchOrders() { watch( () => this.orders, () => { // her değişimde full storage yaz this.saveToStorage() }, { deep: true } ) }, /* ========================================================== CRUD — Frontend grid’i ile senkron temel aksiyonlar ========================================================== */ addRow(row) { if (!row) return this.orders.push({ ...row }) this.saveToStorage() }, updateRow(idOrIndex, patch) { if (idOrIndex == null) return let idx = -1 if (typeof idOrIndex === 'number') { idx = idOrIndex } else { // id ile bul idx = this.orders.findIndex(r => r.id === idOrIndex) } if (idx >= 0 && this.orders[idx]) { this.orders[idx] = { ...this.orders[idx], ...patch } this.saveToStorage() } }, removeRow(idOrIndex) { let idx = -1 if (typeof idOrIndex === 'number') { idx = idOrIndex } else { idx = this.orders.findIndex(r => r.id === idOrIndex) } if (idx >= 0) { this.orders.splice(idx, 1) this.saveToStorage() } }, /* ========================================================== PRICE / LIMIT — Minimum fiyat sorgusu (model + PB) Beklenen response: { price, priceTRY, rateToTRY } ========================================================== */ async fetchMinPrice(modelCode, pb) { if (!modelCode || !pb) return null try { const baseURL = 'http://localhost:8080' const res = await axios.get(`${baseURL}/api/min-price`, { params: { code: modelCode, pb } }) const d = res?.data || null if (!d) return null // normalize return { price: Number(d.price ?? d.Price ?? 0), priceTRY: Number(d.priceTRY ?? d.PriceTRY ?? d.price_try ?? 0), rateToTRY: Number(d.rateToTRY ?? d.RateToTRY ?? d.rate ?? 1) } } catch (e) { console.warn('⚠️ fetchMinPrice hata:', e) return null } }, /* ========================================================== LOAD (EDIT MODE) — Sunucudan Siparişi Açma ========================================================== */ async openById(id) { if (!id) return this.loading = true try { const auth = useAuthStore() const res = await axios.get(`http://localhost:8080/api/order/get/${id}`, { headers: { Authorization: `Bearer ${auth.token}` } }) const data = res.data || {} // 🔹 sql.Null* flatten helper const flat = (v) => { if (v === null || v === undefined) return null if (typeof v === 'object' && 'Valid' in v) { return v.Valid ? v.String ?? v.Float64 ?? v.Int32 ?? v.Time ?? null : null } return v } /* ============================================================ 🧾 HEADER MAPPING (73 kolon) ============================================================ */ const h = data.header || {} const header = { // Görünen alanlar OrderHeaderID: flat(h.OrderHeaderID) || '', OrderNumber: flat(h.OrderNumber) || '', OrderDate: flat(h.OrderDate) ? String(flat(h.OrderDate)).substring(0, 10) : '', AverageDueDate: flat(h.AverageDueDate) ? String(flat(h.AverageDueDate)).substring(0, 10) : '', Description: flat(h.Description) || '', CurrAccCode: flat(h.CurrAccCode) || '', DocCurrencyCode: flat(h.DocCurrencyCode) || 'TRY', // Arka plan alanlar (backend roundtrip) OrderTypeCode: flat(h.OrderTypeCode) || 1, ProcessCode: flat(h.ProcessCode) || 'WS', IsCancelOrder: flat(h.IsCancelOrder) || 0, OrderTime: flat(h.OrderTime) || '', DocumentNumber: flat(h.DocumentNumber) || '', PaymentTerm: flat(h.PaymentTerm) || '', InternalDescription: flat(h.InternalDescription) || '', CurrAccTypeCode: flat(h.CurrAccTypeCode) || '', SubCurrAccID: flat(h.SubCurrAccID) || '', ContactID: flat(h.ContactID) || '', ShipmentMethodCode: flat(h.ShipmentMethodCode) || '', ShippingPostalAddressID: flat(h.ShippingPostalAddressID) || '', BillingPostalAddressID: flat(h.BillingPostalAddressID) || '', GuarantorContactID: flat(h.GuarantorContactID) || '', GuarantorContactID2: flat(h.GuarantorContactID2) || '', RoundsmanCode: flat(h.RoundsmanCode) || '', DeliveryCompanyCode: flat(h.DeliveryCompanyCode) || '', TaxTypeCode: flat(h.TaxTypeCode) || '', WithHoldingTaxTypeCode: flat(h.WithHoldingTaxTypeCode) || '', DOVCode: flat(h.DOVCode) || '', TaxExemptionCode: flat(h.TaxExemptionCode) || 0, CompanyCode: flat(h.CompanyCode) || 1, OfficeCode: flat(h.OfficeCode) || '101', StoreTypeCode: flat(h.StoreTypeCode) || 5, StoreCode: flat(h.StoreCode) || 0, POSTerminalID: flat(h.POSTerminalID) || 0, WarehouseCode: flat(h.WarehouseCode) || '1-0-12', ToWarehouseCode: flat(h.ToWarehouseCode) || '', OrdererCompanyCode: flat(h.OrdererCompanyCode) || 1, OrdererOfficeCode: flat(h.OrdererOfficeCode) || '101', OrdererStoreCode: flat(h.OrdererStoreCode) || '', GLTypeCode: flat(h.GLTypeCode) || '', LocalCurrencyCode: flat(h.LocalCurrencyCode) || 'TRY', ExchangeRate: flat(h.ExchangeRate) || 1, DiscountReasonCode: flat(h.DiscountReasonCode) || 0, SurplusOrderQtyToleranceRate: flat(h.SurplusOrderQtyToleranceRate) || 0, IncotermCode1: flat(h.IncotermCode1) || '', IncotermCode2: flat(h.IncotermCode2) || '', PaymentMethodCode: flat(h.PaymentMethodCode) || '', IsInclutedVat: flat(h.IsInclutedVat) || 0, IsCreditSale: flat(h.IsCreditSale) || 1, IsCreditableConfirmed: flat(h.IsCreditableConfirmed) || 1, CreditableConfirmedUser: flat(h.CreditableConfirmedUser) || '', CreditableConfirmedDate: flat(h.CreditableConfirmedDate) || '', ApplicationCode: flat(h.ApplicationCode) || 'Order', ApplicationID: flat(h.ApplicationID) || '', CreatedUserName: flat(h.CreatedUserName) || '', CreatedDate: flat(h.CreatedDate) || '', LastUpdatedUserName: flat(h.LastUpdatedUserName) || '', LastUpdatedDate: flat(h.LastUpdatedDate) || '', IsProposalBased: flat(h.IsProposalBased) || 0 } this.header = header this.currentOrderId = header.OrderHeaderID || id this.mode = 'edit' // 🔹 Cari görünümü (QSelect) this.selectedCustomer = { value: header.CurrAccCode || '', label: `${header.CurrAccCode || ''} - ${flat(h.CurrAccDescription) || ''}` } /* ============================================================ 📦 LINES MAPPING (57 kolon) ============================================================ */ this.orders = (data.lines || []).map((l, idx) => ({ // Görünen alanlar id: flat(l.OrderLineID) || `row-${idx + 1}`, model: flat(l.ItemCode), renk: flat(l.ColorCode), renk2: flat(l.ItemDim2Code), fiyat: Number(flat(l.Price) || 0), pb: flat(l.DocCurrencyCode) || flat(l.PriceCurrencyCode) || 'USD', adet: Number(flat(l.Qty1) || 0), tutar: Number(flat(l.Price) || 0) * Number(flat(l.Qty1) || 0), aciklama: flat(l.LineDescription) || '', terminTarihi: flat(l.DeliveryDate) ? String(flat(l.DeliveryDate)).substring(0, 10) : '', urunAnaGrubu: flat(l.ProductGroup) || '', urunAltGrubu: flat(l.ProductSubGroup) || '', grpKey: l.grpKey || 'tak', bedenMap: l.BedenMap || {}, // Backend roundtrip alanları SortOrder: flat(l.SortOrder) || 0, ItemTypeCode: flat(l.ItemTypeCode) || 1, ItemDim1Code: flat(l.ItemDim1Code) || '', ItemDim3Code: flat(l.ItemDim3Code) || '', Qty2: flat(l.Qty2) || 0, CancelQty1: flat(l.CancelQty1) || 0, CancelQty2: flat(l.CancelQty2) || 0, CancelDate: flat(l.CancelDate) || null, OrderCancelReasonCode: flat(l.OrderCancelReasonCode) || '', ClosedDate: flat(l.ClosedDate) || null, IsClosed: flat(l.IsClosed) || false, VatRate: flat(l.VatRate) || 10, PCTRate: flat(l.PCTRate) || 0, PriceCurrencyCode: flat(l.PriceCurrencyCode) || 'TRY', PriceExchangeRate: flat(l.PriceExchangeRate) || header.ExchangeRate || 1, CreatedUserName: flat(l.CreatedUserName) || '', CreatedDate: flat(l.CreatedDate) || '', LastUpdatedUserName: flat(l.LastUpdatedUserName) || '', LastUpdatedDate: flat(l.LastUpdatedDate) || '', SurplusOrderQtyToleranceRate: flat(l.SurplusOrderQtyToleranceRate) || 0 })) /* ============================================================ 💾 LOCAL STORAGE ============================================================ */ localStorage.setItem( `bssapp:order:last:${id}`, JSON.stringify({ header, lines: this.orders }) ) console.log(`📦 Sipariş (${id}) yüklendi • rows:${this.orders.length}`) } catch (err) { console.error('❌ openById hatası:', err) this.error = err.message } finally { this.loading = false } } , /* ========================================================== NEW TEMPLATE — Yeni sipariş başlatma ========================================================== */ newOrderTemplate() { const today = dayjs().format('YYYY-MM-DD') const due = dayjs().add(30, 'day').format('YYYY-MM-DD') this.header = { OrderHeaderID: '', OrderTypeCode: 1, ProcessCode: 'WS', OrderNumber: '', OrderDate: today, AverageDueDate: due, Description: '', CurrAccCode: '', CurrAccDescription: '', DocCurrencyCode: 'USD', LocalCurrencyCode: 'TRY', ExchangeRate: 1, CompanyCode: 1, OfficeCode: '101', StoreTypeCode: 5, WarehouseCode: '1-0-12', IsCreditSale: true, CreatedUserName: '', CreatedDate: today, LastUpdatedUserName: '', LastUpdatedDate: today } this.orders = [] this.currentOrderId = null this.activeTransactionId = null this.selectedCustomer = null this.mode = 'new' this.error = null // Temiz bir başlangıcı storage’a yaz this.saveToStorage() console.log('🧾 Yeni sipariş template yüklendi.') }, /* ========================================================== SUBMIT — Create/Update (SQL tablo INSERT/UPDATE) ➜ Kayıt sonrası: transaction kapanır AMA snapshot tutulur. ========================================================== */ async submitAll() { const auth = useAuthStore() const baseURL = 'http://localhost:8080' const toNullable = (v, type = 'string') => { if (v === null || v === undefined || v === '') { if (type === 'number') return { Float64: 0, Valid: false } if (type === 'time') return { Time: null, Valid: false } return { String: '', Valid: false } } if (type === 'number') return { Float64: Number(v), Valid: true } if (type === 'time') return { Time: v, Valid: true } return { String: String(v), Valid: true } } try { this.loading = true // Header payload (backend’in beklediği Null* formatıyla) const h = this.header || {} const headerPayload = { OrderHeaderID: h.OrderHeaderID || this.currentOrderId || '', OrderTypeCode: toNullable(1, 'number'), ProcessCode: toNullable('WS'), OrderNumber: toNullable(h.OrderNumber), OrderDate: toNullable(h.OrderDate || dayjs().format('YYYY-MM-DD'), 'time'), AverageDueDate: toNullable(h.AverageDueDate || dayjs().add(30, 'day').format('YYYY-MM-DD'), 'time'), Description: toNullable(h.Description || ''), CurrAccCode: toNullable(h.CurrAccCode || this.selectedCustomer?.value || ''), CurrAccDescription: toNullable(h.CurrAccDescription || this.selectedCustomer?.label || ''), DocCurrencyCode: toNullable(h.DocCurrencyCode || 'USD'), LocalCurrencyCode: toNullable(h.LocalCurrencyCode || 'TRY'), ExchangeRate: toNullable(h.ExchangeRate || 1, 'number'), CompanyCode: toNullable(1, 'number'), OfficeCode: toNullable('101'), StoreTypeCode: toNullable(5, 'number'), WarehouseCode: toNullable(h.WarehouseCode || '1-0-12'), IsCreditSale: true, CreatedUserName: toNullable(auth.user?.Username || 'admin'), CreatedDate: toNullable(h.CreatedDate || dayjs().format('YYYY-MM-DD'), 'time'), LastUpdatedUserName: toNullable(auth.user?.Username || 'admin'), LastUpdatedDate: toNullable(dayjs().format('YYYY-MM-DD HH:mm:ss'), 'time') } // Lines payload const linesPayload = this.orders.map((l, idx) => ({ OrderLineID: l.id || '', SortOrder: idx + 1, ItemTypeCode: toNullable(1, 'number'), ItemCode: toNullable(l.model), ColorCode: toNullable(l.renk), ItemDim1Code: toNullable(Object.keys(l.bedenMap?.[l.grpKey] || {})[0] || ''), ItemDim2Code: toNullable(l.renk2), Qty1: toNullable(Number(l.adet || 0), 'number'), Price: toNullable(Number(l.fiyat || 0), 'number'), DocCurrencyCode: toNullable(l.pb || 'USD'), VatRate: toNullable(10, 'number'), PCTRate: toNullable(0, 'number'), DeliveryDate: toNullable(l.terminTarihi || null, 'time'), LineDescription: toNullable(l.aciklama || ''), IsClosed: false, CreatedUserName: toNullable(auth.user?.Username || 'admin'), CreatedDate: toNullable(dayjs().format('YYYY-MM-DD HH:mm:ss'), 'time'), LastUpdatedUserName: toNullable(auth.user?.Username || 'admin'), LastUpdatedDate: toNullable(dayjs().format('YYYY-MM-DD HH:mm:ss'), 'time') })) // Final payload const payload = { header: headerPayload, lines: linesPayload, user: auth.user?.Username || 'admin' } let res if (this.currentOrderId) { // UPDATE res = await axios.post(`${baseURL}/api/order/update`, payload, { headers: { Authorization: `Bearer ${auth.token}` } }) console.log('✅ UPDATE ok:', res.data) } else { // CREATE res = await axios.post(`${baseURL}/api/order/create`, payload, { headers: { Authorization: `Bearer ${auth.token}` } }) console.log('✅ CREATE ok:', res.data) if (res.data?.orderID) { this.currentOrderId = res.data.orderID this.header.OrderHeaderID = res.data.orderID this.mode = 'edit' } } // 🟩 Kayıt sonrası: snapshot’ı al ve storage’a da yaz this.saveSnapshot('post-submit') this.saveToStorage() // 🧹 Transaction’ı kapat (UI temizliği ayrı fonksiyonda) this.clearTransaction() this.afterSubmit({ keepLocalStorage: true }) // 👈 önemli } catch (err) { console.error('❌ submitAll hatası:', err) this.error = err.message throw err } finally { this.loading = false } }, /* ========================================================== AFTER SUBMIT — UI temizliği (snapshot kalır!) keepLocalStorage=true → persistKey SİLİNMEZ ========================================================== */ afterSubmit(opts = { keepLocalStorage: true }) { try { // Snapshot zaten kaydedildi; istenirse persistKey’i bırak if (!opts?.keepLocalStorage) { localStorage.removeItem(this.persistKey) } else { // son hal zaten saveToStorage ile yazıldı — dokunma } // UI temizliği (hafızada formu boşaltalım) // Ama edit’e dönmek istersen, snapshot/loadFromStorage ile geri getirirsin. this.orders = [] // header’ı hafızadan temizliyoruz ama snapshot yerinde. this.header = {} this.selectedCustomer = null this.editingIndex = -1 // currentOrderId’yi istersen koruyabilirsin; biz editte geri yüklüyoruz. // burada null’lıyoruz: this.currentOrderId = null this.mode = 'new' this.loading = false this.error = null console.log('🧹 afterSubmit: UI temizlendi, snapshot storage’da.') } catch (err) { console.warn('⚠️ afterSubmit temizleme hatası:', err) } }, /* ========================================================== MANUAL UPDATE — mevcut header/lines yapılarına göre (İsteğe bağlı kullanılır) ========================================================== */ async updateOrder() { if (!this.currentOrderId) { console.warn('⚠️ currentOrderId yok, update yapılamaz.') return } try { const auth = useAuthStore() const payload = { header: this.header, lines: this.orders, username: auth.user?.Username || 'admin' } const res = await axios.post( 'http://localhost:8080/api/order/update', payload, { headers: { Authorization: `Bearer ${auth.token}` } } ) console.log('✅ Güncelleme tamamlandı:', res.data) // kayıt sonrası snapshot + persist this.saveSnapshot('manual-update') this.saveToStorage() } catch (err) { console.error('❌ updateOrder hatası:', err) this.error = err.message } } } // actions }) // defineStore // (opsiyonel) Bu referanslara component tarafından erişmek istersen: export const sharedOrderEntryRefs = { stockMap, bedenStock, sizeCache }