Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-02-20 11:20:53 +03:00
parent d9c527d13f
commit 32d0c38ab9
11 changed files with 1264 additions and 122 deletions

View File

@@ -1,24 +1,177 @@
<template>
<q-page class="q-pa-md">
<div class="text-h6 text-weight-bold">Üretime Verilen Ürünler</div>
<div class="text-caption text-grey-7 q-mt-xs">
OrderHeaderID: {{ orderHeaderID || '-' }}
<div class="row items-center justify-between">
<div>
<div class="text-h6 text-weight-bold">Uretime Verilen Urunleri Guncelle</div>
<div class="text-caption text-grey-7 q-mt-xs">
OrderHeaderID: {{ orderHeaderID || '-' }}
</div>
</div>
<q-btn
color="primary"
icon="refresh"
label="Yenile"
:loading="store.loading"
@click="refreshAll"
/>
</div>
<div class="filter-bar row q-col-gutter-md q-mt-md">
<div class="col-5">
<q-input
:model-value="cariLabel"
label="Cari Secimi"
filled
dense
readonly
/>
</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
:model-value="formatDate(header?.AverageDueDate)"
label="Tahmini Termin Tarihi"
filled
dense
readonly
/>
</div>
</div>
<q-table
<q-table
class="q-mt-md"
flat
bordered
dense
separator="cell"
row-key="OrderLineID"
:rows="store.items"
:rows="rows"
:columns="columns"
:loading="store.loading"
no-data-label="Üretime verilecek ürün bulunamadı"
no-data-label="Uretime verilecek urun bulunamadi"
:rows-per-page-options="[0]"
hide-bottom
/>
>
<template #body-cell-actions="props">
<q-td :props="props" class="text-center">
<q-btn
color="primary"
icon="save"
flat
round
dense
:loading="rowSavingId === props.row.OrderLineID"
@click="onRowSubmit(props.row)"
>
<q-tooltip>Satiri Guncelle</q-tooltip>
</q-btn>
</q-td>
</template>
<template #body-cell-NewItemCode="props">
<q-td :props="props">
<q-input
v-model="props.row.NewItemCode"
dense
filled
label="Yeni Urun"
@update:model-value="val => onNewItemChange(props.row, val)"
>
<template #append>
<q-icon name="arrow_drop_down" class="cursor-pointer" />
</template>
<q-menu
anchor="bottom left"
self="top left"
fit
>
<div class="q-pa-sm" style="min-width:260px">
<q-input
v-model="productSearch"
dense
filled
debounce="200"
placeholder="Urun ara..."
/>
<q-list class="q-mt-xs" bordered separator>
<q-item
v-for="opt in filteredProducts"
:key="opt.ProductCode"
clickable
@click="onSelectProduct(props.row, opt.ProductCode)"
>
<q-item-section>{{ opt.ProductCode }}</q-item-section>
</q-item>
</q-list>
</div>
</q-menu>
</q-input>
</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"
emit-value
map-options
use-input
dense
filled
label="Yeni Renk"
@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_code"
option-value="item_dim2_code"
emit-value
map-options
use-input
dense
filled
label="Yeni 2. Renk"
/>
</q-td>
</template>
<template #body-cell-NewDesc="props">
<q-td :props="props">
<q-input
v-model="props.row.NewDesc"
dense
filled
label="Yeni Aciklama"
/>
</q-td>
</template>
</q-table>
<q-banner v-if="store.error" class="bg-red text-white q-mt-sm">
Hata: {{ store.error }}
@@ -27,31 +180,193 @@
</template>
<script setup>
import { computed, onMounted, watch } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { useQuasar } from 'quasar'
import { useOrderProductionItemStore } from 'src/stores/OrderProductionItemStore'
const route = useRoute()
const $q = useQuasar()
const store = useOrderProductionItemStore()
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 productOptions = ref([])
const productSearch = ref('')
const rowSavingId = ref('')
const columns = [
{ name: 'OldItemCode', label: 'Eski Ürün Kodu', field: 'OldItemCode', align: 'left', sortable: true, style: 'min-width:140px;white-space:nowrap', headerStyle: 'min-width:140px;white-space:nowrap' },
{ name: 'OldColor', label: 'Eski Ürün Rengi', field: 'OldColor', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap' },
{ name: 'OldItemCode', label: 'Eski Urun Kodu', field: 'OldItemCode', align: 'left', sortable: true, style: 'min-width:140px;white-space:nowrap', headerStyle: 'min-width:140px;white-space:nowrap' },
{ name: 'OldColor', label: 'Eski Urun Rengi', field: 'OldColor', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap' },
{ name: 'OldDim2', label: 'Eski 2. Renk', field: 'OldDim2', align: 'left', sortable: true, style: 'min-width:110px;white-space:nowrap', headerStyle: 'min-width:110px;white-space:nowrap' },
{ name: 'OldDesc', label: 'Eski Açıklama', field: 'OldDesc', align: 'left', sortable: false, style: 'min-width:180px;white-space:nowrap', headerStyle: 'min-width:180px;white-space:nowrap' },
{ name: 'NewItemCode', label: 'Yeni Ürün Kodu', field: 'NewItemCode', align: 'left', sortable: true, style: 'min-width:140px;white-space:nowrap', headerStyle: 'min-width:140px;white-space:nowrap' },
{ name: 'NewColor', label: 'Yeni Ürün Rengi', field: 'NewColor', align: 'left', sortable: true, style: 'min-width:120px;white-space:nowrap', headerStyle: 'min-width:120px;white-space:nowrap' },
{ name: 'NewDim2', label: 'Yeni 2. Renk', field: 'NewDim2', align: 'left', sortable: true, style: 'min-width:110px;white-space:nowrap', headerStyle: 'min-width:110px;white-space:nowrap' },
{ name: 'NewDesc', label: 'Yeni Açıklama', field: 'NewDesc', align: 'left', sortable: false, style: 'min-width:180px;white-space:nowrap', headerStyle: 'min-width:180px;white-space:nowrap' }
{ name: 'OldDesc', label: 'Eski Aciklama', field: 'OldDesc', align: 'left', sortable: false, style: 'min-width:180px;white-space:nowrap', headerStyle: 'min-width:180px;white-space:nowrap' },
{ name: 'NewItemCode', label: 'Yeni Urun Kodu', field: 'NewItemCode', align: 'left', sortable: false, style: 'min-width:190px;', headerStyle: 'min-width:190px;' },
{ name: 'NewColor', label: 'Yeni Urun Rengi', field: 'NewColor', align: 'left', sortable: false, style: 'min-width:160px;', headerStyle: 'min-width:160px;' },
{ name: 'NewDim2', label: 'Yeni 2. Renk', field: 'NewDim2', align: 'left', sortable: false, style: 'min-width:160px;', headerStyle: 'min-width:160px;' },
{ name: 'NewDesc', label: 'Yeni Aciklama', field: 'NewDesc', align: 'left', sortable: false, style: 'min-width:220px;', headerStyle: 'min-width:220px;' },
{ name: 'actions', label: '', field: 'actions', align: 'center', sortable: false, style: 'width:60px;', headerStyle: 'width:60px;' }
]
onMounted(() => {
store.fetchItems(orderHeaderID.value)
onMounted(async () => {
await refreshAll()
})
watch(orderHeaderID, (id) => {
store.fetchItems(id)
watch(orderHeaderID, async (id) => {
await refreshAll()
})
watch(
() => store.items,
(items) => {
rows.value = (items || []).map(item => ({
...item,
NewItemCode: '',
NewColor: '',
NewDim2: '',
NewDesc: ''
}))
},
{ 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
}
const filteredProducts = computed(() => {
const needle = String(productSearch.value || '').toLowerCase()
if (!needle) return productOptions.value.slice(0, 50)
return productOptions.value.filter(p =>
String(p?.ProductCode || '').toLowerCase().includes(needle)
).slice(0, 50)
})
function onSelectProduct (row, code) {
productSearch.value = ''
onNewItemChange(row, code)
}
function onNewItemChange (row, val) {
const next = String(val || '').trim()
if (next && !isValidModelCode(next)) {
$q.notify({ type: 'negative', message: 'Model kodu formati gecersiz. Ornek: S000-DMY00001' })
row.NewItemCode = ''
row.NewColor = ''
row.NewDim2 = ''
return
}
row.NewItemCode = next ? next.toUpperCase() : ''
row.NewColor = ''
row.NewDim2 = ''
if (row.NewItemCode) {
store.fetchColors(row.NewItemCode)
}
}
function onNewColorChange (row) {
row.NewDim2 = ''
if (row.NewItemCode && row.NewColor) {
store.fetchSecondColors(row.NewItemCode, row.NewColor)
}
}
function getColorOptions (row) {
const code = row?.NewItemCode || ''
const list = 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}`
return store.secondColorOptionsByKey[key] || []
}
function isValidModelCode (value) {
const text = String(value || '').trim().toUpperCase()
return /^[A-Z][0-9]{3}-[A-Z]{3}[0-9]{5}$/.test(text)
}
function buildPayloadLines () {
return rows.value.map(r => ({
OrderLineID: r.OrderLineID,
NewItemCode: String(r.NewItemCode || '').trim(),
NewColor: String(r.NewColor || '').trim(),
NewDim2: String(r.NewDim2 || '').trim(),
NewDesc: String(r.NewDesc || '').trim()
}))
}
async function refreshAll () {
await store.fetchHeader(orderHeaderID.value)
await store.fetchItems(orderHeaderID.value)
await store.fetchProducts()
}
async function onRowSubmit (row) {
const line = {
OrderLineID: row.OrderLineID,
NewItemCode: String(row.NewItemCode || '').trim(),
NewColor: String(row.NewColor || '').trim(),
NewDim2: String(row.NewDim2 || '').trim(),
NewDesc: String(row.NewDesc || '').trim()
}
if (!line.NewItemCode || !line.NewColor) {
$q.notify({ type: 'negative', message: 'Yeni urun ve renk zorunludur.' })
return
}
rowSavingId.value = row.OrderLineID
try {
const validate = await store.validateUpdates(orderHeaderID.value, [line])
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 store.applyUpdates(orderHeaderID.value, [line], true)
await store.fetchItems(orderHeaderID.value)
})
return
}
await store.applyUpdates(orderHeaderID.value, [line], false)
await store.fetchItems(orderHeaderID.value)
} catch (err) {
$q.notify({ type: 'negative', message: 'Islem basarisiz.' })
} finally {
rowSavingId.value = ''
}
}
</script>