Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-05-20 13:17:14 +03:00
parent dacd3aefa9
commit 295924cf1e
16 changed files with 1500 additions and 601 deletions
@@ -87,6 +87,43 @@
<template #top-left>
<div class="row items-center q-gutter-sm">
<q-select
v-model="commonHammaddeSelected"
dense
filled
multiple
use-chips
clearable
emit-value
map-options
option-label="label"
option-value="value"
:options="commonHammaddeOptions"
:disable="loading || saving || commonHammaddeOptions.length === 0"
class="pcmm-multi-select"
style="min-width: 420px"
>
<template #selected-item="scope">
<q-chip
class="q-mr-xs"
dense
removable
@remove="scope.removeAtIndex(scope.index)"
>
{{ scope.opt.label }}
</q-chip>
</template>
<template #hint>
Ekrandaki satirlarda bulunan hammadde turleri (distinct)
</template>
</q-select>
<q-btn
color="negative"
icon="delete_sweep"
label="Butun Parcalardan Kaldir"
:disable="loading || saving || (commonHammaddeSelected || []).length === 0"
@click="confirmRemoveCommonHammadde"
/>
<q-btn
label="Kolon Filtreleri"
icon="filter_alt_off"
@@ -327,6 +364,8 @@ const hammaddeLoading = ref(false)
const bolumByKey = ref({})
const hammaddeByKey = ref({})
const commonHammaddeSelected = ref([])
const hammaddeLabelCache = ref({}) // no -> "NO - ACIKLAMA"
const columns = [
{ name: 'copy_select', label: '', field: 'copy_select', align: 'center' },
@@ -345,6 +384,22 @@ const canCopySelected = computed(() => copySelectedCount.value >= 2)
const saveSelectedCount = computed(() => Object.keys(saveSelectedKeyMap.value || {}).length)
const canSaveSelected = computed(() => saveSelectedCount.value > 0)
function findHammaddeLabel (no) {
const n = Number(no || 0)
if (!(n > 0)) return ''
const cached = String(hammaddeLabelCache.value?.[String(n)] || '').trim()
if (cached) return cached
const opt = (Array.isArray(hammaddeOptions.value) ? hammaddeOptions.value : []).find(o => Number(o?.value) === n)
const raw = String(opt?.label || '').trim()
if (raw) {
// Ensure "NO - ACIKLAMA" format. Some option sources may only provide the description.
if (/^\d+\s*-\s*/.test(raw)) return raw
if (/^\d+\s*$/.test(raw)) return `${n}`
return `${n} - ${raw}`
}
return `${n}`
}
function normalizeSearch (value) {
const s = String(value ?? '').trim()
if (!s) return ''
@@ -459,6 +514,33 @@ const rows = computed(() => {
return result
})
const commonHammaddeOptions = computed(() => {
const listRows = Array.isArray(rows.value) ? rows.value : []
if (listRows.length === 0) return []
const keys = listRows.map(r => String(r?.__key || '').trim()).filter(Boolean)
if (keys.length === 0) return []
// DISTINCT across visible rows (union).
const set = new Set()
for (const k of keys) {
const ham = normalizeIntList(hammaddeByKey.value?.[k] || [])
ham.forEach(n => { if (n > 0) set.add(n) })
}
return Array.from(set)
.sort((a, b) => a - b)
.map(n => ({ value: n, label: findHammaddeLabel(n) }))
})
watch(commonHammaddeOptions, (opts) => {
// Ensure labels for large/rare nos that won't be in the first 200 option fetch.
try {
const nos = (Array.isArray(opts) ? opts : []).map(o => Number(o?.value || 0)).filter(n => n > 0)
ensureHammaddeLabelsForNos(nos)
} catch {
// ignore
}
}, { immediate: true })
function hardResetAndRefresh () {
// reset view state (filters + selections + dirty)
clearAllColumnFilters()
@@ -626,9 +708,14 @@ function updateBolumSelection (key, newValue) {
function updateHammaddeSelection (key, newValue) {
const k = String(key || '').trim()
if (!k) return
hammaddeByKey.value = {
...(hammaddeByKey.value || {}),
[k]: normalizeIntList(newValue)
const nextList = normalizeIntList(newValue)
hammaddeByKey.value = { ...(hammaddeByKey.value || {}), [k]: nextList }
// Keep table field in sync for column filtering/sorting/export behavior.
const idx = (Array.isArray(mappings.value) ? mappings.value : []).findIndex(r => String(r?.__key || '') === k)
if (idx >= 0) {
const copy = [...mappings.value]
copy[idx] = { ...(copy[idx] || {}), nHammaddeTurleri: [...nextList] }
mappings.value = copy
}
}
@@ -653,6 +740,62 @@ function pruneHammaddeSelection (rowKey, list) {
return allowed
}
function confirmRemoveCommonHammadde () {
const selected = normalizeIntList(commonHammaddeSelected.value || [])
if (selected.length === 0) return
const listRows = Array.isArray(rows.value) ? rows.value : []
const keys = listRows.map(r => String(r?.__key || '').trim()).filter(Boolean)
if (keys.length === 0) return
// Affected "parcalar": only rows that currently contain at least one selected hammadde.
const affectedRows = listRows.filter(r => {
const k = String(r?.__key || '').trim()
if (!k) return false
const current = normalizeIntList(hammaddeByKey.value?.[k] || [])
return current.some(v => selected.includes(v))
})
if (affectedRows.length === 0) {
$q.notify({ type: 'info', message: 'Secilen hammadde turleri bu ekrandaki satirlarda bulunmuyor.', position: 'top-right' })
return
}
const affected = affectedRows
.map(r => `${String(r?.urunIlkGrubu || '').trim() || '-'} | ${String(r?.urunAnaGrubu || '').trim() || '-'} | ${String(r?.urunAltGrubu || '').trim() || '-'}`)
.filter(Boolean)
const removedLabels = selected.map(n => findHammaddeLabel(n))
const htmlList = affected.slice(0, 30).map(x => `<div>${x}</div>`).join('')
const more = affected.length > 30 ? `<div class="text-grey-7 q-mt-sm">(+${affected.length - 30} satir daha)</div>` : ''
$q.dialog({
title: 'Butun Parcalardan Kaldir',
message: `
<div class="q-mb-sm"><b>Kaldirilacak hammadde turleri:</b></div>
<div class="q-mb-sm">${removedLabels.map(x => `<div>${x}</div>`).join('')}</div>
<div class="q-mb-sm"><b>Etkilenecek parcalar:</b></div>
<div style="max-height: 240px; overflow:auto; border: 1px solid #eee; padding: 8px;">${htmlList}${more}</div>
<div class="q-mt-sm text-grey-7">Onaylarsaniz secilen hammadde turleri sadece bu satirlardan kaldirilacak ve Degisenleri Kaydet ile kaydedilebilecek.</div>
`,
html: true,
cancel: true,
persistent: true,
ok: { label: 'Onayla', color: 'negative' },
cancelLabel: 'Geri Don'
}).onOk(() => {
const affectedKeys = affectedRows.map(r => String(r?.__key || '').trim()).filter(Boolean)
for (const k of affectedKeys) {
const current = normalizeIntList(hammaddeByKey.value?.[k] || [])
const next = current.filter(v => !selected.includes(v))
if (String(next) === String(current)) continue
updateHammaddeSelection(k, next)
const row = listRows.find(r => String(r?.__key || '') === k) || (Array.isArray(mappings.value) ? mappings.value : []).find(r => String(r?.__key || '') === k)
if (row) markDirty(row)
}
commonHammaddeSelected.value = []
$q.notify({ type: 'positive', message: 'Secilen hammadde turleri etkilenen satirlardan kaldirildi (taslak).', position: 'top-right' })
})
}
// label resolution now handled by options' `label` field + selected-item slot (see UserDetail.vue "Piyasalar").
async function fetchMappings () {
loading.value = true
@@ -816,11 +959,55 @@ async function fetchHammaddeOptions (search) {
.filter(x => Number.isFinite(x.value) && x.value > 0)
.sort((a, b) => a.value - b.value)
: []
// Prime cache with currently loaded options.
try {
const next = { ...(hammaddeLabelCache.value || {}) }
;(Array.isArray(hammaddeOptions.value) ? hammaddeOptions.value : []).forEach(opt => {
const no = Number(opt?.value || 0)
const label = String(opt?.label || '').trim()
if (no > 0 && label) next[String(no)] = label
})
hammaddeLabelCache.value = next
} catch {
// ignore
}
} finally {
hammaddeLoading.value = false
}
}
async function ensureHammaddeLabelsForNos (nos) {
const list = normalizeIntList(nos || [])
if (list.length === 0) return
const missing = list.filter(n => {
const cached = String(hammaddeLabelCache.value?.[String(n)] || '').trim()
if (cached) return false
const opt = (Array.isArray(hammaddeOptions.value) ? hammaddeOptions.value : []).find(o => Number(o?.value) === n)
return !String(opt?.label || '').trim()
})
if (missing.length === 0) return
try {
const data = await post('/pricing/production-product-costing/options/hammadde-by-nos', {
nHammaddeTuruNos: missing
}, { trace_id: traceId })
const rows = Array.isArray(data) ? data : []
const next = { ...(hammaddeLabelCache.value || {}) }
rows.forEach(r => {
const no = Number(r?.nHammaddeTuruNo || 0)
const name = String(r?.sAciklama || '').trim()
if (no > 0 && name) next[String(no)] = `${no} - ${name}`
})
hammaddeLabelCache.value = next
} catch (e) {
// Non-blocking: fallback will show only the number
slog.error('production-product-costing.mtbolum-map', 'hammadde-by-nos:error', {
trace_id: traceId,
detail: await extractApiErrorDetail(e)
})
}
}
function onFilterMTBolum (val, update) {
update(async () => {
await fetchMTBolumOptions(val)