2100 lines
70 KiB
Vue
2100 lines
70 KiB
Vue
<template>
|
||
<q-page class="q-pa-md full-width order-prod-page">
|
||
<div class="row items-center justify-between page-header">
|
||
<div>
|
||
<div class="text-h6 text-weight-bold">Uretime Verilen Urunleri Guncelle</div>
|
||
</div>
|
||
<q-btn
|
||
color="primary"
|
||
icon="refresh"
|
||
label="Yenile"
|
||
:loading="store.loading"
|
||
@click="refreshAll"
|
||
/>
|
||
<q-btn
|
||
class="q-ml-sm"
|
||
color="secondary"
|
||
icon="save"
|
||
label="Secili Degisiklikleri Kaydet"
|
||
:loading="store.saving"
|
||
:disable="store.loading || store.saving || isBulkSubmitting"
|
||
@click="onBulkSubmit"
|
||
/>
|
||
</div>
|
||
|
||
<div class="filter-bar row q-col-gutter-md">
|
||
<div class="col-5">
|
||
<q-input
|
||
:model-value="cariLabel"
|
||
label="Cari Secimi"
|
||
filled
|
||
dense
|
||
readonly
|
||
/>
|
||
</div>
|
||
<div class="col-3">
|
||
<q-input
|
||
v-model="descFilter"
|
||
label="Aciklama Ara"
|
||
filled
|
||
dense
|
||
clearable
|
||
/>
|
||
</div>
|
||
<div class="col-2">
|
||
<q-input
|
||
:model-value="header?.OrderNumber || ''"
|
||
label="Siparis No"
|
||
filled
|
||
dense
|
||
readonly
|
||
/>
|
||
</div>
|
||
<div class="col-2">
|
||
<q-input
|
||
:model-value="formatDate(header?.OrderDate)"
|
||
label="Olusturulma Tarihi"
|
||
filled
|
||
dense
|
||
readonly
|
||
/>
|
||
</div>
|
||
<div class="col-2">
|
||
<q-input
|
||
v-model="headerAverageDueDate"
|
||
label="Tahmini Termin Tarihi"
|
||
filled
|
||
dense
|
||
type="date"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<q-table
|
||
class="q-mt-md prod-table"
|
||
flat
|
||
bordered
|
||
dense
|
||
separator="cell"
|
||
row-key="RowKey"
|
||
:rows="filteredRows"
|
||
:columns="columns"
|
||
:loading="store.loading"
|
||
no-data-label="Uretime verilecek urun bulunamadi"
|
||
:rows-per-page-options="[0]"
|
||
:table-style="{ tableLayout: 'fixed', width: '100%' }"
|
||
hide-bottom
|
||
>
|
||
<template #header-cell-select="props">
|
||
<q-th :props="props" class="text-center" style="width: 44px">
|
||
<q-checkbox
|
||
size="sm"
|
||
:model-value="allSelectedVisible"
|
||
:indeterminate="someSelectedVisible && !allSelectedVisible"
|
||
@update:model-value="toggleSelectAllVisible"
|
||
/>
|
||
</q-th>
|
||
</template>
|
||
|
||
<template #body-cell-select="props">
|
||
<q-td :props="props" class="text-center" style="width: 44px">
|
||
<q-checkbox
|
||
size="sm"
|
||
:model-value="!!selectedMap[props.row.RowKey]"
|
||
:disable="store.saving"
|
||
@update:model-value="(val) => toggleRowSelection(props.row.RowKey, val)"
|
||
/>
|
||
</q-td>
|
||
</template>
|
||
|
||
<template #body-cell-NewItemCode="props">
|
||
<q-td :props="props">
|
||
<q-select
|
||
v-model="props.row.NewItemEntryMode"
|
||
dense
|
||
filled
|
||
emit-value
|
||
map-options
|
||
:options="newItemEntryModeOptions"
|
||
label="Kod Giris Tipi"
|
||
@update:model-value="val => onNewItemEntryModeChange(props.row, val)"
|
||
/>
|
||
|
||
<q-select
|
||
v-if="props.row.NewItemEntryMode === 'selected'"
|
||
class="q-mt-xs"
|
||
v-model="props.row.NewItemCode"
|
||
dense
|
||
filled
|
||
use-input
|
||
fill-input
|
||
hide-selected
|
||
input-debounce="0"
|
||
emit-value
|
||
map-options
|
||
option-label="label"
|
||
option-value="value"
|
||
:options="productCodeSelectOptions"
|
||
label="Eski Kod Sec"
|
||
@filter="onFilterProductCode"
|
||
@update:model-value="val => onSelectProduct(props.row, val)"
|
||
/>
|
||
|
||
<q-input
|
||
v-else-if="props.row.NewItemEntryMode === 'typed'"
|
||
class="q-mt-xs"
|
||
v-model="props.row.NewItemCode"
|
||
dense
|
||
filled
|
||
maxlength="13"
|
||
placeholder="X999-XX99999"
|
||
label="Yeni Kod Ekle"
|
||
:class="newItemInputClass(props.row)"
|
||
@update:model-value="val => onNewItemChange(props.row, val, 'typed')"
|
||
/>
|
||
|
||
<div v-if="props.row.NewItemMode && props.row.NewItemMode !== 'empty'" class="q-mt-xs row items-center no-wrap">
|
||
<q-badge :color="newItemBadgeColor(props.row)" text-color="white">
|
||
{{ newItemBadgeLabel(props.row) }}
|
||
</q-badge>
|
||
<span class="text-caption q-ml-sm text-grey-8">{{ newItemHintText(props.row) }}</span>
|
||
<q-btn
|
||
v-if="props.row.NewItemMode === 'new'"
|
||
class="q-ml-sm"
|
||
dense
|
||
flat
|
||
size="sm"
|
||
color="warning"
|
||
label="Urun Boyutlandirma"
|
||
@click="openCdItemDialog(props.row.NewItemCode)"
|
||
/>
|
||
<q-btn
|
||
v-if="props.row.NewItemMode && props.row.NewItemMode !== 'empty'"
|
||
class="q-ml-xs"
|
||
dense
|
||
flat
|
||
size="sm"
|
||
color="primary"
|
||
label="Urun Ozellikleri"
|
||
@click="openAttributeDialog(props.row.NewItemCode)"
|
||
/>
|
||
</div>
|
||
</q-td>
|
||
</template>
|
||
|
||
<template #body-cell-NewColor="props">
|
||
<q-td :props="props">
|
||
<q-select
|
||
v-model="props.row.NewColor"
|
||
:options="getColorOptions(props.row)"
|
||
option-label="colorLabel"
|
||
option-value="color_code"
|
||
use-input
|
||
fill-input
|
||
hide-selected
|
||
input-debounce="0"
|
||
emit-value
|
||
map-options
|
||
dense
|
||
filled
|
||
label="Yeni Renk"
|
||
:disable="isColorSelectionLocked(props.row)"
|
||
@update:model-value="() => onNewColorChange(props.row)"
|
||
/>
|
||
</q-td>
|
||
</template>
|
||
|
||
<template #body-cell-NewDim2="props">
|
||
<q-td :props="props">
|
||
<q-select
|
||
v-model="props.row.NewDim2"
|
||
:options="getSecondColorOptions(props.row)"
|
||
option-label="item_dim2_label"
|
||
option-value="item_dim2_code"
|
||
use-input
|
||
fill-input
|
||
hide-selected
|
||
input-debounce="0"
|
||
emit-value
|
||
map-options
|
||
dense
|
||
filled
|
||
label="Yeni 2. Renk"
|
||
:disable="isColorSelectionLocked(props.row)"
|
||
@update:model-value="() => onNewDim2Change(props.row)"
|
||
/>
|
||
</q-td>
|
||
</template>
|
||
|
||
<template #body-cell-NewDueDate="props">
|
||
<q-td :props="props">
|
||
<q-input
|
||
v-model="props.row.NewDueDate"
|
||
dense
|
||
filled
|
||
type="date"
|
||
label="Yeni Termin"
|
||
/>
|
||
</q-td>
|
||
</template>
|
||
|
||
<template #body-cell-NewDesc="props">
|
||
<q-td :props="props" class="cell-new">
|
||
<q-input
|
||
v-model="props.row.NewDesc"
|
||
dense
|
||
filled
|
||
type="textarea"
|
||
autogrow
|
||
label="Yeni Aciklama"
|
||
/>
|
||
</q-td>
|
||
</template>
|
||
</q-table>
|
||
</div>
|
||
|
||
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
|
||
Hata: {{ store.error }}
|
||
</q-banner>
|
||
|
||
<q-dialog v-model="cdItemDialogOpen" persistent>
|
||
<q-card style="min-width: 980px; max-width: 98vw;">
|
||
<q-card-section class="row items-center q-pb-none">
|
||
<div class="text-h6">Urun Boyutlandirma</div>
|
||
<q-space />
|
||
<q-badge color="warning" text-color="black">
|
||
{{ cdItemTargetCode || '-' }}
|
||
</q-badge>
|
||
</q-card-section>
|
||
|
||
<q-card-section class="q-pt-md">
|
||
<div class="row q-col-gutter-sm items-center q-mb-md bg-grey-2 q-pa-sm rounded-borders">
|
||
<div class="col-12 col-md-8">
|
||
<q-select
|
||
v-model="copySourceCode"
|
||
dense
|
||
filled
|
||
use-input
|
||
fill-input
|
||
hide-selected
|
||
input-debounce="0"
|
||
emit-value
|
||
map-options
|
||
option-label="label"
|
||
option-value="value"
|
||
label="Benzer Eski Urun Kodundan Getir"
|
||
placeholder="Kopyalanacak urun kodunu yazin"
|
||
:options="productCodeSelectOptions"
|
||
@filter="onFilterProductCode"
|
||
/>
|
||
</div>
|
||
<div class="col-12 col-md-4">
|
||
<q-btn
|
||
color="secondary"
|
||
icon="content_copy"
|
||
label="Ozellikleri Kopyala"
|
||
class="full-width"
|
||
:disable="!copySourceCode"
|
||
@click="copyFromOldProduct('cdItem')"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row q-col-gutter-sm">
|
||
<div class="col-12 col-md-4">
|
||
<q-select v-model="cdItemDraftForm.ItemDimTypeCode" dense filled use-input fill-input hide-selected input-debounce="0" emit-value map-options option-label="label" option-value="value" :options="lookupOptions('itemDimTypeCodes')" label="Boyut Secenekleri" />
|
||
</div>
|
||
<div class="col-12 col-md-4">
|
||
<q-select v-model="cdItemDraftForm.ProductHierarchyID" dense filled use-input fill-input hide-selected input-debounce="0" emit-value map-options option-label="label" option-value="value" :options="lookupOptions('productHierarchyIDs')" label="Urun Hiyerarsi Grubu" />
|
||
</div>
|
||
</div>
|
||
</q-card-section>
|
||
|
||
<q-card-actions align="right">
|
||
<q-btn flat label="Vazgec" color="grey-8" v-close-popup />
|
||
<q-btn color="primary" label="Taslagi Kaydet" @click="saveCdItemDraft" />
|
||
</q-card-actions>
|
||
</q-card>
|
||
</q-dialog>
|
||
|
||
<q-dialog v-model="attributeDialogOpen" persistent>
|
||
<q-card style="min-width: 1100px; max-width: 98vw;">
|
||
<q-card-section class="row items-center q-pb-none">
|
||
<div class="text-h6">Urun Ozellikleri (2. Pop-up)</div>
|
||
<q-space />
|
||
<q-badge color="primary">{{ attributeTargetCode || '-' }}</q-badge>
|
||
</q-card-section>
|
||
|
||
<q-card-section class="q-pt-md">
|
||
<div class="row q-col-gutter-sm items-center q-mb-md bg-grey-2 q-pa-sm rounded-borders">
|
||
<div class="col-12 col-md-8">
|
||
<q-select
|
||
v-model="copySourceCode"
|
||
dense
|
||
filled
|
||
use-input
|
||
fill-input
|
||
hide-selected
|
||
input-debounce="0"
|
||
emit-value
|
||
map-options
|
||
option-label="label"
|
||
option-value="value"
|
||
label="Benzer Eski Urun Kodundan Getir"
|
||
placeholder="Kopyalanacak urun kodunu yazin"
|
||
:options="productCodeSelectOptions"
|
||
@filter="onFilterProductCode"
|
||
/>
|
||
</div>
|
||
<div class="col-12 col-md-4">
|
||
<q-btn
|
||
color="secondary"
|
||
icon="content_copy"
|
||
label="Ozellikleri Kopyala"
|
||
class="full-width"
|
||
:disable="!copySourceCode"
|
||
@click="copyFromOldProduct('attributes')"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</q-card-section>
|
||
|
||
<q-card-section style="max-height: 68vh; overflow: auto;">
|
||
<div
|
||
v-for="(row, idx) in attributeRows"
|
||
:key="`${row.AttributeTypeCodeNumber}-${idx}`"
|
||
class="row q-col-gutter-sm q-mb-xs items-center"
|
||
>
|
||
<div class="col-12 col-md-5">
|
||
<q-input :model-value="row.TypeLabel" dense filled readonly />
|
||
</div>
|
||
<div class="col-12 col-md-7">
|
||
<q-select
|
||
v-model="row.AttributeCode"
|
||
dense
|
||
filled
|
||
use-input
|
||
fill-input
|
||
hide-selected
|
||
input-debounce="0"
|
||
@filter="(val, update) => onFilterAttributeOption(row, val, update)"
|
||
emit-value
|
||
map-options
|
||
option-label="label"
|
||
option-value="value"
|
||
:options="row.Options"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</q-card-section>
|
||
|
||
<q-card-actions align="right">
|
||
<q-btn flat label="Vazgec" color="grey-8" v-close-popup />
|
||
<q-btn color="primary" label="Ozellikleri Taslaga Kaydet" @click="saveAttributeDraft" />
|
||
</q-card-actions>
|
||
</q-card>
|
||
</q-dialog>
|
||
<q-inner-loading :showing="store.saving">
|
||
<q-spinner-gears size="50px" color="primary" />
|
||
<div class="q-mt-md text-subtitle1">Degisiklikler kaydediliyor, lutfen bekleyiniz...</div>
|
||
</q-inner-loading>
|
||
</q-page>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { computed, onMounted, ref, watch } from 'vue'
|
||
import { useRoute } from 'vue-router'
|
||
import { useQuasar } from 'quasar'
|
||
import { useOrderProductionItemStore } from 'src/stores/OrderProductionItemStore'
|
||
import api from 'src/services/api'
|
||
import { normalizeSearchText } from 'src/utils/searchText'
|
||
|
||
const route = useRoute()
|
||
const $q = useQuasar()
|
||
const store = useOrderProductionItemStore()
|
||
const BAGGI_CODE_PATTERN = /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/
|
||
const BAGGI_CODE_ERROR = 'Girdiginiz kod BAGGI kod sistemine uyumlu degil. Format: X999-XXX99999'
|
||
|
||
function nowMs () {
|
||
if (typeof performance !== 'undefined' && typeof performance.now === 'function') {
|
||
return performance.now()
|
||
}
|
||
return Date.now()
|
||
}
|
||
|
||
const orderHeaderID = computed(() => String(route.params.orderHeaderID || '').trim())
|
||
const header = computed(() => store.header || {})
|
||
const cariLabel = computed(() => {
|
||
const code = header.value?.CurrAccCode || ''
|
||
const name = header.value?.CurrAccDescription || ''
|
||
if (!code && !name) return ''
|
||
if (!name) return code
|
||
return `${code} - ${name}`
|
||
})
|
||
|
||
const rows = ref([])
|
||
const descFilter = ref('')
|
||
const productOptions = ref([])
|
||
const selectedMap = ref({})
|
||
const headerAverageDueDate = ref('')
|
||
const cdItemDialogOpen = ref(false)
|
||
const cdItemTargetCode = ref('')
|
||
const copySourceCode = ref(null)
|
||
const suppressAutoSetupDialogs = ref(false)
|
||
const cdItemDraftForm = ref(createEmptyCdItemDraft(''))
|
||
const attributeDialogOpen = ref(false)
|
||
const attributeTargetCode = ref('')
|
||
const attributeRows = ref([])
|
||
const isBulkSubmitting = ref(false)
|
||
|
||
const columns = [
|
||
{ name: 'select', label: '', field: 'select', align: 'center', sortable: false, style: 'width:44px;', headerStyle: 'width:44px;' },
|
||
{ name: 'OldItemCode', label: 'Eski Urun Kodu', field: 'OldItemCode', align: 'left', sortable: true, style: 'min-width:90px;white-space:normal', headerStyle: 'min-width:90px;white-space:normal', headerClasses: 'col-old', classes: 'col-old' },
|
||
{ name: 'OldColor', label: 'Eski Urun Rengi', field: 'OldColorLabel', align: 'left', sortable: true, style: 'min-width:120px;white-space:normal', headerStyle: 'min-width:120px;white-space:normal', headerClasses: 'col-old', classes: 'col-old' },
|
||
{ name: 'OldDim2', label: 'Eski 2. Renk', field: 'OldDim2', align: 'left', sortable: true, style: 'min-width:80px;white-space:normal', headerStyle: 'min-width:80px;white-space:normal', headerClasses: 'col-old', classes: 'col-old' },
|
||
{ name: 'OldDesc', label: 'Eski Aciklama', field: 'OldDesc', align: 'left', sortable: false, style: 'min-width:130px;', headerStyle: 'min-width:130px;', headerClasses: 'col-old col-desc', classes: 'col-old col-desc' },
|
||
{ name: 'OldSizes', label: 'Bedenler', field: 'OldSizesLabel', align: 'left', sortable: false, style: 'min-width:90px;', headerStyle: 'min-width:90px;', headerClasses: 'col-old col-wrap', classes: 'col-old col-wrap' },
|
||
{ name: 'OldTotalQty', label: 'Siparis Adedi', field: 'OldTotalQtyLabel', align: 'right', sortable: false, style: 'min-width:90px;', headerStyle: 'min-width:90px;', headerClasses: 'col-old', classes: 'col-old' },
|
||
{ name: 'OldDueDate', label: 'Eski Termin', field: 'OldDueDate', align: 'left', sortable: true, style: 'min-width:100px;', headerStyle: 'min-width:100px;', headerClasses: 'col-old', classes: 'col-old' },
|
||
{ name: 'NewItemCode', label: 'Yeni Urun Kodu', field: 'NewItemCode', align: 'left', sortable: false, style: 'min-width:130px;', headerStyle: 'min-width:130px;', headerClasses: 'col-new col-new-first', classes: 'col-new col-new-first' },
|
||
{ name: 'NewColor', label: 'Yeni Urun Rengi', field: 'NewColor', align: 'left', sortable: false, style: 'min-width:100px;', headerStyle: 'min-width:100px;', headerClasses: 'col-new', classes: 'col-new' },
|
||
{ name: 'NewDim2', label: 'Yeni 2. Renk', field: 'NewDim2', align: 'left', sortable: false, style: 'min-width:100px;', headerStyle: 'min-width:100px;', headerClasses: 'col-new', classes: 'col-new' },
|
||
{ name: 'NewDueDate', label: 'Yeni Termin', field: 'NewDueDate', align: 'left', sortable: false, style: 'min-width:120px;', headerStyle: 'min-width:120px;', headerClasses: 'col-new', classes: 'col-new' },
|
||
{ name: 'NewDesc', label: 'Yeni Aciklama', field: 'NewDesc', align: 'left', sortable: false, style: 'min-width:140px;', headerStyle: 'min-width:140px;', headerClasses: 'col-new col-desc', classes: 'col-new col-desc' }
|
||
]
|
||
|
||
onMounted(async () => {
|
||
await refreshAll()
|
||
})
|
||
|
||
watch(orderHeaderID, async (id) => {
|
||
await refreshAll()
|
||
})
|
||
|
||
watch(
|
||
() => store.items,
|
||
(items) => {
|
||
rows.value = groupItems(items || [], rows.value)
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
watch(
|
||
() => store.products,
|
||
(products) => {
|
||
productOptions.value = products || []
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
function formatDate (val) {
|
||
if (!val) return ''
|
||
const text = String(val)
|
||
return text.length >= 10 ? text.slice(0, 10) : text
|
||
}
|
||
|
||
function normalizeDateInput (val) {
|
||
return formatDate(val || '')
|
||
}
|
||
|
||
const hasHeaderAverageDueDateChange = computed(() => (
|
||
normalizeDateInput(headerAverageDueDate.value) !==
|
||
normalizeDateInput(header.value?.AverageDueDate)
|
||
))
|
||
|
||
watch(
|
||
() => header.value?.AverageDueDate,
|
||
(value) => {
|
||
headerAverageDueDate.value = normalizeDateInput(value)
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
const filteredRows = computed(() => {
|
||
const needle = normalizeSearchText(descFilter.value)
|
||
if (!needle) return rows.value
|
||
return rows.value.filter(r =>
|
||
normalizeSearchText(r?.OldDesc).includes(needle)
|
||
)
|
||
})
|
||
|
||
const visibleRowKeys = computed(() => filteredRows.value.map(r => r.RowKey))
|
||
const selectedVisibleCount = computed(() => visibleRowKeys.value.filter(k => !!selectedMap.value[k]).length)
|
||
const allSelectedVisible = computed(() => visibleRowKeys.value.length > 0 && selectedVisibleCount.value === visibleRowKeys.value.length)
|
||
const someSelectedVisible = computed(() => selectedVisibleCount.value > 0)
|
||
const newItemEntryModeOptions = [
|
||
{ label: 'Eski Kod Sec', value: 'selected' },
|
||
{ label: 'Yeni Kod Ekle', value: 'typed' }
|
||
]
|
||
const productCodeAllOptions = computed(() =>
|
||
(productOptions.value || []).map(p => {
|
||
const code = String(p?.ProductCode || '').trim().toUpperCase()
|
||
return { label: code, value: code }
|
||
}).filter(x => !!x.value && x.value.length === 13)
|
||
)
|
||
const productCodeSelectOptions = ref([])
|
||
|
||
watch(
|
||
productCodeAllOptions,
|
||
(list) => {
|
||
productCodeSelectOptions.value = Array.isArray(list) ? [...list] : []
|
||
},
|
||
{ immediate: true }
|
||
)
|
||
|
||
function onFilterProductCode (val, update) {
|
||
const needle = normalizeSearchText(val)
|
||
update(() => {
|
||
if (!needle) {
|
||
productCodeSelectOptions.value = [...productCodeAllOptions.value]
|
||
return
|
||
}
|
||
productCodeSelectOptions.value = (productCodeAllOptions.value || []).filter(opt => {
|
||
const label = normalizeSearchText(opt?.label || '')
|
||
const value = normalizeSearchText(opt?.value || '')
|
||
return label.includes(needle) || value.includes(needle)
|
||
})
|
||
})
|
||
}
|
||
|
||
function applyNewItemVisualState (row, source = 'typed') {
|
||
const info = store.classifyItemCode(row?.NewItemCode || '')
|
||
row.NewItemCode = info.normalized
|
||
row.NewItemMode = info.mode
|
||
row.NewItemSource = info.mode === 'empty' ? '' : source
|
||
}
|
||
|
||
function syncRowsForKnownExistingCode (itemCode) {
|
||
const code = String(itemCode || '').trim().toUpperCase()
|
||
if (!code) return
|
||
for (const row of (rows.value || [])) {
|
||
if (String(row?.NewItemCode || '').trim().toUpperCase() !== code) continue
|
||
row.NewItemCode = code
|
||
row.NewItemMode = 'existing'
|
||
if (!row.NewItemEntryMode) {
|
||
row.NewItemEntryMode = row.NewItemSource === 'selected' ? 'selected' : 'typed'
|
||
}
|
||
}
|
||
}
|
||
|
||
function newItemInputClass (row) {
|
||
return {
|
||
'new-item-existing': row?.NewItemMode === 'existing',
|
||
'new-item-new': row?.NewItemMode === 'new'
|
||
}
|
||
}
|
||
|
||
function newItemBadgeColor (row) {
|
||
return row?.NewItemMode === 'existing' ? 'positive' : 'warning'
|
||
}
|
||
|
||
function newItemBadgeLabel (row) {
|
||
return row?.NewItemMode === 'existing' ? 'MEVCUT KOD' : 'YENI KOD'
|
||
}
|
||
|
||
function newItemHintText (row) {
|
||
if (row?.NewItemMode === 'existing') {
|
||
return row?.NewItemSource === 'selected'
|
||
? 'Urun listesinden secildi'
|
||
: 'Elle girildi (sistemde bulundu)'
|
||
}
|
||
if (row?.NewItemMode === 'new') {
|
||
return store.getCdItemDraft(row?.NewItemCode) ? 'Yeni kod: cdItem taslagi hazir' : 'Yeni kod: cdItem taslagi gerekli'
|
||
}
|
||
return ''
|
||
}
|
||
|
||
function onSelectProduct (row, code) {
|
||
row.NewItemEntryMode = 'selected'
|
||
onNewItemChange(row, code, 'selected')
|
||
}
|
||
|
||
function onNewItemEntryModeChange (row, mode) {
|
||
row.NewItemEntryMode = String(mode || '').trim()
|
||
row.NewItemCode = ''
|
||
row.NewColor = ''
|
||
row.NewDim2 = ''
|
||
row.NewItemMode = 'empty'
|
||
row.NewItemSource = ''
|
||
}
|
||
|
||
function escapeRegExp (value) {
|
||
return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||
}
|
||
|
||
function buildNextCodeFromPrefix (prefix) {
|
||
const normalizedPrefix = String(prefix || '').trim().toUpperCase()
|
||
if (!/^[A-Z][0-9]{3}-[A-Z]{3}$/.test(normalizedPrefix)) return ''
|
||
|
||
const codeRegex = new RegExp(`^${escapeRegExp(normalizedPrefix)}(\\d{5})$`)
|
||
let maxSuffix = 0
|
||
|
||
for (const p of (productOptions.value || [])) {
|
||
const code = String(p?.ProductCode || '').trim().toUpperCase()
|
||
const m = code.match(codeRegex)
|
||
if (!m) continue
|
||
const n = Number(m[1] || 0)
|
||
if (Number.isFinite(n) && n > maxSuffix) maxSuffix = n
|
||
}
|
||
|
||
const next = maxSuffix > 0 ? maxSuffix + 1 : 1
|
||
return `${normalizedPrefix}${String(next).padStart(5, '0')}`
|
||
}
|
||
|
||
function onNewItemChange (row, val, source = 'typed') {
|
||
const prevCode = String(row?.NewItemCode || '').trim().toUpperCase()
|
||
let next = String(val || '').trim().toUpperCase()
|
||
|
||
if (source === 'typed' && row?.NewItemEntryMode === 'typed' && /^[A-Z][0-9]{3}-[A-Z]{3}$/.test(next)) {
|
||
const autoCode = buildNextCodeFromPrefix(next)
|
||
if (autoCode) {
|
||
next = autoCode
|
||
$q.notify({ type: 'info', message: `Yeni kod otomatik tamamlandi: ${autoCode}` })
|
||
}
|
||
}
|
||
|
||
if (next.length > 13) {
|
||
$q.notify({ type: 'negative', message: 'Model kodu en fazla 13 karakter olabilir.' })
|
||
row.NewItemCode = next.slice(0, 13)
|
||
applyNewItemVisualState(row, source)
|
||
return
|
||
}
|
||
if (next.length === 13 && !isValidBaggiModelCode(next)) {
|
||
$q.notify({ type: 'negative', message: BAGGI_CODE_ERROR })
|
||
row.NewItemCode = prevCode
|
||
applyNewItemVisualState(row, source)
|
||
return
|
||
}
|
||
row.NewItemCode = next ? next.toUpperCase() : ''
|
||
applyNewItemVisualState(row, source)
|
||
row.NewColor = ''
|
||
row.NewDim2 = ''
|
||
row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc)
|
||
if (row.NewItemCode && isValidBaggiModelCode(row.NewItemCode)) {
|
||
if (row.NewItemMode === 'new') {
|
||
store.fetchNewColors(row.NewItemCode)
|
||
} else {
|
||
store.fetchColors(row.NewItemCode)
|
||
}
|
||
}
|
||
if (suppressAutoSetupDialogs.value) return
|
||
if (row.NewItemMode === 'new' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
||
openNewCodeSetupFlow(row.NewItemCode)
|
||
} else if (row.NewItemMode === 'existing' && isValidBaggiModelCode(row.NewItemCode) && row.NewItemCode !== prevCode) {
|
||
openAttributeDialog(row.NewItemCode)
|
||
}
|
||
}
|
||
|
||
function isAttributeDraftComplete (rows) {
|
||
if (!Array.isArray(rows) || !rows.length) return false
|
||
return rows.every(r => String(r?.AttributeCode || '').trim())
|
||
}
|
||
|
||
function isNewCodeSetupComplete (itemCode) {
|
||
const code = String(itemCode || '').trim().toUpperCase()
|
||
if (!code) return false
|
||
const hasCdItem = !!store.getCdItemDraft(code)
|
||
const attrRows = store.getProductAttributeDraft(code)
|
||
return hasCdItem && isAttributeDraftComplete(attrRows)
|
||
}
|
||
|
||
function isColorSelectionLocked (row) {
|
||
const code = String(row?.NewItemCode || '').trim().toUpperCase()
|
||
return !code
|
||
}
|
||
|
||
function openNewCodeSetupFlow (itemCode) {
|
||
const code = String(itemCode || '').trim().toUpperCase()
|
||
if (!code) return
|
||
if (!store.getCdItemDraft(code)) {
|
||
openCdItemDialog(code)
|
||
return
|
||
}
|
||
if (!isAttributeDraftComplete(store.getProductAttributeDraft(code))) {
|
||
openAttributeDialog(code)
|
||
}
|
||
}
|
||
|
||
function onNewColorChange (row) {
|
||
row.NewColor = normalizeShortCode(row.NewColor, 3)
|
||
row.NewDim2 = ''
|
||
row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc)
|
||
if (row.NewItemCode && row.NewColor) {
|
||
if (String(row?.NewItemMode || '').trim() === 'new') {
|
||
store.fetchNewSecondColors(row.NewItemCode, row.NewColor)
|
||
} else {
|
||
store.fetchSecondColors(row.NewItemCode, row.NewColor)
|
||
}
|
||
}
|
||
}
|
||
|
||
function onNewDim2Change (row) {
|
||
row.NewDim2 = normalizeShortCode(row.NewDim2, 3)
|
||
row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc)
|
||
}
|
||
|
||
function getColorOptions (row) {
|
||
const code = row?.NewItemCode || ''
|
||
const isNewMode = String(row?.NewItemMode || '').trim() === 'new'
|
||
const list = isNewMode
|
||
? (store.newColorOptionsByCode[code] || [])
|
||
: (store.colorOptionsByCode[code] || [])
|
||
return list.map(c => ({
|
||
...c,
|
||
colorLabel: `${c.color_code} - ${c.color_description || ''}`.trim()
|
||
}))
|
||
}
|
||
|
||
function getSecondColorOptions (row) {
|
||
const code = row?.NewItemCode || ''
|
||
const color = row?.NewColor || ''
|
||
const key = `${code}::${color}`
|
||
const isNewMode = String(row?.NewItemMode || '').trim() === 'new'
|
||
const list = isNewMode
|
||
? (store.newSecondColorOptionsByKey[key] || [])
|
||
: (store.secondColorOptionsByKey[key] || [])
|
||
return list.map(c => ({
|
||
...c,
|
||
item_dim2_label: `${c.item_dim2_code} - ${c.color_description || ''}`.trim()
|
||
}))
|
||
}
|
||
|
||
function toggleRowSelection (rowKey, checked) {
|
||
const next = { ...selectedMap.value }
|
||
if (checked) next[rowKey] = true
|
||
else delete next[rowKey]
|
||
selectedMap.value = next
|
||
}
|
||
|
||
function toggleSelectAllVisible (checked) {
|
||
const next = { ...selectedMap.value }
|
||
for (const key of visibleRowKeys.value) {
|
||
if (checked) next[key] = true
|
||
else delete next[key]
|
||
}
|
||
selectedMap.value = next
|
||
}
|
||
|
||
function normalizeShortCode (value, maxLen) {
|
||
return String(value || '').trim().toUpperCase().slice(0, maxLen)
|
||
}
|
||
|
||
function isValidBaggiModelCode (code) {
|
||
return BAGGI_CODE_PATTERN.test(code)
|
||
}
|
||
|
||
function formatCodeColorDim2 (itemCode, color, dim2) {
|
||
const item = String(itemCode || '').trim().toUpperCase()
|
||
const c1 = normalizeShortCode(color, 3)
|
||
const c2 = normalizeShortCode(dim2, 3)
|
||
const c1Safe = c1 || '-'
|
||
const c2Safe = c2 || '-'
|
||
return `${item}/${c1Safe}/${c2Safe}`
|
||
}
|
||
|
||
function buildAutoUpdateNote (row) {
|
||
const oldInfo = formatCodeColorDim2(row?.OldItemCode, row?.OldColor, row?.OldDim2)
|
||
const nextInfo = formatCodeColorDim2(row?.NewItemCode, row?.NewColor, row?.NewDim2)
|
||
return `Bu siparis satirinda kod ${oldInfo} bilgisinden ${nextInfo} bilgisine guncellenmistir.`
|
||
}
|
||
|
||
function isSelectionCompleteByOldShape (row) {
|
||
const hasModel = String(row?.NewItemCode || '').trim().length > 0
|
||
if (!hasModel) return false
|
||
|
||
const oldHasColor = String(row?.OldColor || '').trim().length > 0
|
||
const hasNewColor = normalizeShortCode(row?.NewColor, 3).length === 3
|
||
|
||
if (oldHasColor) return hasNewColor
|
||
return true
|
||
}
|
||
|
||
function stripAutoUpdateNote (text) {
|
||
const desc = String(text || '').trim()
|
||
if (!desc) return ''
|
||
const marker = ' Bu siparis satirinda kod '
|
||
const idx = desc.indexOf(marker)
|
||
if (idx > -1) return desc.slice(0, idx).trim()
|
||
if (desc.startsWith('Bu siparis satirinda kod ')) return ''
|
||
return desc
|
||
}
|
||
|
||
function mergeDescWithAutoNote (row, baseDesc) {
|
||
const desc = stripAutoUpdateNote(baseDesc)
|
||
if (!isSelectionCompleteByOldShape(row)) return desc
|
||
const note = buildAutoUpdateNote(row)
|
||
if (!note) return desc
|
||
if (desc.includes(note)) return desc
|
||
if (!desc) return note
|
||
return `${desc} ${note}`
|
||
}
|
||
|
||
function validateRowInput (row) {
|
||
const entryMode = String(row?.NewItemEntryMode || '').trim()
|
||
const newItemCode = String(row.NewItemCode || '').trim().toUpperCase()
|
||
const newColor = normalizeShortCode(row.NewColor, 3)
|
||
const newDim2 = normalizeShortCode(row.NewDim2, 3)
|
||
const oldColor = String(row.OldColor || '').trim()
|
||
|
||
if (!entryMode) return 'Lutfen once kod giris tipini seciniz (Eski Kod Sec / Yeni Kod Ekle).'
|
||
if (!newItemCode) return 'Yeni model kodu zorunludur.'
|
||
if (!isValidBaggiModelCode(newItemCode)) {
|
||
return BAGGI_CODE_ERROR
|
||
}
|
||
if (oldColor && !newColor) return 'Eski kayitta 1. renk oldugu icin yeni 1. renk zorunludur.'
|
||
if (newColor && newColor.length !== 3) return 'Yeni 1. renk kodu 3 karakter olmalidir.'
|
||
if (newDim2 && newDim2.length !== 3) return 'Yeni 2. renk kodu 3 karakter olmalidir.'
|
||
if (newDim2 && !newColor) return '2. renk girmek icin 1. renk zorunludur.'
|
||
|
||
row.NewItemCode = newItemCode
|
||
row.NewColor = newColor
|
||
row.NewDim2 = newDim2
|
||
row.NewDesc = mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc)
|
||
return ''
|
||
}
|
||
|
||
function collectLinesFromRows (selectedRows) {
|
||
const lines = []
|
||
for (const row of selectedRows) {
|
||
const errMsg = validateRowInput(row)
|
||
if (errMsg) {
|
||
return { errMsg, lines: [] }
|
||
}
|
||
|
||
const baseLine = {
|
||
NewItemCode: String(row.NewItemCode || '').trim().toUpperCase(),
|
||
NewColor: normalizeShortCode(row.NewColor, 3),
|
||
NewDim2: normalizeShortCode(row.NewDim2, 3),
|
||
NewDesc: mergeDescWithAutoNote(row, row.NewDesc || row.OldDesc),
|
||
OldDueDate: row.OldDueDate || '',
|
||
NewDueDate: row.NewDueDate || ''
|
||
}
|
||
|
||
const oldItemCode = String(row.OldItemCode || '').trim().toUpperCase()
|
||
const oldColor = normalizeShortCode(row.OldColor, 3)
|
||
const oldDim2 = normalizeShortCode(row.OldDim2, 3)
|
||
const oldDesc = String(row.OldDesc || '').trim()
|
||
const oldDueDateValue = row.OldDueDate || ''
|
||
const newDueDateValue = row.NewDueDate || ''
|
||
|
||
const hasChange = (
|
||
baseLine.NewItemCode !== oldItemCode ||
|
||
baseLine.NewColor !== oldColor ||
|
||
baseLine.NewDim2 !== oldDim2 ||
|
||
String(baseLine.NewDesc || '').trim() !== oldDesc ||
|
||
newDueDateValue !== oldDueDateValue
|
||
)
|
||
if (!hasChange) continue
|
||
|
||
const orderLines = Array.isArray(row.OrderLines) && row.OrderLines.length
|
||
? row.OrderLines
|
||
: (row.OrderLineIDs || []).map(id => ({
|
||
OrderLineID: id,
|
||
ItemDim1Code: ''
|
||
}))
|
||
|
||
for (const line of orderLines) {
|
||
lines.push({
|
||
...baseLine,
|
||
OrderLineID: line?.OrderLineID,
|
||
ItemDim1Code: store.toPayloadDim1Code(row, line?.ItemDim1Code || '')
|
||
})
|
||
}
|
||
}
|
||
return { errMsg: '', lines }
|
||
}
|
||
|
||
function hasRowChange (row) {
|
||
const newItemCode = String(row?.NewItemCode || '').trim().toUpperCase()
|
||
const newColor = normalizeShortCode(row?.NewColor, 3)
|
||
const newDim2 = normalizeShortCode(row?.NewDim2, 3)
|
||
const newDesc = mergeDescWithAutoNote(row, row?.NewDesc || row?.OldDesc)
|
||
const oldItemCode = String(row?.OldItemCode || '').trim().toUpperCase()
|
||
const oldColor = normalizeShortCode(row?.OldColor, 3)
|
||
const oldDim2 = normalizeShortCode(row?.OldDim2, 3)
|
||
const oldDesc = String(row?.OldDesc || '').trim()
|
||
const oldDueDateValue = row?.OldDueDate || ''
|
||
const newDueDateValue = row?.NewDueDate || ''
|
||
|
||
return (
|
||
newItemCode !== oldItemCode ||
|
||
newColor !== oldColor ||
|
||
newDim2 !== oldDim2 ||
|
||
String(newDesc || '').trim() !== oldDesc ||
|
||
newDueDateValue !== oldDueDateValue
|
||
)
|
||
}
|
||
|
||
function collectOptionalColorWarnings (rows) {
|
||
const warnings = []
|
||
for (const row of (rows || [])) {
|
||
const code = String(row?.NewItemCode || '').trim().toUpperCase()
|
||
if (!code) continue
|
||
const color = normalizeShortCode(row?.NewColor, 3)
|
||
const dim2 = normalizeShortCode(row?.NewDim2, 3)
|
||
if (!color) {
|
||
warnings.push(`${code} icin renk secmediniz.`)
|
||
continue
|
||
}
|
||
if (!dim2) {
|
||
warnings.push(`${code} icin 2. renk bos kalacak.`)
|
||
}
|
||
}
|
||
return [...new Set(warnings)]
|
||
}
|
||
|
||
function confirmOptionalColorWarnings (rows) {
|
||
const warnings = collectOptionalColorWarnings(rows)
|
||
if (!warnings.length) return Promise.resolve(true)
|
||
return new Promise((resolve) => {
|
||
$q.dialog({
|
||
title: 'Renk Uyarisi',
|
||
message: `${warnings.join('<br>')}<br><br>Devam etmek istiyor musunuz?`,
|
||
html: true,
|
||
ok: { label: 'Evet, Devam Et', color: 'warning' },
|
||
cancel: { label: 'Vazgec', flat: true }
|
||
}).onOk(() => resolve(true)).onCancel(() => resolve(false)).onDismiss(() => resolve(false))
|
||
})
|
||
}
|
||
|
||
function createEmptyCdItemDraft (itemCode) {
|
||
return {
|
||
ItemTypeCode: '1',
|
||
ItemCode: String(itemCode || '').trim().toUpperCase(),
|
||
ItemDimTypeCode: '1',
|
||
ProductTypeCode: '1',
|
||
ProductHierarchyID: '',
|
||
UnitOfMeasureCode1: 'AD',
|
||
ItemAccountGrCode: '',
|
||
ItemTaxGrCode: '%10',
|
||
ItemPaymentPlanGrCode: '',
|
||
ItemDiscountGrCode: '',
|
||
ItemVendorGrCode: '',
|
||
PromotionGroupCode: '',
|
||
ProductCollectionGrCode: '0',
|
||
StorePriceLevelCode: '0',
|
||
PerceptionOfFashionCode: '0',
|
||
CommercialRoleCode: '0',
|
||
StoreCapacityLevelCode: '',
|
||
CustomsTariffNumberCode: '',
|
||
CompanyCode: '1'
|
||
}
|
||
}
|
||
|
||
function lookupOptions (key) {
|
||
if (key === 'itemDimTypeCodes') {
|
||
return [
|
||
{ value: '1', label: '1 - RENK' },
|
||
{ value: '2', label: '2 - RENK-BEDEN' },
|
||
{ value: '3', label: '3 - RENK-BEDEN-YAKA' }
|
||
]
|
||
}
|
||
|
||
const list = store.cdItemLookups?.[key] || []
|
||
return list
|
||
.map(x => {
|
||
const code = String(x?.code || '').trim()
|
||
const desc = String(x?.description || '').trim()
|
||
return {
|
||
value: code,
|
||
label: desc ? `${code} - ${desc}` : code,
|
||
_desc: desc
|
||
}
|
||
})
|
||
.filter(opt => !isDummyLookupOption(key, opt.value, opt._desc))
|
||
.map(({ value, label }) => ({ value, label }))
|
||
}
|
||
|
||
function isDummyLookupOption (key, codeRaw, descRaw) {
|
||
const code = String(codeRaw || '').trim().toUpperCase()
|
||
const desc = String(descRaw || '').trim().toUpperCase()
|
||
|
||
if (!code) return true
|
||
if (code === '0' || code === '00' || code === '000' || code === '0000') return true
|
||
if (desc.includes('DUMMY')) return true
|
||
|
||
return false
|
||
}
|
||
|
||
async function copyFromOldProduct (targetType = 'cdItem') {
|
||
const sourceCode = String(copySourceCode.value || '').trim().toUpperCase()
|
||
if (!sourceCode) return
|
||
|
||
$q.loading.show({ message: 'Ozellikler kopyalaniyor...' })
|
||
try {
|
||
if (targetType === 'cdItem') {
|
||
const data = await store.fetchCdItemByCode(sourceCode)
|
||
if (data) {
|
||
const targetCode = String(cdItemTargetCode.value || '').trim().toUpperCase()
|
||
const draft = createEmptyCdItemDraft(targetCode)
|
||
for (const k of Object.keys(draft)) {
|
||
if (data[k] !== undefined && data[k] !== null) {
|
||
draft[k] = String(data[k])
|
||
}
|
||
}
|
||
// Source item kopyalansa da hedef popup kodu degismemeli.
|
||
draft.ItemCode = targetCode
|
||
cdItemDraftForm.value = draft
|
||
persistCdItemDraft()
|
||
$q.notify({ type: 'positive', message: 'Boyutlandirma bilgileri kopyalandi.' })
|
||
} else {
|
||
$q.notify({ type: 'warning', message: 'Kaynak urun bilgisi bulunamadi.' })
|
||
}
|
||
} else if (targetType === 'attributes') {
|
||
const data = await store.fetchProductItemAttributes(sourceCode, 1, true)
|
||
if (Array.isArray(data) && data.length > 0) {
|
||
// Mevcut attributeRows uzerindeki degerleri guncelle
|
||
for (const row of attributeRows.value) {
|
||
const sourceAttr = data.find(d => Number(d.attribute_type_code || d.AttributeTypeCode) === Number(row.AttributeTypeCodeNumber))
|
||
if (sourceAttr) {
|
||
const attrCode = String(sourceAttr.attribute_code || sourceAttr.AttributeCode || '').trim()
|
||
if (attrCode) {
|
||
// Seceneklerde var mi kontrol et, yoksa ekle (UI'da gorunmesi icin)
|
||
if (!row.AllOptions.some(opt => String(opt.value).trim() === attrCode)) {
|
||
row.AllOptions.unshift({ value: attrCode, label: attrCode })
|
||
row.Options = [...row.AllOptions]
|
||
}
|
||
row.AttributeCode = attrCode
|
||
}
|
||
}
|
||
}
|
||
const targetCode = String(attributeTargetCode.value || '').trim().toUpperCase()
|
||
if (targetCode) {
|
||
store.setProductAttributeDraft(targetCode, JSON.parse(JSON.stringify(attributeRows.value || [])))
|
||
}
|
||
$q.notify({ type: 'positive', message: 'Urun ozellikleri kopyalandi.' })
|
||
} else {
|
||
$q.notify({ type: 'warning', message: 'Kaynak urun ozellikleri bulunamadi.' })
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('[OrderProductionUpdate] copyFromOldProduct failed', err)
|
||
$q.notify({ type: 'negative', message: 'Kopyalama sirasinda hata olustu.' })
|
||
} finally {
|
||
$q.loading.hide()
|
||
}
|
||
}
|
||
|
||
async function openCdItemDialog (itemCode) {
|
||
const code = String(itemCode || '').trim().toUpperCase()
|
||
if (!code) return
|
||
copySourceCode.value = null
|
||
await store.fetchCdItemLookups()
|
||
|
||
cdItemTargetCode.value = code
|
||
const existing = store.getCdItemDraft(code)
|
||
const draft = createEmptyCdItemDraft(code)
|
||
if (existing) {
|
||
for (const [k, v] of Object.entries(existing)) {
|
||
if (v == null) continue
|
||
draft[k] = String(v)
|
||
}
|
||
}
|
||
cdItemDraftForm.value = draft
|
||
cdItemDialogOpen.value = true
|
||
}
|
||
|
||
function persistCdItemDraft () {
|
||
const targetCode = String(cdItemTargetCode.value || '').trim().toUpperCase()
|
||
const payload = normalizeCdItemDraftForPayload({
|
||
...(cdItemDraftForm.value || {}),
|
||
ItemCode: targetCode || String(cdItemDraftForm.value?.ItemCode || '').trim().toUpperCase()
|
||
})
|
||
if (!payload.ItemCode) return null
|
||
store.setCdItemDraft(payload.ItemCode, payload)
|
||
return payload
|
||
}
|
||
|
||
function normalizeCdItemDraftForPayload (draftRaw) {
|
||
const d = draftRaw || {}
|
||
const toIntOrNil = (v) => {
|
||
const n = Number(v)
|
||
return Number.isFinite(n) && n > 0 ? n : null
|
||
}
|
||
const toStrOrNil = (v) => {
|
||
const s = String(v || '').trim()
|
||
return s || null
|
||
}
|
||
return {
|
||
ItemTypeCode: toIntOrNil(d.ItemTypeCode) || 1,
|
||
ItemCode: String(d.ItemCode || '').trim().toUpperCase(),
|
||
ItemDimTypeCode: toIntOrNil(d.ItemDimTypeCode) || 1,
|
||
ProductTypeCode: null,
|
||
ProductHierarchyID: toIntOrNil(d.ProductHierarchyID),
|
||
UnitOfMeasureCode1: 'AD',
|
||
ItemAccountGrCode: null,
|
||
ItemTaxGrCode: '%10',
|
||
ItemPaymentPlanGrCode: null,
|
||
ItemDiscountGrCode: null,
|
||
ItemVendorGrCode: null,
|
||
PromotionGroupCode: null,
|
||
ProductCollectionGrCode: null,
|
||
StorePriceLevelCode: null,
|
||
PerceptionOfFashionCode: null,
|
||
CommercialRoleCode: null,
|
||
StoreCapacityLevelCode: null,
|
||
CustomsTariffNumberCode: null,
|
||
CompanyCode: '1'
|
||
}
|
||
}
|
||
|
||
async function saveCdItemDraft () {
|
||
const payload = persistCdItemDraft()
|
||
if (!payload?.ItemCode) {
|
||
$q.notify({ type: 'negative', message: 'ItemCode bos olamaz.' })
|
||
return
|
||
}
|
||
console.info('[OrderProductionUpdate] saveCdItemDraft', {
|
||
code: payload.ItemCode,
|
||
itemDimTypeCode: payload.ItemDimTypeCode,
|
||
productHierarchyID: payload.ProductHierarchyID
|
||
})
|
||
cdItemDialogOpen.value = false
|
||
await openAttributeDialog(payload.ItemCode)
|
||
}
|
||
|
||
function buildAttributeRowsFromLookup (list) {
|
||
const grouped = new Map()
|
||
for (const it of (list || [])) {
|
||
const typeCode = Number(it?.attribute_type_code || it?.AttributeTypeCode || 0)
|
||
if (!typeCode) continue
|
||
if (!grouped.has(typeCode)) {
|
||
grouped.set(typeCode, {
|
||
typeCode,
|
||
typeDesc: String(it?.attribute_type_description || it?.AttributeTypeDescription || '').trim() || String(typeCode),
|
||
options: []
|
||
})
|
||
}
|
||
const g = grouped.get(typeCode)
|
||
const code = String(it?.attribute_code || it?.AttributeCode || '').trim()
|
||
const desc = String(it?.attribute_description || it?.AttributeDescription || '').trim()
|
||
if (!code) continue
|
||
g.options.push({
|
||
value: code,
|
||
label: `${code} - ${desc || code}`
|
||
})
|
||
}
|
||
|
||
const rows = [...grouped.values()]
|
||
.sort((a, b) => a.typeCode - b.typeCode)
|
||
.map(g => ({
|
||
AttributeTypeCodeNumber: g.typeCode,
|
||
TypeLabel: `${g.typeCode} - ${g.typeDesc}`,
|
||
AttributeCode: '',
|
||
AllOptions: [...g.options],
|
||
Options: [...g.options]
|
||
}))
|
||
return rows
|
||
}
|
||
|
||
function onFilterAttributeOption (row, val, update) {
|
||
const raw = String(val || '')
|
||
const needle = normalizeSearchText(raw)
|
||
const base = Array.isArray(row?.AllOptions) ? row.AllOptions : (Array.isArray(row?.Options) ? row.Options : [])
|
||
update(() => {
|
||
if (!needle) {
|
||
row.Options = [...base]
|
||
return
|
||
}
|
||
row.Options = base.filter(opt => {
|
||
const label = normalizeSearchText(opt?.label || '')
|
||
const value = normalizeSearchText(opt?.value || '')
|
||
return label.includes(needle) || value.includes(needle)
|
||
})
|
||
})
|
||
}
|
||
|
||
function mergeAttributeDraftWithLookupOptions (draftRows, lookupRows) {
|
||
const byType = new Map(
|
||
(lookupRows || []).map(r => [Number(r?.AttributeTypeCodeNumber || 0), r]).filter(x => x[0] > 0)
|
||
)
|
||
|
||
return (draftRows || []).map(d => {
|
||
const typeCode = Number(d?.AttributeTypeCodeNumber || 0)
|
||
const base = byType.get(typeCode)
|
||
const selectedCode = String(d?.AttributeCode || '').trim()
|
||
|
||
const baseAllOptions = Array.isArray(base?.AllOptions)
|
||
? [...base.AllOptions]
|
||
: (Array.isArray(base?.Options) ? [...base.Options] : [])
|
||
|
||
const draftAllOptions = Array.isArray(d?.AllOptions)
|
||
? [...d.AllOptions]
|
||
: (Array.isArray(d?.Options) ? [...d.Options] : [])
|
||
|
||
const allOptions = baseAllOptions.length ? baseAllOptions : draftAllOptions
|
||
|
||
if (selectedCode && !allOptions.some(opt => String(opt?.value || '').trim() === selectedCode)) {
|
||
allOptions.unshift({ value: selectedCode, label: selectedCode })
|
||
}
|
||
|
||
return {
|
||
...(base || d),
|
||
...d,
|
||
AttributeTypeCodeNumber: typeCode,
|
||
AttributeCode: selectedCode,
|
||
AllOptions: allOptions,
|
||
Options: [...allOptions]
|
||
}
|
||
})
|
||
}
|
||
|
||
async function openAttributeDialog (itemCode) {
|
||
const code = String(itemCode || '').trim().toUpperCase()
|
||
if (!code) return
|
||
copySourceCode.value = null
|
||
attributeTargetCode.value = code
|
||
const existingDraft = JSON.parse(JSON.stringify(store.getProductAttributeDraft(code) || []))
|
||
const modeInfo = store.classifyItemCode(code)
|
||
const fetched = await store.fetchProductAttributes(1)
|
||
const fromLookup = buildAttributeRowsFromLookup(fetched)
|
||
console.info('[OrderProductionUpdate] openAttributeDialog lookup', {
|
||
code,
|
||
mode: modeInfo.mode,
|
||
fetchedCount: Array.isArray(fetched) ? fetched.length : 0,
|
||
rowCount: fromLookup.length
|
||
})
|
||
if (!fromLookup.length) {
|
||
$q.notify({ type: 'negative', message: 'Urun ozellikleri listesi alinamadi. Lutfen daha sonra tekrar deneyin.' })
|
||
return
|
||
}
|
||
|
||
// Draft varsa popup her zaman draft'tan acilir (yeniden acinca secimler kaybolmasin).
|
||
if (Array.isArray(existingDraft) && existingDraft.length) {
|
||
attributeRows.value = JSON.parse(JSON.stringify(
|
||
mergeAttributeDraftWithLookupOptions(existingDraft, fromLookup)
|
||
))
|
||
console.info('[OrderProductionUpdate] openAttributeDialog rowsPrepared', {
|
||
code,
|
||
mode: modeInfo.mode,
|
||
useDraft: true,
|
||
rowCount: Array.isArray(attributeRows.value) ? attributeRows.value.length : 0,
|
||
optionCounts: (attributeRows.value || []).map(r => ({
|
||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||
options: Array.isArray(r?.Options) ? r.Options.length : 0,
|
||
allOptions: Array.isArray(r?.AllOptions) ? r.AllOptions.length : 0,
|
||
selected: String(r?.AttributeCode || '').trim()
|
||
}))
|
||
})
|
||
for (const row of (attributeRows.value || [])) {
|
||
if (!Array.isArray(row.AllOptions)) row.AllOptions = Array.isArray(row.Options) ? [...row.Options] : []
|
||
if (!Array.isArray(row.Options)) row.Options = [...row.AllOptions]
|
||
}
|
||
attributeDialogOpen.value = true
|
||
return
|
||
}
|
||
|
||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
||
console.info('[OrderProductionUpdate] openAttributeDialog dbCurrent', {
|
||
code,
|
||
dbCurrentCount: Array.isArray(dbCurrent) ? dbCurrent.length : 0
|
||
})
|
||
if (Array.isArray(dbCurrent) && dbCurrent.length) {
|
||
store.markItemCodeKnownExisting(code, true)
|
||
syncRowsForKnownExistingCode(code)
|
||
}
|
||
|
||
const dbMap = new Map(
|
||
(dbCurrent || []).map(x => [
|
||
Number(x?.attribute_type_code || x?.AttributeTypeCode || 0),
|
||
String(x?.attribute_code || x?.AttributeCode || '').trim()
|
||
]).filter(x => x[0] > 0)
|
||
)
|
||
|
||
const baseRows = fromLookup.map(row => {
|
||
const currentCode = dbMap.get(Number(row.AttributeTypeCodeNumber || 0)) || ''
|
||
const currentOptions = Array.isArray(row.AllOptions)
|
||
? [...row.AllOptions]
|
||
: (Array.isArray(row.Options) ? [...row.Options] : [])
|
||
if (currentCode && !currentOptions.some(opt => String(opt?.value || '').trim() === currentCode)) {
|
||
currentOptions.unshift({ value: currentCode, label: currentCode })
|
||
}
|
||
return {
|
||
...row,
|
||
AttributeCode: currentCode,
|
||
OriginalAttributeCode: currentCode,
|
||
AllOptions: currentOptions,
|
||
Options: [...currentOptions]
|
||
}
|
||
})
|
||
|
||
const useDraft = Array.isArray(existingDraft) && existingDraft.length
|
||
attributeRows.value = JSON.parse(JSON.stringify(baseRows))
|
||
console.info('[OrderProductionUpdate] openAttributeDialog rowsPrepared', {
|
||
code,
|
||
mode: modeInfo.mode,
|
||
useDraft: false,
|
||
rowCount: Array.isArray(attributeRows.value) ? attributeRows.value.length : 0,
|
||
optionCounts: (attributeRows.value || []).map(r => ({
|
||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||
options: Array.isArray(r?.Options) ? r.Options.length : 0,
|
||
allOptions: Array.isArray(r?.AllOptions) ? r.AllOptions.length : 0,
|
||
selected: String(r?.AttributeCode || '').trim()
|
||
}))
|
||
})
|
||
for (const row of (attributeRows.value || [])) {
|
||
if (!Array.isArray(row.AllOptions)) {
|
||
row.AllOptions = Array.isArray(row.Options) ? [...row.Options] : []
|
||
}
|
||
if (!Array.isArray(row.Options)) {
|
||
row.Options = [...row.AllOptions]
|
||
}
|
||
}
|
||
attributeDialogOpen.value = true
|
||
}
|
||
|
||
function saveAttributeDraft () {
|
||
const code = String(attributeTargetCode.value || '').trim().toUpperCase()
|
||
if (!code) return
|
||
const rows = JSON.parse(JSON.stringify(attributeRows.value || []))
|
||
for (const row of rows) {
|
||
const selected = String(row?.AttributeCode || '').trim()
|
||
if (!selected) {
|
||
$q.notify({ type: 'negative', message: `Urun ozelliklerinde secim zorunlu: ${row?.TypeLabel || ''}` })
|
||
return
|
||
}
|
||
}
|
||
store.setProductAttributeDraft(code, rows)
|
||
console.info('[OrderProductionUpdate] saveAttributeDraft', {
|
||
code,
|
||
rowCount: rows.length,
|
||
selectedCount: rows.length,
|
||
selected: rows.map(r => ({
|
||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||
code: String(r?.AttributeCode || '').trim()
|
||
}))
|
||
})
|
||
attributeDialogOpen.value = false
|
||
$q.notify({ type: 'positive', message: 'Urun ozellikleri taslagi kaydedildi.' })
|
||
}
|
||
|
||
watch(
|
||
cdItemDraftForm,
|
||
() => {
|
||
if (!cdItemDialogOpen.value) return
|
||
persistCdItemDraft()
|
||
},
|
||
{ deep: true }
|
||
)
|
||
|
||
async function collectProductAttributesFromSelectedRows (selectedRows) {
|
||
const codeSet = [...new Set(
|
||
(selectedRows || [])
|
||
.map(r => String(r?.NewItemCode || '').trim().toUpperCase())
|
||
.filter(Boolean)
|
||
)]
|
||
const out = []
|
||
|
||
for (const code of codeSet) {
|
||
const modeInfo = store.classifyItemCode(code)
|
||
let rows = store.getProductAttributeDraft(code)
|
||
const dbCurrent = await store.fetchProductItemAttributes(code, 1, true)
|
||
const dbMap = new Map(
|
||
(dbCurrent || []).map(x => [
|
||
Number(x?.attribute_type_code || x?.AttributeTypeCode || 0),
|
||
String(x?.attribute_code || x?.AttributeCode || '').trim()
|
||
]).filter(x => x[0] > 0)
|
||
)
|
||
const hasDbAttributes = dbMap.size > 0
|
||
const effectiveMode = hasDbAttributes ? 'existing' : modeInfo.mode
|
||
console.info('[OrderProductionUpdate] collectProductAttributes start', {
|
||
code,
|
||
mode: modeInfo.mode,
|
||
effectiveMode,
|
||
hasDbAttributes,
|
||
draftRowCount: Array.isArray(rows) ? rows.length : 0
|
||
})
|
||
|
||
if (effectiveMode === 'existing') {
|
||
// Existing kodda kullanıcı değişiklik yaptıysa draftı koru.
|
||
// Draft yoksa DB'den zorunlu/fresh çek.
|
||
if (!Array.isArray(rows) || !rows.length) {
|
||
const lookup = await store.fetchProductAttributes(1)
|
||
const baseRows = buildAttributeRowsFromLookup(lookup)
|
||
console.info('[OrderProductionUpdate] collectProductAttributes existing refetch', {
|
||
code,
|
||
lookupCount: Array.isArray(lookup) ? lookup.length : 0,
|
||
baseRowCount: baseRows.length
|
||
})
|
||
rows = baseRows.map(row => {
|
||
const currentCode = dbMap.get(Number(row.AttributeTypeCodeNumber || 0)) || ''
|
||
const currentOptions = Array.isArray(row.AllOptions)
|
||
? [...row.AllOptions]
|
||
: (Array.isArray(row.Options) ? [...row.Options] : [])
|
||
if (currentCode && !currentOptions.some(opt => String(opt?.value || '').trim() === currentCode)) {
|
||
currentOptions.unshift({ value: currentCode, label: currentCode })
|
||
}
|
||
return {
|
||
...row,
|
||
AttributeCode: currentCode,
|
||
OriginalAttributeCode: currentCode,
|
||
AllOptions: currentOptions,
|
||
Options: [...currentOptions]
|
||
}
|
||
})
|
||
store.setProductAttributeDraft(code, JSON.parse(JSON.stringify(rows)))
|
||
}
|
||
} else if (!Array.isArray(rows) || !rows.length) {
|
||
return { errMsg: `${code} icin urun ozellikleri taslagi kaydedilmedi`, productAttributes: [] }
|
||
}
|
||
|
||
if (!Array.isArray(rows) || !rows.length) {
|
||
return { errMsg: `${code} icin urun ozellikleri secilmedi`, productAttributes: [] }
|
||
}
|
||
for (const row of rows) {
|
||
const attributeTypeCode = Number(row?.AttributeTypeCodeNumber || 0)
|
||
const attributeCode = String(row?.AttributeCode || '').trim()
|
||
|
||
if (!attributeTypeCode) {
|
||
return { errMsg: `${code} icin urun ozellikleri eksik`, productAttributes: [] }
|
||
}
|
||
|
||
if (effectiveMode === 'existing') {
|
||
const originalCode =
|
||
dbMap.get(attributeTypeCode) ||
|
||
String(row?.OriginalAttributeCode || '').trim()
|
||
const changed = attributeCode !== originalCode
|
||
if (!changed) continue
|
||
if (!attributeCode) {
|
||
return { errMsg: `${code} icin urun ozellikleri eksik`, productAttributes: [] }
|
||
}
|
||
} else if (!attributeCode) {
|
||
return { errMsg: `${code} icin urun ozellikleri eksik`, productAttributes: [] }
|
||
}
|
||
|
||
out.push({
|
||
ItemTypeCode: 1,
|
||
ItemCode: code,
|
||
AttributeTypeCode: attributeTypeCode,
|
||
AttributeCode: attributeCode
|
||
})
|
||
}
|
||
console.info('[OrderProductionUpdate] collectProductAttributes done', {
|
||
code,
|
||
mode: modeInfo.mode,
|
||
effectiveMode,
|
||
outCount: out.filter(x => x.ItemCode === code).length,
|
||
rowCount: rows.length,
|
||
optionCounts: rows.map(r => ({
|
||
type: Number(r?.AttributeTypeCodeNumber || 0),
|
||
options: Array.isArray(r?.Options) ? r.Options.length : 0,
|
||
allOptions: Array.isArray(r?.AllOptions) ? r.AllOptions.length : 0
|
||
}))
|
||
})
|
||
}
|
||
return { errMsg: '', productAttributes: out }
|
||
}
|
||
|
||
async function collectCdItemsFromSelectedRows (selectedRows) {
|
||
const codes = [...new Set(
|
||
(selectedRows || [])
|
||
.filter(r => r?.NewItemMode === 'new' && String(r?.NewItemCode || '').trim())
|
||
.map(r => String(r.NewItemCode).trim().toUpperCase())
|
||
)]
|
||
if (!codes.length) return { errMsg: '', cdItems: [] }
|
||
|
||
const out = []
|
||
for (const code of codes) {
|
||
let draft = store.getCdItemDraft(code)
|
||
if (!draft) {
|
||
const existingCdItem = await store.fetchCdItemByCode(code)
|
||
if (existingCdItem) {
|
||
store.markItemCodeKnownExisting(code, true)
|
||
syncRowsForKnownExistingCode(code)
|
||
draft = normalizeCdItemDraftForPayload(existingCdItem)
|
||
store.setCdItemDraft(code, draft)
|
||
}
|
||
}
|
||
if (!draft) {
|
||
return { errMsg: `${code} icin cdItem bilgisi eksik`, cdItems: [] }
|
||
}
|
||
out.push(normalizeCdItemDraftForPayload(draft))
|
||
}
|
||
return { errMsg: '', cdItems: out }
|
||
}
|
||
|
||
function buildMailLineLabelFromRow (row) {
|
||
const item = String(row?.NewItemCode || row?.OldItemCode || '').trim().toUpperCase()
|
||
const color1 = String(row?.NewColor || row?.OldColor || '').trim().toUpperCase()
|
||
const color2 = String(row?.NewDim2 || row?.OldDim2 || '').trim().toUpperCase()
|
||
const desc = String(row?.NewDesc || row?.OldDesc || '').trim()
|
||
|
||
if (!item) return ''
|
||
const colorPart = color2 ? `${color1}-${color2}` : color1
|
||
return [item, colorPart, desc].filter(Boolean).join(' ')
|
||
}
|
||
|
||
function buildUpdateMailLineLabelFromRow (row) {
|
||
const newItem = String(row?.NewItemCode || row?.OldItemCode || '').trim().toUpperCase()
|
||
const newColor = String(row?.NewColor || row?.OldColor || '').trim().toUpperCase()
|
||
const newDim2 = String(row?.NewDim2 || row?.OldDim2 || '').trim().toUpperCase()
|
||
const desc = mergeDescWithAutoNote(row, row?.NewDesc || row?.OldDesc || '')
|
||
|
||
if (!newItem) return ''
|
||
const colorPart = newDim2 ? `${newColor}-${newDim2}` : newColor
|
||
return [newItem, colorPart, desc].filter(Boolean).join(' ')
|
||
}
|
||
|
||
function buildDueDateChangeRowsFromSelectedRows (selectedRows) {
|
||
const seen = new Set()
|
||
const out = []
|
||
|
||
for (const row of (selectedRows || [])) {
|
||
const itemCode = String(row?.NewItemCode || row?.OldItemCode || '').trim().toUpperCase()
|
||
const colorCode = String(row?.NewColor || row?.OldColor || '').trim().toUpperCase()
|
||
const itemDim2Code = String(row?.NewDim2 || row?.OldDim2 || '').trim().toUpperCase()
|
||
const oldDueDate = formatDate(row?.OldDueDate)
|
||
const newDueDate = formatDate(row?.NewDueDate)
|
||
if (!itemCode || !newDueDate || oldDueDate === newDueDate) continue
|
||
|
||
const key = [itemCode, colorCode, itemDim2Code, oldDueDate, newDueDate].join('||')
|
||
if (seen.has(key)) continue
|
||
seen.add(key)
|
||
out.push({
|
||
itemCode,
|
||
colorCode,
|
||
itemDim2Code,
|
||
oldDueDate,
|
||
newDueDate
|
||
})
|
||
}
|
||
|
||
return out
|
||
}
|
||
|
||
function buildProductionUpdateMailPayload (selectedRows) {
|
||
const updatedItems = [
|
||
...new Set(
|
||
(selectedRows || [])
|
||
.map(buildUpdateMailLineLabelFromRow)
|
||
.filter(Boolean)
|
||
)
|
||
]
|
||
|
||
return {
|
||
operation: 'update',
|
||
deletedItems: [],
|
||
updatedItems,
|
||
addedItems: [],
|
||
dueDateChanges: buildDueDateChangeRowsFromSelectedRows(selectedRows)
|
||
}
|
||
}
|
||
|
||
function formatBarcodeValidationMessages (validations) {
|
||
return (Array.isArray(validations) ? validations : [])
|
||
.map(v => String(v?.message || '').trim())
|
||
.filter(Boolean)
|
||
}
|
||
|
||
function showBarcodeValidationDialog (validations) {
|
||
const messages = formatBarcodeValidationMessages(validations)
|
||
if (!messages.length) return false
|
||
$q.dialog({
|
||
title: 'Barkod Validasyonlari',
|
||
message: messages.join('<br>'),
|
||
html: true,
|
||
ok: { label: 'Tamam', color: 'negative' }
|
||
})
|
||
return true
|
||
}
|
||
|
||
async function sendUpdateMailAfterApply (selectedRows) {
|
||
const orderId = String(orderHeaderID.value || '').trim()
|
||
if (!orderId) return
|
||
const host = String(window?.location?.hostname || '').trim().toLowerCase()
|
||
const isLocalHost = host === 'localhost' || host === '127.0.0.1'
|
||
if (isLocalHost) {
|
||
console.info('[OrderProductionUpdate] sendUpdateMailAfterApply skipped (localhost)', { orderHeaderID: orderId, host })
|
||
return
|
||
}
|
||
|
||
try {
|
||
const t0 = nowMs()
|
||
const payload = buildProductionUpdateMailPayload(selectedRows)
|
||
console.info('[OrderProductionUpdate] sendUpdateMailAfterApply start', {
|
||
orderHeaderID: orderId,
|
||
updatedItems: payload?.updatedItems?.length || 0
|
||
})
|
||
const res = await api.post('/order/send-market-mail', {
|
||
orderHeaderID: orderId,
|
||
operation: payload.operation,
|
||
deletedItems: payload.deletedItems,
|
||
updatedItems: payload.updatedItems,
|
||
addedItems: payload.addedItems,
|
||
dueDateChanges: payload.dueDateChanges,
|
||
extraRecipients: ['urun@baggi.com.tr']
|
||
})
|
||
|
||
const sentCount = Number(res?.data?.sentCount || 0)
|
||
console.info('[OrderProductionUpdate] sendUpdateMailAfterApply done', {
|
||
orderHeaderID: orderId,
|
||
sentCount,
|
||
durationMs: Math.round(nowMs() - t0)
|
||
})
|
||
$q.notify({
|
||
type: 'positive',
|
||
message: sentCount > 0
|
||
? `Guncelleme maili gonderildi (${sentCount} alici)`
|
||
: 'Guncelleme maili gonderildi'
|
||
})
|
||
} catch (err) {
|
||
console.error('[OrderProductionUpdate] sendUpdateMailAfterApply failed', {
|
||
orderHeaderID: orderId,
|
||
status: err?.response?.status,
|
||
data: err?.response?.data,
|
||
message: err?.message
|
||
})
|
||
$q.notify({
|
||
type: 'warning',
|
||
message: 'Guncelleme kaydedildi, mail gonderilemedi.'
|
||
})
|
||
}
|
||
}
|
||
|
||
function buildGroupKey (item) {
|
||
const parts = [
|
||
String(item?.OldItemCode || '').trim(),
|
||
String(item?.OldColor || '').trim(),
|
||
String(item?.OldDim2 || '').trim(),
|
||
String(item?.OldDesc || '').trim(),
|
||
String(item?.OldDim3 || '').trim()
|
||
]
|
||
return parts.join('||')
|
||
}
|
||
|
||
function formatSizes (sizeMap) {
|
||
const entries = Object.entries(sizeMap || {})
|
||
if (!entries.length) return { list: [], label: '-' }
|
||
entries.sort((a, b) => {
|
||
const left = String(a[0] || '').trim()
|
||
const right = String(b[0] || '').trim()
|
||
if (/^\d+$/.test(left) && /^\d+$/.test(right)) {
|
||
return Number(left) - Number(right)
|
||
}
|
||
return left.localeCompare(right)
|
||
})
|
||
const label = entries.map(([k, v]) => (v > 1 ? `${k}(${v})` : k)).join(', ')
|
||
return { list: entries.map(([k]) => k), label }
|
||
}
|
||
|
||
function formatCodeDescriptionLabel (code, description) {
|
||
const codeText = String(code || '').trim().toUpperCase()
|
||
const descText = String(description || '').trim()
|
||
if (!codeText) return descText
|
||
if (!descText) return codeText
|
||
return `${codeText} - ${descText}`
|
||
}
|
||
|
||
function formatQtyLabel (value) {
|
||
const qty = Number(value || 0)
|
||
if (!Number.isFinite(qty)) return '0'
|
||
return Number.isInteger(qty)
|
||
? String(qty)
|
||
: qty.toLocaleString('tr-TR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
|
||
}
|
||
|
||
function groupItems (items, prevRows = []) {
|
||
const prevMap = new Map()
|
||
for (const r of prevRows || []) {
|
||
if (!r?.RowKey) continue
|
||
prevMap.set(r.RowKey, {
|
||
NewDesc: String(r.NewDesc || '').trim(),
|
||
NewItemCode: String(r.NewItemCode || '').trim().toUpperCase(),
|
||
NewColor: String(r.NewColor || '').trim().toUpperCase(),
|
||
NewDim2: String(r.NewDim2 || '').trim().toUpperCase(),
|
||
NewItemMode: String(r.NewItemMode || '').trim(),
|
||
NewItemSource: String(r.NewItemSource || '').trim(),
|
||
NewItemEntryMode: String(r.NewItemEntryMode || '').trim(),
|
||
NewDueDate: String(r.NewDueDate || '').trim()
|
||
})
|
||
}
|
||
const map = new Map()
|
||
|
||
for (const it of items) {
|
||
const key = buildGroupKey(it)
|
||
if (!map.has(key)) {
|
||
const prev = prevMap.get(key) || {}
|
||
const prevDesc = prev.NewDesc || ''
|
||
const fallbackDesc = String((it?.NewDesc || it?.OldDesc) || '').trim()
|
||
map.set(key, {
|
||
RowKey: key,
|
||
OrderHeaderID: it.OrderHeaderID,
|
||
OldItemCode: it.OldItemCode,
|
||
OldColor: it.OldColor,
|
||
OldColorDescription: it.OldColorDescription,
|
||
OldColorLabel: formatCodeDescriptionLabel(it.OldColor, it.OldColorDescription),
|
||
OldDim2: it.OldDim2,
|
||
OldDim3: it.OldDim3,
|
||
OldDesc: it.OldDesc,
|
||
OldDueDate: it.OldDueDate || '',
|
||
NewDueDate: (prev.NewDueDate || it.OldDueDate || ''),
|
||
OrderLineIDs: [],
|
||
OrderLines: [],
|
||
OldSizes: [],
|
||
OldSizesLabel: '',
|
||
OldTotalQty: 0,
|
||
OldTotalQtyLabel: '0',
|
||
NewItemCode: prev.NewItemCode || '',
|
||
NewColor: prev.NewColor || '',
|
||
NewDim2: prev.NewDim2 || '',
|
||
NewDesc: prevDesc || fallbackDesc,
|
||
NewItemMode: prev.NewItemMode || 'empty',
|
||
NewItemSource: prev.NewItemSource || '',
|
||
NewItemEntryMode: prev.NewItemEntryMode || '',
|
||
IsVariantMissing: !!it.IsVariantMissing,
|
||
yasPayloadMap: {}
|
||
})
|
||
}
|
||
|
||
const g = map.get(key)
|
||
if (it?.OrderLineID) g.OrderLineIDs.push(it.OrderLineID)
|
||
|
||
const rawSize = String(it?.OldDim1 || '').trim()
|
||
const size = store.normalizeDim1ForUi(rawSize)
|
||
const rawSizeUpper = rawSize.toUpperCase()
|
||
if (/^(\d+)\s*(Y|YAS|YAŞ)$/.test(rawSizeUpper) && size) {
|
||
g.yasPayloadMap[size] = store.pickPreferredYasPayloadLabel(
|
||
g.yasPayloadMap[size],
|
||
rawSizeUpper
|
||
)
|
||
}
|
||
if (it?.OrderLineID) {
|
||
g.OrderLines.push({
|
||
OrderLineID: it.OrderLineID,
|
||
ItemDim1Code: size
|
||
})
|
||
}
|
||
if (size !== '') {
|
||
g.__sizeMap = g.__sizeMap || {}
|
||
g.__sizeMap[size] = (g.__sizeMap[size] || 0) + 1
|
||
}
|
||
g.__oldQtyTotal = Number(g.__oldQtyTotal || 0) + Number(it?.OldQty || 0)
|
||
if (it?.IsVariantMissing) g.IsVariantMissing = true
|
||
}
|
||
|
||
const out = []
|
||
for (const g of map.values()) {
|
||
const sizes = formatSizes(g.__sizeMap || {})
|
||
g.OldSizes = sizes.list
|
||
g.OldSizesLabel = sizes.label
|
||
g.OldTotalQty = Number(g.__oldQtyTotal || 0)
|
||
g.OldTotalQtyLabel = formatQtyLabel(g.OldTotalQty)
|
||
const info = store.classifyItemCode(g.NewItemCode)
|
||
g.NewItemCode = info.normalized
|
||
g.NewItemMode = info.mode
|
||
if (info.mode === 'empty') g.NewItemSource = ''
|
||
if (!g.NewItemEntryMode && g.NewItemCode) {
|
||
g.NewItemEntryMode = g.NewItemSource === 'selected' ? 'selected' : 'typed'
|
||
}
|
||
delete g.__sizeMap
|
||
delete g.__oldQtyTotal
|
||
out.push(g)
|
||
}
|
||
|
||
return out
|
||
}
|
||
|
||
async function refreshAll () {
|
||
await store.fetchHeader(orderHeaderID.value)
|
||
await store.fetchItems(orderHeaderID.value)
|
||
await store.fetchProducts()
|
||
}
|
||
|
||
async function onBulkSubmit () {
|
||
if (isBulkSubmitting.value || store.saving) {
|
||
console.info('[OrderProductionUpdate] onBulkSubmit ignored (already running)', {
|
||
orderHeaderID: orderHeaderID.value,
|
||
isBulkSubmitting: isBulkSubmitting.value,
|
||
storeSaving: store.saving
|
||
})
|
||
return
|
||
}
|
||
|
||
isBulkSubmitting.value = true
|
||
const flowStart = nowMs()
|
||
try {
|
||
suppressAutoSetupDialogs.value = true
|
||
const selectedRows = rows.value.filter(r => !!selectedMap.value[r.RowKey])
|
||
const headerAverageDueDateValue = normalizeDateInput(headerAverageDueDate.value)
|
||
const headerDateChanged = hasHeaderAverageDueDateChange.value
|
||
if (!selectedRows.length && !headerDateChanged) {
|
||
$q.notify({ type: 'warning', message: 'Lutfen en az bir satir seciniz veya ustteki termin tarihini degistiriniz.' })
|
||
return
|
||
}
|
||
|
||
const prepStart = nowMs()
|
||
const { errMsg, lines } = collectLinesFromRows(selectedRows)
|
||
if (errMsg) {
|
||
$q.notify({ type: 'negative', message: errMsg })
|
||
return
|
||
}
|
||
if (!lines.length && !headerDateChanged) {
|
||
$q.notify({ type: 'warning', message: 'Secili satirlarda degisiklik yok.' })
|
||
return
|
||
}
|
||
|
||
if (lines.length > 0) {
|
||
const changedRows = selectedRows.filter(hasRowChange)
|
||
const confirmed = await confirmOptionalColorWarnings(changedRows)
|
||
if (!confirmed) return
|
||
}
|
||
|
||
let cdItems = []
|
||
let productAttributes = []
|
||
if (lines.length > 0) {
|
||
const { errMsg: cdErrMsg, cdItems: nextCdItems } = await collectCdItemsFromSelectedRows(selectedRows)
|
||
if (cdErrMsg) {
|
||
$q.notify({ type: 'negative', message: cdErrMsg })
|
||
return
|
||
}
|
||
cdItems = nextCdItems
|
||
|
||
const { errMsg: attrErrMsg, productAttributes: nextProductAttributes } = await collectProductAttributesFromSelectedRows(selectedRows)
|
||
if (attrErrMsg) {
|
||
$q.notify({ type: 'negative', message: attrErrMsg })
|
||
const firstCode = String(attrErrMsg.split(' ')[0] || '').trim().toUpperCase()
|
||
if (isValidBaggiModelCode(firstCode)) {
|
||
await openAttributeDialog(firstCode)
|
||
}
|
||
return
|
||
}
|
||
productAttributes = nextProductAttributes
|
||
}
|
||
|
||
console.info('[OrderProductionUpdate] onBulkSubmit prepared', {
|
||
orderHeaderID: orderHeaderID.value,
|
||
selectedRowCount: selectedRows.length,
|
||
lineCount: lines.length,
|
||
cdItemCount: cdItems.length,
|
||
attributeCount: productAttributes.length,
|
||
headerAverageDueDate: headerAverageDueDateValue,
|
||
headerDateChanged,
|
||
prepDurationMs: Math.round(nowMs() - prepStart)
|
||
})
|
||
|
||
const applyChanges = async (insertMissing) => {
|
||
const applyStart = nowMs()
|
||
const applyResult = await store.applyUpdates(
|
||
orderHeaderID.value,
|
||
lines,
|
||
insertMissing,
|
||
cdItems,
|
||
productAttributes,
|
||
headerDateChanged ? headerAverageDueDateValue : null
|
||
)
|
||
console.info('[OrderProductionUpdate] apply finished', {
|
||
orderHeaderID: orderHeaderID.value,
|
||
insertMissing: !!insertMissing,
|
||
lineCount: lines.length,
|
||
barcodeInserted: Number(applyResult?.barcodeInserted || 0),
|
||
headerAverageDueDate: headerAverageDueDateValue,
|
||
headerDateChanged,
|
||
durationMs: Math.round(nowMs() - applyStart)
|
||
})
|
||
await store.fetchHeader(orderHeaderID.value)
|
||
if (lines.length > 0) {
|
||
await store.fetchItems(orderHeaderID.value)
|
||
}
|
||
selectedMap.value = {}
|
||
if (lines.length > 0) {
|
||
await sendUpdateMailAfterApply(selectedRows)
|
||
} else {
|
||
$q.notify({ type: 'positive', message: 'Tahmini termin tarihi guncellendi.' })
|
||
}
|
||
}
|
||
|
||
if (lines.length > 0) {
|
||
const validateStart = nowMs()
|
||
const validate = await store.validateUpdates(orderHeaderID.value, lines, cdItems)
|
||
console.info('[OrderProductionUpdate] validate finished', {
|
||
orderHeaderID: orderHeaderID.value,
|
||
lineCount: lines.length,
|
||
missingCount: Number(validate?.missingCount || 0),
|
||
barcodeValidationCount: Number(validate?.barcodeValidationCount || 0),
|
||
durationMs: Math.round(nowMs() - validateStart)
|
||
})
|
||
if (showBarcodeValidationDialog(validate?.barcodeValidations)) {
|
||
return
|
||
}
|
||
const missingCount = validate?.missingCount || 0
|
||
if (missingCount > 0) {
|
||
const missingList = (validate?.missing || []).map(v => (
|
||
`${v.ItemCode} / ${v.ColorCode} / ${v.ItemDim1Code} / ${v.ItemDim2Code}`
|
||
))
|
||
$q.dialog({
|
||
title: 'Eksik Varyantlar',
|
||
message: `Eksik varyant bulundu: ${missingCount}<br><br>${missingList.join('<br>')}`,
|
||
html: true,
|
||
ok: { label: 'Ekle ve Guncelle', color: 'primary' },
|
||
cancel: { label: 'Vazgec', flat: true }
|
||
}).onOk(async () => {
|
||
await applyChanges(true)
|
||
})
|
||
return
|
||
}
|
||
}
|
||
|
||
await applyChanges(false)
|
||
} catch (err) {
|
||
console.error('[OrderProductionUpdate] onBulkSubmit failed', {
|
||
orderHeaderID: orderHeaderID.value,
|
||
selectedRowCount: selectedRows.length,
|
||
lineCount: lines.length,
|
||
headerAverageDueDate: headerAverageDueDateValue,
|
||
headerDateChanged,
|
||
apiError: err?.response?.data,
|
||
message: err?.message
|
||
})
|
||
if (showBarcodeValidationDialog(err?.response?.data?.barcodeValidations)) {
|
||
return
|
||
}
|
||
$q.notify({ type: 'negative', message: store.error || 'Toplu kayit islemi basarisiz.' })
|
||
} finally {
|
||
isBulkSubmitting.value = false
|
||
suppressAutoSetupDialogs.value = false
|
||
console.info('[OrderProductionUpdate] onBulkSubmit total', {
|
||
orderHeaderID: orderHeaderID.value,
|
||
durationMs: Math.round(nowMs() - flowStart)
|
||
})
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.prod-table :deep(th) {
|
||
font-weight: 700;
|
||
letter-spacing: 0.2px;
|
||
}
|
||
|
||
.prod-table :deep(td) {
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.prod-table :deep(.q-table__container) {
|
||
width: 100%;
|
||
}
|
||
|
||
.prod-table :deep(.q-table) {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.order-prod-page {
|
||
--header-height: 56px;
|
||
--filter-bar-height: 72px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100vh;
|
||
overflow: auto;
|
||
}
|
||
|
||
.page-header {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 8;
|
||
background: #fff;
|
||
margin-top: -8px;
|
||
margin-bottom: 8px;
|
||
padding-top: 4px;
|
||
padding-bottom: 6px;
|
||
}
|
||
|
||
.filter-bar {
|
||
position: sticky;
|
||
top: var(--header-height);
|
||
z-index: 7;
|
||
background: #fff;
|
||
padding-top: 4px;
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
.table-wrap {
|
||
flex: 1;
|
||
min-height: 0;
|
||
min-width: 0;
|
||
display: flex;
|
||
width: 100%;
|
||
overflow: auto;
|
||
}
|
||
|
||
.prod-table :deep(.q-table__middle) {
|
||
max-height: calc(100vh - var(--header-height) - var(--filter-bar-height) - 140px);
|
||
overflow: auto;
|
||
}
|
||
|
||
.prod-table :deep(.q-table__container) {
|
||
height: 100%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.prod-table :deep(.q-table thead tr th) {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 6;
|
||
background: #fff;
|
||
}
|
||
|
||
.prod-table :deep(th.col-old),
|
||
.prod-table :deep(td.col-old) {
|
||
background: #fff0d9;
|
||
}
|
||
|
||
.prod-table :deep(th.col-new),
|
||
.prod-table :deep(td.col-new) {
|
||
background: #e3f3ff;
|
||
}
|
||
|
||
.prod-table :deep(th.col-old) {
|
||
color: #8a5a00;
|
||
}
|
||
|
||
.prod-table :deep(th.col-new) {
|
||
color: #0d4f7a;
|
||
}
|
||
|
||
.prod-table :deep(td.col-old) {
|
||
border-left: 4px solid #f0a500;
|
||
}
|
||
|
||
.prod-table :deep(td.col-new) {
|
||
border-left: 4px solid #2d9cdb;
|
||
}
|
||
|
||
.prod-table :deep(th.col-new-first),
|
||
.prod-table :deep(td.col-new-first) {
|
||
border-left: 6px solid #1b7cc8;
|
||
}
|
||
|
||
.prod-table :deep(td.cell-new) {
|
||
background: #e3f3ff;
|
||
}
|
||
|
||
.prod-table :deep(.new-item-existing .q-field__control) {
|
||
background: #eaf9ef !important;
|
||
border-left: 3px solid #21ba45;
|
||
}
|
||
|
||
.prod-table :deep(.new-item-new .q-field__control) {
|
||
background: #fff5e9 !important;
|
||
border-left: 3px solid #f2a100;
|
||
}
|
||
|
||
.prod-table :deep(td.col-desc),
|
||
.prod-table :deep(th.col-desc),
|
||
.prod-table :deep(td.col-wrap),
|
||
.prod-table :deep(th.col-wrap) {
|
||
white-space: normal;
|
||
word-break: break-word;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.prod-table :deep(.q-field),
|
||
.prod-table :deep(.q-field__control),
|
||
.prod-table :deep(.q-select),
|
||
.prod-table :deep(.q-input) {
|
||
width: 100%;
|
||
min-width: 0;
|
||
}
|
||
</style>
|