Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -0,0 +1,744 @@
|
||||
<template>
|
||||
<q-page v-if="canReadOrder" class="pcmm-page q-pa-md">
|
||||
<div class="pcmm-header row items-center q-col-gutter-md">
|
||||
<div class="col">
|
||||
<div class="text-h6">Maliyet Parca Eslestirme</div>
|
||||
<div class="text-caption text-grey-7">
|
||||
V3 Urun Ilk Grubu (42. ozellik) + Urun Ana/Alt Grup + URETIM Parca Bolum + Hammadde Turleri eslestirmesi (URETIM mk_ tablolarinda tutulur)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="refresh"
|
||||
label="Yenile"
|
||||
:loading="loading"
|
||||
@click="refreshAll"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-separator class="q-my-md" />
|
||||
|
||||
<q-table
|
||||
class="ol-table pcmm-table"
|
||||
flat
|
||||
bordered
|
||||
dense
|
||||
separator="cell"
|
||||
row-key="__key"
|
||||
:rows="mappings"
|
||||
:columns="columns"
|
||||
:loading="loading"
|
||||
no-data-label="Kayit bulunamadi"
|
||||
:rows-per-page-options="[0]"
|
||||
hide-bottom
|
||||
>
|
||||
<template #top-right>
|
||||
<div class="row items-center q-gutter-sm">
|
||||
<q-input
|
||||
v-model="filters.search"
|
||||
dense
|
||||
filled
|
||||
clearable
|
||||
debounce="250"
|
||||
label="Ara (Ana/Alt)"
|
||||
style="min-width: 220px"
|
||||
@update:model-value="fetchSheet"
|
||||
@clear="onSearchCleared"
|
||||
/>
|
||||
<q-btn
|
||||
color="secondary"
|
||||
icon="content_copy"
|
||||
label="Kopyala"
|
||||
:disable="loading || saving || !canCopySelected"
|
||||
@click="copySelectedToSelected"
|
||||
/>
|
||||
<q-btn
|
||||
color="secondary"
|
||||
icon="save"
|
||||
label="Secilenleri Kaydet"
|
||||
:loading="saving"
|
||||
:disable="loading || saving || !canSaveSelected"
|
||||
@click="saveSelected"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
icon="save"
|
||||
label="Degisenleri Kaydet"
|
||||
:loading="saving"
|
||||
:disable="loading || saving || dirtyCount === 0"
|
||||
@click="saveAll"
|
||||
/>
|
||||
<div class="text-caption text-grey-7 q-pl-sm">
|
||||
Satir: {{ mappings.length }} | Degisen: {{ dirtyCount }} | Kopya: {{ copySelectedCount }} | Secili: {{ saveSelectedCount }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #body-cell-copy_select="props">
|
||||
<q-td :props="props" style="width: 54px">
|
||||
<div class="row items-center no-wrap">
|
||||
<q-checkbox
|
||||
:model-value="isCopySelected(props.row.__key)"
|
||||
@update:model-value="(v) => toggleCopySelected(props.row.__key, v)"
|
||||
/>
|
||||
<q-badge
|
||||
v-if="copyRoleLabel(props.row.__key)"
|
||||
color="grey-8"
|
||||
class="q-ml-xs"
|
||||
style="max-width: 110px"
|
||||
>
|
||||
{{ copyRoleLabel(props.row.__key) }}
|
||||
<q-tooltip anchor="center left" self="center right">
|
||||
{{ copyRoleLabel(props.row.__key) }}
|
||||
</q-tooltip>
|
||||
</q-badge>
|
||||
</div>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-save_select="props">
|
||||
<q-td :props="props" style="width: 54px">
|
||||
<q-checkbox
|
||||
:model-value="isSaveSelected(props.row.__key)"
|
||||
@update:model-value="(v) => toggleSaveSelected(props.row.__key, v)"
|
||||
/>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-parcaBolumAdi="props">
|
||||
<q-td :props="props">
|
||||
<q-select
|
||||
:model-value="bolumByKey[props.row.__key] || []"
|
||||
dense
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
clearable
|
||||
use-input
|
||||
input-debounce="0"
|
||||
:options="mtBolumOptions"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
emit-value
|
||||
map-options
|
||||
class="pcmm-multi-select"
|
||||
behavior="menu"
|
||||
@filter="onFilterMTBolum"
|
||||
@update:model-value="(val) => { updateBolumSelection(props.row.__key, val); markDirty(props.row) }"
|
||||
style="min-width: 260px"
|
||||
>
|
||||
<template #before-options>
|
||||
<q-item clickable @click="selectAllMTBolum(props.row.__key)">
|
||||
<q-item-section>Tumunu Sec</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="clearMTBolum(props.row.__key)">
|
||||
<q-item-section>Temizle</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
</template>
|
||||
|
||||
<template #selected-item="scope">
|
||||
<q-chip
|
||||
class="q-mr-xs"
|
||||
dense
|
||||
removable
|
||||
@remove="scope.removeAtIndex(scope.index)"
|
||||
>
|
||||
{{ scope.opt.label }}
|
||||
</q-chip>
|
||||
</template>
|
||||
|
||||
<template #option="scope">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<q-item-section avatar>
|
||||
<q-checkbox
|
||||
:model-value="scope.selected"
|
||||
tabindex="-1"
|
||||
@update:model-value="() => scope.toggleOption(scope.opt)"
|
||||
@click.stop
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
<template #body-cell-nHammaddeTurleri="props">
|
||||
<q-td :props="props">
|
||||
<q-select
|
||||
:model-value="hammaddeByKey[props.row.__key] || []"
|
||||
dense
|
||||
filled
|
||||
multiple
|
||||
use-chips
|
||||
clearable
|
||||
use-input
|
||||
input-debounce="0"
|
||||
:options="hammaddeOptions"
|
||||
option-label="label"
|
||||
option-value="value"
|
||||
emit-value
|
||||
map-options
|
||||
class="pcmm-multi-select"
|
||||
behavior="menu"
|
||||
@filter="onFilterHammadde"
|
||||
@update:model-value="(val) => { updateHammaddeSelection(props.row.__key, val); markDirty(props.row) }"
|
||||
style="min-width: 320px"
|
||||
>
|
||||
<template #before-options>
|
||||
<q-item clickable @click="selectAllHammadde(props.row.__key)">
|
||||
<q-item-section>Tumunu Sec</q-item-section>
|
||||
</q-item>
|
||||
<q-item clickable @click="clearHammadde(props.row.__key)">
|
||||
<q-item-section>Temizle</q-item-section>
|
||||
</q-item>
|
||||
<q-separator />
|
||||
</template>
|
||||
|
||||
<template #selected-item="scope">
|
||||
<q-chip
|
||||
class="q-mr-xs"
|
||||
dense
|
||||
removable
|
||||
@remove="scope.removeAtIndex(scope.index)"
|
||||
>
|
||||
{{ scope.opt.label }}
|
||||
</q-chip>
|
||||
</template>
|
||||
|
||||
<template #option="scope">
|
||||
<q-item v-bind="scope.itemProps">
|
||||
<q-item-section avatar>
|
||||
<q-checkbox
|
||||
:model-value="scope.selected"
|
||||
tabindex="-1"
|
||||
@update:model-value="() => scope.toggleOption(scope.opt)"
|
||||
@click.stop
|
||||
/>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
</q-select>
|
||||
</q-td>
|
||||
</template>
|
||||
</q-table>
|
||||
</q-page>
|
||||
|
||||
<q-page v-else class="q-pa-md flex flex-center">
|
||||
<div class="text-negative text-subtitle1">
|
||||
Bu module erisim yetkiniz yok.
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useQuasar } from 'quasar'
|
||||
import { get, post, del, extractApiErrorDetail } from 'src/services/api'
|
||||
import { usePermission } from 'src/composables/usePermission'
|
||||
import { slog } from 'src/utils/slog'
|
||||
|
||||
const $q = useQuasar()
|
||||
const { canRead } = usePermission()
|
||||
const canReadOrder = canRead('order')
|
||||
|
||||
const traceId = `pcd-mtbolum-map-${crypto?.randomUUID?.() || String(Date.now())}`
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
const mappings = ref([])
|
||||
const copySelectedKeys = ref([]) // ordered
|
||||
const saveSelectedKeyMap = ref({}) // key -> true
|
||||
|
||||
const filters = ref({
|
||||
search: ''
|
||||
})
|
||||
|
||||
const mtBolumOptions = ref([])
|
||||
const hammaddeOptions = ref([])
|
||||
const mtBolumLoading = ref(false)
|
||||
const hammaddeLoading = ref(false)
|
||||
|
||||
const bolumByKey = ref({})
|
||||
const hammaddeByKey = ref({})
|
||||
|
||||
const columns = [
|
||||
{ name: 'copy_select', label: 'Kopya', field: 'copy_select', align: 'center' },
|
||||
{ name: 'save_select', label: 'Sec', field: 'save_select', align: 'center' },
|
||||
{ name: 'urunIlkGrubu', label: 'Urun Ilk Grubu', field: 'urunIlkGrubu', align: 'left', sortable: true },
|
||||
{ name: 'urunAnaGrubu', label: 'Urun Ana Grubu', field: 'urunAnaGrubu', align: 'left', sortable: true },
|
||||
{ name: 'urunAltGrubu', label: 'Urun Alt Grubu', field: 'urunAltGrubu', align: 'left', sortable: true },
|
||||
{ name: 'parcaBolumAdi', label: 'Parca Bolum', field: row => (Array.isArray(row?.nUrtMTBolumIDs) ? row.nUrtMTBolumIDs.join(', ') : ''), align: 'left' },
|
||||
{ name: 'nHammaddeTurleri', label: 'Hammadde Turleri', field: row => (Array.isArray(row?.nHammaddeTurleri) ? row.nHammaddeTurleri.join(', ') : ''), align: 'left' }
|
||||
]
|
||||
|
||||
const dirtyMap = ref({})
|
||||
const dirtyCount = computed(() => Object.keys(dirtyMap.value || {}).length)
|
||||
const copySelectedCount = computed(() => (Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value.length : 0))
|
||||
const canCopySelected = computed(() => copySelectedCount.value >= 2)
|
||||
const saveSelectedCount = computed(() => Object.keys(saveSelectedKeyMap.value || {}).length)
|
||||
const canSaveSelected = computed(() => saveSelectedCount.value > 0)
|
||||
|
||||
function markDirty (row) {
|
||||
const key = String(row?.__key || '').trim()
|
||||
if (!key) return
|
||||
dirtyMap.value = { ...(dirtyMap.value || {}), [key]: true }
|
||||
}
|
||||
|
||||
function clearDirty () {
|
||||
dirtyMap.value = {}
|
||||
}
|
||||
|
||||
function isCopySelected (rowKey) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return false
|
||||
return (Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []).includes(key)
|
||||
}
|
||||
|
||||
function toggleCopySelected (rowKey, enabled) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return
|
||||
const current = Array.isArray(copySelectedKeys.value) ? [...copySelectedKeys.value] : []
|
||||
const idx = current.indexOf(key)
|
||||
if (enabled) {
|
||||
if (idx === -1) current.push(key)
|
||||
} else {
|
||||
if (idx >= 0) current.splice(idx, 1)
|
||||
}
|
||||
copySelectedKeys.value = current
|
||||
}
|
||||
|
||||
function copyRoleLabel (rowKey) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return ''
|
||||
const keys = Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []
|
||||
const idx = keys.indexOf(key)
|
||||
if (idx === 0) return 'Kopyalanacak'
|
||||
if (idx > 0) return 'Yapistirilacak'
|
||||
return ''
|
||||
}
|
||||
|
||||
function isSaveSelected (rowKey) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return false
|
||||
return !!saveSelectedKeyMap.value?.[key]
|
||||
}
|
||||
|
||||
function toggleSaveSelected (rowKey, enabled) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return
|
||||
const next = { ...(saveSelectedKeyMap.value || {}) }
|
||||
if (enabled) next[key] = true
|
||||
else delete next[key]
|
||||
saveSelectedKeyMap.value = next
|
||||
}
|
||||
|
||||
function copySelectedToSelected () {
|
||||
const keys = Array.isArray(copySelectedKeys.value) ? copySelectedKeys.value : []
|
||||
if (keys.length < 2) return
|
||||
const srcKey = String(keys[0] || '').trim()
|
||||
if (!srcKey) return
|
||||
|
||||
const srcBolum = bolumByKey.value?.[srcKey] || []
|
||||
const srcHam = hammaddeByKey.value?.[srcKey] || []
|
||||
|
||||
for (let i = 1; i < keys.length; i++) {
|
||||
const key = String(keys[i] || '').trim()
|
||||
if (!key) continue
|
||||
updateBolumSelection(key, srcBolum)
|
||||
updateHammaddeSelection(key, srcHam)
|
||||
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
|
||||
if (row) markDirty(row)
|
||||
}
|
||||
|
||||
$q.notify({ type: 'positive', message: 'Kopyala / Yapistir tamamlandi' })
|
||||
}
|
||||
|
||||
async function saveSelected () {
|
||||
const keys = Object.keys(saveSelectedKeyMap.value || {})
|
||||
await saveKeys(keys)
|
||||
}
|
||||
|
||||
function selectAllMTBolum (rowKey) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return
|
||||
const all = (Array.isArray(mtBolumOptions.value) ? mtBolumOptions.value : [])
|
||||
.map(o => Number(o?.value))
|
||||
.filter(n => Number.isFinite(n) && n > 0)
|
||||
updateBolumSelection(key, all)
|
||||
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
|
||||
if (row) markDirty(row)
|
||||
}
|
||||
|
||||
function clearMTBolum (rowKey) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return
|
||||
updateBolumSelection(key, [])
|
||||
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
|
||||
if (row) markDirty(row)
|
||||
}
|
||||
|
||||
function selectAllHammadde (rowKey) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return
|
||||
const all = (Array.isArray(hammaddeOptions.value) ? hammaddeOptions.value : [])
|
||||
.map(o => Number(o?.value))
|
||||
.filter(n => Number.isFinite(n) && n > 0)
|
||||
updateHammaddeSelection(key, all)
|
||||
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
|
||||
if (row) markDirty(row)
|
||||
}
|
||||
|
||||
function clearHammadde (rowKey) {
|
||||
const key = String(rowKey || '').trim()
|
||||
if (!key) return
|
||||
updateHammaddeSelection(key, [])
|
||||
const row = (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === key)
|
||||
if (row) markDirty(row)
|
||||
}
|
||||
|
||||
function normalizeIntList (list) {
|
||||
const arr = Array.isArray(list) ? list : []
|
||||
const nums = arr
|
||||
.map(x => Number(String(x).trim()))
|
||||
.filter(n => Number.isFinite(n) && n > 0)
|
||||
return Array.from(new Set(nums))
|
||||
}
|
||||
|
||||
function initEditableStateFromRows (rows) {
|
||||
const bolum = {}
|
||||
const ham = {}
|
||||
;(Array.isArray(rows) ? rows : []).forEach(r => {
|
||||
const key = String(r?.__key || '').trim()
|
||||
if (!key) return
|
||||
bolum[key] = normalizeIntList(r?.nUrtMTBolumIDs || [])
|
||||
ham[key] = normalizeIntList(r?.nHammaddeTurleri || [])
|
||||
})
|
||||
bolumByKey.value = bolum
|
||||
hammaddeByKey.value = ham
|
||||
}
|
||||
|
||||
function updateBolumSelection (key, newValue) {
|
||||
const k = String(key || '').trim()
|
||||
if (!k) return
|
||||
bolumByKey.value = {
|
||||
...(bolumByKey.value || {}),
|
||||
[k]: normalizeIntList(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
function updateHammaddeSelection (key, newValue) {
|
||||
const k = String(key || '').trim()
|
||||
if (!k) return
|
||||
hammaddeByKey.value = {
|
||||
...(hammaddeByKey.value || {}),
|
||||
[k]: normalizeIntList(newValue)
|
||||
}
|
||||
}
|
||||
|
||||
// label resolution now handled by options' `label` field + selected-item slot (see UserDetail.vue "Piyasalar").
|
||||
async function fetchMappings () {
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await get('/pricing/production-product-costing/maliyet-parca-eslestirme', {
|
||||
trace_id: traceId,
|
||||
only_active: 1
|
||||
})
|
||||
return Array.isArray(data) ? data : []
|
||||
} catch (e) {
|
||||
const detail = await extractApiErrorDetail(e)
|
||||
$q.notify({ type: 'negative', message: detail || 'Liste okunamadi' })
|
||||
return []
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchSheet () {
|
||||
loading.value = true
|
||||
try {
|
||||
// Search changes should not carry over row-level selections from a previous sheet.
|
||||
copySelectedKeys.value = []
|
||||
saveSelectedKeyMap.value = {}
|
||||
|
||||
const [combos, existing] = await Promise.all([
|
||||
get('/pricing/production-product-costing/options/urun-ana-alt-combos', {
|
||||
trace_id: traceId,
|
||||
search: String(filters.value.search || '').trim(),
|
||||
limit: 5000
|
||||
}),
|
||||
fetchMappings()
|
||||
])
|
||||
|
||||
const existingByKey = new Map()
|
||||
;(Array.isArray(existing) ? existing : []).forEach(x => {
|
||||
const k = `${String(x?.urunIlkGrubu || '').trim()}|${String(x?.urunAltGrubu || '').trim()}|${String(x?.urunAnaGrubu || '').trim()}`
|
||||
const prev = existingByKey.get(k)
|
||||
const nextList = Array.isArray(prev) ? prev : (prev ? [prev] : [])
|
||||
nextList.push(x)
|
||||
existingByKey.set(k, nextList)
|
||||
})
|
||||
|
||||
const rows = (Array.isArray(combos) ? combos : []).map((c, idx) => {
|
||||
const ilk = String(c?.urunIlkGrubu || '').trim()
|
||||
const ana = String(c?.urunAnaGrubu || '').trim()
|
||||
const alt = String(c?.urunAltGrubu || '').trim()
|
||||
const k = `${ilk}|${alt}|${ana}`
|
||||
const hitList = existingByKey.get(k)
|
||||
const list = Array.isArray(hitList) ? hitList : []
|
||||
const mtIds = list
|
||||
.map(x => String(x?.nUrtMTBolumID || '').trim())
|
||||
.filter(Boolean)
|
||||
const uniqMt = Array.from(new Set(mtIds))
|
||||
|
||||
// If multiple mappings exist, we merge hammadde types (union) for editing convenience.
|
||||
const hSet = new Set()
|
||||
list.forEach(x => {
|
||||
if (Array.isArray(x?.nHammaddeTurleri)) {
|
||||
x.nHammaddeTurleri.forEach(v => {
|
||||
v = String(v || '').trim()
|
||||
if (v) hSet.add(v)
|
||||
})
|
||||
}
|
||||
})
|
||||
return {
|
||||
__key: k,
|
||||
__rowIndex: idx,
|
||||
__existingIDs: list.map(x => Number(x?.id || 0)).filter(n => n > 0),
|
||||
urunIlkGrubu: ilk,
|
||||
urunAnaGrubu: ana,
|
||||
urunAltGrubu: alt,
|
||||
nUrtMTBolumIDs: uniqMt,
|
||||
nHammaddeTurleri: Array.from(hSet)
|
||||
}
|
||||
})
|
||||
mappings.value = rows
|
||||
initEditableStateFromRows(rows)
|
||||
clearDirty()
|
||||
} catch (e) {
|
||||
const detail = await extractApiErrorDetail(e)
|
||||
$q.notify({ type: 'negative', message: detail || 'Carsaf yuklenemedi' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onSearchCleared () {
|
||||
filters.value.search = ''
|
||||
copySelectedKeys.value = []
|
||||
saveSelectedKeyMap.value = {}
|
||||
clearDirty()
|
||||
fetchSheet()
|
||||
}
|
||||
|
||||
async function refreshAll () {
|
||||
await Promise.all([
|
||||
fetchMTBolumOptions(''),
|
||||
fetchHammaddeOptions('')
|
||||
])
|
||||
await fetchSheet()
|
||||
}
|
||||
|
||||
async function fetchMTBolumOptions (search) {
|
||||
mtBolumLoading.value = true
|
||||
try {
|
||||
const data = await get('/pricing/production-product-costing/options/mtbolum', {
|
||||
trace_id: traceId,
|
||||
search: search || '',
|
||||
limit: 200
|
||||
})
|
||||
mtBolumOptions.value = Array.isArray(data)
|
||||
? data.map(x => ({
|
||||
value: Number(x?.value ?? x?.nUrtMTBolumID ?? x?.id ?? 0),
|
||||
label: (() => {
|
||||
const v = Number(x?.value ?? x?.nUrtMTBolumID ?? x?.id ?? 0)
|
||||
const name = String(x?.label || x?.sAdi || '').trim()
|
||||
if (Number.isFinite(v) && v > 0 && name) return `${v} - ${name}`
|
||||
if (Number.isFinite(v) && v > 0) return String(v)
|
||||
return name
|
||||
})()
|
||||
}))
|
||||
.filter(x => Number.isFinite(x.value) && x.value > 0)
|
||||
.sort((a, b) => a.value - b.value)
|
||||
: []
|
||||
} finally {
|
||||
mtBolumLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchHammaddeOptions (search) {
|
||||
hammaddeLoading.value = true
|
||||
try {
|
||||
const data = await get('/pricing/production-product-costing/detail-editor-options', {
|
||||
trace_id: traceId,
|
||||
kind: 'hammadde',
|
||||
search: search || '',
|
||||
limit: 200
|
||||
})
|
||||
hammaddeOptions.value = Array.isArray(data)
|
||||
? data.map(x => ({
|
||||
value: Number(String(x?.nHammaddeTuruNo ?? x?.value ?? '').trim()),
|
||||
label: (() => {
|
||||
const v = Number(String(x?.nHammaddeTuruNo ?? x?.value ?? '').trim())
|
||||
const name = String(x?.sHammaddeTuruAdi || x?.label || '').trim()
|
||||
if (Number.isFinite(v) && v > 0 && name) return `${v} - ${name}`
|
||||
if (Number.isFinite(v) && v > 0) return String(v)
|
||||
return name
|
||||
})()
|
||||
}))
|
||||
.filter(x => Number.isFinite(x.value) && x.value > 0)
|
||||
.sort((a, b) => a.value - b.value)
|
||||
: []
|
||||
} finally {
|
||||
hammaddeLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function onFilterMTBolum (val, update) {
|
||||
update(async () => {
|
||||
await fetchMTBolumOptions(val)
|
||||
})
|
||||
}
|
||||
|
||||
function onFilterHammadde (val, update) {
|
||||
update(async () => {
|
||||
await fetchHammaddeOptions(val)
|
||||
})
|
||||
}
|
||||
|
||||
async function saveAll () {
|
||||
const dirtyKeys = Object.keys(dirtyMap.value || {})
|
||||
await saveKeys(dirtyKeys)
|
||||
}
|
||||
|
||||
async function saveKeys (keys) {
|
||||
const list = Array.isArray(keys) ? keys.map(k => String(k || '').trim()).filter(Boolean) : []
|
||||
if (list.length === 0) return
|
||||
|
||||
saving.value = true
|
||||
try {
|
||||
const rowsToSave = mappings.value.filter(r => list.includes(String(r.__key || '').trim()))
|
||||
for (const row of rowsToSave) {
|
||||
const key = String(row.__key || '').trim()
|
||||
const mtIds = normalizeIntList(bolumByKey.value?.[key] || [])
|
||||
const hList = normalizeIntList(hammaddeByKey.value?.[key] || [])
|
||||
|
||||
// If hammadde cleared OR no bölüm selected: delete all existing mappings for this combo
|
||||
const existingIDs = Array.isArray(row.__existingIDs) ? row.__existingIDs : []
|
||||
if (existingIDs.length > 0 && (mtIds.length === 0 || hList.length === 0)) {
|
||||
for (const id of existingIDs) {
|
||||
await del('/pricing/production-product-costing/maliyet-parca-eslestirme', { trace_id: traceId, id })
|
||||
}
|
||||
row.__existingIDs = []
|
||||
bolumByKey.value = { ...(bolumByKey.value || {}), [key]: [] }
|
||||
hammaddeByKey.value = { ...(hammaddeByKey.value || {}), [key]: [] }
|
||||
continue
|
||||
}
|
||||
|
||||
if (mtIds.length > 0 && hList.length > 0) {
|
||||
const existing = await fetchMappings()
|
||||
const ilk = String(row.urunIlkGrubu || '').trim()
|
||||
const ana = String(row.urunAnaGrubu || '').trim()
|
||||
const alt = String(row.urunAltGrubu || '').trim()
|
||||
const existingForCombo = (Array.isArray(existing) ? existing : []).filter(x =>
|
||||
String(x?.urunIlkGrubu || '').trim() === ilk &&
|
||||
String(x?.urunAnaGrubu || '').trim() === ana &&
|
||||
String(x?.urunAltGrubu || '').trim() === alt
|
||||
)
|
||||
|
||||
const selectedSet = new Set(mtIds.map(String))
|
||||
for (const ex of existingForCombo) {
|
||||
const exMt = String(ex?.nUrtMTBolumID || '').trim()
|
||||
if (exMt && !selectedSet.has(exMt) && Number(ex?.id || 0) > 0) {
|
||||
await del('/pricing/production-product-costing/maliyet-parca-eslestirme', { trace_id: traceId, id: Number(ex.id) })
|
||||
}
|
||||
}
|
||||
|
||||
const idsAfter = []
|
||||
for (const mtId of mtIds) {
|
||||
const payload = {
|
||||
urunIlkGrubu: ilk,
|
||||
urunAnaGrubu: ana,
|
||||
urunAltGrubu: alt,
|
||||
nUrtMTBolumID: mtId,
|
||||
nHammaddeTurleri: hList,
|
||||
bAktif: true
|
||||
}
|
||||
const resp = await post('/pricing/production-product-costing/maliyet-parca-eslestirme/upsert', payload, { trace_id: traceId })
|
||||
if (resp?.id) idsAfter.push(Number(resp.id))
|
||||
}
|
||||
row.__existingIDs = idsAfter
|
||||
}
|
||||
}
|
||||
|
||||
$q.notify({ type: 'positive', message: 'Degisiklikler kaydedildi' })
|
||||
clearDirty()
|
||||
// after saving, clear save selection to avoid accidental re-save
|
||||
saveSelectedKeyMap.value = {}
|
||||
await refreshAll()
|
||||
} catch (e) {
|
||||
const detail = await extractApiErrorDetail(e)
|
||||
$q.notify({ type: 'negative', message: detail || 'Kaydetme basarisiz' })
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
fetchMTBolumOptions(''),
|
||||
fetchHammaddeOptions(''),
|
||||
fetchSheet()
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pcmm-page {
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.pcmm-header {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pcmm-form {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.pcmm-table {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Allow multi-select chips to wrap and grow vertically (PowerBI-like) */
|
||||
.pcmm-table :deep(.pcmm-multi-select .q-field__control) {
|
||||
height: auto !important;
|
||||
min-height: 38px;
|
||||
}
|
||||
|
||||
.pcmm-table :deep(.pcmm-multi-select .q-field__native) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.pcmm-table :deep(.pcmm-multi-select .q-select__chips) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.pcmm-table :deep(.pcmm-multi-select .q-chip) {
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user