Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -513,7 +513,8 @@ func ValidateItemVariant(tx *sql.Tx, ln models.OrderDetail) error {
|
|||||||
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
|
||||||
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
|
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
|
||||||
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
|
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
|
||||||
AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3
|
AND UPPER(REPLACE(REPLACE(REPLACE(ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') ,' ', ''), 'YAS', ''), 'Y', ''))
|
||||||
|
= UPPER(REPLACE(REPLACE(REPLACE(ISNULL(LTRIM(RTRIM(@p3)),'') ,' ', ''), 'YAS', ''), 'Y', ''))
|
||||||
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
|
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
|
||||||
) THEN 1 ELSE 0 END
|
) THEN 1 ELSE 0 END
|
||||||
`, item, color, dim1, dim2).Scan(&exists)
|
`, item, color, dim1, dim2).Scan(&exists)
|
||||||
@@ -555,7 +556,8 @@ func ValidateOrderVariants(db *sql.DB, lines []models.OrderDetail) ([]models.Inv
|
|||||||
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
|
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
|
||||||
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
|
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
|
||||||
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
|
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
|
||||||
AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3
|
AND UPPER(REPLACE(REPLACE(REPLACE(ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') ,' ', ''), 'YAS', ''), 'Y', ''))
|
||||||
|
= UPPER(REPLACE(REPLACE(REPLACE(ISNULL(LTRIM(RTRIM(@p3)),'') ,' ', ''), 'YAS', ''), 'Y', ''))
|
||||||
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
|
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
|
||||||
) THEN 1 ELSE 0 END
|
) THEN 1 ELSE 0 END
|
||||||
`)
|
`)
|
||||||
|
|||||||
3761
ui/package-lock.json
generated
3761
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,760 +19,760 @@
|
|||||||
======================================================== -->
|
======================================================== -->
|
||||||
<div class="sticky-stack">
|
<div class="sticky-stack">
|
||||||
|
|
||||||
<!-- 🔸 1. Satır: Filtre Bar -->
|
<!-- 🔸 1. Satır: Filtre Bar -->
|
||||||
<div class="filter-bar row q-col-gutter-md q-mb-sm">
|
<div class="filter-bar row q-col-gutter-md q-mb-sm">
|
||||||
|
|
||||||
<!-- 🧾 Cari Seçimi -->
|
<!-- 🧾 Cari Seçimi -->
|
||||||
<div class="col-5">
|
<div class="col-5">
|
||||||
<q-select
|
<q-select
|
||||||
v-model="form.CurrAccCode"
|
v-model="form.CurrAccCode"
|
||||||
:options="filteredCariOptions"
|
:options="filteredCariOptions"
|
||||||
label="Cari Seçimi"
|
label="Cari Seçimi"
|
||||||
filled
|
|
||||||
dense
|
|
||||||
use-input
|
|
||||||
input-debounce="300"
|
|
||||||
emit-value
|
|
||||||
map-options
|
|
||||||
option-value="Cari_Kod"
|
|
||||||
:option-label="opt => `${opt.Cari_Kod} - ${opt.Cari_Ad}`"
|
|
||||||
@filter="filterCari"
|
|
||||||
@update:model-value="onCariChange"
|
|
||||||
:loading="loadingCari"
|
|
||||||
:disable="isEditMode || isClosedOrder || isViewOnly"
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
clearable
|
|
||||||
|
|
||||||
>
|
|
||||||
|
|
||||||
<template #option="scope">
|
|
||||||
<q-item v-bind="scope.itemProps">
|
|
||||||
<q-item-section>
|
|
||||||
<q-item-label>{{ scope.opt.Cari_Ad }}</q-item-label>
|
|
||||||
<q-item-label caption>{{ scope.opt.Cari_Kod }}</q-item-label>
|
|
||||||
</q-item-section>
|
|
||||||
</q-item>
|
|
||||||
</template>
|
|
||||||
</q-select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 🔢 Sipariş No -->
|
|
||||||
<div class="col-2">
|
|
||||||
<q-input
|
|
||||||
v-model="form.OrderNumber"
|
|
||||||
label="Sipariş No"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
:disable="isEditMode || isClosedOrder || isViewOnly"
|
|
||||||
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 📅 Oluşturulma Tarihi -->
|
|
||||||
<div class="col-2">
|
|
||||||
<q-input
|
|
||||||
:model-value="formatDateInput(form.OrderDate)"
|
|
||||||
label="Oluşturulma Tarihi"
|
|
||||||
type="date"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
@update:model-value="v => form.OrderDate = v"
|
|
||||||
:disable="isEditMode || isClosedOrder || isViewOnly"
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 📅 Tahmini Termin Tarihi (AverageDueDate kilitlenmeyecek) -->
|
|
||||||
<div class="col-2">
|
|
||||||
<q-input
|
|
||||||
:model-value="formatDateInput(form.AverageDueDate)"
|
|
||||||
label="Tahmini Termin Tarihi"
|
|
||||||
type="date"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
@update:model-value="v => form.AverageDueDate = v"
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
:disable="isViewOnly"
|
|
||||||
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 🔹 Cari Bilgi Satırı (2. Satır) -->
|
|
||||||
<div
|
|
||||||
v-if="cariInfo"
|
|
||||||
v-show="!filterBarCollapsed"
|
|
||||||
class="col-12 row q-col-gutter-md q-mt-xs cari-info-bar"
|
|
||||||
>
|
|
||||||
<div class="col-3">
|
|
||||||
<q-input
|
|
||||||
:model-value="cariInfo.Musteri_Temsilcisi || '-'"
|
|
||||||
label="Müşteri Temsilcisi"
|
|
||||||
filled dense readonly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<q-input
|
|
||||||
:model-value="cariInfo.Musteri_Ana_Grubu || '-'"
|
|
||||||
label="Ana Grup"
|
|
||||||
filled dense readonly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<q-input
|
|
||||||
:model-value="cariInfo.Piyasa || '-'"
|
|
||||||
label="Piyasa"
|
|
||||||
filled dense readonly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<q-input
|
|
||||||
:model-value="cariInfo.Ulke || '-'"
|
|
||||||
label="Ülke"
|
|
||||||
filled dense readonly
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 💰 TOPLAM TUTAR + KDV -->
|
|
||||||
<div v-show="!filterBarCollapsed" class="col-12 row q-col-gutter-sm q-mt-xs items-center">
|
|
||||||
<!-- 💰 Toplam Tutar -->
|
|
||||||
<div class="col-3">
|
|
||||||
<q-input
|
|
||||||
dense
|
|
||||||
filled
|
filled
|
||||||
:model-value="Number(orderStore.totalAmount || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 })"
|
dense
|
||||||
label="Toplam Tutar"
|
use-input
|
||||||
readonly
|
input-debounce="300"
|
||||||
|
emit-value
|
||||||
|
map-options
|
||||||
|
option-value="Cari_Kod"
|
||||||
|
:option-label="opt => `${opt.Cari_Kod} - ${opt.Cari_Ad}`"
|
||||||
|
@filter="filterCari"
|
||||||
|
@update:model-value="onCariChange"
|
||||||
|
:loading="loadingCari"
|
||||||
|
:disable="isEditMode || isClosedOrder || isViewOnly"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
clearable
|
||||||
|
|
||||||
>
|
>
|
||||||
<template #append>{{ form.pb }}</template>
|
|
||||||
</q-input>
|
<template #option="scope">
|
||||||
|
<q-item v-bind="scope.itemProps">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.Cari_Ad }}</q-item-label>
|
||||||
|
<q-item-label caption>{{ scope.opt.Cari_Kod }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 🔘 KDV Checkbox -->
|
<!-- 🔢 Sipariş No -->
|
||||||
<div class="col-auto flex items-center">
|
<div class="col-2">
|
||||||
<q-checkbox
|
<q-input
|
||||||
v-model="form.includeVat"
|
v-model="form.OrderNumber"
|
||||||
label="KDV Dahil"
|
label="Sipariş No"
|
||||||
color="primary"
|
filled
|
||||||
@update:model-value="onVatToggle"
|
dense
|
||||||
:disable="isClosedRow||isViewOnly"
|
:disable="isEditMode || isClosedOrder || isViewOnly"
|
||||||
|
|
||||||
:readonly="isViewOnly"
|
:readonly="isViewOnly"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ⚙️ KDV ALANLARI: sadece tikliyken görünür -->
|
<!-- 📅 Oluşturulma Tarihi -->
|
||||||
<template v-if="form.includeVat">
|
<div class="col-2">
|
||||||
<!-- % oran sadece bilgi -->
|
<q-input
|
||||||
<div class="col-1">
|
:model-value="formatDateInput(form.OrderDate)"
|
||||||
<q-input
|
label="Oluşturulma Tarihi"
|
||||||
dense
|
type="date"
|
||||||
filled
|
filled
|
||||||
:model-value="form.vatRate"
|
dense
|
||||||
label="%"
|
@update:model-value="v => form.OrderDate = v"
|
||||||
readonly
|
:disable="isEditMode || isClosedOrder || isViewOnly"
|
||||||
>
|
:readonly="isViewOnly"
|
||||||
<template #append>%</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 🧮 KDV Tutarı (manuel düzenlenebilir) -->
|
/>
|
||||||
<div class="col-2">
|
</div>
|
||||||
<q-input
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
v-model="form.vatAmountInput"
|
|
||||||
label="KDV Tutarı"
|
|
||||||
@update:model-value="onVatAmountChange"
|
|
||||||
input-class="text-right"
|
|
||||||
:disable="isClosedRow || isViewOnly"
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
>
|
|
||||||
<template #append>{{ form.pb }}</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
<!-- 🧾 KDV Dahil Toplam -->
|
|
||||||
<div class="col-2">
|
|
||||||
<q-input
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
:model-value="Number(form.totalWithVat || 0).toLocaleString('tr-TR',{minimumFractionDigits:2})"
|
|
||||||
label="KDV Dahil Toplam"
|
|
||||||
readonly
|
|
||||||
>
|
|
||||||
<template #append>{{ form.pb }}</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<!-- 📅 Tahmini Termin Tarihi (AverageDueDate kilitlenmeyecek) -->
|
||||||
<!-- 📝 Sipariş Genel Açıklaması (filter bar altında) -->
|
<div class="col-2">
|
||||||
<div v-show="!filterBarCollapsed" class="filter-bar-desc q-mt-sm">
|
<q-input
|
||||||
<q-input
|
:model-value="formatDateInput(form.AverageDueDate)"
|
||||||
v-model="form.Description"
|
label="Tahmini Termin Tarihi"
|
||||||
type="textarea"
|
type="date"
|
||||||
label="Sipariş Genel Açıklaması"
|
filled
|
||||||
filled
|
dense
|
||||||
dense
|
@update:model-value="v => form.AverageDueDate = v"
|
||||||
autogrow
|
:readonly="isViewOnly"
|
||||||
maxlength="1500"
|
:disable="isViewOnly"
|
||||||
counter
|
|
||||||
placeholder="Siparişe genel açıklama giriniz (örn. teslimat, üretim notu, müşteri isteği...)"
|
|
||||||
:disable="isClosedRow"
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<!-- 🔹 Save Toolbar -->
|
|
||||||
<div class="save-toolbar">
|
|
||||||
<div class="text-subtitle2 text-weight-bold">Sipariş Formu</div>
|
|
||||||
<div>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
color="grey-7"
|
|
||||||
class="q-ml-sm"
|
|
||||||
:label="filterBarCollapsed ? 'FİLTREYİ GENİŞLET' : 'FİLTREYİ DARALT'"
|
|
||||||
:icon="filterBarCollapsed ? 'expand_more' : 'expand_less'"
|
|
||||||
@click="toggleFilterBarCollapsed"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
flat
|
|
||||||
color="grey-7"
|
|
||||||
class="q-ml-sm"
|
|
||||||
:label="compactGridHeader ? 'BAŞLIK GENİŞLET' : 'BAŞLIK DARALT'"
|
|
||||||
:icon="compactGridHeader ? 'unfold_more' : 'unfold_less'"
|
|
||||||
@click="compactGridHeader = !compactGridHeader"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
v-if="isViewOnly && canExportOrder"
|
|
||||||
label="🖨 SİPARİŞİ YAZDIR"
|
|
||||||
color="primary"
|
|
||||||
icon="print"
|
|
||||||
class="q-ml-sm"
|
|
||||||
@click="onPrintOrder"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-btn
|
/>
|
||||||
v-if="canMutateRows"
|
</div>
|
||||||
label="SATIR EKLE"
|
|
||||||
color="secondary"
|
|
||||||
icon="add"
|
|
||||||
class="q-ml-sm"
|
|
||||||
@click="openNewRowEditor"
|
|
||||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
v-if="canSubmitOrder"
|
|
||||||
:label="isEditMode ? 'TÜMÜNÜ GÜNCELLE' : 'TÜMÜNÜ KAYDET'"
|
|
||||||
color="primary"
|
|
||||||
icon="save"
|
|
||||||
class="q-ml-sm"
|
|
||||||
:loading="orderStore.loading"
|
|
||||||
:disable="!canSubmitOrder"
|
|
||||||
@click="confirmAndSubmit"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 🔹 Grid Header -->
|
<!-- 🔹 Cari Bilgi Satırı (2. Satır) -->
|
||||||
<div class="order-grid-header" :class="{ compact: compactGridHeader }">
|
|
||||||
<div class="col-fixed model">MODEL</div>
|
|
||||||
<div class="col-fixed renk">RENK</div>
|
|
||||||
<div class="col-fixed ana">ÜRÜN ANA GRUBU</div>
|
|
||||||
<div class="col-fixed alt">ÜRÜN ALT GRUBU</div>
|
|
||||||
<div class="col-fixed aciklama-col">AÇIKLAMA</div>
|
|
||||||
|
|
||||||
<div class="beden-block">
|
|
||||||
<div
|
<div
|
||||||
v-for="grp in (
|
v-if="cariInfo"
|
||||||
|
v-show="!filterBarCollapsed"
|
||||||
|
class="col-12 row q-col-gutter-md q-mt-xs cari-info-bar"
|
||||||
|
>
|
||||||
|
<div class="col-3">
|
||||||
|
<q-input
|
||||||
|
:model-value="cariInfo.Musteri_Temsilcisi || '-'"
|
||||||
|
label="Müşteri Temsilcisi"
|
||||||
|
filled dense readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<q-input
|
||||||
|
:model-value="cariInfo.Musteri_Ana_Grubu || '-'"
|
||||||
|
label="Ana Grup"
|
||||||
|
filled dense readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<q-input
|
||||||
|
:model-value="cariInfo.Piyasa || '-'"
|
||||||
|
label="Piyasa"
|
||||||
|
filled dense readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<q-input
|
||||||
|
:model-value="cariInfo.Ulke || '-'"
|
||||||
|
label="Ülke"
|
||||||
|
filled dense readonly
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 💰 TOPLAM TUTAR + KDV -->
|
||||||
|
<div v-show="!filterBarCollapsed" class="col-12 row q-col-gutter-sm q-mt-xs items-center">
|
||||||
|
<!-- 💰 Toplam Tutar -->
|
||||||
|
<div class="col-3">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
:model-value="Number(orderStore.totalAmount || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 })"
|
||||||
|
label="Toplam Tutar"
|
||||||
|
readonly
|
||||||
|
>
|
||||||
|
<template #append>{{ form.pb }}</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 🔘 KDV Checkbox -->
|
||||||
|
<div class="col-auto flex items-center">
|
||||||
|
<q-checkbox
|
||||||
|
v-model="form.includeVat"
|
||||||
|
label="KDV Dahil"
|
||||||
|
color="primary"
|
||||||
|
@update:model-value="onVatToggle"
|
||||||
|
:disable="isClosedRow||isViewOnly"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ⚙️ KDV ALANLARI: sadece tikliyken görünür -->
|
||||||
|
<template v-if="form.includeVat">
|
||||||
|
<!-- % oran sadece bilgi -->
|
||||||
|
<div class="col-1">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
:model-value="form.vatRate"
|
||||||
|
label="%"
|
||||||
|
readonly
|
||||||
|
>
|
||||||
|
<template #append>%</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 🧮 KDV Tutarı (manuel düzenlenebilir) -->
|
||||||
|
<div class="col-2">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
v-model="form.vatAmountInput"
|
||||||
|
label="KDV Tutarı"
|
||||||
|
@update:model-value="onVatAmountChange"
|
||||||
|
input-class="text-right"
|
||||||
|
:disable="isClosedRow || isViewOnly"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
>
|
||||||
|
<template #append>{{ form.pb }}</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
<!-- 🧾 KDV Dahil Toplam -->
|
||||||
|
<div class="col-2">
|
||||||
|
<q-input
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
:model-value="Number(form.totalWithVat || 0).toLocaleString('tr-TR',{minimumFractionDigits:2})"
|
||||||
|
label="KDV Dahil Toplam"
|
||||||
|
readonly
|
||||||
|
>
|
||||||
|
<template #append>{{ form.pb }}</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- 📝 Sipariş Genel Açıklaması (filter bar altında) -->
|
||||||
|
<div v-show="!filterBarCollapsed" class="filter-bar-desc q-mt-sm">
|
||||||
|
<q-input
|
||||||
|
v-model="form.Description"
|
||||||
|
type="textarea"
|
||||||
|
label="Sipariş Genel Açıklaması"
|
||||||
|
filled
|
||||||
|
dense
|
||||||
|
autogrow
|
||||||
|
maxlength="1500"
|
||||||
|
counter
|
||||||
|
placeholder="Siparişe genel açıklama giriniz (örn. teslimat, üretim notu, müşteri isteği...)"
|
||||||
|
:disable="isClosedRow"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<!-- 🔹 Save Toolbar -->
|
||||||
|
<div class="save-toolbar">
|
||||||
|
<div class="text-subtitle2 text-weight-bold">Sipariş Formu</div>
|
||||||
|
<div>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="grey-7"
|
||||||
|
class="q-ml-sm"
|
||||||
|
:label="filterBarCollapsed ? 'FİLTREYİ GENİŞLET' : 'FİLTREYİ DARALT'"
|
||||||
|
:icon="filterBarCollapsed ? 'expand_more' : 'expand_less'"
|
||||||
|
@click="toggleFilterBarCollapsed"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
color="grey-7"
|
||||||
|
class="q-ml-sm"
|
||||||
|
:label="compactGridHeader ? 'BAŞLIK GENİŞLET' : 'BAŞLIK DARALT'"
|
||||||
|
:icon="compactGridHeader ? 'unfold_more' : 'unfold_less'"
|
||||||
|
@click="compactGridHeader = !compactGridHeader"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
v-if="isViewOnly && canExportOrder"
|
||||||
|
label="🖨 SİPARİŞİ YAZDIR"
|
||||||
|
color="primary"
|
||||||
|
icon="print"
|
||||||
|
class="q-ml-sm"
|
||||||
|
@click="onPrintOrder"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<q-btn
|
||||||
|
v-if="canMutateRows"
|
||||||
|
label="SATIR EKLE"
|
||||||
|
color="secondary"
|
||||||
|
icon="add"
|
||||||
|
class="q-ml-sm"
|
||||||
|
@click="openNewRowEditor"
|
||||||
|
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
v-if="canSubmitOrder"
|
||||||
|
:label="isEditMode ? 'TÜMÜNÜ GÜNCELLE' : 'TÜMÜNÜ KAYDET'"
|
||||||
|
color="primary"
|
||||||
|
icon="save"
|
||||||
|
class="q-ml-sm"
|
||||||
|
:loading="orderStore.loading"
|
||||||
|
:disable="!canSubmitOrder"
|
||||||
|
@click="confirmAndSubmit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 🔹 Grid Header -->
|
||||||
|
<div class="order-grid-header" :class="{ compact: compactGridHeader }">
|
||||||
|
<div class="col-fixed model">MODEL</div>
|
||||||
|
<div class="col-fixed renk">RENK</div>
|
||||||
|
<div class="col-fixed ana">ÜRÜN ANA GRUBU</div>
|
||||||
|
<div class="col-fixed alt">ÜRÜN ALT GRUBU</div>
|
||||||
|
<div class="col-fixed aciklama-col">AÇIKLAMA</div>
|
||||||
|
|
||||||
|
<div class="beden-block">
|
||||||
|
<div
|
||||||
|
v-for="grp in (
|
||||||
Object.keys(orderStore?.schemaMap || {}).length
|
Object.keys(orderStore?.schemaMap || {}).length
|
||||||
? Object.values(orderStore.schemaMap)
|
? Object.values(orderStore.schemaMap)
|
||||||
: Object.values(storeSchemaByKey)
|
: Object.values(storeSchemaByKey)
|
||||||
)"
|
)"
|
||||||
:key="grp.key"
|
:key="grp.key"
|
||||||
class="grp-row"
|
class="grp-row"
|
||||||
:class="{ 'hl-pan': grp.key === 'pan' && highlightPantolon }"
|
:class="{ 'hl-pan': grp.key === 'pan' && highlightPantolon }"
|
||||||
>
|
>
|
||||||
|
|
||||||
<div class="grp-title">{{ grp.title }}</div>
|
<div class="grp-title">{{ grp.title }}</div>
|
||||||
<div class="grp-body">
|
<div class="grp-body">
|
||||||
<div
|
|
||||||
v-for="v in (grp.values || [])"
|
|
||||||
:key="'b-' + grp.key + '-' + v"
|
|
||||||
class="grp-cell hdr"
|
|
||||||
>
|
|
||||||
{{ v }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="total-row">
|
|
||||||
<div class="total-cell">ADET</div>
|
|
||||||
<div class="total-cell">FİYAT</div>
|
|
||||||
<div class="total-cell">PB</div>
|
|
||||||
<div class="total-cell">TUTAR</div>
|
|
||||||
<div class="total-cell">Tahmini Gönderim Tarihi</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- =======================================================
|
|
||||||
🔹 GRID BODY (Final Stabil) + EDITOR aynı scroll’da
|
|
||||||
======================================================== -->
|
|
||||||
<div class="order-scroll-y" :class="{ 'compact-grid-header': compactGridHeader }"> <!-- ✅ YENİ: Grid + Editor ortak dikey scroll -->
|
|
||||||
<div class="order-grid-body">
|
|
||||||
<template v-for="grp in groupedRows" :key="grp.name">
|
|
||||||
<div :class="['summary-group', grp.open ? 'open' : 'closed']">
|
|
||||||
|
|
||||||
<!-- 🟡 Sub-header -->
|
|
||||||
<div class="order-sub-header" @click="toggleGroup(grp.name)">
|
|
||||||
<div class="sub-left">{{ grp.name }}</div>
|
|
||||||
|
|
||||||
<div class="sub-center">
|
|
||||||
<div
|
<div
|
||||||
v-for="v in (
|
v-for="v in (grp.values || [])"
|
||||||
orderStore.schemaMap?.[grp.grpKey]?.values
|
:key="'b-' + grp.key + '-' + v"
|
||||||
|| storeSchemaByKey?.[grp.grpKey]?.values
|
class="grp-cell hdr"
|
||||||
|| []
|
|
||||||
)"
|
|
||||||
:key="'hdr-' + grp.grpKey + '-' + v"
|
|
||||||
class="beden-cell"
|
|
||||||
>
|
>
|
||||||
{{ v }}
|
{{ v }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sub-right">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="order-text-caption">
|
|
||||||
Toplam {{ grp.name }} Adet: {{ grp.toplamAdet }}
|
|
||||||
</div>
|
|
||||||
<div class="order-text-caption">
|
|
||||||
Toplam {{ grp.name }} Tutar:
|
|
||||||
{{ Number(grp.toplamTutar || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }}
|
|
||||||
{{ form.pb || aktifPB }}
|
|
||||||
</div>
|
|
||||||
<q-icon
|
|
||||||
:name="grp.open ? 'expand_less' : 'expand_more'"
|
|
||||||
size="20px"
|
|
||||||
class="cursor-pointer text-grey-8 q-ml-sm"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 🧩 Grup satırları -->
|
</div>
|
||||||
<template v-if="grp.open">
|
|
||||||
<div
|
<div class="total-row">
|
||||||
v-for="row in grp.rows"
|
<div class="total-cell">ADET</div>
|
||||||
:key="rowKey(row)"
|
<div class="total-cell">FİYAT</div>
|
||||||
class="summary-row"
|
<div class="total-cell">PB</div>
|
||||||
:data-clientkey="row.clientKey"
|
<div class="total-cell">TUTAR</div>
|
||||||
:class="{
|
<div class="total-cell">Tahmini Gönderim Tarihi</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- =======================================================
|
||||||
|
🔹 GRID BODY (Final Stabil) + EDITOR aynı scroll’da
|
||||||
|
======================================================== -->
|
||||||
|
<div class="order-scroll-y" :class="{ 'compact-grid-header': compactGridHeader }"> <!-- ✅ YENİ: Grid + Editor ortak dikey scroll -->
|
||||||
|
<div class="order-grid-body">
|
||||||
|
<template v-for="grp in groupedRows" :key="grp.name">
|
||||||
|
<div :class="['summary-group', grp.open ? 'open' : 'closed']">
|
||||||
|
|
||||||
|
<!-- 🟡 Sub-header -->
|
||||||
|
<div class="order-sub-header" @click="toggleGroup(grp.name)">
|
||||||
|
<div class="sub-left">{{ grp.name }}</div>
|
||||||
|
|
||||||
|
<div class="sub-center">
|
||||||
|
<div
|
||||||
|
v-for="v in (
|
||||||
|
orderStore.schemaMap?.[grp.grpKey]?.values
|
||||||
|
|| storeSchemaByKey?.[grp.grpKey]?.values
|
||||||
|
|| []
|
||||||
|
)"
|
||||||
|
:key="'hdr-' + grp.grpKey + '-' + v"
|
||||||
|
class="beden-cell"
|
||||||
|
>
|
||||||
|
{{ v }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sub-right">
|
||||||
|
|
||||||
|
|
||||||
|
<div class="order-text-caption">
|
||||||
|
Toplam {{ grp.name }} Adet: {{ grp.toplamAdet }}
|
||||||
|
</div>
|
||||||
|
<div class="order-text-caption">
|
||||||
|
Toplam {{ grp.name }} Tutar:
|
||||||
|
{{ Number(grp.toplamTutar || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }}
|
||||||
|
{{ form.pb || aktifPB }}
|
||||||
|
</div>
|
||||||
|
<q-icon
|
||||||
|
:name="grp.open ? 'expand_less' : 'expand_more'"
|
||||||
|
size="20px"
|
||||||
|
class="cursor-pointer text-grey-8 q-ml-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 🧩 Grup satırları -->
|
||||||
|
<template v-if="grp.open">
|
||||||
|
<div
|
||||||
|
v-for="row in grp.rows"
|
||||||
|
:key="rowKey(row)"
|
||||||
|
class="summary-row"
|
||||||
|
:data-clientkey="row.clientKey"
|
||||||
|
:class="{
|
||||||
active: orderStore.editingKey === rowKey(row),
|
active: orderStore.editingKey === rowKey(row),
|
||||||
'is-editing': orderStore.editingKey === rowKey(row),
|
'is-editing': orderStore.editingKey === rowKey(row),
|
||||||
'row-closed': row.isClosed,
|
'row-closed': row.isClosed,
|
||||||
'row-error': row._error
|
'row-error': row._error
|
||||||
}"
|
}"
|
||||||
@click="!row.isClosed && !isViewOnly && editRow(row)"
|
@click="!row.isClosed && !isViewOnly && editRow(row)"
|
||||||
>
|
|
||||||
|
|
||||||
<!-- 🔴 HATA İKONU (SADECE HATALI SATIRDA) -->
|
|
||||||
<q-icon
|
|
||||||
v-if="row._error"
|
|
||||||
name="error"
|
|
||||||
color="negative"
|
|
||||||
size="18px"
|
|
||||||
class="q-mr-sm row-error-icon"
|
|
||||||
>
|
>
|
||||||
<q-tooltip>
|
|
||||||
{{ row._error.message }}
|
|
||||||
</q-tooltip>
|
|
||||||
</q-icon>
|
|
||||||
|
|
||||||
<!-- Sol kolonlar -->
|
<!-- 🔴 HATA İKONU (SADECE HATALI SATIRDA) -->
|
||||||
<div class="cell model">{{ row.model }}</div>
|
<q-icon
|
||||||
<div class="cell renk">
|
v-if="row._error"
|
||||||
{{ row.renk }}{{ row.renk2 ? '-' + row.renk2 : '' }}
|
name="error"
|
||||||
</div>
|
color="negative"
|
||||||
<div class="cell ana">{{ row.urunAnaGrubu }}</div>
|
size="18px"
|
||||||
<div class="cell alt">{{ row.urunAltGrubu }}</div>
|
class="q-mr-sm row-error-icon"
|
||||||
<div class="cell aciklama">{{ row.aciklama }}</div>
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
{{ row._error.message }}
|
||||||
|
</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
|
||||||
<!-- Beden kolonları -->
|
<!-- Sol kolonlar -->
|
||||||
<div class="grp-area">
|
<div class="cell model">{{ row.model }}</div>
|
||||||
<div class="grp-row">
|
<div class="cell renk">
|
||||||
<div
|
{{ row.renk }}{{ row.renk2 ? '-' + row.renk2 : '' }}
|
||||||
v-for="v in (
|
</div>
|
||||||
|
<div class="cell ana">{{ row.urunAnaGrubu }}</div>
|
||||||
|
<div class="cell alt">{{ row.urunAltGrubu }}</div>
|
||||||
|
<div class="cell aciklama">{{ row.aciklama }}</div>
|
||||||
|
|
||||||
|
<!-- Beden kolonları -->
|
||||||
|
<div class="grp-area">
|
||||||
|
<div class="grp-row">
|
||||||
|
<div
|
||||||
|
v-for="v in (
|
||||||
(orderStore.schemaMap?.[row.grpKey]?.values) ||
|
(orderStore.schemaMap?.[row.grpKey]?.values) ||
|
||||||
(storeSchemaByKey[row.grpKey]?.values) ||
|
(storeSchemaByKey[row.grpKey]?.values) ||
|
||||||
(storeSchemaByKey.tak.values)
|
(storeSchemaByKey.tak.values)
|
||||||
)"
|
)"
|
||||||
|
|
||||||
:key="'val-' + v"
|
:key="'val-' + v"
|
||||||
class="cell beden"
|
class="cell beden"
|
||||||
>
|
>
|
||||||
{{ resolveBedenValue(row.bedenMap, row.grpKey, v) }}
|
{{ resolveBedenValue(row.bedenMap, row.grpKey, v) }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-for="i2 in Math.max(0, 16 - (
|
v-for="i2 in Math.max(0, 16 - (
|
||||||
(orderStore.schemaMap?.[row.grpKey]?.values?.length) ||
|
(orderStore.schemaMap?.[row.grpKey]?.values?.length) ||
|
||||||
(storeSchemaByKey[row.grpKey]?.values?.length) ||
|
(storeSchemaByKey[row.grpKey]?.values?.length) ||
|
||||||
(storeSchemaByKey.tak.values.length)
|
(storeSchemaByKey.tak.values.length)
|
||||||
))"
|
))"
|
||||||
|
|
||||||
:key="'empty-' + i2"
|
:key="'empty-' + i2"
|
||||||
class="cell beden ghost"
|
class="cell beden ghost"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Sağ kolonlar -->
|
||||||
|
<div class="cell adet">{{ row.adet }}</div>
|
||||||
|
<div class="cell fiyat">{{ row.fiyat }}</div>
|
||||||
|
<div class="cell pb">{{ row.pb }}</div>
|
||||||
|
<div class="cell tutar">
|
||||||
|
{{ Number(row.tutar || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 🗓 Termin Tarihi -->
|
||||||
|
<div class="cell termin">
|
||||||
|
<div class="termin-label text-center">
|
||||||
|
{{ formatDate(row.terminTarihi) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<!-- =======================================================
|
||||||
|
🔹 SATIR DÜZENLEYİCİ FORM (EDITOR)
|
||||||
|
======================================================== -->
|
||||||
|
<q-dialog
|
||||||
|
v-model="showEditor"
|
||||||
|
class="order-editor-dialog"
|
||||||
|
:maximized="$q.screen.lt.md"
|
||||||
|
full-width
|
||||||
|
transition-show="jump-down"
|
||||||
|
transition-hide="jump-up"
|
||||||
|
persistent
|
||||||
|
>
|
||||||
|
<q-card class="order-editor-card">
|
||||||
|
<q-card-section class="row items-center justify-between">
|
||||||
|
<div class="text-subtitle1 text-weight-bold">
|
||||||
|
{{ isEditing ? 'Satır Düzenle' : 'Yeni Satır' }}
|
||||||
|
</div>
|
||||||
|
<q-btn flat round icon="close" @click="showEditor = false" />
|
||||||
|
</q-card-section>
|
||||||
|
<q-separator />
|
||||||
|
<q-card-section class="editor q-pa-sm">
|
||||||
|
|
||||||
|
<!-- 🔸 1. Satır: Model ve Ürün Bilgileri -->
|
||||||
|
<div class="row q-col-gutter-sm q-mb-sm">
|
||||||
|
<div class="col-3">
|
||||||
|
<!-- 🔹 Model Seçimi -->
|
||||||
|
<q-select
|
||||||
|
v-model="form.model"
|
||||||
|
:options="filteredModelOptions"
|
||||||
|
label="Model"
|
||||||
|
filled dense
|
||||||
|
use-input input-debounce="250"
|
||||||
|
emit-value map-options
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
clearable behavior="menu"
|
||||||
|
hint="Model kodu ile arayabilirsiniz"
|
||||||
|
:loading="loadingModels"
|
||||||
|
:disable=" isClosedRow||isViewOnly"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
@filter="filterModel"
|
||||||
|
@update:model-value="(val) => useComboWatcher('model', onModelChange)(val)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- 🔹 1. Renk Seçimi -->
|
||||||
|
<div class="q-mt-sm">
|
||||||
|
<q-select
|
||||||
|
ref="renkSelect"
|
||||||
|
v-model="form.renk"
|
||||||
|
:options="renkOptions"
|
||||||
|
label="Renk"
|
||||||
|
filled dense clearable
|
||||||
|
emit-value map-options
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
:disable="isClosedRow||isViewOnly"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
@update:model-value="(val) => useComboWatcher('renk', onColorChange)(val)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 🔹 2. Renk Seçimi -->
|
||||||
|
<div class="q-mt-sm">
|
||||||
|
<q-select
|
||||||
|
ref="renk2Select"
|
||||||
|
v-model="form.renk2"
|
||||||
|
:options="renkOptions2"
|
||||||
|
label="2. Renk"
|
||||||
|
filled dense clearable
|
||||||
|
emit-value map-options
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
:disable="!renkOptions2.length || isEditing || isClosedRow"
|
||||||
|
@update:model-value="(val) => useComboWatcher('renk2', onColor2Change)(val)"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Sağ kolonlar -->
|
<!-- Ürün teknik alanları -->
|
||||||
<div class="cell adet">{{ row.adet }}</div>
|
<div class="col-2">
|
||||||
<div class="cell fiyat">{{ row.fiyat }}</div>
|
<q-input v-model="form.urunAnaGrubu" label="Ürün Ana Grubu" filled dense readonly />
|
||||||
<div class="cell pb">{{ row.pb }}</div>
|
</div>
|
||||||
<div class="cell tutar">
|
<div class="col-1">
|
||||||
{{ Number(row.tutar || 0).toLocaleString('tr-TR', { minimumFractionDigits: 2 }) }}
|
<q-input v-model="form.urunAltGrubu" label="Alt Grup" filled dense readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<q-input v-model="form.fit" label="Fit" filled dense readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<q-input v-model="form.urunIcerik" label="İçerik" filled dense readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<q-input v-model="form.drop" label="Drop" filled dense readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<q-input v-model="form.askiliyan" label="ASKILI/YAN" filled dense readonly />
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<q-input v-model="form.kategori" label="Kategori" filled dense readonly />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 🔸 2. Satır: Seri Seçimi -->
|
||||||
|
<div class="row q-col-gutter-sm q-mt-xs">
|
||||||
|
<div class="col-3">
|
||||||
|
<q-select
|
||||||
|
ref="seriSelect"
|
||||||
|
v-show="Array.isArray(activeSeriesOptions) && activeSeriesOptions.length > 0"
|
||||||
|
v-model="selectedSeriSet"
|
||||||
|
:options="activeSeriesOptions"
|
||||||
|
label="Beden Seti Seç"
|
||||||
|
filled dense
|
||||||
|
emit-value map-options
|
||||||
|
option-value="value"
|
||||||
|
option-label="label"
|
||||||
|
:disable="isClosedRow"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 🗓 Termin Tarihi -->
|
<div class="col-2 q-mt-sm">
|
||||||
<div class="cell termin">
|
<q-input
|
||||||
<div class="termin-label text-center">
|
v-if="selectedSeriSet"
|
||||||
{{ formatDate(row.terminTarihi) }}
|
v-model.number="seriMultiplier"
|
||||||
|
type="number"
|
||||||
|
label="Çarpan"
|
||||||
|
min="1"
|
||||||
|
filled dense
|
||||||
|
:disable="isClosedRow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-2 q-mt-sm">
|
||||||
|
<q-btn
|
||||||
|
v-if="selectedSeriSet && canMutateRows"
|
||||||
|
color="primary"
|
||||||
|
icon="add"
|
||||||
|
label="Seri Ekle"
|
||||||
|
@click="applySeriSet"
|
||||||
|
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- =======================================================
|
||||||
|
🔹 BEDEN GİRİŞ ALANI + STOK ETİKETİ GÖRÜNÜMÜ
|
||||||
|
======================================================== -->
|
||||||
|
<div class="row q-mt-sm q-col-gutter-xs beden-grid">
|
||||||
|
<div
|
||||||
|
v-for="(lbl, i) in form.bedenLabels || []"
|
||||||
|
:key="'beden-'+i"
|
||||||
|
class="col-auto beden-wrap"
|
||||||
|
>
|
||||||
|
<div class="beden-label">{{ lbl }}</div>
|
||||||
|
|
||||||
|
<q-input
|
||||||
|
v-model.number="form.bedenler[i]"
|
||||||
|
dense outlined type="number" min="0"
|
||||||
|
style="width:60px"
|
||||||
|
@focus="activeBeden = i"
|
||||||
|
@blur="activeBeden = null"
|
||||||
|
@update:model-value="updateTotals(form)"
|
||||||
|
:class="{ 'beden-active': activeBeden === i }"
|
||||||
|
:disable="isClosedRow||isViewOnly"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="getStockFor(lbl) !== null"
|
||||||
|
class="stok-label text-caption text-center q-mt-xs"
|
||||||
|
:class="stockColorClass(getStockFor(lbl))"
|
||||||
|
>
|
||||||
|
Stok: {{ getStockFor(lbl) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<!-- 🔹 Aktif beden için küçük stok etiketi -->
|
||||||
</div>
|
<div
|
||||||
</template>
|
v-if="form.model && activeBeden !== null && getStockFor(form.bedenLabels[activeBeden]) !== null"
|
||||||
</div>
|
class="stok-label-sm"
|
||||||
<!-- =======================================================
|
:class="stockColorClass(getStockFor(form.bedenLabels[activeBeden]))"
|
||||||
🔹 SATIR DÜZENLEYİCİ FORM (EDITOR)
|
>
|
||||||
======================================================== -->
|
Stok: {{ getStockFor(form.bedenLabels[activeBeden]) }}
|
||||||
<q-dialog
|
</div>
|
||||||
v-model="showEditor"
|
|
||||||
class="order-editor-dialog"
|
|
||||||
:maximized="$q.screen.lt.md"
|
|
||||||
full-width
|
|
||||||
transition-show="jump-down"
|
|
||||||
transition-hide="jump-up"
|
|
||||||
persistent
|
|
||||||
>
|
|
||||||
<q-card class="order-editor-card">
|
|
||||||
<q-card-section class="row items-center justify-between">
|
|
||||||
<div class="text-subtitle1 text-weight-bold">
|
|
||||||
{{ isEditing ? 'Satır Düzenle' : 'Yeni Satır' }}
|
|
||||||
</div>
|
|
||||||
<q-btn flat round icon="close" @click="showEditor = false" />
|
|
||||||
</q-card-section>
|
|
||||||
<q-separator />
|
|
||||||
<q-card-section class="editor q-pa-sm">
|
|
||||||
|
|
||||||
<!-- 🔸 1. Satır: Model ve Ürün Bilgileri -->
|
<!-- =======================================================
|
||||||
<div class="row q-col-gutter-sm q-mb-sm">
|
🔹 ADET / FİYAT / PB / TUTAR
|
||||||
<div class="col-3">
|
======================================================== -->
|
||||||
<!-- 🔹 Model Seçimi -->
|
<div class="row q-mt-sm q-col-gutter-sm">
|
||||||
<q-select
|
<div class="col-2">
|
||||||
v-model="form.model"
|
<q-input
|
||||||
:options="filteredModelOptions"
|
v-model.number="form.adet"
|
||||||
label="Model"
|
label="Adet"
|
||||||
filled dense
|
dense
|
||||||
use-input input-debounce="250"
|
filled
|
||||||
emit-value map-options
|
readonly
|
||||||
option-value="value"
|
:disable="isClosedRow"
|
||||||
option-label="label"
|
/>
|
||||||
clearable behavior="menu"
|
</div>
|
||||||
hint="Model kodu ile arayabilirsiniz"
|
<div class="col-2">
|
||||||
:loading="loadingModels"
|
<q-input
|
||||||
:disable=" isClosedRow||isViewOnly"
|
v-model.number="form.fiyat"
|
||||||
:readonly="isViewOnly"
|
label="Fiyat"
|
||||||
@filter="filterModel"
|
dense
|
||||||
@update:model-value="(val) => useComboWatcher('model', onModelChange)(val)"
|
filled
|
||||||
/>
|
type="number"
|
||||||
|
min="0"
|
||||||
|
@update:model-value="() => updateTotals(form)"
|
||||||
|
:disable="isClosedRow||isViewOnly"
|
||||||
|
:readonly="isViewOnly"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<q-select
|
||||||
|
v-model="form.pb"
|
||||||
|
:options="paraBirimOptions"
|
||||||
|
label="PB"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
:disable="isClosedRow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="col-3">
|
||||||
|
<q-input
|
||||||
|
v-model="form.tutar"
|
||||||
|
label="Tutar"
|
||||||
|
dense
|
||||||
|
filled
|
||||||
|
readonly
|
||||||
|
:disable="isClosedRow"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 🔹 1. Renk Seçimi -->
|
<!-- =======================================================
|
||||||
<div class="q-mt-sm">
|
🔹 SATIR BAZINDA TAHMİNİ TERMİN TARİHİ
|
||||||
<q-select
|
======================================================== -->
|
||||||
ref="renkSelect"
|
<div class="row q-mt-sm">
|
||||||
v-model="form.renk"
|
<div class="col-4">
|
||||||
:options="renkOptions"
|
<q-input
|
||||||
label="Renk"
|
v-model="form.terminTarihi"
|
||||||
filled dense clearable
|
type="date"
|
||||||
emit-value map-options
|
label="Tahmini Termin Tarihi"
|
||||||
option-value="value"
|
filled
|
||||||
option-label="label"
|
dense
|
||||||
:disable="isClosedRow||isViewOnly"
|
:disable="isClosedRow"
|
||||||
:readonly="isViewOnly"
|
/>
|
||||||
@update:model-value="(val) => useComboWatcher('renk', onColorChange)(val)"
|
</div>
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 🔹 2. Renk Seçimi -->
|
<!-- =======================================================
|
||||||
<div class="q-mt-sm">
|
🔹 AÇIKLAMA ALANI
|
||||||
<q-select
|
======================================================== -->
|
||||||
ref="renk2Select"
|
<div class="row q-mt-sm">
|
||||||
v-model="form.renk2"
|
<div class="col-12">
|
||||||
:options="renkOptions2"
|
<q-input
|
||||||
label="2. Renk"
|
v-model="form.aciklama"
|
||||||
filled dense clearable
|
label="Açıklama"
|
||||||
emit-value map-options
|
type="textarea"
|
||||||
option-value="value"
|
filled
|
||||||
option-label="label"
|
dense
|
||||||
:disable="!renkOptions2.length || isEditing || isClosedRow"
|
autogrow
|
||||||
@update:model-value="(val) => useComboWatcher('renk2', onColor2Change)(val)"
|
maxlength="1500"
|
||||||
/>
|
counter
|
||||||
</div>
|
:disable="isClosedRow"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- =======================================================
|
||||||
|
🔹 BUTONLAR (Kaydet / Güncelle / Sil / Temizle)
|
||||||
|
======================================================== -->
|
||||||
|
<div class="row justify-between items-center q-mt-md">
|
||||||
|
<div class="row q-gutter-sm">
|
||||||
|
<q-btn
|
||||||
|
v-if="canMutateRows"
|
||||||
|
:color="isEditing ? 'positive' : 'primary'"
|
||||||
|
:label="isEditing ? 'Güncelle' : 'Kaydet'"
|
||||||
|
@click="onSaveOrUpdateRow"
|
||||||
|
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||||
|
|
||||||
<!-- Ürün teknik alanları -->
|
/>
|
||||||
<div class="col-2">
|
<q-btn
|
||||||
<q-input v-model="form.urunAnaGrubu" label="Ürün Ana Grubu" filled dense readonly />
|
v-if="canMutateRows"
|
||||||
</div>
|
color="secondary"
|
||||||
<div class="col-1">
|
label="Kaydet ve Diğer Renge Geç"
|
||||||
<q-input v-model="form.urunAltGrubu" label="Alt Grup" filled dense readonly />
|
@click="onSaveAndNextColor"
|
||||||
</div>
|
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||||
<div class="col-1">
|
/>
|
||||||
<q-input v-model="form.fit" label="Fit" filled dense readonly />
|
<q-btn
|
||||||
</div>
|
v-if="isEditing && canMutateRows"
|
||||||
<div class="col-2">
|
color="negative"
|
||||||
<q-input v-model="form.urunIcerik" label="İçerik" filled dense readonly />
|
flat
|
||||||
</div>
|
label="Satırı Sil"
|
||||||
<div class="col-1">
|
@click="removeSelected"
|
||||||
<q-input v-model="form.drop" label="Drop" filled dense readonly />
|
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
||||||
</div>
|
/>
|
||||||
<div class="col-1">
|
<q-btn
|
||||||
<q-input v-model="form.askiliyan" label="ASKILI/YAN" filled dense readonly />
|
v-if="canMutateRows"
|
||||||
</div>
|
flat
|
||||||
<div class="col-1">
|
color="grey-8"
|
||||||
<q-input v-model="form.kategori" label="Kategori" filled dense readonly />
|
label="Formu Temizle"
|
||||||
</div>
|
@click="onResetEditorClick"
|
||||||
</div>
|
:disable="isClosedRow||isViewOnly || !canMutateRows"
|
||||||
|
|
||||||
<!-- 🔸 2. Satır: Seri Seçimi -->
|
/>
|
||||||
<div class="row q-col-gutter-sm q-mt-xs">
|
</div>
|
||||||
<div class="col-3">
|
</div>
|
||||||
<q-select
|
|
||||||
ref="seriSelect"
|
|
||||||
v-show="Array.isArray(activeSeriesOptions) && activeSeriesOptions.length > 0"
|
|
||||||
v-model="selectedSeriSet"
|
|
||||||
:options="activeSeriesOptions"
|
|
||||||
label="Beden Seti Seç"
|
|
||||||
filled dense
|
|
||||||
emit-value map-options
|
|
||||||
option-value="value"
|
|
||||||
option-label="label"
|
|
||||||
:disable="isClosedRow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-2 q-mt-sm">
|
<!-- =======================================================
|
||||||
<q-input
|
🔹 ALT BİLGİLENDİRME ALANI
|
||||||
v-if="selectedSeriSet"
|
======================================================== -->
|
||||||
v-model.number="seriMultiplier"
|
<div class="q-mt-md text-caption text-grey-7 text-center">
|
||||||
type="number"
|
<q-icon name="info" size="16px" class="q-mr-xs" />
|
||||||
label="Çarpan"
|
Bu sayfada yapılan siparişler henüz gönderilmemiştir.
|
||||||
min="1"
|
<br />
|
||||||
filled dense
|
<span class="text-negative">"Tümünü Kaydet (Toplu Gönder)"</span>
|
||||||
:disable="isClosedRow"
|
butonuna basarak işlemleri kaydedebilirsiniz.
|
||||||
/>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-2 q-mt-sm">
|
</q-card-section>
|
||||||
<q-btn
|
</q-card>
|
||||||
v-if="selectedSeriSet && canMutateRows"
|
</q-dialog>
|
||||||
color="primary"
|
|
||||||
icon="add"
|
|
||||||
label="Seri Ekle"
|
|
||||||
@click="applySeriSet"
|
|
||||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- =======================================================
|
|
||||||
🔹 BEDEN GİRİŞ ALANI + STOK ETİKETİ GÖRÜNÜMÜ
|
|
||||||
======================================================== -->
|
|
||||||
<div class="row q-mt-sm q-col-gutter-xs beden-grid">
|
|
||||||
<div
|
|
||||||
v-for="(lbl, i) in form.bedenLabels || []"
|
|
||||||
:key="'beden-'+i"
|
|
||||||
class="col-auto beden-wrap"
|
|
||||||
>
|
|
||||||
<div class="beden-label">{{ lbl }}</div>
|
|
||||||
|
|
||||||
<q-input
|
|
||||||
v-model.number="form.bedenler[i]"
|
|
||||||
dense outlined type="number" min="0"
|
|
||||||
style="width:60px"
|
|
||||||
@focus="activeBeden = i"
|
|
||||||
@blur="activeBeden = null"
|
|
||||||
@update:model-value="updateTotals(form)"
|
|
||||||
:class="{ 'beden-active': activeBeden === i }"
|
|
||||||
:disable="isClosedRow||isViewOnly"
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="getStockFor(lbl) !== null"
|
|
||||||
class="stok-label text-caption text-center q-mt-xs"
|
|
||||||
:class="stockColorClass(getStockFor(lbl))"
|
|
||||||
>
|
|
||||||
Stok: {{ getStockFor(lbl) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- 🔹 Aktif beden için küçük stok etiketi -->
|
|
||||||
<div
|
|
||||||
v-if="form.model && activeBeden !== null && getStockFor(form.bedenLabels[activeBeden]) !== null"
|
|
||||||
class="stok-label-sm"
|
|
||||||
:class="stockColorClass(getStockFor(form.bedenLabels[activeBeden]))"
|
|
||||||
>
|
|
||||||
Stok: {{ getStockFor(form.bedenLabels[activeBeden]) }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- =======================================================
|
|
||||||
🔹 ADET / FİYAT / PB / TUTAR
|
|
||||||
======================================================== -->
|
|
||||||
<div class="row q-mt-sm q-col-gutter-sm">
|
|
||||||
<div class="col-2">
|
|
||||||
<q-input
|
|
||||||
v-model.number="form.adet"
|
|
||||||
label="Adet"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
readonly
|
|
||||||
:disable="isClosedRow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<q-input
|
|
||||||
v-model.number="form.fiyat"
|
|
||||||
label="Fiyat"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
@update:model-value="() => updateTotals(form)"
|
|
||||||
:disable="isClosedRow||isViewOnly"
|
|
||||||
:readonly="isViewOnly"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-2">
|
|
||||||
<q-select
|
|
||||||
v-model="form.pb"
|
|
||||||
:options="paraBirimOptions"
|
|
||||||
label="PB"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
:disable="isClosedRow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="col-3">
|
|
||||||
<q-input
|
|
||||||
v-model="form.tutar"
|
|
||||||
label="Tutar"
|
|
||||||
dense
|
|
||||||
filled
|
|
||||||
readonly
|
|
||||||
:disable="isClosedRow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- =======================================================
|
|
||||||
🔹 SATIR BAZINDA TAHMİNİ TERMİN TARİHİ
|
|
||||||
======================================================== -->
|
|
||||||
<div class="row q-mt-sm">
|
|
||||||
<div class="col-4">
|
|
||||||
<q-input
|
|
||||||
v-model="form.terminTarihi"
|
|
||||||
type="date"
|
|
||||||
label="Tahmini Termin Tarihi"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
:disable="isClosedRow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- =======================================================
|
|
||||||
🔹 AÇIKLAMA ALANI
|
|
||||||
======================================================== -->
|
|
||||||
<div class="row q-mt-sm">
|
|
||||||
<div class="col-12">
|
|
||||||
<q-input
|
|
||||||
v-model="form.aciklama"
|
|
||||||
label="Açıklama"
|
|
||||||
type="textarea"
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
autogrow
|
|
||||||
maxlength="1500"
|
|
||||||
counter
|
|
||||||
:disable="isClosedRow"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- =======================================================
|
|
||||||
🔹 BUTONLAR (Kaydet / Güncelle / Sil / Temizle)
|
|
||||||
======================================================== -->
|
|
||||||
<div class="row justify-between items-center q-mt-md">
|
|
||||||
<div class="row q-gutter-sm">
|
|
||||||
<q-btn
|
|
||||||
v-if="canMutateRows"
|
|
||||||
:color="isEditing ? 'positive' : 'primary'"
|
|
||||||
:label="isEditing ? 'Güncelle' : 'Kaydet'"
|
|
||||||
@click="onSaveOrUpdateRow"
|
|
||||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
|
||||||
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
v-if="canMutateRows"
|
|
||||||
color="secondary"
|
|
||||||
label="Kaydet ve Diğer Renge Geç"
|
|
||||||
@click="onSaveAndNextColor"
|
|
||||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
v-if="isEditing && canMutateRows"
|
|
||||||
color="negative"
|
|
||||||
flat
|
|
||||||
label="Satırı Sil"
|
|
||||||
@click="removeSelected"
|
|
||||||
:disable="isClosedRow || isViewOnly || !canMutateRows"
|
|
||||||
/>
|
|
||||||
<q-btn
|
|
||||||
v-if="canMutateRows"
|
|
||||||
flat
|
|
||||||
color="grey-8"
|
|
||||||
label="Formu Temizle"
|
|
||||||
@click="onResetEditorClick"
|
|
||||||
:disable="isClosedRow||isViewOnly || !canMutateRows"
|
|
||||||
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- =======================================================
|
|
||||||
🔹 ALT BİLGİLENDİRME ALANI
|
|
||||||
======================================================== -->
|
|
||||||
<div class="q-mt-md text-caption text-grey-7 text-center">
|
|
||||||
<q-icon name="info" size="16px" class="q-mr-xs" />
|
|
||||||
Bu sayfada yapılan siparişler henüz gönderilmemiştir.
|
|
||||||
<br />
|
|
||||||
<span class="text-negative">"Tümünü Kaydet (Toplu Gönder)"</span>
|
|
||||||
butonuna basarak işlemleri kaydedebilirsiniz.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
||||||
</div> <!-- ✅ order-scroll-y -->
|
</div> <!-- ✅ order-scroll-y -->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -2674,13 +2674,20 @@ async function onModelChange(modelCode) {
|
|||||||
- En sonda güvenli fallback: 'tak'
|
- En sonda güvenli fallback: 'tak'
|
||||||
======================================================= */
|
======================================================= */
|
||||||
const ana = String(form.urunAnaGrubu || '').toLowerCase().trim()
|
const ana = String(form.urunAnaGrubu || '').toLowerCase().trim()
|
||||||
const kat = String(form.kategori || '').toLowerCase().trim()
|
const kat = String(form.kategori || form.urunAltGrubu || '').toLowerCase().trim()
|
||||||
|
const yg = String(form.askiliyan || '').toLowerCase().trim()
|
||||||
|
const hasGarsonMeta =
|
||||||
|
ana.includes('garson') ||
|
||||||
|
kat.includes('garson') ||
|
||||||
|
kat.includes('yetiskin/garson') ||
|
||||||
|
yg.includes('garson') ||
|
||||||
|
yg.includes('yetiskin/garson')
|
||||||
|
|
||||||
let bedenGrpKey = null
|
let bedenGrpKey = null
|
||||||
|
|
||||||
// ✅ Hard-match (senin ana gruplarına göre genişletebilirsin)
|
// ✅ Hard-match (senin ana gruplarına göre genişletebilirsin)
|
||||||
if (
|
if (
|
||||||
(kat.includes('garson') || kat.includes('yetiskin/garson')) &&
|
hasGarsonMeta &&
|
||||||
(
|
(
|
||||||
ana.includes('gomlek atayaka') ||
|
ana.includes('gomlek atayaka') ||
|
||||||
ana.includes('gomlek ata yaka') ||
|
ana.includes('gomlek ata yaka') ||
|
||||||
@@ -2688,8 +2695,8 @@ async function onModelChange(modelCode) {
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
bedenGrpKey = 'yas'
|
bedenGrpKey = 'yas'
|
||||||
} else if ((ana.includes('garson') || kat.includes('garson') || kat.includes('yetiskin/garson') || ana.includes('yetiskin/garson')) &&
|
} else if (hasGarsonMeta &&
|
||||||
(ana.includes('ayakkabı') || ana.includes('ayakkabi') || kat.includes('ayakkabı') || kat.includes('ayakkabi'))) {
|
(ana.includes('ayakkabı') || ana.includes('ayakkabi') || kat.includes('ayakkabı') || kat.includes('ayakkabi'))) {
|
||||||
bedenGrpKey = 'ayk_garson'
|
bedenGrpKey = 'ayk_garson'
|
||||||
} else if (ana.includes('pantolon') || kat.includes('pantolon')) {
|
} else if (ana.includes('pantolon') || kat.includes('pantolon')) {
|
||||||
bedenGrpKey = 'pan'
|
bedenGrpKey = 'pan'
|
||||||
@@ -2705,7 +2712,12 @@ async function onModelChange(modelCode) {
|
|||||||
if (!bedenGrpKey) {
|
if (!bedenGrpKey) {
|
||||||
try {
|
try {
|
||||||
// ⚠️ Boş array verme; ürün bilgisini kullanarak belirle
|
// ⚠️ Boş array verme; ürün bilgisini kullanarak belirle
|
||||||
bedenGrpKey = detectBedenGroup(null, form.urunAnaGrubu, form.kategori)
|
bedenGrpKey = detectBedenGroup(
|
||||||
|
null,
|
||||||
|
form.urunAnaGrubu,
|
||||||
|
form.kategori || form.urunAltGrubu,
|
||||||
|
form.askiliyan
|
||||||
|
)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('⚠️ detectBedenGroup hata:', e)
|
console.warn('⚠️ detectBedenGroup hata:', e)
|
||||||
bedenGrpKey = null
|
bedenGrpKey = null
|
||||||
|
|||||||
@@ -90,6 +90,7 @@
|
|||||||
|
|
||||||
<div class="total-row">
|
<div class="total-row">
|
||||||
<div class="total-cell">ADET</div>
|
<div class="total-cell">ADET</div>
|
||||||
|
<div class="total-cell">FOTO</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,6 +177,24 @@
|
|||||||
<q-icon :name="isOpen(grp2.key) ? 'expand_less' : 'expand_more'" size="18px" />
|
<q-icon :name="isOpen(grp2.key) ? 'expand_less' : 'expand_more'" size="18px" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="sub-image level2-image">
|
||||||
|
<q-card flat bordered class="product-image-card">
|
||||||
|
<q-card-section class="q-pa-xs product-image-wrap">
|
||||||
|
<q-img
|
||||||
|
v-if="getProductImageUrl(grp1.productCode, grp2.colorCode)"
|
||||||
|
:src="getProductImageUrl(grp1.productCode, grp2.colorCode)"
|
||||||
|
fit="cover"
|
||||||
|
class="product-image"
|
||||||
|
loading="lazy"
|
||||||
|
@error="onProductImageError(grp1.productCode, grp2.colorCode)"
|
||||||
|
/>
|
||||||
|
<div v-else class="product-image-placeholder">
|
||||||
|
<q-icon name="image_not_supported" size="22px" color="grey-6" />
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="isOpen(grp2.key)">
|
<template v-if="isOpen(grp2.key)">
|
||||||
@@ -200,6 +219,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="cell adet">{{ formatNumber(row.adet) }}</div>
|
<div class="cell adet">{{ formatNumber(row.adet) }}</div>
|
||||||
|
<div class="cell img-placeholder"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -217,7 +237,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import { useQuasar } from 'quasar'
|
import { useQuasar } from 'quasar'
|
||||||
import api from 'src/services/api'
|
import api from 'src/services/api'
|
||||||
import { usePermission } from 'src/composables/usePermission'
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
@@ -240,6 +260,17 @@ const selectedProductCode = ref('')
|
|||||||
const productOptions = ref([])
|
const productOptions = ref([])
|
||||||
const filteredProductOptions = ref([])
|
const filteredProductOptions = ref([])
|
||||||
const rawRows = ref([])
|
const rawRows = ref([])
|
||||||
|
const productImageCache = ref({})
|
||||||
|
const productImageLoading = ref({})
|
||||||
|
const productImageListByCode = ref({})
|
||||||
|
const productImageListLoading = ref({})
|
||||||
|
const productImageFallbackByKey = ref({})
|
||||||
|
const productImageContentLoading = ref({})
|
||||||
|
const productImageBlobUrls = ref([])
|
||||||
|
const productImageListBlockedUntil = ref(0)
|
||||||
|
const IMAGE_LIST_CONCURRENCY = 8
|
||||||
|
let imageListActiveRequests = 0
|
||||||
|
const imageListWaitQueue = []
|
||||||
const activeSchema = ref(storeSchemaByKey.tak)
|
const activeSchema = ref(storeSchemaByKey.tak)
|
||||||
const activeGrpKey = ref('tak')
|
const activeGrpKey = ref('tak')
|
||||||
const openState = ref({})
|
const openState = ref({})
|
||||||
@@ -280,6 +311,155 @@ function parseNumber(value) {
|
|||||||
return Number.isFinite(n) ? n : 0
|
return Number.isFinite(n) ? n : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildImageKey(code, color) {
|
||||||
|
return `${String(code || '').trim().toUpperCase()}::${String(color || '').trim().toUpperCase()}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeUploadsPath(storagePath) {
|
||||||
|
const raw = String(storagePath || '').trim()
|
||||||
|
if (!raw) return ''
|
||||||
|
const normalized = raw.replace(/\\/g, '/')
|
||||||
|
const idx = normalized.toLowerCase().indexOf('/uploads/')
|
||||||
|
if (idx >= 0) return normalized.slice(idx)
|
||||||
|
if (normalized.toLowerCase().startsWith('uploads/')) return `/${normalized}`
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveProductImageUrl(item) {
|
||||||
|
if (!item || typeof item !== 'object') {
|
||||||
|
return { contentUrl: '', publicUrl: '' }
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentUrl = ''
|
||||||
|
const imageId = Number(item.id || item.ID || 0)
|
||||||
|
if (Number.isFinite(imageId) && imageId > 0) {
|
||||||
|
contentUrl = `/api/product-images/${imageId}/content`
|
||||||
|
} else {
|
||||||
|
const contentURL = String(item.content_url || item.ContentURL || '').trim()
|
||||||
|
if (contentURL.startsWith('/api/')) contentUrl = contentURL
|
||||||
|
else if (contentURL.startsWith('/')) contentUrl = `/api${contentURL}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage || '')
|
||||||
|
let publicUrl = ''
|
||||||
|
const thumbFolder = (typeof window !== 'undefined' && window.devicePixelRatio > 1.5) ? 't600' : 't300'
|
||||||
|
if (uploadsPath) {
|
||||||
|
if (uploadsPath.includes('/uploads/image/') && !uploadsPath.includes('/uploads/image/t300/') && !uploadsPath.includes('/uploads/image/t600/')) {
|
||||||
|
publicUrl = uploadsPath.replace('/uploads/image/', `/uploads/image/${thumbFolder}/`)
|
||||||
|
} else {
|
||||||
|
publicUrl = uploadsPath
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const fileName = String(item.file_name || item.FileName || '').trim()
|
||||||
|
if (fileName) publicUrl = `/uploads/image/${thumbFolder}/${fileName}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return { contentUrl, publicUrl }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProductImageUrl(code, color) {
|
||||||
|
const key = buildImageKey(code, color)
|
||||||
|
const existing = productImageCache.value[key]
|
||||||
|
if (existing !== undefined) return existing || ''
|
||||||
|
void ensureProductImage(code, color)
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onProductImageError(code, color) {
|
||||||
|
const key = buildImageKey(code, color)
|
||||||
|
const current = String(productImageCache.value[key] || '')
|
||||||
|
const fallback = String(productImageFallbackByKey.value[key] || '')
|
||||||
|
if (fallback && current !== fallback && !productImageContentLoading.value[key]) {
|
||||||
|
productImageContentLoading.value[key] = true
|
||||||
|
try {
|
||||||
|
const blobRes = await api.get(fallback, { baseURL: '', responseType: 'blob' })
|
||||||
|
const blob = blobRes?.data
|
||||||
|
if (blob instanceof Blob) {
|
||||||
|
const objectUrl = URL.createObjectURL(blob)
|
||||||
|
productImageBlobUrls.value.push(objectUrl)
|
||||||
|
productImageCache.value[key] = objectUrl
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// no-op
|
||||||
|
} finally {
|
||||||
|
delete productImageContentLoading.value[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
productImageCache.value[key] = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureProductImage(code, color) {
|
||||||
|
const key = buildImageKey(code, color)
|
||||||
|
const codeTrim = String(code || '').trim().toUpperCase()
|
||||||
|
const colorTrim = String(color || '').trim().toUpperCase()
|
||||||
|
if (!codeTrim) {
|
||||||
|
productImageCache.value[key] = ''
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (Date.now() < Number(productImageListBlockedUntil.value || 0)) {
|
||||||
|
productImageCache.value[key] = ''
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
if (productImageCache.value[key] !== undefined) return productImageCache.value[key] || ''
|
||||||
|
if (productImageLoading.value[key]) return ''
|
||||||
|
|
||||||
|
productImageLoading.value[key] = true
|
||||||
|
try {
|
||||||
|
if (!productImageListByCode.value[codeTrim]) {
|
||||||
|
if (!productImageListLoading.value[codeTrim]) {
|
||||||
|
productImageListLoading.value[codeTrim] = true
|
||||||
|
try {
|
||||||
|
if (imageListActiveRequests >= IMAGE_LIST_CONCURRENCY) {
|
||||||
|
await new Promise((resolve) => imageListWaitQueue.push(resolve))
|
||||||
|
}
|
||||||
|
imageListActiveRequests++
|
||||||
|
const res = await api.get('/product-images', { params: { code: codeTrim } })
|
||||||
|
productImageListByCode.value[codeTrim] = Array.isArray(res?.data) ? res.data : []
|
||||||
|
} catch (err) {
|
||||||
|
productImageListByCode.value[codeTrim] = []
|
||||||
|
const status = Number(err?.response?.status || 0)
|
||||||
|
if (status >= 500 || status === 403 || status === 0) {
|
||||||
|
productImageListBlockedUntil.value = Date.now() + 30 * 1000
|
||||||
|
}
|
||||||
|
console.warn('[ProductStockQuery] product image list fetch failed', { code: codeTrim, err })
|
||||||
|
} finally {
|
||||||
|
imageListActiveRequests = Math.max(0, imageListActiveRequests - 1)
|
||||||
|
const nextInQueue = imageListWaitQueue.shift()
|
||||||
|
if (nextInQueue) nextInQueue()
|
||||||
|
delete productImageListLoading.value[codeTrim]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
while (productImageListLoading.value[codeTrim]) {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 25))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = productImageListByCode.value[codeTrim] || []
|
||||||
|
let first = null
|
||||||
|
if (colorTrim) {
|
||||||
|
const needle = `-${colorTrim.toLowerCase()}-`
|
||||||
|
first = list.find((item) =>
|
||||||
|
String(item?.file_name || item?.FileName || '').toLowerCase().includes(needle)
|
||||||
|
) || null
|
||||||
|
}
|
||||||
|
if (!first) first = list[0] || null
|
||||||
|
|
||||||
|
const resolved = resolveProductImageUrl(first)
|
||||||
|
productImageCache.value[key] = resolved.publicUrl || resolved.contentUrl || ''
|
||||||
|
productImageFallbackByKey.value[key] = resolved.contentUrl || ''
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
|
||||||
|
productImageCache.value[key] = ''
|
||||||
|
productImageFallbackByKey.value[key] = ''
|
||||||
|
} finally {
|
||||||
|
delete productImageLoading.value[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
return productImageCache.value[key] || ''
|
||||||
|
}
|
||||||
|
|
||||||
function formatNumber(v) {
|
function formatNumber(v) {
|
||||||
return parseNumber(v).toLocaleString('tr-TR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
|
return parseNumber(v).toLocaleString('tr-TR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
|
||||||
}
|
}
|
||||||
@@ -534,6 +714,13 @@ async function fetchStockByCode() {
|
|||||||
activeSchema.value = schemaMap?.[grpKey] || storeSchemaByKey.tak
|
activeSchema.value = schemaMap?.[grpKey] || storeSchemaByKey.tak
|
||||||
|
|
||||||
rawRows.value = list
|
rawRows.value = list
|
||||||
|
productImageCache.value = {}
|
||||||
|
productImageLoading.value = {}
|
||||||
|
productImageListByCode.value = {}
|
||||||
|
productImageListLoading.value = {}
|
||||||
|
productImageFallbackByKey.value = {}
|
||||||
|
productImageContentLoading.value = {}
|
||||||
|
productImageListBlockedUntil.value = 0
|
||||||
initOpenState(level1Groups.value)
|
initOpenState(level1Groups.value)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('fetchStockByCode error:', err)
|
console.error('fetchStockByCode error:', err)
|
||||||
@@ -550,8 +737,11 @@ async function fetchStockByCode() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLevel2Click(_productCode, grp2) {
|
function onLevel2Click(productCode, grp2) {
|
||||||
toggleOpen(grp2.key)
|
toggleOpen(grp2.key)
|
||||||
|
if (isOpen(grp2.key)) {
|
||||||
|
void ensureProductImage(productCode, grp2.colorCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetForm() {
|
function resetForm() {
|
||||||
@@ -560,11 +750,25 @@ function resetForm() {
|
|||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
openState.value = {}
|
openState.value = {}
|
||||||
activeSchema.value = storeSchemaByKey.tak
|
activeSchema.value = storeSchemaByKey.tak
|
||||||
|
productImageCache.value = {}
|
||||||
|
productImageLoading.value = {}
|
||||||
|
productImageListByCode.value = {}
|
||||||
|
productImageListLoading.value = {}
|
||||||
|
productImageFallbackByKey.value = {}
|
||||||
|
productImageContentLoading.value = {}
|
||||||
|
productImageListBlockedUntil.value = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
loadProductOptions()
|
loadProductOptions()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
for (const url of productImageBlobUrls.value) {
|
||||||
|
try { URL.revokeObjectURL(url) } catch {}
|
||||||
|
}
|
||||||
|
productImageBlobUrls.value = []
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -573,6 +777,7 @@ onMounted(() => {
|
|||||||
--grp-title-w: 44px;
|
--grp-title-w: 44px;
|
||||||
--psq-header-h: 56px;
|
--psq-header-h: 56px;
|
||||||
--psq-col-adet: calc(var(--col-adet) + var(--beden-w));
|
--psq-col-adet: calc(var(--col-adet) + var(--beden-w));
|
||||||
|
--psq-col-img: 126px;
|
||||||
--psq-l1-lift: 42px;
|
--psq-l1-lift: 42px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,7 +790,8 @@ onMounted(() => {
|
|||||||
var(--col-alt)
|
var(--col-alt)
|
||||||
var(--col-aciklama)
|
var(--col-aciklama)
|
||||||
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
|
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
|
||||||
var(--psq-col-adet) !important;
|
var(--psq-col-adet)
|
||||||
|
var(--psq-col-img) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-grid-header .col-fixed,
|
.order-grid-header .col-fixed,
|
||||||
@@ -631,7 +837,7 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.order-grid-header .total-cell:last-child {
|
.order-grid-header .total-cell:last-child {
|
||||||
width: var(--psq-col-adet) !important;
|
width: var(--psq-col-img) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.order-sub-header {
|
.order-sub-header {
|
||||||
@@ -642,7 +848,8 @@ onMounted(() => {
|
|||||||
var(--col-alt)
|
var(--col-alt)
|
||||||
var(--col-aciklama)
|
var(--col-aciklama)
|
||||||
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
|
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
|
||||||
var(--psq-col-adet) !important;
|
var(--psq-col-adet)
|
||||||
|
var(--psq-col-img) !important;
|
||||||
top: calc(
|
top: calc(
|
||||||
var(--header-h)
|
var(--header-h)
|
||||||
+ var(--filter-h)
|
+ var(--filter-h)
|
||||||
@@ -897,6 +1104,41 @@ onMounted(() => {
|
|||||||
border-left: 1px solid #d4c79f;
|
border-left: 1px solid #d4c79f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-sub-header.level-2 .sub-image.level2-image {
|
||||||
|
grid-column: 8 / 9;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 6px;
|
||||||
|
border-left: 1px solid #d4c79f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-card {
|
||||||
|
width: 110px;
|
||||||
|
height: 68px;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-wrap {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-placeholder {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f5f6f7;
|
||||||
|
}
|
||||||
|
|
||||||
.order-sub-header.level-2 .sub-right .top-total {
|
.order-sub-header.level-2 .sub-right .top-total {
|
||||||
grid-column: 1 / 2;
|
grid-column: 1 / 2;
|
||||||
grid-row: 1;
|
grid-row: 1;
|
||||||
@@ -931,7 +1173,8 @@ onMounted(() => {
|
|||||||
var(--col-alt)
|
var(--col-alt)
|
||||||
var(--col-aciklama)
|
var(--col-aciklama)
|
||||||
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)))
|
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)))
|
||||||
var(--psq-col-adet) !important;
|
var(--psq-col-adet)
|
||||||
|
var(--psq-col-img) !important;
|
||||||
min-height: 56px;
|
min-height: 56px;
|
||||||
height: 56px;
|
height: 56px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
@@ -1048,6 +1291,11 @@ onMounted(() => {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.order-grid-body .summary-row .cell.img-placeholder {
|
||||||
|
justify-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.order-grid-body {
|
.order-grid-body {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
const detail = await extractApiErrorDetail(err)
|
const detail = await extractApiErrorDetail(err)
|
||||||
const orderId = id || this.header?.OrderHeaderID || '-'
|
const orderId = id || this.header?.OrderHeaderID || '-'
|
||||||
const status = err?.status || err?.response?.status || '-'
|
const status = err?.status || err?.response?.status || '-'
|
||||||
console.error(`? PDF a<EFBFBD>ma hatas<EFBFBD> [${status}] order=${orderId}: ${detail}`)
|
console.error(`? PDF a<>ma hatas<61> [${status}] order=${orderId}: ${detail}`)
|
||||||
throw new Error(detail)
|
throw new Error(detail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1702,15 +1702,15 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
: {}
|
: {}
|
||||||
|
|
||||||
/* ===== SAME COMBO → UPDATE ===== */
|
/* ===== SAME COMBO → UPDATE ===== */
|
||||||
if (sameCombo) {
|
if (sameCombo) {
|
||||||
rows[idx] = {
|
rows[idx] = {
|
||||||
...prev,
|
...prev,
|
||||||
...newRow,
|
...newRow,
|
||||||
_dirty: true,
|
_dirty: true,
|
||||||
id: prev.id,
|
id: prev.id,
|
||||||
OrderLineID: prev.OrderLineID || null,
|
OrderLineID: prev.OrderLineID || null,
|
||||||
lineIdMap: preservedLineIdMap
|
lineIdMap: preservedLineIdMap
|
||||||
}
|
}
|
||||||
|
|
||||||
this.summaryRows = rows
|
this.summaryRows = rows
|
||||||
this.orders = rows
|
this.orders = rows
|
||||||
@@ -1763,13 +1763,13 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
comboLineIds: { ...(prev.comboLineIds || {}) }
|
comboLineIds: { ...(prev.comboLineIds || {}) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const insertedRow = {
|
const insertedRow = {
|
||||||
...newRow,
|
...newRow,
|
||||||
_dirty: true,
|
_dirty: true,
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
OrderLineID: null,
|
OrderLineID: null,
|
||||||
lineIdMap: {}
|
lineIdMap: {}
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.splice(idx, 1, insertedRow)
|
rows.splice(idx, 1, insertedRow)
|
||||||
|
|
||||||
@@ -1838,14 +1838,14 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
const price = Number(newRow?.fiyat ?? prev?.fiyat ?? 0)
|
const price = Number(newRow?.fiyat ?? prev?.fiyat ?? 0)
|
||||||
const totalTutar = Number((totalAdet * price).toFixed(2))
|
const totalTutar = Number((totalAdet * price).toFixed(2))
|
||||||
|
|
||||||
rows[dupIdx] = {
|
rows[dupIdx] = {
|
||||||
...prev,
|
...prev,
|
||||||
...newRow,
|
...newRow,
|
||||||
_dirty: true,
|
_dirty: true,
|
||||||
|
|
||||||
// kritik korumalar
|
// kritik korumalar
|
||||||
id: prev.id,
|
id: prev.id,
|
||||||
OrderLineID: prev.OrderLineID || null,
|
OrderLineID: prev.OrderLineID || null,
|
||||||
lineIdMap: { ...(prev.lineIdMap || {}) },
|
lineIdMap: { ...(prev.lineIdMap || {}) },
|
||||||
|
|
||||||
// MERGED bedenMap
|
// MERGED bedenMap
|
||||||
@@ -1873,13 +1873,13 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// dup yoksa (veya dup delete satırıydı) → yeni satır
|
// dup yoksa (veya dup delete satırıydı) → yeni satır
|
||||||
rows.push({
|
rows.push({
|
||||||
...newRow,
|
...newRow,
|
||||||
_dirty: true,
|
_dirty: true,
|
||||||
id: newRow.id || crypto.randomUUID(),
|
id: newRow.id || crypto.randomUUID(),
|
||||||
OrderLineID: null,
|
OrderLineID: null,
|
||||||
lineIdMap: { ...(newRow.lineIdMap || {}) }
|
lineIdMap: { ...(newRow.lineIdMap || {}) }
|
||||||
})
|
})
|
||||||
|
|
||||||
this.summaryRows = rows
|
this.summaryRows = rows
|
||||||
this.orders = rows
|
this.orders = rows
|
||||||
@@ -2206,8 +2206,33 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
// ❗ BEDEN YOK → bu SADECE üst seviye grup anahtarı
|
// ❗ BEDEN YOK → bu SADECE üst seviye grup anahtarı
|
||||||
const modelKey = `${model}||${renk}||${renk2}`
|
const modelKey = `${model}||${renk}||${renk2}`
|
||||||
|
|
||||||
const grpKey = raw.grpKey || 'tak'
|
// grouped map: { [grpKey]: { beden: qty } } or flat map: { beden: qty }
|
||||||
const srcMap = raw.bedenMap[grpKey] || {}
|
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(
|
||||||
|
Object.keys(srcMap || {}),
|
||||||
|
raw.urunAnaGrubu || raw.UrunAnaGrubu || '',
|
||||||
|
raw.kategori || raw.Kategori || raw.urunAltGrubu || raw.UrunAltGrubu || '',
|
||||||
|
raw.yetiskinGarson ||
|
||||||
|
raw.YETISKIN_GARSON ||
|
||||||
|
raw.YetiskinGarson ||
|
||||||
|
raw.AskiliYan ||
|
||||||
|
raw.ASKILIYAN ||
|
||||||
|
raw.askiliyan ||
|
||||||
|
''
|
||||||
|
) ||
|
||||||
|
'tak'
|
||||||
|
|
||||||
const adet = Object.values(srcMap).reduce((a, b) => a + (Number(b) || 0), 0)
|
const adet = Object.values(srcMap).reduce((a, b) => a + (Number(b) || 0), 0)
|
||||||
const fiyat = Number(raw.fiyat || 0)
|
const fiyat = Number(raw.fiyat || 0)
|
||||||
@@ -2264,7 +2289,16 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
|
|
||||||
urunAnaGrubu: raw.UrunAnaGrubu || 'GENEL',
|
urunAnaGrubu: raw.UrunAnaGrubu || 'GENEL',
|
||||||
urunAltGrubu: raw.UrunAltGrubu || '',
|
urunAltGrubu: raw.UrunAltGrubu || '',
|
||||||
kategori: raw.Kategori || '',
|
kategori: raw.Kategori || raw.UrunAltGrubu || '',
|
||||||
|
urunAltGrubu: raw.UrunAltGrubu || '',
|
||||||
|
yetiskinGarson:
|
||||||
|
raw.YETISKIN_GARSON ||
|
||||||
|
raw.YetiskinGarson ||
|
||||||
|
raw.yetiskinGarson ||
|
||||||
|
raw.AskiliYan ||
|
||||||
|
raw.ASKILIYAN ||
|
||||||
|
raw.askiliyan ||
|
||||||
|
'',
|
||||||
|
|
||||||
aciklama: raw.LineDescription || '',
|
aciklama: raw.LineDescription || '',
|
||||||
fiyat: Number(raw.Price || 0),
|
fiyat: Number(raw.Price || 0),
|
||||||
@@ -2321,7 +2355,8 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
const grpKey = detectBedenGroup(
|
const grpKey = detectBedenGroup(
|
||||||
bedenList,
|
bedenList,
|
||||||
row.urunAnaGrubu,
|
row.urunAnaGrubu,
|
||||||
row.kategori
|
row.kategori || row.urunAltGrubu,
|
||||||
|
row.yetiskinGarson
|
||||||
)
|
)
|
||||||
|
|
||||||
const cleanedMap = { ...row.__tmpMap }
|
const cleanedMap = { ...row.__tmpMap }
|
||||||
@@ -2516,7 +2551,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
return detectBedenGroup(
|
return detectBedenGroup(
|
||||||
null,
|
null,
|
||||||
row?.urunAnaGrubu || '',
|
row?.urunAnaGrubu || '',
|
||||||
row?.kategori || '',
|
row?.kategori || row?.urunAltGrubu || '',
|
||||||
row?.YETISKIN_GARSON || row?.yetiskinGarson || ''
|
row?.YETISKIN_GARSON || row?.yetiskinGarson || ''
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -3055,12 +3090,12 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
// ComboKey stabil kalsın diye bedenKey kullan
|
// ComboKey stabil kalsın diye bedenKey kullan
|
||||||
const comboKey = buildComboKey(row, bedenKey)
|
const comboKey = buildComboKey(row, bedenKey)
|
||||||
|
|
||||||
const makeLine = () => ({
|
const makeLine = () => ({
|
||||||
OrderLineID: orderLineId || '',
|
OrderLineID: orderLineId || '',
|
||||||
ClientKey: makeLineClientKey(row, grpKey, bedenKey),
|
ClientKey: makeLineClientKey(row, grpKey, bedenKey),
|
||||||
ComboKey: comboKey,
|
ComboKey: comboKey,
|
||||||
_dirty: row?._dirty === true,
|
_dirty: row?._dirty === true,
|
||||||
_deleteSignal: isDeleteSignal === true,
|
_deleteSignal: isDeleteSignal === true,
|
||||||
|
|
||||||
SortOrder: 0,
|
SortOrder: 0,
|
||||||
ItemTypeCode: 1,
|
ItemTypeCode: 1,
|
||||||
@@ -3128,30 +3163,30 @@ export const useOrderEntryStore = defineStore('orderentry', {
|
|||||||
DOVCode: ''
|
DOVCode: ''
|
||||||
})
|
})
|
||||||
|
|
||||||
const existing = lineByCombo.get(comboKey)
|
const existing = lineByCombo.get(comboKey)
|
||||||
|
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
const ln = makeLine()
|
const ln = makeLine()
|
||||||
lineByCombo.set(comboKey, ln)
|
lineByCombo.set(comboKey, ln)
|
||||||
lines.push(ln)
|
lines.push(ln)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/* DELETE */
|
/* DELETE */
|
||||||
if (isDeleteSignal) {
|
if (isDeleteSignal) {
|
||||||
if (orderLineId && !existing.OrderLineID) {
|
if (orderLineId && !existing.OrderLineID) {
|
||||||
existing.OrderLineID = orderLineId
|
existing.OrderLineID = orderLineId
|
||||||
}
|
|
||||||
existing._deleteSignal = true
|
|
||||||
existing.Qty1 = 0
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
existing._deleteSignal = true
|
||||||
|
existing.Qty1 = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
/* MERGE */
|
/* MERGE */
|
||||||
existing.Qty1 += qty
|
existing.Qty1 += qty
|
||||||
if (row?._dirty === true) {
|
if (row?._dirty === true) {
|
||||||
existing._dirty = true
|
existing._dirty = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.mode === 'edit' && orderLineId && !existing.OrderLineID) {
|
if (this.mode === 'edit' && orderLineId && !existing.OrderLineID) {
|
||||||
existing.OrderLineID = orderLineId
|
existing.OrderLineID = orderLineId
|
||||||
@@ -3442,6 +3477,15 @@ export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = ''
|
|||||||
? bedenList.map(v => (v || '').toString().trim().toUpperCase())
|
? bedenList.map(v => (v || '').toString().trim().toUpperCase())
|
||||||
: [' ']
|
: [' ']
|
||||||
|
|
||||||
|
// 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 rawAna = normalizeTextForMatch(urunAnaGrubu || '')
|
const rawAna = normalizeTextForMatch(urunAnaGrubu || '')
|
||||||
const rawKat = normalizeTextForMatch(urunKategori || '')
|
const rawKat = normalizeTextForMatch(urunKategori || '')
|
||||||
const rawYetiskinGarson = normalizeTextForMatch(yetiskinGarson || '')
|
const rawYetiskinGarson = normalizeTextForMatch(yetiskinGarson || '')
|
||||||
|
|||||||
Reference in New Issue
Block a user