Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -44,7 +44,15 @@
|
||||
@click="toggleHeaderInfo"
|
||||
/>
|
||||
<q-btn icon="arrow_back" label="Geri" dense flat color="grey-8" class="pcd-toolbar-btn" @click="goBack" />
|
||||
<q-btn label="Yenile" icon="refresh" dense color="primary" class="pcd-toolbar-btn" :loading="detailLoading" @click="fetchDetail" />
|
||||
<q-btn
|
||||
label="Yenile"
|
||||
icon="refresh"
|
||||
dense
|
||||
color="primary"
|
||||
class="pcd-toolbar-btn"
|
||||
:loading="detailLoading"
|
||||
@click="fetchDetail({ clearDraft: true, hydrateDraft: false })"
|
||||
/>
|
||||
<q-btn
|
||||
label="Toplu Fiyat Cagir"
|
||||
icon="playlist_add_check"
|
||||
@@ -107,7 +115,6 @@
|
||||
|
||||
<div class="col-12 col-md-3">
|
||||
<q-select
|
||||
v-if="!isNoCostDetail"
|
||||
v-model="detailHeader.UretimSekliID"
|
||||
:options="productionTypes"
|
||||
option-value="id"
|
||||
@@ -120,29 +127,6 @@
|
||||
class="pcd-emphasis-field-alt"
|
||||
@update:model-value="onUretimSekliChange"
|
||||
/>
|
||||
<q-input
|
||||
v-else
|
||||
dense
|
||||
filled
|
||||
readonly
|
||||
label="Uretim Sekli"
|
||||
:model-value="detailHeader.UretimSekli || '-'"
|
||||
class="pcd-emphasis-field-alt"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="row q-col-gutter-xs">
|
||||
<div class="col-4">
|
||||
<q-input dense filled readonly label="USD Kuru" :model-value="formatMoney(exchangeRates.usdRate)" class="pcd-emphasis-field-alt" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input dense filled readonly label="EUR Kuru" :model-value="formatMoney(exchangeRates.eurRate)" class="pcd-emphasis-field-alt" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input dense filled readonly label="GBP Kuru" :model-value="formatMoney(exchangeRates.gbpRate)" class="pcd-emphasis-field-alt" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4">
|
||||
@@ -160,6 +144,9 @@
|
||||
<div class="col-12 col-md-3">
|
||||
<q-input dense filled readonly label="UrunAdi" :model-value="detailHeader.UrunAdi || '-'" />
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<q-input dense filled readonly label="Urun Ilk Grubu" :model-value="detailHeader.UrunIlkGrubu || '-'" />
|
||||
</div>
|
||||
<div class="col-12 col-md-3">
|
||||
<q-input dense filled readonly label="Urun Ana Grubu" :model-value="detailHeader.UrunAnaGrubu || '-'" />
|
||||
</div>
|
||||
@@ -180,9 +167,9 @@
|
||||
<q-input dense filled readonly label="nUrtReceteID" :model-value="detailHeader.nUrtReceteID || '-'" />
|
||||
</div>
|
||||
|
||||
<div v-if="!isNoCostDetail && partSummary && partSummary.length > 0" class="col-12">
|
||||
<div v-if="partSummary && partSummary.length > 0" class="col-12">
|
||||
<div class="pcd-part-summary-card">
|
||||
<div class="pcd-part-summary-title">Parça Bazlı Maliyet Özetleri</div>
|
||||
<div class="pcd-part-summary-title">Parça Bazlı Maliyet Özellikleri</div>
|
||||
<q-markup-table dense flat bordered separator="cell" class="pcd-part-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -359,7 +346,7 @@
|
||||
|
||||
<template #body-cell-sParcaAdi="props">
|
||||
<q-td :props="props">
|
||||
{{ props.value || props.row.sAciklama3 || '-' }}
|
||||
{{ props.value || '-' }}
|
||||
</q-td>
|
||||
</template>
|
||||
|
||||
@@ -826,14 +813,18 @@ const flatDetailRows = computed(() => detailGroups.value.flatMap(grp => Array.is
|
||||
// no-cost: required parca slots (from Maliyet Parca Eslestirme)
|
||||
const requiredParcaMappings = ref([])
|
||||
const requiredAttentionRowKeys = ref({})
|
||||
const requiredHammaddeMetaCache = ref({}) // hammaddeNo -> { groupName, parcaAdi }
|
||||
|
||||
// Bump this when draft payload semantics change, to avoid hydrating stale rows after backend logic updates.
|
||||
const DRAFT_STORAGE_VERSION = 'v2'
|
||||
|
||||
const draftStorageKey = computed(() => {
|
||||
if (isNoCostDetail.value) {
|
||||
if (!recipeCode.value) return ''
|
||||
return `pcd-costing:no-cost:${String(productCode.value || '').trim()}:${String(recipeCode.value || '').trim()}`
|
||||
return `pcd-costing:${DRAFT_STORAGE_VERSION}:no-cost:${String(productCode.value || '').trim()}:${String(recipeCode.value || '').trim()}`
|
||||
}
|
||||
if (!onMLNo.value) return ''
|
||||
return `pcd-costing:has-cost:${String(onMLNo.value).trim()}`
|
||||
return `pcd-costing:${DRAFT_STORAGE_VERSION}:has-cost:${String(onMLNo.value).trim()}`
|
||||
})
|
||||
|
||||
const currentHeaderSnapshot = computed(() => JSON.stringify({
|
||||
@@ -1051,6 +1042,27 @@ function isCMGroupName (value) {
|
||||
return normalizedValue.includes('CM1') || normalizedValue.includes('CM2')
|
||||
}
|
||||
|
||||
function isKnownGroupName (value) {
|
||||
const v = String(value || '').trim().toUpperCase()
|
||||
if (!v) return false
|
||||
return v === 'DT' || v === 'TP' || v === 'FABRIC' || v === 'CM1' || v === 'CM2' ||
|
||||
v.includes(' DT') || v.includes(' TP') || v.includes('FABRIC') || v.includes('CM1') || v.includes('CM2')
|
||||
}
|
||||
|
||||
function normalizeLegacyParcaAndGroup (seed = {}) {
|
||||
const parca = String(seed?.sParcaAdi || '').trim()
|
||||
const group = String(seed?.sAciklama3 || '').trim()
|
||||
if (!parca && !group) return seed
|
||||
|
||||
// Some legacy rows were saved with parca/group swapped. Fix it at the UI layer:
|
||||
// - Parca: CEKET/PANTOLON/YELEK...
|
||||
// - Group: DT/TP/CM1/CM2/FABRIC...
|
||||
if (isKnownGroupName(parca) && !isKnownGroupName(group)) {
|
||||
return { ...seed, sParcaAdi: group, sAciklama3: parca }
|
||||
}
|
||||
return seed
|
||||
}
|
||||
|
||||
function createEmptyExchangeRates () {
|
||||
return {
|
||||
rateDate: '',
|
||||
@@ -1062,6 +1074,7 @@ function createEmptyExchangeRates () {
|
||||
}
|
||||
|
||||
function createRowEditorForm (seed = {}) {
|
||||
seed = normalizeLegacyParcaAndGroup(seed)
|
||||
const defaultCurrency = normalizePriceCurrency(seed?.inputPricePrBr || seed?.fiyat_doviz || detailHeader.value?.sDovizCinsi) || 'USD'
|
||||
const cmPriceTypeId = normalizeCMPriceTypeId(seed?.cmPriceTypeId ?? seed?.cm_price_type_id, seed?.sAciklama3 ?? seed?.sParcaAdi)
|
||||
return {
|
||||
@@ -1070,10 +1083,10 @@ function createRowEditorForm (seed = {}) {
|
||||
nStokID: String(seed?.nStokID || '').trim(),
|
||||
sModel: String(seed?.sModel || '').trim(),
|
||||
nOnMLDetNo: String(seed?.nOnMLDetNo || '').trim(),
|
||||
sParcaAdi: String(seed?.sParcaAdi || seed?.sAciklama3 || '').trim(),
|
||||
sParcaAdi: String(seed?.sParcaAdi || '').trim(),
|
||||
nHammaddeTuruNo: String(seed?.nHammaddeTuruNo || '').trim(),
|
||||
sHammaddeTuruAdi: String(seed?.sHammaddeTuruAdi || '').trim(),
|
||||
sAciklama3: String(seed?.sAciklama3 || seed?.sParcaAdi || '').trim(),
|
||||
sAciklama3: String(seed?.sAciklama3 || '').trim(),
|
||||
sKodu: String(seed?.sKodu || '').trim(),
|
||||
sAciklama: String(seed?.sAciklama || '').trim(),
|
||||
sRenk: String(seed?.sRenk || seed?.ColorCode || '').trim(),
|
||||
@@ -1828,7 +1841,7 @@ function normalizeDetailRows (items, groupName = '') {
|
||||
|
||||
function normalizeDetailGroups (groups) {
|
||||
const list = Array.isArray(groups) ? groups : []
|
||||
return list.map(grp => {
|
||||
const out = list.map(grp => {
|
||||
const groupName = String(grp?.sAciklama3 || '').trim()
|
||||
const items = normalizeDetailRows(grp?.items, groupName).map(row => ({
|
||||
...row,
|
||||
@@ -1836,16 +1849,52 @@ function normalizeDetailGroups (groups) {
|
||||
cmPriceTypeId: normalizeCMPriceTypeId(row?.cmPriceTypeId ?? row?.cm_price_type_id, groupName || row?.sAciklama3)
|
||||
}))
|
||||
// USD TUTAR (DESC) sıralama
|
||||
items.sort((a, b) => {
|
||||
const valA = resolveRowUSDTutar(a)
|
||||
const valB = resolveRowUSDTutar(b)
|
||||
return valB - valA
|
||||
})
|
||||
if (isNoCostDetail.value) {
|
||||
items.sort((a, b) => {
|
||||
const ha = parseInt(String(a?.nHammaddeTuruNo || '0'), 10) || 0
|
||||
const hb = parseInt(String(b?.nHammaddeTuruNo || '0'), 10) || 0
|
||||
if (ha !== hb) return ha - hb
|
||||
const ka = String(a?.sKodu || '').trim()
|
||||
const kb = String(b?.sKodu || '').trim()
|
||||
return ka.localeCompare(kb, 'tr')
|
||||
})
|
||||
} else {
|
||||
items.sort((a, b) => {
|
||||
const valA = resolveRowUSDTutar(a)
|
||||
const valB = resolveRowUSDTutar(b)
|
||||
return valB - valA
|
||||
})
|
||||
}
|
||||
return {
|
||||
...grp,
|
||||
items
|
||||
}
|
||||
})
|
||||
return sortDetailGroups(out)
|
||||
}
|
||||
|
||||
function groupOrderIndex (name) {
|
||||
const v = String(name || '').trim().toUpperCase()
|
||||
if (!v) return 999
|
||||
if (v.includes('CM2')) return 0
|
||||
if (v.includes('FABRIC')) return 1
|
||||
if (v === 'TP' || v.includes(' TP')) return 2
|
||||
if (v === 'DT' || v.includes(' DT')) return 3
|
||||
if (v.includes('CM1')) return 4
|
||||
return 999
|
||||
}
|
||||
|
||||
function sortDetailGroups (groups) {
|
||||
const list = Array.isArray(groups) ? [...groups] : []
|
||||
list.sort((a, b) => {
|
||||
const ga = String(a?.sAciklama3 || '').trim()
|
||||
const gb = String(b?.sAciklama3 || '').trim()
|
||||
const ia = groupOrderIndex(ga)
|
||||
const ib = groupOrderIndex(gb)
|
||||
if (ia !== ib) return ia - ib
|
||||
return ga.localeCompare(gb, 'tr')
|
||||
})
|
||||
return list
|
||||
}
|
||||
|
||||
function recalculateDetailRow (row, options = {}) {
|
||||
@@ -1977,11 +2026,7 @@ function resolveElHeight (refVal) {
|
||||
|
||||
function updateStickyTop () {
|
||||
const stackH = resolveElHeight(stickyStackRef.value)
|
||||
// Quasar default header height is usually around 50px
|
||||
// If we are in a sub-layout or context where top header is not 50px, this might need adjustment
|
||||
const layoutHeader = document.querySelector('.q-header')
|
||||
const layoutHeaderH = layoutHeader ? layoutHeader.offsetHeight : 50
|
||||
subHeaderTop.value = (stackH || 0) + layoutHeaderH
|
||||
subHeaderTop.value = (stackH || 0) + 50
|
||||
}
|
||||
|
||||
function toggleHeaderInfo () {
|
||||
@@ -2052,7 +2097,9 @@ async function fetchExchangeRatesForCostDate (targetDate = costDate.value) {
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDetail () {
|
||||
async function fetchDetail (options = {}) {
|
||||
const hydrateDraft = options.hydrateDraft !== false
|
||||
const clearDraft = options.clearDraft === true
|
||||
if (isNoCostDetail.value && !recipeCode.value) {
|
||||
detailError.value = 'Recete kodu bulunamadi'
|
||||
detailGroups.value = []
|
||||
@@ -2102,6 +2149,9 @@ async function fetchDetail () {
|
||||
lineHistoryLastRecipeMatchStage.value = ''
|
||||
|
||||
try {
|
||||
if (clearDraft) {
|
||||
clearLocalDraft()
|
||||
}
|
||||
const detailParams = buildDetailFetchParams()
|
||||
slog.info('production-product-costing.detail', 'fetch-detail:start', {
|
||||
trace_id: traceId.value,
|
||||
@@ -2123,11 +2173,13 @@ async function fetchDetail () {
|
||||
detailGroups.value = normalizeDetailGroups(groupsData)
|
||||
initialHeaderSnapshot.value = currentHeaderSnapshot.value
|
||||
// Optional: hydrate local draft after base data load.
|
||||
tryHydrateFromLocalDraft()
|
||||
if (hydrateDraft) {
|
||||
tryHydrateFromLocalDraft()
|
||||
}
|
||||
// ensure required placeholder rows exist (based on mapping screen)
|
||||
try {
|
||||
const mappings = await fetchRequiredParcaMappings()
|
||||
ensureNoCostRequiredRowsFromMappings(mappings)
|
||||
await ensureNoCostRequiredRowsFromMappings(mappings)
|
||||
} catch (err) {
|
||||
slog.error('production-product-costing.detail', 'required-mapping:error', {
|
||||
trace_id: traceId.value,
|
||||
@@ -2526,7 +2578,8 @@ async function bootstrapRowEditorOptions () {
|
||||
if (selectedHammadde) {
|
||||
rowEditorForm.value.sHammaddeTuruAdi = String(selectedHammadde.sHammaddeTuruAdi || rowEditorForm.value.sHammaddeTuruAdi || '').trim()
|
||||
rowEditorForm.value.sAciklama3 = String(selectedHammadde.sAciklama3 || rowEditorForm.value.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ'
|
||||
rowEditorForm.value.sParcaAdi = String(selectedHammadde.sParcaAdi || selectedHammadde.sAciklama3 || rowEditorForm.value.sParcaAdi || '').trim()
|
||||
// Parca adi should come from spUrtMTBolum.sAdi (backend sends as sParcaAdi). Never fall back to group label (DT/TP/...).
|
||||
rowEditorForm.value.sParcaAdi = String(selectedHammadde.sParcaAdi || rowEditorForm.value.sParcaAdi || '').trim()
|
||||
}
|
||||
const selectedItem = itemRows.find(opt => String(opt?.value || '') === itemSearch)
|
||||
if (selectedItem) {
|
||||
@@ -2558,7 +2611,8 @@ function onRowEditorHammaddeChange (value) {
|
||||
if (!selected) return
|
||||
rowEditorForm.value.sHammaddeTuruAdi = String(selected.sHammaddeTuruAdi || '').trim()
|
||||
rowEditorForm.value.sAciklama3 = String(selected.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ'
|
||||
rowEditorForm.value.sParcaAdi = String(selected.sParcaAdi || selected.sAciklama3 || '').trim()
|
||||
// Parca adi should come from spUrtMTBolum.sAdi (backend sends as sParcaAdi). Never fall back to group label (DT/TP/...).
|
||||
rowEditorForm.value.sParcaAdi = String(selected.sParcaAdi || '').trim()
|
||||
if (!isCMGroupName(rowEditorForm.value.sAciklama3)) {
|
||||
rowEditorForm.value.cmPriceTypeChecked = false
|
||||
}
|
||||
@@ -2603,7 +2657,7 @@ function onRowEditorColorChange (value) {
|
||||
}
|
||||
|
||||
function buildRowFromEditorForm () {
|
||||
const form = rowEditorForm.value
|
||||
const form = normalizeLegacyParcaAndGroup(rowEditorForm.value)
|
||||
const existingRow = flatDetailRows.value.find(row => row.__rowKey === rowEditorTargetRowKey.value)
|
||||
const cmPriceTypeId = normalizeCMPriceTypeId(form.cmPriceTypeChecked ? 2 : 1, form.sAciklama3)
|
||||
if (!existingRow) {
|
||||
@@ -2618,8 +2672,9 @@ function buildRowFromEditorForm () {
|
||||
nStokID: String(form.nStokID || '').trim(),
|
||||
sModel: String(form.sModel || '').trim(),
|
||||
nOnMLDetNo: String(form.nOnMLDetNo || '').trim(),
|
||||
sParcaAdi: String(form.sParcaAdi || form.sAciklama3 || '').trim(),
|
||||
sAciklama3: String(form.sAciklama3 || form.sParcaAdi || 'TANIMSIZ').trim() || 'TANIMSIZ',
|
||||
// Keep Parca Adi and Parca Grubu distinct. sAciklama3 is the group key (DT/TP/CM2/FABRIC).
|
||||
sParcaAdi: String(form.sParcaAdi || '').trim(),
|
||||
sAciklama3: String(form.sAciklama3 || 'TANIMSIZ').trim() || 'TANIMSIZ',
|
||||
nHammaddeTuruNo: String(form.nHammaddeTuruNo || '').trim(),
|
||||
sHammaddeTuruAdi: String(form.sHammaddeTuruAdi || '').trim(),
|
||||
sKodu: String(form.sKodu || '').trim(),
|
||||
@@ -2662,7 +2717,14 @@ function applyEditorRowToGroups (nextRow) {
|
||||
nextGroups[targetIndex] = {
|
||||
...nextGroups[targetIndex],
|
||||
items: [...(Array.isArray(nextGroups[targetIndex].items) ? nextGroups[targetIndex].items : []), nextRow]
|
||||
.sort((left, right) => parseInt(String(left?.nOnMLDetNo || '0'), 10) - parseInt(String(right?.nOnMLDetNo || '0'), 10))
|
||||
.sort((left, right) => {
|
||||
if (isNoCostDetail.value) {
|
||||
const ha = parseInt(String(left?.nHammaddeTuruNo || '0'), 10) || 0
|
||||
const hb = parseInt(String(right?.nHammaddeTuruNo || '0'), 10) || 0
|
||||
if (ha !== hb) return ha - hb
|
||||
}
|
||||
return parseInt(String(left?.nOnMLDetNo || '0'), 10) - parseInt(String(right?.nOnMLDetNo || '0'), 10)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
nextGroups.push({
|
||||
@@ -2673,7 +2735,7 @@ function applyEditorRowToGroups (nextRow) {
|
||||
})
|
||||
}
|
||||
|
||||
detailGroups.value = nextGroups
|
||||
detailGroups.value = sortDetailGroups(nextGroups)
|
||||
syncAllGroupsOpen()
|
||||
schedulePersistLocalDraft()
|
||||
}
|
||||
@@ -2695,49 +2757,143 @@ function normalizeGroupName (value) {
|
||||
}
|
||||
|
||||
async function fetchRequiredParcaMappings () {
|
||||
const ilk = String(detailHeader.value?.UrunIlkGrubu || '').trim()
|
||||
const ana = String(detailHeader.value?.UrunAnaGrubu || '').trim()
|
||||
const alt = String(detailHeader.value?.UrunAltGrubu || '').trim()
|
||||
if (!ana || !alt) return []
|
||||
if (!ilk || !ana || !alt) return []
|
||||
|
||||
const data = await get('/pricing/production-product-costing/maliyet-parca-eslestirme', {
|
||||
trace_id: traceId.value,
|
||||
only_active: 1,
|
||||
urun_ilk_grubu: ilk,
|
||||
urun_ana_grubu: ana,
|
||||
urun_alt_grubu: alt
|
||||
})
|
||||
return Array.isArray(data) ? data : []
|
||||
}
|
||||
|
||||
function ensureNoCostRequiredRowsFromMappings (mappings) {
|
||||
async function resolveHammaddeMetaForRequired (hNo) {
|
||||
const key = String(hNo || '').trim()
|
||||
if (!key) return null
|
||||
const cached = requiredHammaddeMetaCache.value?.[key]
|
||||
if (cached) return cached
|
||||
|
||||
try {
|
||||
const rows = await get('/pricing/production-product-costing/detail-editor-options', {
|
||||
kind: 'hammadde',
|
||||
search: key,
|
||||
limit: 25,
|
||||
trace_id: traceId.value
|
||||
})
|
||||
const list = Array.isArray(rows) ? rows : []
|
||||
const hit = list.find(x => String(x?.nHammaddeTuruNo ?? x?.value ?? '').trim() === key)
|
||||
const meta = hit
|
||||
? {
|
||||
// In URETIM lookups, DT/TP/CM2/FABRIC is often stored in sAciklama2 (and sometimes in sAciklama3).
|
||||
groupName: String(hit?.sAciklama3 || hit?.sAciklama2 || '').trim(),
|
||||
// Hammadde type description is usually sAciklama (e.g. "CKT KUMAS").
|
||||
// NOTE: detail-editor-options returns `sHammaddeTuruAdi` already without the number. Do not fall back to `label`,
|
||||
// otherwise we end up rendering "3300 - 3300 - CKT ASKI".
|
||||
hammaddeAdi: String(hit?.sHammaddeTuruAdi || hit?.sAciklama || '').trim(),
|
||||
// Part info: spUrtOnMLHammaddeTuru.nUrtMTBolumID -> spUrtMTBolum.sAdi
|
||||
mtBolumID: parseInt(String(hit?.mtUrtMTBolumID ?? hit?.MTUrtMTBolumID ?? hit?.nUrtMTBolumID ?? hit?.NUrtMTBolumID ?? '0'), 10) || 0,
|
||||
parcaAdi: String(hit?.sParcaAdi || '').trim()
|
||||
}
|
||||
: null
|
||||
requiredHammaddeMetaCache.value = { ...(requiredHammaddeMetaCache.value || {}), [key]: meta || { groupName: '', hammaddeAdi: '', mtBolumID: 0, parcaAdi: '' } }
|
||||
return meta
|
||||
} catch {
|
||||
requiredHammaddeMetaCache.value = { ...(requiredHammaddeMetaCache.value || {}), [key]: { groupName: '', hammaddeAdi: '', mtBolumID: 0, parcaAdi: '' } }
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureNoCostRequiredRowsFromMappings (mappings) {
|
||||
const list = Array.isArray(mappings) ? mappings : []
|
||||
requiredParcaMappings.value = list
|
||||
if (list.length === 0) return
|
||||
|
||||
// Add missing placeholder rows (qty=1, price=0) to remind user
|
||||
list.forEach(mapping => {
|
||||
const groupName = normalizeGroupName(mapping?.parcaBolumAdi || mapping?.mtBolumAdi || mapping?.sAciklama3)
|
||||
const hList = Array.isArray(mapping?.nHammaddeTurleri) ? mapping.nHammaddeTurleri : []
|
||||
hList.forEach(hNoRaw => {
|
||||
const hNo = normalizeHammaddeNo(hNoRaw)
|
||||
if (!hNo) return
|
||||
// Defensive: The mapping payload may contain duplicate hammadde numbers across rows (or even inside a single row).
|
||||
// IMPORTANT: Placeholders are keyed by (nUrtMTBolumID + nHammaddeTuruNo). The same hammadde type can be required
|
||||
// for multiple parts (Ceket/Pantolon/Yelek...), so de-duping only by hNo is incorrect.
|
||||
const processedRequiredKeys = new Set()
|
||||
|
||||
const exists = flatDetailRows.value.some(r =>
|
||||
normalizeGroupName(r?.sAciklama3) === groupName &&
|
||||
normalizeHammaddeNo(r?.nHammaddeTuruNo) === hNo
|
||||
)
|
||||
if (exists) return
|
||||
// Add missing placeholder rows (qty=1, price=0) to remind user
|
||||
for (const mapping of list) {
|
||||
// Parca adi (CEKET/PANTOLON/YELEK...) comes from the MT bolum description (joined in backend as parcaBolumAdi).
|
||||
// sAciklama3 is reserved for the group header (DT/TP/CM2/FABRIC).
|
||||
const mappingParcaAdi = normalizeGroupName(mapping?.parcaBolumAdi || mapping?.mtBolumAdi)
|
||||
const mappingMtBolumID = parseInt(String(mapping?.nUrtMTBolumID ?? mapping?.NUrtMTBolumID ?? '0'), 10) || 0
|
||||
const hList = Array.isArray(mapping?.nHammaddeTurleri) ? mapping.nHammaddeTurleri : []
|
||||
for (const hNoRaw of hList) {
|
||||
const hNo = normalizeHammaddeNo(hNoRaw)
|
||||
if (!hNo) continue
|
||||
const reqKey = `${mappingMtBolumID}:${hNo}`
|
||||
if (processedRequiredKeys.has(reqKey)) continue
|
||||
processedRequiredKeys.add(reqKey)
|
||||
|
||||
// Prefer the hammadde lookup's groupName (DT/TP/CM2/FABRIC)
|
||||
// so placeholder rows land under the correct group headers.
|
||||
const meta = await resolveHammaddeMetaForRequired(hNo)
|
||||
const groupName = normalizeGroupName(meta?.groupName || '')
|
||||
const hammaddeAdi = String(meta?.hammaddeAdi || '').trim()
|
||||
const effectiveGroupName = groupName || 'TANIMSIZ'
|
||||
const mtBolumID = (meta?.mtBolumID > 0 ? meta.mtBolumID : mappingMtBolumID) || 0
|
||||
const metaParcaCandidate = normalizeGroupName((meta?.parcaAdi || '').trim())
|
||||
// Defensive: if backend/lookup accidentally returns group label (DT/TP/...) as part name, ignore it.
|
||||
const desiredParcaAdi = normalizeGroupName((metaParcaCandidate && !isKnownGroupName(metaParcaCandidate)) ? metaParcaCandidate : mappingParcaAdi)
|
||||
|
||||
const anyMatch = flatDetailRows.value.find(r => {
|
||||
if (normalizeHammaddeNo(r?.nHammaddeTuruNo) !== hNo) return false
|
||||
const rowMtBolumID = parseInt(String(r?.nUrtMTBolumID ?? r?.NUrtMTBolumID ?? '0'), 10) || 0
|
||||
if (rowMtBolumID > 0 && mtBolumID > 0) return rowMtBolumID === mtBolumID
|
||||
// If we don't have row's nUrtMTBolumID (legacy rows / older placeholders), fall back to matching by part name.
|
||||
// Also accept legacy placeholders where sParcaAdi accidentally contains the group label (DT/TP/...), so we can move/fix them.
|
||||
const rowParca = normalizeGroupName(r?.sParcaAdi)
|
||||
return rowParca === desiredParcaAdi || isKnownGroupName(rowParca)
|
||||
})
|
||||
if (anyMatch) {
|
||||
// If we previously created a placeholder under a wrong group name, move it instead of duplicating.
|
||||
if (anyMatch?.requiredPlaceholder) {
|
||||
const currentGroup = normalizeGroupName(anyMatch?.sAciklama3)
|
||||
const currentParcaAdi = normalizeGroupName(anyMatch?.sParcaAdi || '')
|
||||
const currentHammaddeAdi = String(anyMatch?.sHammaddeTuruAdi || '').trim()
|
||||
if (currentGroup !== effectiveGroupName || currentParcaAdi !== desiredParcaAdi || (hammaddeAdi && currentHammaddeAdi !== hammaddeAdi)) {
|
||||
const moved = recalculateDetailRow({
|
||||
...anyMatch,
|
||||
sAciklama3: effectiveGroupName,
|
||||
sParcaAdi: desiredParcaAdi,
|
||||
nUrtMTBolumID: mtBolumID || anyMatch?.nUrtMTBolumID || anyMatch?.NUrtMTBolumID || 0,
|
||||
sHammaddeTuruAdi: hammaddeAdi || String(anyMatch?.sHammaddeTuruAdi || '').trim(),
|
||||
requiredPlaceholder: true,
|
||||
draftChanged: true
|
||||
}, {
|
||||
preserveInputs: true,
|
||||
priceType: 'REQ',
|
||||
updateState: 'required',
|
||||
markChanged: true
|
||||
})
|
||||
applyEditorRowToGroups(moved)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
newRowSequence.value += 1
|
||||
const rowKey = `req-auto-row-${newRowSequence.value}`
|
||||
const placeholder = recalculateDetailRow({
|
||||
__rowKey: rowKey,
|
||||
isNew: true,
|
||||
nUrtMTBolumID: mtBolumID,
|
||||
nOnMLNo: detailHeader.value?.nOnMLNo || onMLNo.value || '',
|
||||
nOnMLDetNo: '',
|
||||
sParcaAdi: groupName,
|
||||
sAciklama3: groupName,
|
||||
// Group header key
|
||||
sAciklama3: effectiveGroupName,
|
||||
// Parca adi column value (CEKET/PANTOLON/YELEK...)
|
||||
sParcaAdi: desiredParcaAdi,
|
||||
nHammaddeTuruNo: hNo,
|
||||
sHammaddeTuruAdi: '',
|
||||
// Make "Hammadde Turu" render like existing rows: "no - aciklama"
|
||||
sHammaddeTuruAdi: hammaddeAdi,
|
||||
sKodu: '',
|
||||
sAciklama: '',
|
||||
sRenk: '',
|
||||
@@ -2765,8 +2921,8 @@ function ensureNoCostRequiredRowsFromMappings (mappings) {
|
||||
})
|
||||
|
||||
applyEditorRowToGroups(placeholder)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function computeMissingRequiredSlots () {
|
||||
@@ -3061,6 +3217,9 @@ watch(
|
||||
}
|
||||
|
||||
.pcd-sticky-stack {
|
||||
position: sticky;
|
||||
top: 50px;
|
||||
z-index: 1000;
|
||||
background: #fff;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid #ddd;
|
||||
@@ -3253,6 +3412,9 @@ watch(
|
||||
}
|
||||
|
||||
.pcd-sub-header {
|
||||
position: sticky !important;
|
||||
top: var(--pcd-subheader-top) !important;
|
||||
z-index: 990 !important;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -3296,6 +3458,9 @@ watch(
|
||||
}
|
||||
|
||||
.pcd-detail-table :deep(.q-table thead tr:first-child th) {
|
||||
position: sticky !important;
|
||||
top: calc(var(--pcd-subheader-top) + 42px) !important;
|
||||
z-index: 980 !important;
|
||||
background: #f8f9fa !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user