/* ===========================================================
 📦 orderentryStore.js (v3.4 CLEAN — AUTH + LOCAL PERSIST + AUTO RESUME)
=========================================================== */

import { defineStore } from 'pinia'
import api, { extractApiErrorDetail } from 'src/services/api'
import dayjs from 'src/boot/dayjs'
import { ref, toRaw, nextTick } from 'vue'   // ✅ düzeltildi
import { useAuthStore } from 'src/stores/authStore'

// ===========================================================
// 🔹 Shared Reactive Referanslar (Global, Reaktif Nesneler)
// ===========================================================
/* ===========================================================
   🔹 BEDEN ŞEMALARI — STORE SOURCE OF TRUTH
=========================================================== */
// ⬆️ orderentryStore.js EN ÜSTÜNE
// ===========================================================
// 🔑 COMBO KEY CONTRACT (Frontend ↔ Backend) — v1
//   - trim + UPPER
//   - dim1 boşsa " "
//   - dim2 boşsa ""
// ===========================================================
const BEDEN_EMPTY = '_'

const norm = (v) => (v == null ? '' : String(v)).trim()
const normUpper = (v) => norm(v).toUpperCase()

export function buildComboKey(row, beden) {
  const model = normUpper(row?.model || row?.ItemCode)
  const renk  = normUpper(row?.renk  || row?.ColorCode)
  const renk2 = normUpper(row?.renk2 || row?.ItemDim2Code)
  const bdn   = normUpper(beden)
  const bedenFinal = bdn === '' ? BEDEN_EMPTY : bdn

  // 🔒 KANONİK SIRA
  return `${model}||${renk}||${renk2}||${bedenFinal}`
}






const SIZE_GROUP_TITLES = {
  tak: 'TAKIM ELBISE',
  ayk: 'AYAKKABI',
  ayk_garson: 'AYAKKABI GARSON',
  yas: 'YAS',
  pan: 'PANTOLON',
  gom: 'GOMLEK',
  aksbir: 'AKSESUAR'
}

const FALLBACK_SCHEMA_MAP = {
  tak: { key: 'tak', title: 'TAKIM ELBISE', values: ['44', '46', '48', '50', '52', '54', '56', '58', '60', '62', '64', '66', '68', '70', '72', '74'] },
  ayk: { key: 'ayk', title: 'AYAKKABI', values: ['39', '40', '41', '42', '43', '44', '45'] },
  ayk_garson: { key: 'ayk_garson', title: 'AYAKKABI GARSON', values: ['31', '32', '33', '34', '35', '36', '37', '38'] },
  yas: { key: 'yas', title: 'YAS', values: ['2', '4', '6', '8', '10', '12', '14'] },
  pan: { key: 'pan', title: 'PANTOLON', values: ['38', '40', '42', '44', '46', '48', '50', '52', '54', '56', '58', '60'] },
  gom: { key: 'gom', title: 'GOMLEK', values: ['XS', 'S', 'M', 'L', 'XL', '2XL', '3XL', '4XL', '5XL', '6XL', '7XL'] },
  aksbir: { key: 'aksbir', title: 'AKSESUAR', values: [' '] }
}

export const schemaByKey = { ...FALLBACK_SCHEMA_MAP }

const productSizeMatchCache = {
  loaded: false,
  rules: [],
  schemas: {}
}
const PRODUCT_SIZE_MATCH_TTL_MS = 4 * 60 * 60 * 1000
let productSizeMatchLastFetchAt = 0
let productSizeMatchInflightPromise = null

function resetProductSizeMatchCache() {
  productSizeMatchCache.loaded = false
  productSizeMatchCache.rules = []
  productSizeMatchCache.schemas = {}
  productSizeMatchLastFetchAt = 0
}

function setProductSizeMatchCache(payload) {
  const rules = Array.isArray(payload?.rules) ? payload.rules : []
  const schemasRaw = payload?.schemas && typeof payload.schemas === 'object'
    ? payload.schemas
    : {}

  const normalizedRules = rules
    .map(r => ({
      productGroupID: Number(r?.product_group_id || r?.productGroupID || 0),
      kategori: normalizeTextForMatch(r?.kategori || ''),
      urunAnaGrubu: normalizeTextForMatch(r?.urun_ana_grubu || r?.urunAnaGrubu || ''),
      urunAltGrubu: normalizeTextForMatch(r?.urun_alt_grubu || r?.urunAltGrubu || ''),
      groupKeys: Array.isArray(r?.group_keys || r?.groupKeys)
        ? (r.group_keys || r.groupKeys).map(g => String(g || '').trim()).filter(Boolean)
        : []
    }))
    .filter(r => r.groupKeys.length > 0)
    .sort((a, b) => {
      if (a.productGroupID && b.productGroupID) return a.productGroupID - b.productGroupID
      return 0
    })

  const normalizedSchemas = {}
  for (const [k, vals] of Object.entries(schemasRaw)) {
    const key = String(k || '').trim()
    if (!key) continue
    const arr = Array.isArray(vals)
      ? vals
      : String(vals || '').split(',')
    normalizedSchemas[key] = arr
      .map(v => {
        const s = String(v == null ? '' : v).trim()
        return s === '' ? ' ' : s
      })
      .filter((v, idx, all) => all.indexOf(v) === idx)
  }

  productSizeMatchCache.loaded = true
  productSizeMatchCache.rules = normalizedRules
  productSizeMatchCache.schemas = normalizedSchemas
  productSizeMatchLastFetchAt = Date.now()
}

function buildSchemaMapFromCacheSchemas() {
  const out = {}
  const src = productSizeMatchCache.schemas || {}
  for (const [keyRaw, valuesRaw] of Object.entries(src)) {
    const key = String(keyRaw || '').trim()
    if (!key) continue
    const values = Array.isArray(valuesRaw) ? valuesRaw : []
    out[key] = {
      key,
      title: SIZE_GROUP_TITLES[key] || key.toUpperCase(),
      values: values.map(v => String(v == null ? '' : v))
    }
  }
  if (!out.tak) out.tak = { ...FALLBACK_SCHEMA_MAP.tak }
  return out
}


export const stockMap = ref({})
export const bedenStock = ref([])
export const sizeCache = ref({})
// ===========================================================
// 🔹 Shared Reactive Referanslar (Global, Reaktif Nesneler)
// ===========================================================
// ========================
// 🧰 GLOBAL DATE NORMALIZER
// ========================

function newGuid() {
  return crypto.randomUUID()
}



// 🔑 Her beden satırı için deterministik clientKey üretimi
function makeLineClientKey(row, grpKey, beden) {
  const base =
    row.clientRowKey ||
    row.clientKey ||
    row.id ||
    row._id ||
    row.tmpId ||
    `${row.model || ''}|${row.renk || ''}|${row.renk2 || ''}`

  return `${base}::${grpKey}::${beden}`
}



// ===========================================================
// 🧩 Pinia Store — ORDER ENTRY STORE (REV 2025-11-03.2)
// ===========================================================
export const useOrderEntryStore = defineStore('orderentry', {
  state: () => ({

    isControlledSubmit: false,

    allowRouteLeaveOnce: false,
    schemaMap: {},
    productCache: {},
    _lastSavedFingerprint: null,

    activeNewHeaderId: localStorage.getItem("bss_active_new_header") || null,

    loading: false,
    selected: null,
    error: null,

    customers: [],
    selectedCustomer: null,

    products: [],
    colors: [],
    secondColors: [],
    inventory: [],

    selectedProduct: null,
    selectedColor: null,
    selectedColor2: null,

    OrderHeaderID: null,

    // Persist config
    persistKey: 'bss_orderentry_data',
    lastSnapshotKey: 'bss_orderentry_snapshot',

    // Editor state
    editingKey: null,
    currentOrderId: null,
    mode: 'new',

    // Grid state
    orders: [],
    header: {},
    summaryRows: [],
    originalHeader: {},
    originalLines: [],

    lastSavedAt: null,

    // Guards
    preventPersist: false,
    _uiBusy: false,
    _unsavedChanges: false,
  }),

  getters: {
    getDraftKey() {
      // NEW taslak → GLOBAL ama tekil
      return 'bss_orderentry_new_draft'
    },

    getEditKey() {
      // EDIT → OrderHeaderID’ye bağlı
      const id = this.header?.OrderHeaderID
      return id ? `bss_orderentry_edit:${id}` : null
    }
    ,
    hasUnsavedChanges(state) {
      try {
        return (
          state._lastSavedFingerprint !==
          state._persistFingerprint?.()
        )
      } catch {
        return false
      }
    },

    getPersistKey: (state) =>
      state.header?.OrderHeaderID
        ? `${state.persistKey}:${state.header.OrderHeaderID}`
        : state.persistKey,

    getSnapshotKey: (state) =>
      state.header?.OrderHeaderID
        ? `${state.lastSnapshotKey}:${state.header.OrderHeaderID}`
        : state.lastSnapshotKey,

    totalQty: (state) =>
      (state.orders || []).reduce((sum, r) => sum + (Number(r?.adet) || 0), 0),

    hasAnyClosedLine(state) {
      return Array.isArray(state.summaryRows) &&
        state.summaryRows.some(r => r?.isClosed === true)
    },

    totalAmount(state) {
      if (!Array.isArray(state.summaryRows)) return 0
      return state.summaryRows.reduce(
        (sum, r) => sum + Number(r?.tutar || 0),
        0
      )
    }
  },

  actions: {

    normalizeComboUI(row) {
      return buildComboKey(row, BEDEN_EMPTY)
    }

    ,
    /* ===========================================================
   🧩 initSchemaMap — BEDEN ŞEMA İNİT
   - TEK SOURCE OF TRUTH: SQL mk_size_group (cache)
=========================================================== */
    initSchemaMap() {
      if (this.schemaMap && Object.keys(this.schemaMap).length > 0) {
        return
      }
      this.schemaMap = buildSchemaMapFromCacheSchemas()
      if (!Object.keys(this.schemaMap).length) {
        this.schemaMap = { ...FALLBACK_SCHEMA_MAP }
      }

      console.log(
        '🧩 schemaMap INIT edildi:',
        Object.keys(this.schemaMap)
      )
    },

    async ensureProductSizeMatchRules($q = null, force = false) {
      const hasCache = productSizeMatchCache.loaded && productSizeMatchCache.rules.length > 0
      const cacheAge = hasCache ? (Date.now() - productSizeMatchLastFetchAt) : Number.POSITIVE_INFINITY
      const isFresh = hasCache && cacheAge < PRODUCT_SIZE_MATCH_TTL_MS

      if (!force && isFresh) {
        this.schemaMap = buildSchemaMapFromCacheSchemas()
        return true
      }

      if (!force && productSizeMatchInflightPromise) {
        return productSizeMatchInflightPromise
      }

      productSizeMatchInflightPromise = (async () => {
        try {
        const res = await api.get('/product-size-match/rules')
        setProductSizeMatchCache(res?.data || {})
        this.schemaMap = buildSchemaMapFromCacheSchemas()
        return true
      } catch (err) {
        if (hasCache) {
          this.schemaMap = buildSchemaMapFromCacheSchemas()
          console.warn('product-size-match refresh failed, using existing cache:', err)
          return true
        }
        if (force) {
          resetProductSizeMatchCache()
        }
        this.schemaMap = { ...FALLBACK_SCHEMA_MAP }
        console.warn('⚠ product-size-match rules alınamadı:', err)
        $q?.notify?.({
          type: 'warning',
          message: 'Beden eşleme kuralları alınamadı.'
        })
        return false
      } finally {
        productSizeMatchInflightPromise = null
      }
      })()

      return productSizeMatchInflightPromise
    },


    getRowKey(row) {
      if (!row) return null
      return row.OrderLineID || row.id || null
    }


    ,
    updateHeaderTotals() {
      try {
        if (!Array.isArray(this.summaryRows)) return 0

        const total = this.summaryRows.reduce(
          (sum, r) => sum + Number(r?.tutar || 0),
          0
        )

        // Header sadece GÖSTERİM / BACKEND için
        if (this.header) {
          this.header.TotalAmount = Number(total.toFixed(2))
        }

        return total
      } catch (err) {
        console.error('❌ updateHeaderTotals hata:', err)
        return 0
      }
    }

    ,
    /* ===========================================================
       🚨 showInvalidVariantDialog — FINAL
       -----------------------------------------------------------
       ✔ prItemVariant olmayan satırları listeler
       ✔ Satıra tıkla → scroll + highlight
       ✔ Kaydı BLOKLAYAN tek UI noktası
    =========================================================== */
    async showInvalidVariantDialog($q, invalidList = []) {
      if (!Array.isArray(invalidList) || invalidList.length === 0) return

      return new Promise(resolve => {
        const dlg = $q.dialog({
          title: '🚨 Tanımsız Ürün Kombinasyonları',
          message: `
        <div style="max-height:60vh;overflow:auto">
          ${invalidList.map((v, i) => `
            <div
              class="invalid-row"
              data-clientkey="${v.clientKey}"
              style="
                padding:8px 10px;
                margin-bottom:6px;
                border-left:4px solid #c10015;
                background:#fff5f5;
                cursor:pointer;
              "
            >
              <div style="font-weight:600">
                #${i + 1} | Item: ${v.itemCode}
              </div>
              <div style="font-size:13px">
                Beden: ${v.dim1 || '(boş)'} |
                Renk: ${v.colorCode || '-'} |
                Qty: ${v.qty1}
              </div>
              <div style="font-size:12px;color:#c10015">
                Sebep: ${v.reason || 'Tanımsız ürün kombinasyonu'}
              </div>
            </div>
          `).join('')}
        </div>
      `,
          html: true,
          ok: {
            label: 'Düzelt',
            color: 'negative'
          },
          cancel: false,
          persistent: true
        })
          .onOk(() => resolve())
          .onDismiss(() => resolve())

        // 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)
      })
    }
    ,
    /* ===========================================================
       🎯 scrollToInvalidRow — FINAL
       -----------------------------------------------------------
       ✔ ClientKey bazlı scroll
       ✔ Hem summaryRows hem orders destekli
       ✔ Highlight otomatik kalkar
    =========================================================== */
    scrollToInvalidRow(clientKey) {
      if (!clientKey) return

      // 1️⃣ Store içindeki satırı bul
      const idx = this.summaryRows?.findIndex(
        r => r.clientKey === clientKey
      )

      if (idx === -1) {
        console.warn('❌ Satır bulunamadı:', clientKey)
        return
      }

      // 2️⃣ DOM node
      const el = document.querySelector(
        `[data-clientkey="${clientKey}"]`
      )

      if (!el) {
        console.warn('❌ DOM satırı bulunamadı:', clientKey)
        return
      }

      // 3️⃣ Scroll
      el.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      })

      // 4️⃣ Highlight
      el.classList.add('invalid-highlight')

      setTimeout(() => {
        el.classList.remove('invalid-highlight')
      }, 2500)
    }
    ,

    async checkHeaderExists(orderHeaderID) {
      try {
        if (!orderHeaderID) return false

        const res = await api.get(`/orders/check/${orderHeaderID}`)

        // Backend “true/false” döner varsayımı
        return res?.data?.exists === true
      } catch (err) {
        console.warn("⚠ checkHeaderExists hata:", err)
        return false
      }
    }
    ,

    async fetchOrderPdf(orderId) {
      try {
        const resp = await api.get(`/order/pdf/${orderId}`, {
          responseType: 'blob'
        })
        return resp.data
      } catch (err) {
        const detail = await extractApiErrorDetail(err)
        const status = err?.status || err?.response?.status || '-'
        console.error(`? fetchOrderPdf hata [${status}] order=${orderId}: ${detail}`)
        throw new Error(detail)
      }
    }
    ,

    buildMailLineLabel(line) {
      if (!line || typeof line !== 'object') return ''

      const item = String(line.ItemCode || '').trim()
      const color1 = String(line.ColorCode || '').trim()
      const color2 = String(line.ItemDim2Code || '').trim()
      const desc = String(line.LineDescription || '').trim()

      if (!item) return ''

      const colorPart = color2 ? `${color1}-${color2}` : color1
      return [item, colorPart, desc].filter(Boolean).join(' ')
    }
    ,

    buildOrderMailPayload(lines = [], isNew = false) {
      const uniq = (arr) => [...new Set((arr || []).map(v => String(v || '').trim()).filter(Boolean))]

      const normalized = Array.isArray(lines) ? lines : []
      const mapLabel = (ln) => this.buildMailLineLabel(ln)

      const formatDate = (d) => {
        if (!d) return ''
        const s = String(d).split('T')[0]
        return s
      }
      const getLineDueDate = (ln) => (
        formatDate(
          ln?.DueDate ||
          ln?.DeliveryDate ||
          ln?.PlannedDateOfLading ||
          ''
        )
      )

      const oldDate = formatDate(this.originalHeader?.AverageDueDate)
      const newDate = formatDate(this.header?.AverageDueDate)
      const origMap = new Map()
      if (Array.isArray(this.originalLines)) {
        this.originalLines.forEach(ln => {
          if (ln.OrderLineID) origMap.set(String(ln.OrderLineID), ln)
        })
      }

      const buildDueDateChanges = () => {
        const out = []
        const seen = new Set()

        normalized.forEach(ln => {
          if (ln?._deleteSignal || !ln?.OrderLineID) return

          const orig = origMap.get(String(ln.OrderLineID))
          if (!orig) return

          const itemCode = String(ln?.ItemCode || '').trim().toUpperCase()
          const colorCode = String(ln?.ColorCode || '').trim().toUpperCase()
          const itemDim2Code = String(ln?.ItemDim2Code || '').trim().toUpperCase()
          const oldLnDate = getLineDueDate(orig)
          const newLnDate = getLineDueDate(ln)
          if (!itemCode || !newLnDate || oldLnDate === newLnDate) return

          const key = [itemCode, colorCode, itemDim2Code, oldLnDate, newLnDate].join('||')
          if (seen.has(key)) return
          seen.add(key)

          out.push({
            itemCode,
            colorCode,
            itemDim2Code,
            oldDueDate: oldLnDate,
            newDueDate: newLnDate
          })
        })

        return out
      }

      if (isNew) {
        return {
          operation: 'create',
          deletedItems: [],
          updatedItems: [],
          addedItems: uniq(
            normalized
              .filter(ln => !ln?._deleteSignal)
              .map(mapLabel)
          ),
          oldDueDate: '',
          newDueDate: '',
          dueDateChanges: []
        }
      }

      const deletedItems = uniq(
        normalized
          .filter(ln => ln?._deleteSignal === true)
          .map(mapLabel)
      )

      const updatedItems = []

      normalized.forEach(ln => {
        if (!ln?._deleteSignal && !!ln?.OrderLineID && ln?._dirty === true) {
          let label = mapLabel(ln)
          const orig = origMap.get(String(ln.OrderLineID))
          if (orig) {
            const oldLnDate = getLineDueDate(orig)
            const newLnDate = getLineDueDate(ln)
            if (newLnDate && oldLnDate !== newLnDate) {
              label += ` (Termin: ${oldLnDate} -> ${newLnDate})`
            }
          }
          updatedItems.push(label)
        }
      })

      const addedItems = uniq(
        normalized
          .filter(ln => !ln?._deleteSignal && !ln?.OrderLineID)
          .map(mapLabel)
      )

      return {
        operation: 'update',
        deletedItems,
        updatedItems: uniq(updatedItems),
        addedItems,
        oldDueDate: oldDate,
        newDueDate: newDate,
        dueDateChanges: buildDueDateChanges()
      }
    }
    ,

    async sendOrderToMarketMails(orderId, payload = {}) {
      const id = String(orderId || this.header?.OrderHeaderID || '').trim()
      if (!id) {
        throw new Error('Sipariş ID bulunamadı')
      }

      try {
        const res = await api.post('/order/send-market-mail', {
          orderHeaderID: id,
          operation: payload?.operation || 'create',
          deletedItems: Array.isArray(payload?.deletedItems) ? payload.deletedItems : [],
          updatedItems: Array.isArray(payload?.updatedItems) ? payload.updatedItems : [],
          addedItems: Array.isArray(payload?.addedItems) ? payload.addedItems : [],
          oldDueDate: payload?.oldDueDate || '',
          newDueDate: payload?.newDueDate || '',
          dueDateChanges: Array.isArray(payload?.dueDateChanges) ? payload.dueDateChanges : []
        })
        return res?.data || {}
      } catch (err) {
        const detail = await extractApiErrorDetail(err)
        const status = err?.status || err?.response?.status || '-'
        console.error(`❌ sendOrderToMarketMails hata [${status}] order=${id}: ${detail}`)
        throw new Error(detail)
      }
    }
    ,

    async bulkUpdateOrderLineDueDate(orderId, dueDate) {
      const id = String(orderId || this.header?.OrderHeaderID || '').trim()
      const dateText = String(dueDate || '').trim()
      if (!id) {
        throw new Error('Siparis ID bulunamadi')
      }
      if (!dateText) {
        throw new Error('Termin tarihi secilmedi')
      }

      try {
        this.loading = true
        const res = await api.post(`/order/${encodeURIComponent(id)}/bulk-due-date`, {
          dueDate: dateText
        })
        return res?.data || {}
      } catch (err) {
        const detail = await extractApiErrorDetail(err)
        const status = err?.status || err?.response?.status || '-'
        console.error(`❌ bulkUpdateOrderLineDueDate hata [${status}] order=${id}: ${detail}`)
        throw new Error(detail)
      } finally {
        this.loading = false
      }
    }
    ,

    applyBulkLineDueDateLocally(dueDate) {
      const dateText = String(dueDate || '').trim()
      if (!dateText) return

      const hadUnsavedChanges = this.hasUnsavedChanges
      const patchRow = (row) => ({
        ...row,
        terminTarihi: dateText,
        DueDate: dateText,
        DeliveryDate: dateText,
        PlannedDateOfLading: dateText
      })

      this.orders = Array.isArray(this.orders)
        ? this.orders.map(patchRow)
        : []

      this.summaryRows = Array.isArray(this.summaryRows)
        ? this.summaryRows.map(patchRow)
        : []

      this.header = {
        ...(this.header || {}),
        AverageDueDate: dateText
      }

      // Keep originalHeader/originalLines untouched for submit-mail diff.
      // Otherwise due-date change table becomes empty.

      this.persistLocalStorage?.()
      if (!hadUnsavedChanges) {
        this.markAsSaved?.()
      }
    }
    ,

    async downloadOrderPdf(id = null) {
      try {
        const orderId = id || this.header?.OrderHeaderID
        if (!orderId) {
          console.error('❌ PDF ID bulunamadı')
          return
        }

        const res = await api.get(`/order/pdf/${orderId}`, {
          responseType: 'blob'
        })

        const blob = new Blob([res.data], { type: 'application/pdf' })
        const url = URL.createObjectURL(blob)

        window.open(url, '_blank')
        setTimeout(() => URL.revokeObjectURL(url), 60_000)

      } catch (err) {
        const detail = await extractApiErrorDetail(err)
        const orderId = id || this.header?.OrderHeaderID || '-'
        const status = err?.status || err?.response?.status || '-'
        console.error(`? PDF a<>ma hatas<61> [${status}] order=${orderId}: ${detail}`)
        throw new Error(detail)
      }
    }





    ,
    setActiveNewHeader(id) {
      this.activeNewHeaderId = id || null
      if (id) localStorage.setItem("bss_active_new_header", id)
      else localStorage.removeItem("bss_active_new_header")
    },

    getActiveNewHeaderId() {
      return this.activeNewHeaderId || localStorage.getItem("bss_active_new_header")
    },

    /* ===========================================================
       🧩 initFromRoute (v5.6 — groupedRows TOUCH YOK)
       -----------------------------------------------------------
       - Route ID ve bss_last_txn arasında en dolu snapshot'ı seçer
       - header + orders + summaryRows restore edilir
       - groupedRows hydrate edilmez / resetlenmez / dokunulmaz
       - Route ID farklıysa router.replace ile URL düzeltilir
    =========================================================== */
    async initFromRoute(orderId, router = null) {  // ✅ NEW MODE → SADECE global draft
      if (this.mode === 'new') {
        const raw = localStorage.getItem(this.getDraftKey)
        if (raw) {
          try {
            const payload = JSON.parse(raw)
            this.header = payload.header || {}
            this.orders = payload.orders || []
            this.summaryRows = payload.summaryRows || this.orders
            console.log('♻️ NEW draft restore edildi (global)')
            return
          } catch {}
        }
        console.log('⚪ NEW draft yok, boş başlatılıyor')
        return
      }
      if (!this.schemaMap || !Object.keys(this.schemaMap).length) {
        this.initSchemaMap()
      }

      try {
        console.log('🧩 [initFromRoute] orderId:', orderId)

        const lastTxn = localStorage.getItem('bss_last_txn') || null

        const readPayload = (id) => {
          if (!id) return null
          const raw = localStorage.getItem(`bss_orderentry_data:${id}`)
          if (!raw) return null
          try {
            return JSON.parse(raw)
          } catch {
            return null
          }
        }

        const fromRoute = readPayload(orderId)
        const fromLast  = readPayload(lastTxn)

        const hasData = (p) =>
          !!p && (
            (Array.isArray(p.orders) && p.orders.length > 0) ||
            (Array.isArray(p.summaryRows) && p.summaryRows.length > 0)
          )

        let chosenId = null
        let chosenPayload = null

        if (hasData(fromRoute)) {
          chosenId = orderId
          chosenPayload = fromRoute
          console.log('✅ [initFromRoute] Route ID snapshot seçildi:', chosenId)
        } else if (hasData(fromLast)) {
          chosenId = lastTxn
          chosenPayload = fromLast
          console.log('✅ [initFromRoute] lastTxn snapshot seçildi:', chosenId)
        }

        /* -------------------------------------------------------
           🚫 SNAPSHOT YOK → BOŞ BAŞLA
        -------------------------------------------------------- */
        if (!chosenId || !chosenPayload) {
          console.log('⚪ [initFromRoute] Snapshot yok, boş başlatılıyor')

          this.header = {
            ...(this.header || {}),
            OrderHeaderID: orderId || lastTxn || crypto.randomUUID()
          }

          this.orders = []
          this.summaryRows = []

          // ❗ groupedRows'a DOKUNMA
          return
        }

        /* -------------------------------------------------------
           ✅ SNAPSHOT RESTORE (SAFE CLONE)
        -------------------------------------------------------- */
        this.header = {
          ...(chosenPayload.header || {}),
          OrderHeaderID: chosenId
        }

        const orders = Array.isArray(chosenPayload.orders)
          ? [...chosenPayload.orders]
          : []

        const summaryRows = Array.isArray(chosenPayload.summaryRows)
          ? [...chosenPayload.summaryRows]
          : orders

        this.orders = orders
        this.summaryRows = summaryRows

        // ❗ groupedRows hydrate edilmez, resetlenmez

        /* -------------------------------------------------------
           🔁 lastTxn SENKRON
        -------------------------------------------------------- */
        try {
          localStorage.setItem('bss_last_txn', chosenId)
        } catch (e) {
          console.warn('⚠️ bss_last_txn yazılamadı:', e)
        }

        /* -------------------------------------------------------
           🔁 ROUTE DÜZELTME (GEREKİRSE)
        -------------------------------------------------------- */
        if (router && orderId && orderId !== chosenId) {
          console.log('🔁 [initFromRoute] Route ID düzeltiliyor →', chosenId)
          await router.replace({
            name: 'order-entry',
            params: { orderHeaderID: chosenId }
          })
        }

        console.log(
          '✅ [initFromRoute] Restore tamam. Satır sayısı:',
          this.summaryRows.length
        )

      } catch (err) {
        console.error('❌ [initFromRoute] hata:', err)
      }
    }

    ,

    /* ===========================================================
       🆕 startNewOrder (v8.3 — FINAL & STABLE)
    =========================================================== */
    async startNewOrder({ $q }) {

      if (!this.schemaMap || !Object.keys(this.schemaMap).length) {
        this.initSchemaMap()
      }

      const headerId = crypto.randomUUID()

      let orderNumber = `LOCAL-${dayjs().format("YYMMDD-HHmmss")}`

      try {
        const res = await api.get("/order/new-number")
        if (res?.data?.OrderNumber) {
          orderNumber = res.data.OrderNumber
        }
      } catch {
        console.info('ℹ️ Backend order number yok, LOCAL kullanıldı')
      }

      this.mode = 'new'
      this.isControlledSubmit = false
      this.allowRouteLeaveOnce = false

      this.header = {
        OrderHeaderID: headerId,
        OrderNumber: orderNumber,
        OrderDate: new Date().toISOString().slice(0, 10),
        CurrAccCode: null,
        DocCurrencyCode: 'USD',
        PriceCurrencyCode: 'USD',
        PriceExchangeRate: 1
      }

      this.orders = []
      this.summaryRows = []

      // ✅ fingerprint bazlı sistem için reset
      this._lastSavedFingerprint = null

      // ✅ NEW draft hemen yazılır
      this.persistLocalStorage?.()

      return this.header
    }



    ,
    dedupeActiveLinesByCombo(lines) {
      const map = new Map()
      for (const ln of lines) {
        const key = buildComboKey({
          model: ln.ItemCode,
          renk: ln.ColorCode,
          renk2: ln.ItemDim2Code
        }, ln.ItemDim1Code)

        if (!map.has(key)) {
          ln.ComboKey = key
          map.set(key, ln)
          continue
        }

        const ex = map.get(key)
        ex.Qty1 = (Number(ex.Qty1) || 0) + (Number(ln.Qty1) || 0)

        // OrderLineID boşsa doldur (editte önemli)
        if (!ex.OrderLineID && ln.OrderLineID) ex.OrderLineID = ln.OrderLineID
      }
      return Array.from(map.values())
    }


    ,
    /* ===========================================================
       🧹 Core reset helper — sadece state'i sıfırlar
    =========================================================== */
    resetCoreState() {
      this.orders = []
      this.summaryRows = []
      this.groupedRows = []
      this.header = {}
      this.editingKey = null
      this.currentOrderId = null
    },resetForNewOrder() {
      // mevcut her şeyi temizle
      this.header = {
        OrderHeaderID: this.header?.OrderHeaderID || null,
        OrderDate: new Date().toISOString().slice(0,10),
        CurrAccCode: null,
        DocCurrencyCode: 'TRY',
        PriceCurrencyCode: 'TRY',
        // ihtiyaç duyduğun diğer default header alanları
      }

      this.orders = []
      this.summaryRows = []
      this.productCache = {}
      this.stockMap = {}

      this.setMode('new')
    }
    ,
    resetForEdit() {
      // EDIT modda grid temizlenmez — sadece UI state resetlenir
      this.editingKey = null
      this.groupedRows = []
      this.mode = 'edit'
    }

    ,markAsSaved() {
      try {
        this._lastSavedFingerprint = this._persistFingerprint()
        console.log('✅ markAsSaved → fingerprint senkron')
      } catch (e) {
        console.warn('⚠️ markAsSaved hata:', e)
      }
    }
    ,clearLocalSnapshot() {
      try {
        const id = this.header?.OrderHeaderID
        if (!id) return
        localStorage.removeItem(`bss_orderentry_data:${id}`)
        console.log('🧹 Local snapshot temizlendi:', id)
      } catch (e) {
        console.warn('⚠️ clearLocalSnapshot hata:', e)
      }
    },/* ===========================================================
   🧹 HARD CLEAN — ALL ORDERENTRY SNAPSHOTS
=========================================================== */
    clearAllOrderSnapshots () {
      Object.keys(localStorage)
        .filter(k =>
          k.startsWith('bss_orderentry_data:') ||
          k.startsWith('bss_orderentry_edit:')
        )
        .forEach(k => {
          console.log('🧹 snapshot silindi:', k)
          localStorage.removeItem(k)
        })

      localStorage.removeItem(this.getDraftKey)
      localStorage.removeItem('bss_active_new_header')
      localStorage.removeItem('bss_last_txn')
      this.activeNewHeaderId = null
    }




    ,
    /* ===========================================================
       🧹 Store Hard Reset — Submit Sonrası Temizlik (FIXED)
       - Grid, header, toplamlar, local state'ler sıfırlanır
       - persistKey / lastSnapshotKey NULL yapılmaz (config sabit kalır)
       - localStorage txn/snapshot temizliği güvenli yapılır
    =========================================================== */
    hardResetAfterSubmit() {
      try {
        // 🔑 mevcut id’yi yakala (local temizliği için)
        const id = this.header?.OrderHeaderID || null

        /* -------------------------------------------------------
           1) Grid ve satırlar
        -------------------------------------------------------- */
        this.orders = []
        this.summaryRows = []
        this.groupedRows = []

        /* -------------------------------------------------------
           2) Header & meta
        -------------------------------------------------------- */
        this.header = {}

        /* -------------------------------------------------------
           3) Mode & edit state
        -------------------------------------------------------- */
        this.mode = 'new'
        this.editingKey = null
        this.currentOrderId = null

        /* -------------------------------------------------------
           4) Snapshot / transaction meta
           ⚠️ persistKey / lastSnapshotKey store config → NULL YAPMA
        -------------------------------------------------------- */
        this.activeTransactionId = null
        this.submitted = false

        // fingerprint / debounce meta varsa sıfırla
        this._lastSavedFingerprint = null
        this._lastPersistFingerprint = null
        if (this._persistTimeout) {
          clearTimeout(this._persistTimeout)
          this._persistTimeout = null
        }

        /* -------------------------------------------------------
           5) LocalStorage temizlik (opsiyonel ama submit sonrası doğru)
        -------------------------------------------------------- */
        try {
          if (id) {
            localStorage.removeItem(`bss_orderentry_data:${id}`)
            localStorage.removeItem(`bss_orderentry_snapshot:${id}`)
          }
          localStorage.removeItem('bss_last_txn')
          localStorage.removeItem('bss_active_new_header')
        } catch (e) {
          console.warn('⚠️ hardResetAfterSubmit localStorage temizliği hata:', e)
        }

        console.log('🧹 Store resetlendi (submit sonrası).')
      } catch (err) {
        console.error('❌ hardResetAfterSubmit hata:', err)
      }
    }

    ,

    /* ===========================================================
       ✏️ openExistingForEdit (v12 — FINAL & CLEAN)
       -----------------------------------------------------------
       ✔ Backend authoritative (orderlist açılışı local'i dikkate almaz)
       ✔ mode=new → backend çağrısı YOK
       ✔ normalizeOrderLines → grpKey + bedenMap garanti
       ✔ isClosed varsa → view, yoksa → edit
       ✔ Form sync opsiyonel
       ✔ İlk açılışta snapshot yazılır (edit boyunca persist ile güncellenir)
    =========================================================== */
    async openExistingForEdit(
      orderId,
      { $q = null, form = null, productCache = null } = {}
    ) {
      // 🔑 schemaMap garanti
      if (!this.schemaMap || !Object.keys(this.schemaMap).length) {
        this.initSchemaMap?.()
      }

      if (!orderId) return false

      /* =======================================================
         🟦 NEW MODE — ASLA backend çağrısı yok
      ======================================================= */
      if (this.mode === 'new') {
        console.log('⚪ openExistingForEdit skip (mode=new)')
        return false
      }

      // productCache hem ref hem reactive olabilir → güvenli oku
      const pc =
        productCache?.value
          ? productCache.value
          : (productCache && typeof productCache === 'object' ? productCache : {})

      try {
        // geçici varsayım (sonra isClosed durumuna göre set edilecek)
        this.setMode?.('edit')
        const rulesReady = await this.ensureProductSizeMatchRules?.($q)
        if (!rulesReady) {
          $q?.notify?.({
            type: 'negative',
            message: 'Beden eşleme kuralları yüklenemedi.'
          })
          return false
        }

        /* =======================================================
           🔹 BACKEND — authoritative load
        ======================================================= */
        const res = await api.get(`/order/get/${orderId}`)
        const backend = res?.data

        if (!backend?.header) {
          throw new Error('Backend header yok')
        }

        // Editor ile aynı grpKey kararını verebilmek için
        // eksik model metadata'sını (kategori/ana-alt grup) cache'e al.
        const backendLines = Array.isArray(backend?.lines) ? backend.lines : []
        const modelCodes = [...new Set(
          backendLines
            .map(l => String(l?.ItemCode || l?.Model || '').trim())
            .filter(Boolean)
        )]
        const missingCodes = modelCodes.filter(code => !pc?.[code])
        if (missingCodes.length) {
          await Promise.all(
            missingCodes.map(async code => {
              try {
                const d = (await api.get('/product-detail', { params: { code } }))?.data || {}
                pc[code] = {
                  ...(pc[code] || {}),
                  ...d,
                  UrunAnaGrubu: d.UrunAnaGrubu || d.ProductGroup || d.ProductAtt01Desc || '',
                  UrunAltGrubu: d.UrunAltGrubu || d.ProductSubGroup || d.ProductAtt02Desc || '',
                  Kategori: d.Kategori || d.ProductAtt44Desc || d.YETISKIN_GARSON || d.YetiskinGarson || '',
                  ProductAtt01Desc: d.ProductAtt01Desc || d.UrunAnaGrubu || d.ProductGroup || '',
                  ProductAtt44Desc: d.ProductAtt44Desc || d.Kategori || d.YETISKIN_GARSON || d.YetiskinGarson || '',
                  YETISKIN_GARSON: d.YETISKIN_GARSON || d.YetiskinGarson || d.AskiliYan || '',
                  YetiskinGarson: d.YetiskinGarson || d.YETISKIN_GARSON || d.AskiliYan || '',
                  AskiliYan: d.AskiliYan || ''
                }
              } catch (e) {
                console.warn(`⚠ model detail alınamadı (${code})`, e)
              }
            })
          )
        }

        /* =======================================================
           🔹 HEADER — SADECE BACKEND
           (orderlist açılışında local merge YOK)
        ======================================================= */
        this.header = {
          ...backend.header,
          OrderHeaderID: backend.header.OrderHeaderID || orderId
        }

        /* =======================================================
           🔹 NORMALIZE LINES (TEK KAYNAK)
           normalizeOrderLines şu alanları üretmeli:
             ✔ row.grpKey
             ✔ row.bedenMap[grpKey]
             ✔ row.isClosed (boolean)
        ======================================================= */
        const normalized = this.normalizeOrderLines(
          backendLines,
          this.header.DocCurrencyCode || 'USD',
          pc
        )

        this.orders = Array.isArray(normalized) ? normalized : []
        this.summaryRows = [...this.orders]

        // 💾 Snapshot for email comparison (v3.5)
        this.originalHeader = JSON.parse(JSON.stringify(this.header))
        this.originalLines = JSON.parse(JSON.stringify(this.summaryRows))

        /* =======================================================
           🔹 MODE KARARI (BACKEND SATIRLARI ÜZERİNDEN)
           - herhangi bir isClosed=true → view
           - değilse → edit
        ======================================================= */
        const hasClosedLine = (this.summaryRows || []).some(r => r?.isClosed === true)
        this.setMode?.(hasClosedLine ? 'view' : 'edit')

        /* =======================================================
           🔹 FORM SYNC (opsiyonel)
        ======================================================= */
        if (form) {
          Object.assign(form, this.header)
        }

        /* =======================================================
           🔹 LOCAL SNAPSHOT (edit boyunca tutulacak temel)
           - Açılışta snapshot yaz
           - Sonraki değişikliklerde zaten persistLocalStorage çağrıları var
        ======================================================= */
        this.persistLocalStorage?.()
        try {
          localStorage.setItem('bss_last_txn', String(orderId))
        } catch {}

        console.log('✅ openExistingForEdit OK:', {
          id: orderId,
          rows: this.summaryRows.length,
          mode: this.mode,
          hasClosedLine
        })

        return true
      } catch (err) {
        console.error('❌ openExistingForEdit hata:', err)

        // new değilse uyar
        if (this.mode !== 'new') {
          $q?.notify?.({
            type: 'negative',
            message: 'Sipariş yüklenemedi'
          })
        }

        return false
      }
    }

    ,
    /* ===========================================================
       ♻️ hydrateFromLocalStorage (v5.5 — FIXED & CLEAN)
       -----------------------------------------------------------
       - Tek assign (double overwrite YOK)
       - groupedRows hydrate edilmez
       - mode ASLA set edilmez
       - header + rows güvenli restore
    =========================================================== */
    async hydrateFromLocalStorage(orderId, log = false) {if (this.mode === 'new') {
      return this.hydrateFromLocalStorageIfExists()
    }

      try {
        const key = `bss_orderentry_data:${orderId}`
        const payload = JSON.parse(localStorage.getItem(key) || 'null')

        if (!payload) {
          log && console.log('ℹ️ hydrate → snapshot yok:', orderId)
          return null
        }

        // 🔑 source bilgisi (mode set edilmez)
        this.source = payload.source || 'local'

        /* -------------------------------------------------------
           MSSQL tarih helper’ları
        -------------------------------------------------------- */
        const safeDateTime = v => {
          if (!v) return null
          const d = dayjs(v)
          return d.isValid() ? d.format('YYYY-MM-DD HH:mm:ss') : null
        }

        const safeDateOnly = v => {
          if (!v) return null
          const d = dayjs(v)
          return d.isValid() ? d.format('YYYY-MM-DD') : null
        }

        const safeTimeOnly = v => {
          if (!v) return null
          const d = dayjs(v)
          return d.isValid() ? d.format('HH:mm:ss') : null
        }

        /* -------------------------------------------------------
           HEADER
        -------------------------------------------------------- */
        this.header = {
          ...(payload.header || {}),
          OrderHeaderID: payload.header?.OrderHeaderID ?? orderId,
          OrderNumber  : payload.header?.OrderNumber  ?? null
        }

        const h = this.header
        h.CreatedDate             = safeDateTime(h.CreatedDate)
        h.LastUpdatedDate         = safeDateTime(h.LastUpdatedDate)
        h.CreditableConfirmedDate = safeDateTime(h.CreditableConfirmedDate)
        h.OrderDate               = safeDateOnly(h.OrderDate)
        h.OrderTime               = safeTimeOnly(h.OrderTime)
        this.header = h

        /* -------------------------------------------------------
           ROWS (TEK KAYNAK)
        -------------------------------------------------------- */
        const orders = Array.isArray(payload.orders)
          ? payload.orders
          : []

        this.orders = orders

        this.summaryRows = Array.isArray(payload.summaryRows)
          ? payload.summaryRows
          : orders

        // ❌ groupedRows hydrate edilmez (computed olmalı)
        this.groupedRows = []

        /* -------------------------------------------------------
           SNAPSHOT ÖZET
        -------------------------------------------------------- */
        const output = {
          type       : payload.submitted === true ? 'submitted' : 'draft',
          source     : this.source,
          headerId   : orderId,
          orderNumber: this.header?.OrderNumber ?? null,
          rows       : this.summaryRows.length,
          submitted  :
            payload.submitted === true ||
            payload.header?.IsSubmitted === true
        }

        log && console.log('♻️ hydrate sonuc (FIXED):', output)
        return output

      } catch (err) {
        console.warn('⚠️ hydrateFromLocalStorage hata:', err)
        return null
      }
    }


    ,
    hydrateFromLocalStorageIfExists() {
      try {
        let raw = null

        if (this.mode === 'new') {
          raw = localStorage.getItem(this.getDraftKey) // ✅
        }

        if (this.mode === 'edit') {
          const key = this.getEditKey // ✅
          if (key) raw = localStorage.getItem(key)
        }

        if (!raw) return false

        const payload = JSON.parse(raw)

        this.header = payload.header || {}
        this.orders = payload.orders || []
        this.summaryRows = payload.summaryRows || this.orders

        console.log('♻️ hydrate OK:', this.mode)
        return true

      } catch (err) {
        console.warn('hydrateFromLocalStorageIfExists hata:', err)
        return false
      }
    }

    ,

    /* ===========================================================
       🔀 mergeOrders (local + backend)
       normalizeISO → kaldırıldı
       safe MSSQL helpers eklendi
    =========================================================== */
    mergeOrders(local, backend, preferLocal = true) {
      if (!backend && !local) return { header: {}, orders: [] }

      const safeMerge = (base = {}, override = {}) => {
        const out = { ...base }
        for (const [k, v] of Object.entries(override || {})) {
          if (v === undefined || v === null) continue
          if (typeof v === 'string' && v.trim() === '') continue
          out[k] = v
        }
        return out
      }

      // Header merge
      const header = safeMerge(backend?.header || {}, local?.header || {})
      header.OrderHeaderID =
        backend?.header?.OrderHeaderID ||
        local?.header?.OrderHeaderID ||
        header.OrderHeaderID ||
        null

      const getKey = (r) =>
        (r.OrderLineID ||
          `${r.model || r.ItemCode}_${r.renk || r.ColorCode}_${r.renk2 || r.ColorCode2}`
        ).toString().toUpperCase()

      const map = new Map()

      // Backend satırları
      for (const b of (backend?.lines || backend?.orders || [])) {
        map.set(getKey(b), { ...b, _src: 'backend' })
      }

      // Local satırları merge et
      for (const l of (local?.orders || [])) {
        const k = getKey(l)
        if (map.has(k)) {
          const merged = safeMerge(map.get(k), l)
          merged._src = preferLocal ? 'local' : 'backend'
          map.set(k, merged)
        } else {
          map.set(k, { ...l, _src: 'local-only' })
        }
      }

      const mergedOrders = Array.from(map.values())
      console.log(`🧩 mergeOrders → ${mergedOrders.length} satır birleşti (ID:${header.OrderHeaderID})`)

      // ====================================================
      // 🕒 HEADER TARİHLERİNİ MSSQL FORMATINA NORMALİZE ET
      // ====================================================
      const safeDateTime = v => {
        if (!v) return null
        const d = dayjs(v)
        return d.isValid() ? d.format("YYYY-MM-DD HH:mm:ss") : null
      }

      const safeDateOnly = v => {
        if (!v) return null
        const d = dayjs(v)
        return d.isValid() ? d.format("YYYY-MM-DD") : null
      }

      const safeTimeOnly = v => {
        if (!v) return null
        const d = dayjs(v)
        return d.isValid() ? d.format("HH:mm:ss") : null
      }

      header.CreatedDate = safeDateTime(header.CreatedDate)
      header.LastUpdatedDate = safeDateTime(header.LastUpdatedDate)
      header.CreditableConfirmedDate = safeDateTime(header.CreditableConfirmedDate)

      header.OrderDate = safeDateOnly(header.OrderDate)
      header.OrderTime = safeTimeOnly(header.OrderTime)

      return { header, orders: mergedOrders }

    }
    ,

    markRowSource(row) {
      if (row._src === 'local-only') return '🟠 Offline'
      if (row._src === 'local') return '🔵 Local'
      return '⚪ Backend'
    }
    ,

    /* ===========================================================
       🔄 mergeAndPersistBackendOrder (edit mode)
    =========================================================== */
    mergeAndPersistBackendOrder(orderId, backendPayload) {
      const key = `bss_orderentry_data:${orderId}`
      const localPayload = JSON.parse(localStorage.getItem(key) || 'null')

      const merged = this.mergeOrders(localPayload, backendPayload, true)

      localStorage.setItem(key, JSON.stringify({
        ...merged,
        source: 'db',
        mode: 'edit',
        updatedAt: new Date().toISOString()
      }))

      console.log(`💾 mergeAndPersistBackendOrder → ${orderId} localStorage’a yazıldı`)
    }
    ,

    persistLocalStorage() {
      try {
        if (this.preventPersist || this._uiBusy) return

        const payload = {
          mode: this.mode,
          header: toRaw(this.header || {}),
          orders: toRaw(this.orders || []),
          summaryRows: toRaw(this.summaryRows || []),
          updatedAt: new Date().toISOString()
        }

        /* ===============================
           🟢 NEW MODE — GLOBAL TEK TASLAK
        =============================== */
        if (this.mode === 'new') {
          localStorage.setItem(this.getDraftKey, JSON.stringify(payload))

          // 🔒 sadece aktif new header bilgisi
          this.setActiveNewHeader?.(this.header?.OrderHeaderID)

          return
        }

        /* ===============================
           🔵 EDIT MODE — ID BAZLI
        =============================== */
        if (this.mode === 'edit') {
          const key = this.getEditKey
          if (!key) return
          localStorage.setItem(key, JSON.stringify(payload))
        }

      } catch (e) {
        console.warn('persistLocalStorage error:', e)
      }
    }



    ,
    clearEditSnapshotIfExists() {
      if (this.mode !== 'edit') return

      const key = this.getEditKey // ✅
      if (!key) return

      localStorage.removeItem(key)
      console.log('🧹 EDIT snapshot silindi:', key)
    }




    ,/* ===========================================================
   🧠 _persistFingerprint — kritik state’leri tek stringe indirger
   - X3: orders+header yetmez → mode, summaryRows, id/no, map’ler dahil
=========================================================== */
    _persistFingerprint() {
      // 🔹 orders: çok büyürse pahalı olabilir ama snapshot tutarlılığı için önemli
      // (istersen burada sadece length + rowKey listesi gibi optimize ederiz)
      const ordersSnap = JSON.stringify(this.orders || [])

      // 🔹 header: sadece kritik alanları al (tam header yerine daha stabil)
      const h = this.header || {}
      const headerSnap = JSON.stringify({
        OrderHeaderID: h.OrderHeaderID || '',
        OrderNumber: h.OrderNumber || '',
        CurrAccCode: h.CurrAccCode || '',
        DocCurrencyCode: h.DocCurrencyCode || '',
        ExchangeRate: h.ExchangeRate ?? null
      })

      // 🔹 summaryRows: hash yerine şimdilik “length + rowKey listesi” (hafif + etkili)
      const sr = Array.isArray(this.summaryRows) ? this.summaryRows : []
      const summaryMeta = JSON.stringify({
        len: sr.length,
        keys: sr.map(r => this.getRowKey?.(r) || r?.key || r?.id || '').filter(Boolean)
      })

      // 🔹 comboLineIds / lineIdMap gibi kritik map’ler
      // (sende hangisi varsa onu otomatik topluyoruz)
      const mapSnap = JSON.stringify({
        lineIdMap: this.lineIdMap || null,
        comboLineIds: this.comboLineIds || null,
        comboLineIdMap: this.comboLineIdMap || null,
        comboLineIdSet: this.comboLineIdSet ? Array.from(this.comboLineIdSet) : null
      })

      // 🔹 mode
      const modeSnap = String(this.mode || 'new')

      // ✅ Tek fingerprint
      return `${modeSnap}|${headerSnap}|${summaryMeta}|${mapSnap}|${ordersSnap}`
    }
    ,
    /* ===========================================================
       🕒 _safePersistDebounced — snapshot değişmediği sürece yazmaz (X3)
       - fingerprint: mode + header(id/no) + summaryRows meta + lineIdMap/combo + orders
    =========================================================== */
    _safePersistDebounced(delay = 1200) {
      clearTimeout(this._persistTimeout)

      this._persistTimeout = setTimeout(() => {
        try {
          // ✅ Persist guard’ları (varsa)
          if (this.preventPersist) return
          if (this._uiBusy) return

          const fp = this._persistFingerprint()

          if (fp === this._lastPersistFingerprint) {
            return
          }

          this._lastPersistFingerprint = fp

          this.persistLocalStorage()
          console.log(`🕒 Otomatik LocalStorage senkron (${this.orders?.length || 0} satır).`)
        } catch (err) {
          console.warn('⚠️ Debounce persist hata:', err)
        }
      }, delay)
    }

    ,

    /* ===========================================================
       💰 fetchMinPrice — model/pb için min fiyat
    =========================================================== */
    async fetchMinPrice(model, currency, $q) {
      try {
        const res = await api.get('/min-price', {
          params: { model, currency }
        })
        const data = res?.data || {}
        console.log('💰 [store.fetchMinPrice] yanıt:', data)
        return {
          price: Number(data.price || 0),
          rateToTRY: Number(data.rateToTRY || 1),
          priceTRY: Number(data.priceTRY || 0)
        }
      } catch (err) {
        console.error('❌ [store.fetchMinPrice] Min fiyat alınamadı:', err)
        $q?.notify?.({
          type: 'warning',
          message: 'Min. fiyat bilgisi alınamadı, kontrol atlandı ⚠️',
          position: 'top-right'
        })
        return { price: 0, rateToTRY: 1, priceTRY: 0 }
      }
    }
    ,
    applyCurrencyToLines(newPB) {
      if (!newPB) return

      // 🔹 Header
      if (this.header) {
        this.header.DocCurrencyCode = newPB
        this.header.PriceCurrencyCode = newPB
      }

      // 🔹 Lines
      if (Array.isArray(this.orders)) {
        this.orders = this.orders.map(r => ({
          ...r,
          pb: newPB,
          DocCurrencyCode: newPB,
          PriceCurrencyCode: newPB
        }))
      }

      // 🔹 Summary
      if (Array.isArray(this.summaryRows)) {
        this.summaryRows = this.summaryRows.map(r => ({
          ...r,
          pb: newPB,
          DocCurrencyCode: newPB,
          PriceCurrencyCode: newPB
        }))
      }

      // ❗ totalAmount SET ETME
      // ✔️ TEK MERKEZ
      this.updateHeaderTotals?.()
    }
    ,

    /* ===========================================================
       💠 HEADER SET & CURRENCY PROPAGATION
    =========================================================== */
    setHeaderFields(fields, opts = {}) {
      const {
        applyCurrencyToLines = false,
        immediatePersist = false
      } = opts

      // 1️⃣ HEADER
      this.header = {
        ...(this.header || {}),
        ...fields
      }

      // 2️⃣ SATIRLARA GERÇEKTEN YAY
      if (applyCurrencyToLines && Array.isArray(this.summaryRows)) {
        this.summaryRows = this.summaryRows.map(r => ({
          ...r,
          pb: fields.DocCurrencyCode ?? r.pb,
          DocCurrencyCode: fields.DocCurrencyCode ?? r.DocCurrencyCode,
          PriceCurrencyCode: fields.PriceCurrencyCode ?? fields.DocCurrencyCode ?? r.PriceCurrencyCode
        }))
      }

      // 3️⃣ STORE ORDERS REFERANSI
      this.orders = [...this.summaryRows]


      // 4️⃣ PERSIST
      if (immediatePersist) {
        this.persistLocalStorage('header-change')
      }
    }

    ,

    applyHeaderCurrencyToOrders() {
      if (!Array.isArray(this.orders)) return

      const doc  = this.header?.DocCurrencyCode ?? null
      const prc  = this.header?.PriceCurrencyCode ?? null
      const rate = this.header?.PriceExchangeRate ?? null

      let cnt = 0

      for (const r of this.orders) {
        if (doc)  r.DocCurrencyCode   = doc
        if (prc)  r.PriceCurrencyCode = prc
        if (rate != null) r.PriceExchangeRate = rate
        cnt++
      }

      console.log(`💱 ${cnt} satırda PB güncellendi → Doc:${doc} Price:${prc} Rate:${rate}`)
    }


    ,/* ===========================================================
   📸 saveSnapshot — küçük debug snapshot
=========================================================== */
    saveSnapshot(tag = 'snapshot') {
      try {
        const id = this.header?.OrderHeaderID
        if (!id) return

        const key = `bss_orderentry_snapshot:${id}`

        const snap = {
          tag,
          mode: this.mode,
          orders: toRaw(this.orders || []),
          header: toRaw(this.header || {}),
          savedAt: dayjs().toISOString()
        }

        localStorage.setItem(key, JSON.stringify(snap))
        console.log(`📸 Snapshot kaydedildi [${key}]`)
      } catch (err) {
        console.warn('⚠️ saveSnapshot hata:', err)
      }
    }
    ,

    /* ===========================================================
       ♻️ loadFromStorage — eski generic persist için
    =========================================================== */
    loadFromStorage(force = false) {
      try {
        const raw = localStorage.getItem(this.getPersistKey)
        if (!raw) {
          console.info('ℹ️ LocalStorage boş, grid başlatılmadı.')
          return false
        }

        if (!force && this.mode === 'edit') {
          console.info('⚠️ Edit modda local restore atlandı (force=false).')
          return false
        }

        const data = JSON.parse(raw)

        this.orders = Array.isArray(data.orders) ? data.orders : []
        this.header = data.header || {}
        this.currentOrderId = data.currentOrderId || null
        this.selectedCustomer = data.selectedCustomer || null

        // 🔧 Temiz ID
        this.header.OrderHeaderID = data.header?.OrderHeaderID || null

        this.mode = data.mode || 'new'
        this.lastSavedAt = data.savedAt || null

        console.log(`♻️ Storage yüklendi • txn:${this.header.OrderHeaderID} (${this.orders.length} satır)`)

        // Header PB -> satırlara
        this.applyHeaderCurrencyToOrders()
        this._safePersistDebounced(200)

        return data
      } catch (err) {
        console.warn('⚠️ localStorage okuma hatası:', err)
        return false
      }
    }
    ,

    clearStorage() {
      try {
        localStorage.removeItem(this.getPersistKey)
        console.log(`🗑️ LocalStorage temizlendi [${this.getPersistKey}]`)
      } catch (err) {
        console.warn('⚠️ clearStorage hatası:', err)
      }
    }
    ,
    clearNewDraft() {
      localStorage.removeItem(this.getDraftKey) // ✅
      localStorage.removeItem('bss_last_txn')
      localStorage.removeItem('bss_active_new_header')
      this.activeNewHeaderId = null
      console.log('🧹 NEW taslak temizlendi')
    }

    ,
// ===========================================================
// 🔹 isSameCombo — STORE LEVEL (TEK KAYNAK)
//  - model ZORUNLU eşleşir
//  - renk / renk2 boşsa → joker
// ===========================================================
    isSameCombo(a, b) {
      if (!a || !b) return false

      const n = v => (v == null ? '' : String(v).trim().toUpperCase())

      const A = { model: n(a.model), renk: n(a.renk), renk2: n(a.renk2) }
      const B = { model: n(b.model), renk: n(b.renk), renk2: n(b.renk2) }

      if (!A.model || !B.model) return false

      const renkOk  = (A.renk  === B.renk)  || !A.renk  || !B.renk
      const renk2Ok = (A.renk2 === B.renk2) || !A.renk2 || !B.renk2

      return A.model === B.model && renkOk && renk2Ok
    },



// ===========================================================
// 🔹 saveOrUpdateRowUnified (v6.6 — COMBO SAFE + FIXED STOCK+PRICE + UI)
//  - v6.5 korunur (stok+min fiyat + this.loadProductSizes)
//  - ✅ NEW MODE: dupIdx artık _deleteSignal satırlarını BAŞTAN hariç tutar
//  - EDIT MODE: sameCombo → update, combo değişti → delete + insert (korundu)
//  - lineIdMap koruması korunur
// ===========================================================
    async saveOrUpdateRowUnified({
                                   form,
                                   recalcVat = null,
                                   resetEditor = null,
                                   stockMap = null,
                                   loadProductSizes = null,
                                   $q = null
                                 }) {
      try {
        console.log('🔥 saveOrUpdateRowUnified v6.6', {
          model: form?.model,
          mode: this.mode,
          editingKey: this.editingKey
        })

        const getKey =
          typeof this.getRowKey === 'function'
            ? this.getRowKey
            : (r => r?.clientKey || r?.id || r?.OrderLineID)

        const rows = Array.isArray(this.summaryRows)
          ? [...this.summaryRows]
          : []

        /* =======================================================
           1️⃣ ZORUNLU KONTROLLER
        ======================================================= */
        if (!form?.model) {
          $q?.notify?.({ type: 'warning', message: 'Model seçiniz' })
          return false
        }

        if (!form.pb) {
          form.pb = this.header?.DocCurrencyCode || 'USD'
        }

        /* =======================================================
           2️⃣ STOK KONTROLÜ (FIXED)
           - stok guard’dan önce this.loadProductSizes(form,true,$q)
           - opsiyonel callback loadProductSizes(true)
           - tek dialog + doğru await
        ======================================================= */

        // ✅ store fonksiyonu
        try {
          if (typeof this.loadProductSizes === 'function') {
            await this.loadProductSizes(form, true, $q)
          }
        } catch (err) {
          console.warn('⚠ this.loadProductSizes hata:', err)
        }

        // ✅ dışarıdan callback geldiyse
        try {
          if (typeof loadProductSizes === 'function') {
            await loadProductSizes(true)
          }
        } catch (err) {
          console.warn('⚠ loadProductSizes hata:', err)
        }

        const stockMapLocal = stockMap?.value || stockMap || {}
        const bedenLabels = form.bedenLabels || []
        const bedenValues = form.bedenler || []

        const overLimit = []
        for (let i = 0; i < bedenLabels.length; i++) {
          const rawLbl = String(bedenLabels[i] ?? '')
          const lbl = rawLbl.trim() === '' ? ' ' : rawLbl
          const hasExactKey = Object.prototype.hasOwnProperty.call(stockMapLocal || {}, lbl)
          const hasTrimKey = Object.prototype.hasOwnProperty.call(stockMapLocal || {}, rawLbl.trim())
          const stokRaw = hasExactKey
            ? stockMapLocal?.[lbl]
            : hasTrimKey
              ? stockMapLocal?.[rawLbl.trim()]
              : undefined
          const stok = Number(stokRaw ?? 0)
          const girilen = Number(bedenValues?.[i] ?? 0)

          // Stok 0 veya stok kaydı yokken giriş yapılırsa da uyarı ver.
          if (girilen > 0 && girilen > stok) {
            overLimit.push({
              beden: lbl,
              stok,
              girilen,
              stokKaydiVar: hasExactKey || hasTrimKey
            })
          }
        }

        if (overLimit.length && $q) {
          const msg = overLimit
            .map(x => `• <b>${x.beden}</b>: ${x.girilen} (Stok: ${x.stokKaydiVar ? x.stok : 'kayıt yok'})`)
            .join('<br>')

          const stokOK = await new Promise(resolve => {
            $q.dialog({
              title: 'Stok Uyarısı',
              message: `Bazı bedenlerde stoktan fazla giriş yaptınız:<br><br>${msg}`,
              html: true,
              ok: { label: 'Devam', color: 'primary' },
              cancel: { label: 'İptal', color: 'negative' }
            })
              .onOk(() => resolve(true))
              .onCancel(() => resolve(false))
              .onDismiss(() => resolve(false))
          })

          if (!stokOK) return false
        }

        /* =======================================================
           3️⃣ FİYAT (MIN) KONTROLÜ (FIXED)
        ======================================================= */
        let fiyatOK = true
        try {
          let minFiyat = 0

          if (typeof this.fetchMinPrice === 'function') {
            const p = await this.fetchMinPrice(form.model, form.pb, $q)
            minFiyat = Number(p?.price || 0)
          } else if (Number(form.minFiyat || 0) > 0) {
            minFiyat = Number(form.minFiyat)
          }

          const girilen = Number(form.fiyat || 0)

          if (minFiyat > 0 && girilen > 0 && girilen < minFiyat && $q) {
            fiyatOK = await new Promise(resolve => {
              $q.dialog({
                title: 'Fiyat Uyarısı',
                message:
                  `<b>Min. Fiyat:</b> ${minFiyat} ${form.pb}<br>` +
                  `<b>Girdiğiniz:</b> ${girilen} ${form.pb}`,
                html: true,
                ok: { label: 'Devam', color: 'primary' },
                cancel: { label: 'İptal', color: 'negative' }
              })
                .onOk(() => resolve(true))
                .onCancel(() => resolve(false))
                .onDismiss(() => resolve(false))
            })
          }
        } catch (err) {
          console.warn('⚠ Min fiyat hata:', err)
        }
        if (!fiyatOK) return false

        /* =======================================================
           4️⃣ TOPLAM HESABI
        ======================================================= */
        const adet = (form.bedenler || []).reduce((a, b) => a + Number(b || 0), 0)
        form.adet = adet
        form.tutar = Number((adet * Number(form.fiyat || 0)).toFixed(2))

        const newRow = toSummaryRowFromForm(form)
        if (!newRow) {
          $q?.notify?.({
            type: 'negative',
            message: 'Beden grubu eşleşmesi bulunamadı.'
          })
          return false
        }

        /* =======================================================
           5️⃣ EDIT MODE (editingKey ZORUNLU)
        ======================================================= */
        if (this.editingKey) {
          const idx = rows.findIndex(r => getKey(r) === this.editingKey)
          if (idx === -1) {
            this.editingKey = null
            resetEditor?.(true)
            return false
          }

          const prev = rows[idx]

          if (this.isRowLocked?.(prev)) {
            $q?.notify?.({ type: 'warning', message: 'Satır kapalı' })
            this.editingKey = null
            resetEditor?.(true)
            return false
          }

          // ✅ kritik: store-level
          const sameCombo = this.isSameCombo(prev, newRow)

          const preservedLineIdMap =
            (prev?.lineIdMap && typeof prev.lineIdMap === 'object')
              ? { ...prev.lineIdMap }
              : (newRow?.lineIdMap && typeof newRow.lineIdMap === 'object')
                ? { ...newRow.lineIdMap }
                : {}

          /* ===== SAME COMBO → UPDATE ===== */
          if (sameCombo) {
            rows[idx] = {
              ...prev,
              ...newRow,
              _dirty: true,
              id: prev.id,
              OrderLineID: prev.OrderLineID || null,
              lineIdMap: preservedLineIdMap
            }

            this.summaryRows = rows
            this.orders = rows

            this.updateHeaderTotals?.()
            this.persistLocalStorage?.()

            this.editingKey = null
            resetEditor?.(true)
            recalcVat?.()

            $q?.notify?.({ type: 'positive', message: 'Satır güncellendi' })
            return true
          }

          /* ===== COMBO CHANGED → DELETE + INSERT ===== */
          const grpKey =
            prev?.grpKey ||
            Object.keys(prev?.bedenMap || {})[0] ||
            'tak'

          const emptyMap = {}
          const srcMap =
            (prev?.bedenMap?.[grpKey] && typeof prev.bedenMap[grpKey] === 'object')
              ? prev.bedenMap[grpKey]
              : (preservedLineIdMap && typeof preservedLineIdMap === 'object')
                ? preservedLineIdMap
                : null

          if (srcMap) {
            for (const beden of Object.keys(srcMap)) emptyMap[beden] = 0
          } else {
            emptyMap['STD'] = 0
          }

          const deleteRow = {
            ...prev,
            id: `DEL::${prev.id || prev.OrderLineID || crypto.randomUUID()}`,
            _deleteSignal: true,
            adet: 0,
            Qty1: 0,
            tutar: 0,
            ComboKey: '',

            OrderLineID: prev.OrderLineID || null,

            grpKey,
            bedenMap: { [grpKey]: emptyMap },
            lineIdMap: preservedLineIdMap,
            comboLineIds: { ...(prev.comboLineIds || {}) }
          }

          const insertedRow = {
            ...newRow,
            _dirty: true,
            id: crypto.randomUUID(),
            OrderLineID: null,
            lineIdMap: {}
          }

          rows.splice(idx, 1, insertedRow)

          this.summaryRows = rows
          this.orders = [...rows, deleteRow]

          this.updateHeaderTotals?.()
          this.persistLocalStorage?.()

          this.editingKey = null
          resetEditor?.(true)
          recalcVat?.()

          $q?.notify?.({ type: 'positive', message: 'Kombinasyon değişti' })
          return true
        }

        /* =======================================================
           6️⃣ NEW MODE (MERGE / INSERT) — COMBO SAFE
           - aynı combo → bedenMap merge (satır sayısı artmaz)
           - farklı combo → yeni satır
           - ✅ FIX: _deleteSignal satırlarını dup aramasında hariç tut
        ======================================================= */
        const dupIdx = rows.findIndex(r =>
          !r?._deleteSignal &&
          this.isSameCombo(r, newRow)
        )

        // helper: bedenMap çıkar (gruplu ya da düz)
        const extractMap = (row) => {
          const grpKey =
            row?.grpKey ||
            Object.keys(row?.bedenMap || {})[0] ||
            'GENEL'

          const grouped = row?.bedenMap?.[grpKey]
          const flat = (row?.bedenMap && typeof row.bedenMap === 'object' && !grouped)
            ? row.bedenMap
            : null

          return { grpKey, map: (grouped || flat || {}) }
        }

        if (dupIdx !== -1) {
          const prev = rows[dupIdx]

          // delete satırına merge yapma (ek güvenlik)
          if (prev?._deleteSignal !== true) {
            const { grpKey: prevGrp, map: prevMap } = extractMap(prev)
            const { grpKey: newGrp, map: newMap } = extractMap(newRow)

            // hangi grpKey kullanılacak?
            const grpKey = newRow?.grpKey || prevGrp || newGrp || 'GENEL'

            // MERGE: bedenleri topluyoruz (override değil)
            const merged = { ...(prevMap || {}) }
            for (const [k, v] of Object.entries(newMap || {})) {
              const beden = (k == null || String(k).trim() === '')
                ? ' '
                : normalizeBedenLabel(String(k))
              merged[beden] = Number(merged[beden] || 0) + Number(v || 0)
            }

            // toplam adet/tutar recalc
            const totalAdet = Object.values(merged).reduce((a, b) => a + Number(b || 0), 0)
            const price = Number(newRow?.fiyat ?? prev?.fiyat ?? 0)
            const totalTutar = Number((totalAdet * price).toFixed(2))

            rows[dupIdx] = {
              ...prev,
              ...newRow,
              _dirty: true,

              // kritik korumalar
              id: prev.id,
              OrderLineID: prev.OrderLineID || null,
              lineIdMap: { ...(prev.lineIdMap || {}) },

              // MERGED bedenMap
              grpKey,
              bedenMap: { [grpKey]: merged },

              // adet/tutar
              adet: totalAdet,
              tutar: totalTutar,

              updatedAt: dayjs().toISOString()
            }

            this.summaryRows = rows
            this.orders = rows

            this.updateHeaderTotals?.()
            this.persistLocalStorage?.()
            resetEditor?.(true)
            recalcVat?.()

            $q?.notify?.({ type: 'positive', message: 'Aynı kombinasyon bulundu, bedenler birleştirildi' })
            return true
          }
        }

        // dup yoksa (veya dup delete satırıydı) → yeni satır
        rows.push({
          ...newRow,
          _dirty: true,
          id: newRow.id || crypto.randomUUID(),
          OrderLineID: null,
          lineIdMap: { ...(newRow.lineIdMap || {}) }
        })

        this.summaryRows = rows
        this.orders = rows

        this.updateHeaderTotals?.()
        this.persistLocalStorage?.()
        resetEditor?.(true)
        recalcVat?.()

        $q?.notify?.({ type: 'positive', message: 'Yeni satır eklendi' })
        return true

      } catch (err) {
        console.error('❌ saveOrUpdateRowUnified:', err)
        $q?.notify?.({ type: 'negative', message: 'Satır kaydı başarısız' })
        return false
      }
    }




    ,

    /* ===========================================================
       🔄 setTransaction — yeni transaction ID set et
    =========================================================== */
    setTransaction(id, autoResume = true) {
      if (!id) return

      // 🔧 temiz ID
      this.header.OrderHeaderID = id

      localStorage.setItem('bss_last_txn', id)
      console.log('🔄 Transaction değiştirildi:', id)

      if (autoResume) {
        const hasData = Array.isArray(this.orders) && this.orders.length > 0
        if (!hasData) {
          const ok = this.hydrateFromLocalStorage(id,true)
          if (ok) console.info('📦 Local kayıt geri yüklendi (boş grid için).')
        } else {
          console.log('🚫 Grid dolu, auto-resume atlandı (mevcut satırlar korundu).')
        }
      }
    }
    ,


    /* ===========================================================
       🧹 clearTransaction — sadece NEW MODE taslaklarını temizler
    =========================================================== */
    clearTransaction() {
      try {
        const id = this.header?.OrderHeaderID
        if (id) {
          localStorage.removeItem(`bss_orderentry_data:${id}`)
        }

        this.orders = []
        this.summaryRows = []
        this.groupedRows = []
        this.header = {}
        this.mode = 'new'

        localStorage.removeItem('bss_last_txn')

        console.log('🧹 Transaction temizlendi')
      } catch (err) {
        console.warn('⚠️ clearTransaction hata:', err)
      }
    }
    ,


    // =======================================================
    // 🔒 KİLİT KONTROLÜ — Sadece EDIT modunda, backend satırı
    // =======================================================
    isRowLocked(row) {
      if (!row) return false
      // Sadece edit modunda,
      // ve backend'den gelen gerçek OrderLineID varsa,
      // ve IsClosed=1 ise satır kilitli
      return (
        this.mode === 'edit' &&
        !!row.OrderLineID &&
        row.isClosed === true
      )
    },


    findExistingIndexByForm(form) {
      return this.orders.findIndex(r => this.isSameCombo(r, form))
    },

    addRow(row) {
      if (!row) return

      const existingIndex = this.orders.findIndex(r => {
        const sameId = r.id && row.id && r.id === row.id
        const sameCombo = this.isSameCombo(r, row)
        return sameId || sameCombo
      })

      if (existingIndex !== -1) {
        const old = this.orders[existingIndex]
        this.orders[existingIndex] = {
          ...old,
          adet: Number(row.adet ?? old.adet ?? 0),
          fiyat: Number(row.fiyat ?? old.fiyat ?? 0),
          tutar: Number(row.fiyat ?? old.fiyat ?? 0) * Number(row.adet ?? old.adet ?? 0),
          ItemDim1Code: row.ItemDim1Code || old.ItemDim1Code,
          aciklama: row.aciklama || old.aciklama,
          updatedAt: dayjs().toISOString()
        }
        console.log(`⚠️ Aynı kombinasyon bulundu, satır güncellendi: ${row.model} ${row.renk || ''} ${row.renk2 || ''}`)
      } else {
        this.orders.push(toRaw(row))
        console.log(`➕ Yeni kombinasyon eklendi: ${row.model} ${row.renk || ''} ${row.renk2 || ''}`)
      }

      this.persistLocalStorage()
      this.saveSnapshot('after-add')
    },

    updateRow(index, patch) {
      if (index < 0 || index >= this.orders.length) return
      this.orders[index] = {
        ...this.orders[index],
        ...toRaw(patch),
        updatedAt: dayjs().toISOString()
      }
      this.persistLocalStorage()
      this.saveSnapshot('after-update')
      console.log(`✏️ Satır güncellendi (store): #${index}`)
    },


    removeRow(index) {
      if (index < 0 || index >= this.orders.length) return

      const removed = this.orders.splice(index, 1)
      if (Array.isArray(this.summaryRows)) {
        this.summaryRows.splice(index, 1)
      }

      this.persistLocalStorage()
      this.saveSnapshot('after-remove')
      console.log(`🗑️ Satır silindi: ${removed[0]?.model || '(model yok)'}`)
    },
    removeSelectedRow(row, $q = null) {
      if (!row) return

      // 1) Kilitli satır silinemez
      if (this.isRowLocked(row)) {
        $q?.notify?.({
          type: 'warning',
          message: '🔒 Bu satır (IsClosed=1) kapatılmış. Silinemez.'
        })
        return false
      }

      // 2) Kullanıcıya onay sor
      return new Promise(resolve => {
        $q?.dialog({
          title: 'Satır Sil',
          message: `${row.model} / ${row.renk} / ${row.renk2} kombinasyonu silinsin mi?`,
          ok: { label: 'Evet', color: 'negative' },
          cancel: { label: 'Vazgeç' }
        })
          .onOk(() => {
            this.removeRowInternal(row)
            resolve(true)
          })
          .onCancel(() => resolve(false))
      })
    }
    ,
    removeRowInternal(row) {
      if (!row) return false

      // 1️⃣ Kilit kontrolü
      if (this.isRowLocked(row)) {
        console.warn('🔒 Kilitli satır silinemez.')
        return false
      }

      const getKey =
        typeof this.getRowKey === 'function'
          ? this.getRowKey
          : (r => r?.clientKey || r?.id || r?.OrderLineID)

      const rowKey = getKey(row)
      if (!rowKey) return false

      const idx = this.summaryRows.findIndex(r => getKey(r) === rowKey)
      if (idx === -1) return false

      console.log('🗑️ X2 removeRowInternal →', row)

      // 🔐 UI BUSY
      this._uiBusy = true
      this.preventPersist = true

      try {
        // 2️⃣ UI’dan kaldır
        this.summaryRows.splice(idx, 1)

        // orders = UI satırları (temiz kopya)
        this.orders = [...this.summaryRows]

        // 3️⃣ EDIT MODE → DELETE SİNYALİ
        if (this.mode === 'edit') {
          const grpKey =
            row.grpKey ||
            Object.keys(row.bedenMap || {})[0] ||
            'tak'

          // ✅ lineIdMap referansı (varsa)
          const lineIdMap =
            (row.lineIdMap && typeof row.lineIdMap === 'object')
              ? { ...row.lineIdMap }
              : {}

          const emptyMap = {}

          // Öncelik: bedenMap[grpKey] → lineIdMap → fallback
          if (row.bedenMap && row.bedenMap[grpKey]) {
            for (const beden of Object.keys(row.bedenMap[grpKey] || {})) {
              emptyMap[beden] = 0
            }
          } else if (Object.keys(lineIdMap).length) {
            for (const beden of Object.keys(lineIdMap)) {
              emptyMap[beden] = 0
            }
          } else {
            emptyMap['STD'] = 0
          }

          const deleteSignalRow = {
            ...row,

            // 🔴 UI KEY
            id: `DEL::${row.id || row.OrderLineID || crypto.randomUUID()}`,

            // 🔴 BACKEND DELETE SIGNAL
            adet: 0,
            Qty1: 0,
            tutar: 0,

            // 🔴 CRITICAL: duplicate guard'a girmesin
            ComboKey: '',

            // 🔴 legacy tekil alan (varsa kalsın)
            OrderLineID: row.OrderLineID || null,

            // ✅ CRITICAL
            grpKey,
            bedenMap: { [grpKey]: emptyMap },
            lineIdMap,
            comboLineIds: { ...(row.comboLineIds || {}) },

            _deleteSignal: true
          }

          console.log('📡 DELETE sinyali üretildi:', deleteSignalRow)

          this.orders.push(deleteSignalRow)
        }

        // 4️⃣ Totals (persist YOK)
        this.updateHeaderTotals?.()

      } finally {
        // 🔓 GUARD KAPAT
        this.preventPersist = false
        this._uiBusy = false
      }

      // 5️⃣ TEK VE KONTROLLÜ persist
      this.persistLocalStorage()

      return true
    }

    ,


    /* ===========================================================
       📦 normalizeOrderLines (v9 — lineIdMap FIXED + AKSBİR SAFE)
       -----------------------------------------------------------
       ✔ grpKey SADECE burada set edilir
       ✔ detectBedenGroup SADECE store’da kullanılır
       ✔ aksbir → ' ' bedeni = GERÇEK adet
       ✔ backend satırlarında BEDEN → OrderLineID map’i üretilir
    =========================================================== */
    normalizeOrderLines(lines, pbFallback = 'USD', productCache = null) {
      if (!Array.isArray(lines)) return []

      const merged = Object.create(null)
      const pc = (productCache && typeof productCache === 'object') ? productCache : {}

      const makeBaseKey = (model, renk, renk2) =>
        `${model || ''}||${renk || ''}||${renk2 || ''}`

      for (const raw of lines) {
        if (!raw) continue

        const isClosed =
          raw.IsClosed === true ||
          raw.isClosed === true ||
          raw.IsClosed?.Bool === true

        /* =======================================================
        1️⃣ UI / SNAPSHOT KAYNAKLI SATIR
        -------------------------------------------------------
        ✔ ComboKey YOK
        ✔ Sadece model / renk / renk2 bazında gruplanır
      ======================================================= */
        if (raw.bedenMap && Object.keys(raw.bedenMap).length) {
          const model = (raw.model || raw.ItemCode || '').trim()
          const renk  = (raw.renk  || raw.ColorCode || '').trim()
          const renk2 = (raw.renk2 || raw.ItemDim2Code || '').trim()
          const meta = pc?.[model] || {}

          // ❗ BEDEN YOK → bu SADECE üst seviye grup anahtarı
          const modelKey = `${model}||${renk}||${renk2}`

          // grouped map: { [grpKey]: { beden: qty } } or flat map: { beden: qty }
          const groupedKey =
            raw.grpKey ||
            (raw.bedenMap && typeof raw.bedenMap === 'object'
              ? Object.keys(raw.bedenMap).find(k => raw.bedenMap[k] && typeof raw.bedenMap[k] === 'object')
              : null)

          const srcMap =
            groupedKey && raw.bedenMap?.[groupedKey] && typeof raw.bedenMap[groupedKey] === 'object'
              ? raw.bedenMap[groupedKey]
              : raw.bedenMap

          const grpKey =
            groupedKey ||
            detectBedenGroup(
              null,
              raw.urunAnaGrubu || raw.UrunAnaGrubu || raw.ProductAtt01Desc || meta.UrunAnaGrubu || meta.ProductGroup || meta.ProductAtt01Desc || '',
              raw.kategori || raw.Kategori || raw.ProductAtt44Desc || meta.Kategori || meta.ProductAtt44Desc || '',
              raw.yetiskinGarson ||
              raw.YETISKIN_GARSON ||
              raw.YetiskinGarson ||
              raw.AskiliYan ||
              raw.ASKILIYAN ||
              raw.askiliyan ||
              meta.YETISKIN_GARSON ||
              meta.YetiskinGarson ||
              meta.AskiliYan ||
              '',
              raw.urunAltGrubu || raw.UrunAltGrubu || meta.UrunAltGrubu || meta.ProductSubGroup || ''
            ) ||
            'tak'

          const adet  = Object.values(srcMap).reduce((a, b) => a + (Number(b) || 0), 0)
          const fiyat = Number(raw.fiyat || 0)
          const pb    = raw.pb || raw.DocCurrencyCode || pbFallback
          const tutar = Number(raw.tutar ?? adet * fiyat)

          merged[modelKey] ??= []
          merged[modelKey].push({
            ...raw,
            grpKey,
            bedenMap: { [grpKey]: { ...srcMap } },
            adet,
            fiyat,
            pb,
            tutar,
            isClosed
          })
          continue
        }


        /* =======================================================
        2️⃣ BACKEND / LEGACY SATIR (FIXED)
        -------------------------------------------------------
        ✔ ComboKey YOK
        ✔ Sadece model / renk / renk2 bazlı gruplanır
        ✔ BEDEN sadece bedenMap + lineIdMap için kullanılır
      ======================================================= */
        const model = (raw.Model || raw.ItemCode || '').trim()
        const renk  = (raw.ColorCode || '').trim()
        const renk2 = (raw.ItemDim2Code || '').trim()
        const meta = pc?.[model] || {}

        // ❗ BEDEN HARİÇ — üst seviye grup anahtarı
        const modelKey = `${model}||${renk}||${renk2}`

        merged[modelKey] ??= []

        const bedenRaw =
          raw.ItemDim1Code == null
            ? ''
            : String(raw.ItemDim1Code).trim()
        const beden = bedenRaw === '' ? ' ' : normalizeBedenLabel(bedenRaw)
        const bedenRawUpper = safeTrimUpperJs(bedenRaw)

        const qty = Number(raw.Qty1 || raw.Qty || 0)

        let entry = merged[modelKey][0]
        if (!entry) {
          entry = {
            id: raw.OrderLineID || crypto.randomUUID(),

            model,
            renk,
            renk2,

            urunAnaGrubu: raw.UrunAnaGrubu || raw.ProductAtt01Desc || meta.UrunAnaGrubu || meta.ProductGroup || meta.ProductAtt01Desc || 'GENEL',
            urunAltGrubu: raw.UrunAltGrubu || meta.UrunAltGrubu || meta.ProductSubGroup || '',
            kategori:
              raw.Kategori ||
              raw.ProductAtt44Desc ||
              raw.YETISKIN_GARSON ||
              raw.YetiskinGarson ||
              raw.yetiskinGarson ||
              meta.Kategori ||
              meta.ProductAtt44Desc ||
              meta.YETISKIN_GARSON ||
              meta.YetiskinGarson ||
              '',
            yetiskinGarson:
              raw.YETISKIN_GARSON ||
              raw.YetiskinGarson ||
              raw.yetiskinGarson ||
              raw.AskiliYan ||
              raw.ASKILIYAN ||
              raw.askiliyan ||
              meta.YETISKIN_GARSON ||
              meta.YetiskinGarson ||
              meta.AskiliYan ||
              '',

            aciklama: raw.LineDescription || '',
            fiyat: Number(raw.Price || 0),
            pb: raw.DocCurrencyCode || pbFallback,

            __tmpMap: {},              // beden → qty
            lineIdMap: {},             // beden → OrderLineID
            yasPayloadMap: {},

            adet: 0,
            tutar: 0,

            terminTarihi: raw.DeliveryDate || null,
            isClosed
          }

          merged[modelKey].push(entry)
        }

        /* -------------------------------------------------------
           🔑 BEDEN → OrderLineID (DETERMINISTIC & SAFE)
        -------------------------------------------------------- */
        const rawLineId =
          raw.OrderLineID ||
          raw.OrderLineId ||
          raw.orderLineID ||
          null

        if (rawLineId) {
          entry.lineIdMap[beden] = String(rawLineId)
        }

        if (bedenRawUpper && /^(\d+)\s*(Y|YAS|YAŞ)$/.test(bedenRawUpper)) {
          const canonical = normalizeBedenLabel(bedenRawUpper)
          entry.yasPayloadMap[canonical] = pickPreferredYasPayloadLabel(
            entry.yasPayloadMap[canonical],
            bedenRawUpper
          )
        }

        if (qty > 0) {
          entry.__tmpMap[beden] = (entry.__tmpMap[beden] || 0) + qty
          entry.adet  += qty
          entry.tutar += qty * entry.fiyat
        }
      }

      /* =======================================================
         3️⃣ FINAL — grpKey KESİN + AKSBİR FIX
      ======================================================= */
      const out = []

      for (const rows of Object.values(merged)) {
        for (const row of rows) {
          if (!row.__tmpMap) {
            out.push(row)
            continue
          }

          // 🔒 TEK VE KESİN KARAR
          const grpKey = detectBedenGroup(
            null,
            row.urunAnaGrubu,
            row.kategori || '',
            row.yetiskinGarson,
            row.urunAltGrubu || ''
          )

          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]: { ...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
             ❗ STD’ye dönme YOK
             ❗ 0 yazma YOK
          =================================================== */
          if (grpKey === 'aksbir') {
            row.bedenMap[grpKey] ??= {}
            row.bedenMap[grpKey][' '] = Number(row.adet || 0)
          }

          if (grpKey === 'yas') {
            row.yasPayloadMap = row.yasPayloadMap || {}
            for (const b of Object.keys(cleanedMap || {})) {
              const s = String(b || '').trim()
              if (/^\d+$/.test(s) && !row.yasPayloadMap[s]) {
                row.yasPayloadMap[s] = `${s}Y`
              }
            }
          }

          delete row.__tmpMap
          out.push(row)
        }
      }

      console.log(
        `📦 normalizeOrderLines (v9 + lineIdMap) → ${out.length} satır`
      )

      return out
    }

    ,

    /**
     * ===========================================================
     * loadProductSizes — FINAL v4.2 (EDITOR SAFE)
     * -----------------------------------------------------------
     * ✔ grpKey SADECE form.grpKey
     * ✔ schemaMap TEK OTORİTE
     * ✔ edit modda BEDEN LABEL DOKUNULMAZ
     * ✔ ' ' (boş beden) korunur
     * ===========================================================
     */
    async loadProductSizes(form, forceRefresh = false, $q = null) {
      if (!form?.model) return

      const store = this
      const prevBusy = !!store._uiBusy
      const prevPrevent = !!store.preventPersist
      store._uiBusy = true
      store.preventPersist = true

      try {
        const grpKey = form.grpKey
        if (!grpKey) {
          console.warn('⛔ loadProductSizes iptal → grpKey yok')
          return
        }

        const colorKey  = form.renk  || 'nocolor'
        const color2Key = form.renk2 || 'no2color'
        const cacheKey  = `${form.model}_${colorKey}_${color2Key}_${grpKey}`

        /* =======================================================
           ♻️ CACHE (LABEL DOKUNMADAN)
        ======================================================= */
        if (!forceRefresh && sizeCache.value?.[cacheKey]) {
          const cached = sizeCache.value[cacheKey]
          bedenStock.value = [...cached.stockArray]
          stockMap.value   = { ...cached.stockMap }
          form.yasPayloadMap = { ...(cached.yasPayloadMap || {}) }
          console.log(`♻️ loadProductSizes CACHE → ${grpKey}`)
          return
        }

        /* =======================================================
           📡 API
        ======================================================= */
        const params = { code: form.model }
        if (form.renk)  params.color  = form.renk
        if (form.renk2) params.color2 = form.renk2

        const res = await api.get('/product-colorsize', { params })
        const data = Array.isArray(res?.data) ? res.data : []

        if (!data.length) {
          bedenStock.value = []
          stockMap.value = {}
          return
        }

        /* =======================================================
           📦 STOK MAP (' ' KORUNUR)
        ======================================================= */
        const apiStockMap = {}
        const apiYasPayloadMap = {}
        for (const x of data) {
          const rawDim1 =
            x.item_dim1_code == null
              ? ' '
              : String(x.item_dim1_code)
          const key = rawDim1 === ' '
            ? ' '
            : normalizeBedenLabel(rawDim1)
          apiStockMap[key] = Number(x.kullanilabilir_envanter ?? 0)

          const rawUpper = safeTrimUpperJs(rawDim1)
          if (grpKey === 'yas' && /^(\d+)\s*(Y|YAS|YAŞ)$/.test(rawUpper)) {
            const canonical = normalizeBedenLabel(rawUpper)
            apiYasPayloadMap[canonical] = pickPreferredYasPayloadLabel(
              apiYasPayloadMap[canonical],
              rawUpper
            )
          }
        }

        const finalStockMap = {}
        for (const lbl of (form.bedenLabels || [])) {
          const normalizedLbl =
            lbl == null || String(lbl).trim() === ''
              ? ' '
              : normalizeBedenLabel(String(lbl))
          finalStockMap[lbl] = apiStockMap[normalizedLbl] ?? apiStockMap[lbl] ?? 0
        }

        stockMap.value = { ...finalStockMap }
        bedenStock.value = Object.entries(stockMap.value).map(
          ([beden, stok]) => ({ beden, stok })
        )
        form.yasPayloadMap = { ...(form.yasPayloadMap || {}), ...apiYasPayloadMap }

        /* =======================================================
           💾 CACHE
        ======================================================= */
        sizeCache.value[cacheKey] = {
          labels: [...form.bedenLabels],
          stockArray: [...bedenStock.value],
          stockMap: { ...stockMap.value },
          yasPayloadMap: { ...(form.yasPayloadMap || {}) }
        }

        console.log(`✅ loadProductSizes FINAL v4.2 → ${grpKey}`)
      } catch (err) {
        console.error('❌ loadProductSizes hata:', err)
        $q?.notify?.({ type: 'negative', message: 'Beden / stok alınamadı' })
      } finally {
        store._uiBusy = prevBusy
        store.preventPersist = prevPrevent
        console.log('🧩 Editor beden hydrate', {
          grpKey: form.grpKey,
          labels: form.bedenLabels,
          values: form.bedenler
        })

      }
    }




    ,

    // =======================================================
// 🔸 TOPLAM HESAPLAMA (store içi) — X3 SAFE
// -------------------------------------------------------
// ✔ f.adet / f.tutar hesaplanır
// ✔ store.totalAmount ASLA set edilmez
// ✔ gerçek toplam → header.TotalAmount
// =======================================================
    updateTotals(f) {
      // 1️⃣ Satır adet
      f.adet = (f.bedenler || []).reduce(
        (a, b) => a + Number(b || 0),
        0
      )

      // 2️⃣ Satır tutar
      const fiyat = Number(f.fiyat) || 0
      f.tutar = Number((f.adet * fiyat).toFixed(2))

      // 3️⃣ Header toplam (tek gerçek state)
      if (this.header) {
        const total = (this.summaryRows || []).reduce(
          (sum, r) => sum + Number(r?.tutar || 0),
          0
        )

        this.header.TotalAmount = Number(total.toFixed(2))
      }

      return f
    }
    ,

    // =======================================================
    // 🔸 GRUP ANAHTARI TESPİTİ
    // =======================================================
    activeGroupKeyForRow(row) {
      const bedenSet = new Set()

      if (row?.bedenMap && typeof row.bedenMap === 'object') {
        const grp = row?.grpKey && row.bedenMap[row.grpKey] && typeof row.bedenMap[row.grpKey] === 'object'
          ? row.bedenMap[row.grpKey]
          : null

        if (grp) {
          Object.keys(grp).forEach(k => bedenSet.add(String(k || '')))
        } else {
          Object.values(row.bedenMap).forEach(m => {
            if (m && typeof m === 'object') {
              Object.keys(m).forEach(k => bedenSet.add(String(k || '')))
            }
          })
        }
      }

      if (bedenSet.size === 0 && Array.isArray(row?.bedenLabels)) {
        row.bedenLabels.forEach(lbl => {
          bedenSet.add(String(lbl == null ? '' : lbl))
        })
      }

      return detectBedenGroup(
        Array.from(bedenSet),
        row?.urunAnaGrubu || '',
        row?.kategori || '',
        row?.YETISKIN_GARSON || row?.yetiskinGarson || '',
        row?.urunAltGrubu || ''
      )
    },
    /* =======================================================
   🔹 MODE YÖNETİMİ — new / edit arası geçiş
======================================================= */
    setMode(mode) {
      if (!['new', 'edit', 'view'].includes(mode)) {
        console.warn('⚠️ Geçersiz mode:', mode)
        return
      }

      this.mode = mode
      console.log(`🧭 Order mode set edildi → ${mode}`)
    }
    ,
    // Sync only header-related fields from the form before submit.
    syncHeaderFromForm(form) {
      if (!form || typeof form !== 'object') return

      const keys = [
        'OrderHeaderID',
        'OrderTypeCode',
        'ProcessCode',
        'OrderNumber',
        'OrderDate',
        'AverageDueDate',
        'Description',
        'InternalDescription',
        'CurrAccTypeCode',
        'CurrAccCode',
        'ShippingPostalAddressID',
        'BillingPostalAddressID',
        'CurrAccDescription',
        'DocCurrencyCode',
        'LocalCurrencyCode',
        'ExchangeRate',
        'OfficeCode',
        'CreatedUserName',
        'CreatedDate',
        'LastUpdatedUserName',
        'LastUpdatedDate',
        'PaymentTerm',
        'WarehouseCode',
        'StoreCode'
      ]

      const patch = {}
      for (const k of keys) {
        if (Object.prototype.hasOwnProperty.call(form, k)) {
          patch[k] = form[k]
        }
      }

      if (Object.keys(patch).length > 0) {
        this.header = {
          ...(this.header || {}),
          ...patch
        }
      }
    }
    ,
    /* ===========================================================
       🟦 submitAllReal (v12.1c — FINAL / CLEAN + PRE-VALIDATE)
       -----------------------------------------------------------
       ✔ NEW → INSERT, EDIT → UPDATE (tek karar noktası)
       ✔ Controlled submit → route guard SUSAR
       ✔ Snapshot temizliği route öncesi
       ✔ Kaydet → edit replace → backend reload
       ✔ Listeye giderken guard popup 1 kez bypass
       ✔ ✅ PRE-VALIDATE → prItemVariant olmayan kombinasyonlar kaydı DURDURUR
    =========================================================== */
    async submitAllReal($q, router, form, summaryRows, productCache) {
      let serverOrderId = null
      let serverOrderNo = null
      let purgeNewDraftOnExit = false

      try {
        this.loading = true

        // 🔒 Kontrollü submit → route leave guard susar
        this.isControlledSubmit = true

        // ✅ Formdaki header alanlarını store'a taşı
        this.syncHeaderFromForm?.(form)

        const isNew = this.mode === 'new'
        const { header, lines } = this.buildFinalOrderJson()

        // =======================================================
        // 🧾 DEBUG — FRONTEND → BACKEND GİDEN PAYLOAD
        // =======================================================
        console.groupCollapsed(
          `%c📤 ORDER PAYLOAD (${this.mode})`,
          'color:#c9a873;font-weight:bold'
        )

        // Ensure backend string fields are sent as strings.
        if (header) {
          header.OfficeCode = header.OfficeCode != null ? String(header.OfficeCode) : header.OfficeCode
          header.StoreCode = header.StoreCode != null ? String(header.StoreCode) : header.StoreCode
          header.OrdererOfficeCode = header.OrdererOfficeCode != null ? String(header.OrdererOfficeCode) : header.OrdererOfficeCode
          header.OrdererStoreCode = header.OrdererStoreCode != null ? String(header.OrdererStoreCode) : header.OrdererStoreCode
          // PaymentTerm must be numeric (int16) or null for backend.
          if (header.PaymentTerm === '' || header.PaymentTerm == null) {
            header.PaymentTerm = null
          } else if (typeof header.PaymentTerm === 'string') {
            const n = Number(header.PaymentTerm)
            header.PaymentTerm = Number.isNaN(n) ? null : n
          }
        }
        console.log('HEADER:', JSON.parse(JSON.stringify(header)))
        console.log('HEADER KEYS:', {
          OrderHeaderID: header?.OrderHeaderID,
          CurrAccTypeCode: header?.CurrAccTypeCode,
          CurrAccCode: header?.CurrAccCode,
          OfficeCode: header?.OfficeCode,
          StoreTypeCode: header?.StoreTypeCode,
          StoreCode: header?.StoreCode,
          OrdererOfficeCode: header?.OrdererOfficeCode,
          OrdererStoreCode: header?.OrdererStoreCode,
          PaymentTerm: header?.PaymentTerm,
          DocCurrencyCode: header?.DocCurrencyCode
        })

        lines.forEach((l, i) => {
          console.log(`LINE[${i}]`, {
            OrderLineID: l.OrderLineID,
            ClientKey: l.ClientKey,
            ItemCode: l.ItemCode,
            ColorCode: l.ColorCode,
            ItemDim1Code: l.ItemDim1Code,
            ItemDim2Code: l.ItemDim2Code,
            ItemDim3Code: l.ItemDim3Code,
            Qty1: l.Qty1,
            ComboKey: l.ComboKey
          })
        })

        console.groupEnd()

        // =======================================================
        // 🧾 DEBUG (opsiyonel helper)
        // =======================================================
        this.debugOrderPayload?.(header, lines, 'PRE-VALIDATE')

        // =======================================================
        // 🧩 DUMMY CURRENCY PAYLOAD (model genişletmeden)
        // - trOrderLineCurrency için gerekli alanları satıra basar
        // - örnek satırdaki gibi: PriceVI/AmountVI = KDV dahil, Price/Amount = KDV hariç
        // =======================================================
        const r2 = (n) => Number((Number(n) || 0).toFixed(2))
        const r4 = (n) => Number((Number(n) || 0).toFixed(4))

        for (const ln of lines) {
          const qty = Number(ln?.Qty1 || 0)
          const unitBase = Number(ln?.Price || 0) // KDV hariç birim
          const vatRate = Number(ln?.VatRate || 0)
          const exRate = Number(ln?.PriceExchangeRate || header?.ExchangeRate || 1) || 1

          const taxBase = r2(unitBase * qty)                    // Amount
          const vat = r2((taxBase * vatRate) / 100)             // Vat
          const net = r2(taxBase + vat)                         // AmountVI / NetAmount
          const unitWithVat = qty > 0 ? r4(net / qty) : r4(unitBase * (1 + vatRate / 100))

          const docCurrency = String(ln?.DocCurrencyCode || header?.DocCurrencyCode || 'TRY').trim() || 'TRY'

          // Backend model alanları
          ln.RelationCurrencyCode = docCurrency
          ln.DocPrice = unitWithVat
          ln.DocAmount = net
          ln.LocalPrice = unitBase
          ln.LocalAmount = taxBase
          ln.LineDiscount = Number(ln?.LineDiscount || 0)
          ln.TotalDiscount = Number(ln?.TotalDiscount || 0)
          ln.TaxBase = taxBase
          ln.Pct = Number(ln?.Pct || 0)
          ln.VatAmount = vat
          ln.VatDeducation = 0
          ln.NetAmount = net

          // SQL kolonu isimleriyle dummy alias (decoder ignore etse de payload'da görünür)
          ln.CurrencyCode = docCurrency
          ln.ExchangeRate = exRate
          ln.PriceVI = unitWithVat
          ln.AmountVI = net
          ln.Amount = taxBase
          ln.LDiscount1 = Number(ln?.LDiscount1 || 0)
          ln.TDiscount1 = Number(ln?.TDiscount1 || 0)
          ln.Vat = vat
        }

        // =======================================================
        // 🧪 PRE-VALIDATE — prItemVariant ön kontrol (NEW + EDIT)
        //   - invalid varsa CREATE/UPDATE çalışmaz
        // =======================================================
        const linesToValidate = lines.filter(l =>
          Number(l?.Qty1 || 0) > 0 &&
          l?._deleteSignal !== true &&
          String(l?.ItemCode || '').trim() !== ''
        )

        if (linesToValidate.length > 0) {
          const v = await api.post('/order/validate', { header, lines: linesToValidate })
          const invalid = v?.data?.invalid || []

          if (invalid.length > 0) {
            await this.showInvalidVariantDialog?.($q, invalid)
            return // ❌ create/update ÇALIŞMAZ
          }
        }

        console.log('📤 submitAllReal payload', {
          mode: this.mode,
          lines: lines.length,
          deletes: lines.filter(l => l._deleteSignal).length
        })

        /* =======================================================
           🚀 API CALL — TEK NOKTA
        ======================================================= */
        const resp = await api.post(
          isNew ? '/order/create' : '/order/update',
          { header, lines }
        )

        const data = resp?.data || {}

        serverOrderId =
          data.orderID ||
          data.orderHeaderID ||
          data.id ||
          header?.OrderHeaderID

        serverOrderNo =
          data.orderNumber ||
          data.orderNo ||
          header?.OrderNumber

        if (!serverOrderId) {
          throw new Error('OrderHeaderID backend’den dönmedi')
        }
        const mailPayload = this.buildOrderMailPayload(lines, isNew)
        console.info('[orderentryStore] mail payload prepared', {
          operation: mailPayload?.operation,
          deletedCount: Array.isArray(mailPayload?.deletedItems) ? mailPayload.deletedItems.length : 0,
          updatedCount: Array.isArray(mailPayload?.updatedItems) ? mailPayload.updatedItems.length : 0,
          addedCount: Array.isArray(mailPayload?.addedItems) ? mailPayload.addedItems.length : 0,
          dueDateChangeCount: Array.isArray(mailPayload?.dueDateChanges) ? mailPayload.dueDateChanges.length : 0
        })
        purgeNewDraftOnExit = isNew

        /* =======================================================
           🔁 MODE SWITCH → EDIT
        ======================================================= */
        this.setMode('edit')

        // Header patch (ID / No)
        this.header = {
          ...this.header,
          OrderHeaderID: serverOrderId,
          OrderNumber: serverOrderNo
        }

        /* =======================================================
           🧹 KRİTİK: Snapshot + Dirty temizliği
           ❗ ROUTE değişmeden ÖNCE
        ======================================================= */
        this.updateHeaderTotals?.()
        this.markAsSaved?.()

        /* =======================================================
           🧹 KRİTİK: NEW → EDIT geçişinde TÜM SNAPSHOT + NEW DRAFT temizlenir
        ======================================================= */
        this.clearAllOrderSnapshots()
        if (isNew) {
          this.clearNewDraft()
        }

        $q.notify({
          type: 'positive',
          message: `Sipariş kaydedildi: ${serverOrderNo || ''}`.trim()
        })

        /* =======================================================
           🔀 ROUTE REPLACE (EDIT MODE)
           - aynı sayfa → param değişti
           - guard 1 kez bypass
        ======================================================= */
        this.allowRouteLeaveOnce = true

        await router.replace({
          name: 'order-entry',
          params: { orderHeaderID: serverOrderId },
          query: { mode: 'edit', source: 'backend' }
        })

        /* =======================================================
           🔄 BACKEND RELOAD (TEK GERÇEK KAYNAK)
        ======================================================= */
        await this.openExistingForEdit(serverOrderId, {
          $q,
          form,
          summaryRowsRef: summaryRows,
          productCache
        })

        // 📧 Piyasa eşleşen alıcılara sipariş PDF gönderimi (kayıt başarılı olduktan sonra)
        try {
          // UPDATE durumunda da mail gönderimi istendiği için isNew kontrolü kaldırıldı (v3.5)
          const mailRes = await this.sendOrderToMarketMails(serverOrderId, mailPayload)
          const sentCount = Number(mailRes?.sentCount || 0)
          $q.notify({
            type: 'positive',
            message: sentCount > 0
              ? `Sipariş PDF mail gönderildi (${sentCount} alıcı)`
              : 'Sipariş PDF mail gönderimi tamamlandı'
          })
        } catch (mailErr) {
          $q.notify({
            type: 'warning',
            message: `Sipariş kaydedildi, mail gönderilemedi: ${mailErr?.message || 'Bilinmeyen hata'}`
          })
        }

        /* =======================================================
           ❓ USER NEXT STEP
        ======================================================= */
        const choice = await new Promise(resolve => {
          $q.dialog({
            title: 'Sipariş Kaydedildi',
            options: {
              type: 'radio',
              model: 'continue',
              items: [
                { label: '✏️ Düzenlemeye Devam', value: 'continue' },
                { label: '🖨 Yazdır', value: 'print' },
                { label: '📋 Listeye Dön', value: 'list' }
              ]
            },
            ok: { label: 'Seç' },
            cancel: { label: 'Kapat' }
          })
            .onOk(v => resolve(v))
            .onCancel(() => resolve('continue'))
        })

        /* =======================================================
           🧭 USER ROUTING
        ======================================================= */
        if (choice === 'print') {
          const id = this.header?.OrderHeaderID || serverOrderId
          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
        }

        if (choice === 'list') {
          this.allowRouteLeaveOnce = true
          await router.push({ name: 'order-list' })
          return
        }

        // continue → sayfada kal (hiçbir şey yapma)

      } catch (err) {
        console.error('❌ submitAllReal:', err)

        const data = err?.response?.data || {}
        const status = Number(err?.response?.status || 0)
        if (err?.response?.status === 400 && data?.code === 'INVALID_ITEM_VARIANT') {
          const oneInvalid = [{
            clientKey: data?.clientKey || '',
            itemCode: data?.itemCode || '',
            colorCode: data?.colorCode || '',
            dim1: data?.dim1 || '',
            dim2: data?.dim2 || '',
            qty1: 0,
            reason: data?.message || 'Tanımsız ürün kombinasyonu'
          }]
          await this.showInvalidVariantDialog?.($q, oneInvalid)
          return
        }

        if (status === 524) {
          $q.notify({
            type: 'warning',
            timeout: 36000,
            message: 'Sunucu zaman aşımına uğradı (524). Sipariş kısmen kaydedilmiş olabilir; önce listeden kontrol edin, sonra tekrar deneyin.'
          })
          return
        }

        $q.notify({
          type: 'negative',
          message:
            err?.response?.data?.detail ||
            err?.response?.data?.message ||
            err?.message ||
            'Kayıt sırasında hata'
        })

      } finally {
        // ✅ NEW kaydı başarılıysa taslağı exit noktasında da zorla temizle
        if (purgeNewDraftOnExit) {
          this.clearNewDraft()
        }

        // 🔓 Guard’lar normale dönsün
        this.isControlledSubmit = false
        this.loading = false
      }
    }

    ,

    /* =======================================================
       🧪 SUBMIT ALL TEST
    ======================================================= */
    async submitAllTest($q = null) {
      try {
        const { header, lines } = this.buildFinalOrderJson()

        console.log('🧾 TEST HEADER', Object.keys(header).length, 'alan')
        console.log(JSON.stringify(header, null, 2))

        console.log('🧾 TEST LINES', lines.length, 'satır')
        console.log(JSON.stringify(lines, null, 2))

        $q?.notify?.({
          type: 'info',
          message: `Header (${Object.keys(header).length}) + Lines (${lines.length}) gösterildi`,
          position: 'top'
        })
      } catch (err) {
        console.error('❌ submitAllTest hata:', err)
        $q?.notify?.({
          type: 'negative',
          message: 'Gösterimde hata oluştu ❌',
          position: 'top'
        })
      }
    },


    /* =======================================================
       🧹 KAYIT SONRASI TEMİZLİK
    ======================================================= */
    afterSubmit(opts = {
      keepLocalStorage: true,
      backendPayload: null,
      resetMode: true   // 🔑 yeni
    }) {
      try {
        console.log('🧹 afterSubmit başlatıldı', opts)

        if (opts?.backendPayload?.header?.OrderHeaderID) {
          this.mergeAndPersistBackendOrder(
            opts.backendPayload.header.OrderHeaderID,
            opts.backendPayload
          )
        }

        if (!opts?.keepLocalStorage) {
          this.clearStorage()
          this.clearTransaction()
        } else {
          this.saveSnapshot()
        }

        this.orders = []
        this.header = {}
        this.editingKey = null
        this.currentOrderId = null

        // 🔐 MODE RESET OPSİYONEL
        if (opts.resetMode === true) {
          this.mode = 'new'
        }

        console.log('✅ afterSubmit tamamlandı.')
      } catch (err) {
        console.error('❌ afterSubmit hata:', err)
      }
    }

    ,


    /* ===========================================================
       🟦 BUILD FINAL ORDER JSON — SAFE v26.1 (FINAL)
       -----------------------------------------------------------
       ✔ ComboKey TEK OTORİTE → buildComboKey (bedenKey ile)
       ✔ UI/Map placeholder: '_' (bedenKey)
       ✔ DB/payload: '' (bedenPayload)  → "_" ASLA GİTMEZ
       ✔ payload içinde aynı ComboKey TEK satır
       ✔ backend duplicate guard %100 uyumlu (ComboKey stabil)
       ✔ Final assert: payload’da "_" yakalanırsa patlatır
    =========================================================== */
    buildFinalOrderJson () {
      const auth = useAuthStore()
      const u = auth?.user || {}
      const now = dayjs()

      /* =========================
         HELPERS
      ========================== */
      const toNum = v => Number(v) || 0
      const safeStr = v => (v == null ? '' : String(v).trim())

      const formatDateOnly = v => (v ? dayjs(v).format('YYYY-MM-DD') : null)
      const formatTimeOnly = v => dayjs(v).format('HH:mm:ss')
      const formatDateTime = v => (v ? dayjs(v).format('YYYY-MM-DD HH:mm:ss') : null)

      // ✅ Payload beden normalize: "_" / "-" / "" => ''
      const normBeden = (v) => {
        const s = safeStr(v)
        if (s === '' || s === '_' || s === '-') return ''   // payload empty
        return s
      }

      // UI'de yas grubu 2/4/6... gösterilir; payload'a 2Y/4Y/6... yazılır.
      const toPayloadBeden = (row, grpKey, v) => {
        const base = normBeden(v)
        if (!base) return ''
        if (grpKey === 'yas' && /^\d+$/.test(base)) {
          const map =
            row?.yasPayloadMap && typeof row.yasPayloadMap === 'object'
              ? row.yasPayloadMap
              : {}
          const mapped = safeStr(map[base]).toUpperCase()
          if (mapped) return mapped
          return `${base}Y`
        }
        return base
      }

      /* =========================
         USER META
      ========================== */
      const group = safeStr(u?.v3usergroup)
      const v3name = safeStr(u?.v3_username)
      const who = (group && v3name) ? `${group} ${v3name}` : (v3name || 'BSS')

      const PCT_CODE_ZERO = '%0'
      const VAT_CODE_ZERO = '%0'

      /* =========================
         HEADER
      ========================== */
      const headerId = this.header?.OrderHeaderID || crypto.randomUUID()
      const docCurrency = safeStr(this.header?.DocCurrencyCode) || 'TRY'
      const exRate = toNum(this.header?.ExchangeRate) || 1
      const boolOr = (value, fallback) => {
        if (typeof value === 'boolean') return value
        if (value && typeof value === 'object' && typeof value.Bool === 'boolean') return value.Bool
        return fallback
      }

      const avgDueSource =
        this.header?.AverageDueDate ||
        dayjs(this.header?.OrderDate || now).add(14, 'day')

      const header = {
        ...this.header,

        OrderHeaderID: headerId,
        OrderDate: formatDateOnly(this.header?.OrderDate || now),
        OrderTime: formatTimeOnly(now),
        AverageDueDate: formatDateOnly(avgDueSource),

        DocCurrencyCode: docCurrency,
        LocalCurrencyCode: safeStr(this.header?.LocalCurrencyCode) || 'TRY',
        ExchangeRate: exRate,
        IsCancelOrder: boolOr(this.header?.IsCancelOrder, false),
        IsInclutedVat: boolOr(this.header?.IsInclutedVat, false),
        IsCreditSale: boolOr(this.header?.IsCreditSale, true),
        IsCreditableConfirmed: true,
        CreditableConfirmedUser: who,
        CreditableConfirmedDate: formatDateTime(now),
        IsSalesViaInternet: boolOr(this.header?.IsSalesViaInternet, false),
        IsSuspended: boolOr(this.header?.IsSuspended, false),
        IsCompleted: boolOr(this.header?.IsCompleted, true),
        IsPrinted: boolOr(this.header?.IsPrinted, false),
        IsLocked: boolOr(this.header?.IsLocked, false),
        UserLocked: boolOr(this.header?.UserLocked, false),
        IsClosed: boolOr(this.header?.IsClosed, false),
        IsProposalBased: boolOr(this.header?.IsProposalBased, false),

        CreatedUserName:
          this.mode === 'edit'
            ? (this.header?.CreatedUserName || who)
            : who,

        CreatedDate:
          this.mode === 'edit'
            ? formatDateTime(this.header?.CreatedDate || now)
            : formatDateTime(now),

        LastUpdatedUserName: who,
        LastUpdatedDate: formatDateTime(now)
      }

      /* =======================================================
         LINES — COMBOKEY AGGREGATE (TEK MAP)
      ======================================================= */
      const lines = []
      const lineByCombo = new Map() // 🔒 KEY = ComboKey

      const pushOrMerge = (row, ctx) => {
        const {
          grpKey,
          bedenKey,       // ✅ sadece ComboKey / Map için ('_' olabilir)
          bedenPayload,   // ✅ DB için ('' / 'S' / 'M' ...)
          qty,
          orderLineId,
          isDeleteSignal
        } = ctx

        if (qty <= 0 && !isDeleteSignal) return

        // ComboKey stabil kalsın diye bedenKey kullan
        const comboKey = buildComboKey(row, bedenKey)

        const makeLine = () => ({
          OrderLineID: orderLineId || '',
          ClientKey: makeLineClientKey(row, grpKey, bedenKey),
          ComboKey: comboKey,
          _dirty: row?._dirty === true,
          _deleteSignal: isDeleteSignal === true,

          SortOrder: 0,
          ItemTypeCode: 1,

          ItemCode: safeStr(row.model),
          ColorCode: safeStr(row.renk),

          // ✅ PAYLOAD: "_" ASLA YOK
          ItemDim1Code: bedenPayload,

          ItemDim2Code: safeStr(row.renk2),
          ItemDim3Code: '',

          Qty1: isDeleteSignal ? 0 : qty,
          Qty2: 0,

          CancelQty1: 0,
          CancelQty2: 0,

          DeliveryDate: row.terminTarihi
            ? formatDateTime(row.terminTarihi)
            : null,

          PlannedDateOfLading: row.terminTarihi
            ? formatDateOnly(row.terminTarihi)
            : null,

          LineDescription: safeStr(row.aciklama),
          UsedBarcode: '',
          CostCenterCode: '',

          VatCode: VAT_CODE_ZERO,
          VatRate: toNum(row.vatRate ?? row.VatRate ?? 0),

          PCTCode: PCT_CODE_ZERO,
          PCTRate: 0,

          LDisRate1: 0,
          LDisRate2: 0,
          LDisRate3: 0,
          LDisRate4: 0,
          LDisRate5: 0,

          DocCurrencyCode: header.DocCurrencyCode,
          PriceCurrencyCode: header.DocCurrencyCode,
          PriceExchangeRate: toNum(header.ExchangeRate),
          Price: toNum(row.fiyat),

          BaseProcessCode: 'WS',
          BaseOrderNumber: header.OrderNumber,
          BaseCustomerTypeCode: 0,
          BaseCustomerCode: header.CurrAccCode,
          BaseSubCurrAccID: null,
          BaseStoreCode: '',

          OrderHeaderID: headerId,

          CreatedUserName: who,
          CreatedDate: formatDateTime(row.CreatedDate || now),
          LastUpdatedUserName: who,
          LastUpdatedDate: formatDateTime(now),

          SurplusOrderQtyToleranceRate: 0,
          WithHoldingTaxTypeCode: '',
          DOVCode: ''
        })

        const existing = lineByCombo.get(comboKey)

        if (!existing) {
          const ln = makeLine()
          lineByCombo.set(comboKey, ln)
          lines.push(ln)
          return
        }

        /* DELETE */
        if (isDeleteSignal) {
          if (orderLineId && !existing.OrderLineID) {
            existing.OrderLineID = orderLineId
          }
          existing._deleteSignal = true
          existing.Qty1 = 0
          return
        }

        /* MERGE */
        existing.Qty1 += qty
        if (row?._dirty === true) {
          existing._dirty = true
        }

        if (this.mode === 'edit' && orderLineId && !existing.OrderLineID) {
          existing.OrderLineID = orderLineId
        }

        existing.Price = toNum(row.fiyat)
      }

      /* =======================================================
         ORDER ROW LOOP
      ======================================================= */
      for (const row of this.orders || []) {
        if (row?.isClosed === true) continue

        const grpKey =
          row.grpKey ||
          Object.keys(row.bedenMap || {})[0] ||
          'GENEL'

        const lineIdMap = row.lineIdMap || {}

        const grouped = row.bedenMap?.[grpKey]
        const flat =
          (row.bedenMap && typeof row.bedenMap === 'object' && !grouped)
            ? row.bedenMap
            : null

        const map = grouped || flat
        const hasAnyBeden =
          map && typeof map === 'object' && Object.keys(map).length > 0

        /* 🔹 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 = '_'
          const bedenKey = '_'
          // ✅ Payload: boş string
          const bedenPayload = ''

          let orderLineId = ''
          if (this.mode === 'edit') {
            // lineIdMap burada '_' ile tutuluyorsa onu da oku
            orderLineId =
              safeStr(lineIdMap?.[bedenKey]) ||
              safeStr(lineIdMap?.[bedenPayload]) ||
              safeStr(lineIdMap?.[' ']) ||
              safeStr(row.OrderLineID)
          }

          pushOrMerge(row, {
            grpKey,
            bedenKey,
            bedenPayload,
            qty,
            orderLineId,
            isDeleteSignal: row._deleteSignal === true && !!orderLineId
          })
          continue
        }

        /* 🔹 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)

          // ✅ UI/combokey için kanonik beden (yas'ta 2/4/6...)
          const bedenCanonical = normBeden(bedenRaw)
          // ✅ payload beden: yas grubunda 2Y/4Y/6..., diğerlerinde normal
          const bedenPayload = toPayloadBeden(row, grpKey, bedenRaw)
          // ✅ combokey beden: boşsa '_' ile stabil kalsın
          const bedenKey = bedenCanonical || '_'

          let orderLineId = ''
          if (this.mode === 'edit') {
            // lineIdMap anahtarı sizde hangi bedenle tutuluyorsa ikisini de dene
            orderLineId =
              safeStr(lineIdMap?.[bedenKey]) ||
              safeStr(lineIdMap?.[bedenCanonical]) ||
              safeStr(lineIdMap?.[bedenPayload]) ||
              safeStr(lineIdMap?.[' ']) ||
              (Object.keys(map).length === 1
                ? safeStr(row.OrderLineID)
                : '')
          }

          pushOrMerge(row, {
            grpKey,
            bedenKey,
            bedenPayload,
            qty,
            orderLineId,
            isDeleteSignal: row._deleteSignal === true && !!orderLineId
          })
        }
      }

      /* =======================================================
         FINAL SORT
      ======================================================= */
      lines.forEach((ln, i) => { ln.SortOrder = i + 1 })

      /* =======================================================
         ASSERT — payload’da "_" OLAMAZ
      ======================================================= */
      if (lines.some(l => (l.ItemDim1Code || '') === '_' )) {
        console.error('❌ Payload’da "_" yakalandı', lines.filter(l => l.ItemDim1Code === '_'))
        throw new Error('Payload ItemDim1Code "_" olamaz')
      }
      /* =======================================================
       🔍 DEBUG — BUILD FINAL ORDER JSON OUTPUT
    ======================================================= */
      console.groupCollapsed('%c📦 BUILD FINAL ORDER JSON', 'color:#c9a873;font-weight:bold')

      console.log('🧾 HEADER:', header)

      console.table(
        lines.map((l, i) => ({
          i: i + 1,
          OrderLineID: l.OrderLineID,
          ClientKey: l.ClientKey,
          ComboKey: l.ComboKey,
          ItemCode: l.ItemCode,
          ColorCode: l.ColorCode,
          ItemDim1Code: JSON.stringify(l.ItemDim1Code), // <-- kritik
          ItemDim2Code: l.ItemDim2Code,
          Qty1: l.Qty1,
          Price: l.Price
        }))
      )

      console.groupEnd()

      return { header, lines }
    }







    ,/* ===========================================================
   ✅ STORE ACTIONS — FIXED HELPERS
   - setRowErrorByClientKey
   - clearRowErrorByClientKey
   - applyTerminToRowsIfEmpty
=========================================================== */
    setRowErrorByClientKey(clientKey, payload) {
      if (!clientKey) return
      if (!Array.isArray(this.summaryRows)) return

      const row = this.summaryRows.find(r => r?.clientKey === clientKey)
      if (!row) return

      row._error = {
        code: payload?.code,
        message: payload?.message
      }
    },

    clearRowErrorByClientKey(clientKey) {
      if (!clientKey) return
      if (!Array.isArray(this.summaryRows)) return

      const row = this.summaryRows.find(r => r?.clientKey === clientKey)
      if (!row) return

      if (row._error) {
        delete row._error
      }
    },

    applyTerminToRowsIfEmpty(dateStr) {
      if (!dateStr) return
      if (!Array.isArray(this.summaryRows)) return

      // ❗ reassign YOK — patch/mutate
      for (const r of this.summaryRows) {
        if (!r?.terminTarihi || r.terminTarihi === '') {
          r.terminTarihi = dateStr
        }
      }

      // opsiyonel ama genelde doğru:
      this.persistLocalStorage?.()
    }













  } // actions sonu
}) // defineStore sonu


/* ===========================================================
   Size Label Normalization (frontend helper)
   =========================================================== */
function safeTrimUpperJs(v) {
  return (v == null ? '' : String(v)).trim().toUpperCase()
}

function normalizeTextForMatch(v) {
  return safeTrimUpperJs(v)
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
}

function parseNumericSizeJs(v) {
  const s = safeTrimUpperJs(v)
  if (s === '' || !/^\d+$/.test(s)) return null
  const n = Number.parseInt(s, 10)
  return Number.isNaN(n) ? null : n
}

function pickPreferredYasPayloadLabel(currentRaw, nextRaw) {
  const cur = safeTrimUpperJs(currentRaw)
  const nxt = safeTrimUpperJs(nextRaw)
  if (!nxt) return cur
  if (!cur) return nxt
  const curYas = /YAS$|YAŞ$/.test(cur)
  const nxtYas = /YAS$|YAŞ$/.test(nxt)
  if (!curYas && nxtYas) return nxt
  return cur
}

export function normalizeBedenLabel(v) {
  let s = (v == null ? '' : String(v)).trim()
  if (s === '') return ' '

  s = s.toUpperCase()

  // Yas bedenleri backendte 2Y/4Y/6Y veya 2YAS/2YAŞ gibi gelebilir.
  // UI şeması 2/4/6/8/10/12/14 ile çalıştığı için numeric'e indir.
  const yasMatch = s.match(/^(\d+)\s*(Y|YAS|YAŞ)$/)
  if (yasMatch && yasMatch[1]) {
    return yasMatch[1]
  }

  // Backend parity: normalize common "standard size" aliases.
  switch (s) {
    case 'STD':
    case 'STANDART':
    case 'STANDARD':
    case 'ONE SIZE':
    case 'ONESIZE':
      return 'STD'
  }

  // Backend parity: only values ending with CM are converted to numeric part.
  if (s.endsWith('CM')) {
    const num = s.slice(0, -2).trim()
    if (num !== '') return num
  }

  switch (s) {
    case 'XS':
    case 'S':
    case 'M':
    case 'L':
    case 'XL':
    case '2XL':
    case '3XL':
    case '4XL':
    case '5XL':
    case '6XL':
    case '7XL':
      return s
  }

  return s
}

// Backward-compatible alias kept for older call sites/bundles.
export function normalizeBeden(v) {
  return normalizeBedenLabel(v)
}

function deriveKategoriToken(urunKategori = '', yetiskinGarson = '') {
  const kat = normalizeTextForMatch(urunKategori || '')
  if (kat.includes('GARSON')) return 'GARSON'
  if (kat.includes('YETISKIN')) return 'YETISKIN'
  return ''
}

function normalizeRuleAltGroup(urunAltGrubu = '') {
  return normalizeTextForMatch(urunAltGrubu || '')
}

function pickBestGroupFromCandidates(groupKeys = [], bedenList = []) {
  if (!Array.isArray(groupKeys) || groupKeys.length === 0) return ''
  if (groupKeys.length === 1) return groupKeys[0]

  const normalizedBeden = (Array.isArray(bedenList) ? bedenList : [])
    .map(v => normalizeBedenLabel(v))
    .filter(Boolean)

  if (!normalizedBeden.length) return groupKeys[0]

  let bestKey = groupKeys[0]
  let bestScore = -1

  for (const key of groupKeys) {
    const schema = Array.isArray(productSizeMatchCache.schemas?.[key])
      ? productSizeMatchCache.schemas[key]
      : []
    const normalizedSchema = new Set(schema.map(v => normalizeBedenLabel(v)))
    let score = 0
    for (const b of normalizedBeden) {
      if (normalizedSchema.has(b)) score += 1
    }
    if (score > bestScore) {
      bestScore = score
      bestKey = key
    }
  }

  return bestKey || groupKeys[0]
}

function resolveGroupFromProductSizeMatchRules(
  bedenList,
  urunAnaGrubu = '',
  urunKategori = '',
  yetiskinGarson = '',
  urunAltGrubu = ''
) {
  if (!productSizeMatchCache.loaded || !Array.isArray(productSizeMatchCache.rules) || !productSizeMatchCache.rules.length) {
    return ''
  }

  const kategoriToken = deriveKategoriToken(urunKategori, yetiskinGarson)
  const kategoriRaw = normalizeTextForMatch(urunKategori || '')
  const ana = normalizeTextForMatch(urunAnaGrubu || '')
  const alt = normalizeRuleAltGroup(urunAltGrubu)
  if (!ana) return ''

  const bestScoreGroupKeys = []
  let bestScore = -1

  for (const rule of productSizeMatchCache.rules) {
    if (!rule?.urunAnaGrubu || rule.urunAnaGrubu !== ana) continue

    const ruleKategori = normalizeTextForMatch(rule.kategori || '')
    const catExact =
      (kategoriToken && ruleKategori === kategoriToken) ||
      (kategoriRaw && ruleKategori === kategoriRaw)
    const catWildcard = ruleKategori === ''
    if (!catExact && !catWildcard) continue

    const ruleAlt = normalizeTextForMatch(rule.urunAltGrubu || '')
    const altExact = !!alt && ruleAlt === alt
    const altWildcard = ruleAlt === ''
    if (!altExact && !altWildcard) continue

    const score = (catExact ? 2 : 0) + (altExact ? 1 : 0)
    if (score < bestScore) continue
    if (score > bestScore) {
      bestScore = score
      bestScoreGroupKeys.length = 0
    }

    for (const g of (rule.groupKeys || [])) {
      const key = String(g || '').trim()
      if (key && !bestScoreGroupKeys.includes(key)) {
        bestScoreGroupKeys.push(key)
      }
    }
  }

  if (!bestScoreGroupKeys.length) return ''
  return pickBestGroupFromCandidates(bestScoreGroupKeys, bedenList)
}

/* ===========================================================
   Size Group Detection
   - Core logic aligned with backend detectBedenGroupGo
   - Keeps frontend aksbir bucket for accessory lines
   =========================================================== */
export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = '', yetiskinGarson = '', urunAltGrubu = '') {
  const list = Array.isArray(bedenList) && bedenList.length > 0
    ? bedenList.map(v => (v || '').toString().trim().toUpperCase())
    : [' ']

  const ruleBased = resolveGroupFromProductSizeMatchRules(
    list,
    urunAnaGrubu,
    urunKategori,
    yetiskinGarson,
    urunAltGrubu
  )
  if (productSizeMatchCache.loaded) {
    if (!ruleBased) {
      console.warn('⚠ product-size-match eşleşme bulunamadı', {
        kategori: deriveKategoriToken(urunKategori, yetiskinGarson),
        urunAnaGrubu: normalizeTextForMatch(urunAnaGrubu || ''),
        urunAltGrubu: normalizeRuleAltGroup(urunAltGrubu),
        bedenList: list
      })
    }
    return ruleBased || 'tak'
  }

  console.warn('⚠ product-size-match cache hazir degil, fallback=tak', {
    kategori: deriveKategoriToken(urunKategori, yetiskinGarson),
    urunAnaGrubu: normalizeTextForMatch(urunAnaGrubu || ''),
    urunAltGrubu: normalizeRuleAltGroup(urunAltGrubu),
    bedenList: list
  })
  return 'tak'

  const rawAna = normalizeTextForMatch(urunAnaGrubu || '')
  const rawKat = normalizeTextForMatch(urunKategori || '')
  const rawYetiskinGarson = normalizeTextForMatch(yetiskinGarson || '')
  const isYetiskin = rawKat.includes('YETISKIN')
  const isGomlekKlasikOrAtayaka =
    rawAna.includes('GOMLEK KLASIK') ||
    rawAna.includes('GOMLEK ATA YAKA') ||
    rawAna.includes('GOMLEK ATAYAKA')

  // Özel kural:
  // Kategorisi YETISKIN ve ana grubu GOMLEK KLASIK/ATA YAKA olanlar her zaman "gom" grubundadır.
  if (isYetiskin && isGomlekKlasikOrAtayaka) {
    return 'gom'
  }

  // Beden seti çocuk yaş formatındaysa metadata beklemeden "yas" aç.
  // Örn: 2,4,6,8,10,12,14 veya 2Y,4Y,6Y...
  const yasNums = new Set(['2', '4', '6', '8', '10', '12', '14'])
  const yasParsed = list
    .map(v => v.replace(/\s+/g, '').replace(/YAS$/i, '').replace(/Y$/i, ''))
  if (yasParsed.length > 0 && yasParsed.every(v => yasNums.has(v))) {
    return 'yas'
  }

  const isYetiskinGomlekKlasik = isYetiskin && rawAna.includes('GOMLEK KLASIK')
  const mappedRawAna = isYetiskinGomlekKlasik
    ? rawAna.replace('GOMLEK KLASIK', 'GOMLEK ATA YAKA')
    : rawAna

  // Ozel kural:
  // YETISKIN/GARSON = GARSON ve URUN ANA GRUBU "GOMLEK ATA YAKA" veya "GOMLEK KLASIK" ise
  // sonuc "yas" olmalidir.
  const isGarsonGomlekAnaGrubu =
    mappedRawAna.includes('GOMLEK ATAYAKA') ||
    mappedRawAna.includes('GOMLEK ATA YAKA') ||
    mappedRawAna.includes('GOMLEK KLASIK')
  const hasGarsonSignal = mappedRawAna.includes('GARSON') || rawKat.includes('GARSON') || rawYetiskinGarson.includes('GARSON')
  if (isGarsonGomlekAnaGrubu && (rawKat.includes('GARSON') || rawYetiskinGarson.includes('GARSON'))) {
    return 'yas'
  }

  // Ayakkabi kurali garsondan once uygulanmali:
  // GARSON + AYAKKABI => ayk_garson, digerleri => ayk
  if (mappedRawAna.includes('AYAKKABI') || rawKat.includes('AYAKKABI')) {
    return hasGarsonSignal ? 'ayk_garson' : 'ayk'
  }

  const hasGarson = hasGarsonSignal
  if (hasGarson) return 'yas'

  // 🔸 Harfli beden varsa doğrudan "gom" (gömlek/üst giyim)
  // STD/ONE SIZE aksbir için saklı kalsın.
  const harfliBedenler = ['XS','S','M','L','XL','2XL','3XL','4XL','5XL','6XL','7XL']
  if (list.some(b => harfliBedenler.includes(b))) return 'gom'

  const ana = mappedRawAna
    .trim()
    .replace(/\(.*?\)/g, '')
    .replace(/[^A-Z0-9\s]/g, '')
    .replace(/\s+/g, ' ')

  const kat = normalizeTextForMatch(urunKategori || '').trim()
// 🔸 Aksesuar ise "aksbir"
  const aksesuarGruplari = [
    'AKSESUAR','KRAVAT','PAPYON','KEMER','CORAP',
    'FULAR','MENDIL','KASKOL','ASKI',
    'YAKA','KOL DUGMESI'
  ]
  const giyimGruplari = ['GOMLEK','CEKET','PANTOLON','MONT','YELEK','TAKIM','TSHIRT','TISORT']
  // 🔸 Pantolon özel durumu
  if (
    aksesuarGruplari.some(g => ana.includes(g) || kat.includes(g)) &&
    !giyimGruplari.some(g => ana.includes(g))
  ) return 'aksbir'

  if (ana.includes('PANTOLON') && kat.includes('YETISKIN')) return 'pan'
  // 🔸 Tamamen numerik (örneğin 39-44 arası) → ayakkabı
  const allNumeric = list.every(v => /^\d+$/.test(v))
  if (allNumeric) {
    const nums = list.map(v => parseInt(v, 10)).filter(Boolean)
    const diffs = nums.slice(1).map((v, i) => v - nums[i])
    if (diffs.every(d => d === 1) && nums[0] >= 35 && nums[0] <= 46) return 'ayk'
  }

  // 🔸 Yaş grubu (çocuk/garson)
  if (kat.includes('GARSON') || kat.includes('COCUK')) return 'yas'

  // 🔸 Varsayılan: takım elbise
  return 'tak'
}



export function toSummaryRowFromForm(form) {
  if (!form) return null

  const grpKey = form.grpKey
  if (!grpKey) return null
  const bedenMap = {}

  const labels = Array.isArray(form.bedenLabels) ? form.bedenLabels : []
  const values = Array.isArray(form.bedenler) ? form.bedenler : []

  for (let i = 0; i < labels.length; i++) {
    const rawLbl = labels[i]
    const lbl =
      rawLbl == null || String(rawLbl).trim() === ''
        ? ' '
        : normalizeBedenLabel(String(rawLbl))

    const val = Number(values[i] || 0)
    if (val > 0) {
      bedenMap[lbl] = val
    }
  }

  return {
    id: form.id || newGuid(),
    OrderLineID: form.OrderLineID || null,

    model: form.model || '',
    renk: form.renk || '',
    renk2: form.renk2 || '',

    urunAnaGrubu: form.urunAnaGrubu || '',
    urunAltGrubu: form.urunAltGrubu || '',
    kategori: form.kategori || '',
    yetiskinGarson: form.yetiskinGarson || form.askiliyan || '',
    yasPayloadMap: { ...(form.yasPayloadMap || {}) },
    aciklama: form.aciklama || '',

    fiyat: Number(form.fiyat || 0),
    pb: form.pb || 'USD',

    adet: Number(form.adet || 0),
    tutar: Number(form.tutar || 0),

    grpKey,
    bedenMap: {
      [grpKey]: { ...bedenMap }
    },

    terminTarihi: (form.terminTarihi || '').substring(0, 10)
  }
}




/* ===========================================================
   🔹 TOPLAM HESAPLAMA (EXPORT)
   -----------------------------------------------------------
   Hem store içinde hem de component tarafında kullanılabilir.
=========================================================== */
export function updateTotals(f) {
  f.adet = (f.bedenler || []).reduce((a, b) => a + Number(b || 0), 0)
  const fiyat = Number(f.fiyat) || 0
  f.tutar = (f.adet * fiyat).toFixed(2)
  return f
}

/* ===========================================================
   🔹 EXPORT SET — Tek Merkezli Dışa Aktarımlar
   =========================================================== */

/**
 * 🧩 Shared Reactive Refs
 * -----------------------------------------------------------
 * import { sharedOrderEntryRefs } from 'src/stores/orderentryStore'
 * const { stockMap, bedenStock, sizeCache } = sharedOrderEntryRefs
 */
export const sharedOrderEntryRefs = {
  stockMap,
  bedenStock,
  sizeCache,


}
