Merge remote-tracking branch 'origin/master'

This commit is contained in:
M_Kececi
2026-03-05 09:53:48 +03:00
parent 94244b194a
commit 95cdf6c5da
6 changed files with 3787 additions and 1814 deletions

View File

@@ -513,7 +513,8 @@ func ValidateItemVariant(tx *sql.Tx, ln models.OrderDetail) error {
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK) FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1 WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2 AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3 AND UPPER(REPLACE(REPLACE(REPLACE(ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') ,' ', ''), 'YAS', ''), 'Y', ''))
= UPPER(REPLACE(REPLACE(REPLACE(ISNULL(LTRIM(RTRIM(@p3)),'') ,' ', ''), 'YAS', ''), 'Y', ''))
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4 AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
) THEN 1 ELSE 0 END ) THEN 1 ELSE 0 END
`, item, color, dim1, dim2).Scan(&exists) `, item, color, dim1, dim2).Scan(&exists)
@@ -555,7 +556,8 @@ func ValidateOrderVariants(db *sql.DB, lines []models.OrderDetail) ([]models.Inv
FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK) FROM BAGGI_V3.dbo.prItemVariant V WITH (NOLOCK)
WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1 WHERE ISNULL(LTRIM(RTRIM(V.ItemCode)),'') = @p1
AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2 AND ISNULL(LTRIM(RTRIM(V.ColorCode)),'') = @p2
AND ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') = @p3 AND UPPER(REPLACE(REPLACE(REPLACE(ISNULL(LTRIM(RTRIM(V.ItemDim1Code)),'') ,' ', ''), 'YAS', ''), 'Y', ''))
= UPPER(REPLACE(REPLACE(REPLACE(ISNULL(LTRIM(RTRIM(@p3)),'') ,' ', ''), 'YAS', ''), 'Y', ''))
AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4 AND ISNULL(LTRIM(RTRIM(V.ItemDim2Code)),'') = @p4
) THEN 1 ELSE 0 END ) THEN 1 ELSE 0 END
`) `)

3761
ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2674,13 +2674,20 @@ async function onModelChange(modelCode) {
- En sonda güvenli fallback: 'tak' - En sonda güvenli fallback: 'tak'
======================================================= */ ======================================================= */
const ana = String(form.urunAnaGrubu || '').toLowerCase().trim() const ana = String(form.urunAnaGrubu || '').toLowerCase().trim()
const kat = String(form.kategori || '').toLowerCase().trim() const kat = String(form.kategori || form.urunAltGrubu || '').toLowerCase().trim()
const yg = String(form.askiliyan || '').toLowerCase().trim()
const hasGarsonMeta =
ana.includes('garson') ||
kat.includes('garson') ||
kat.includes('yetiskin/garson') ||
yg.includes('garson') ||
yg.includes('yetiskin/garson')
let bedenGrpKey = null let bedenGrpKey = null
// ✅ Hard-match (senin ana gruplarına göre genişletebilirsin) // ✅ Hard-match (senin ana gruplarına göre genişletebilirsin)
if ( if (
(kat.includes('garson') || kat.includes('yetiskin/garson')) && hasGarsonMeta &&
( (
ana.includes('gomlek atayaka') || ana.includes('gomlek atayaka') ||
ana.includes('gomlek ata yaka') || ana.includes('gomlek ata yaka') ||
@@ -2688,7 +2695,7 @@ async function onModelChange(modelCode) {
) )
) { ) {
bedenGrpKey = 'yas' bedenGrpKey = 'yas'
} else if ((ana.includes('garson') || kat.includes('garson') || kat.includes('yetiskin/garson') || ana.includes('yetiskin/garson')) && } else if (hasGarsonMeta &&
(ana.includes('ayakkabı') || ana.includes('ayakkabi') || kat.includes('ayakkabı') || kat.includes('ayakkabi'))) { (ana.includes('ayakkabı') || ana.includes('ayakkabi') || kat.includes('ayakkabı') || kat.includes('ayakkabi'))) {
bedenGrpKey = 'ayk_garson' bedenGrpKey = 'ayk_garson'
} else if (ana.includes('pantolon') || kat.includes('pantolon')) { } else if (ana.includes('pantolon') || kat.includes('pantolon')) {
@@ -2705,7 +2712,12 @@ async function onModelChange(modelCode) {
if (!bedenGrpKey) { if (!bedenGrpKey) {
try { try {
// ⚠️ Boş array verme; ürün bilgisini kullanarak belirle // ⚠️ Boş array verme; ürün bilgisini kullanarak belirle
bedenGrpKey = detectBedenGroup(null, form.urunAnaGrubu, form.kategori) bedenGrpKey = detectBedenGroup(
null,
form.urunAnaGrubu,
form.kategori || form.urunAltGrubu,
form.askiliyan
)
} catch (e) { } catch (e) {
console.warn('⚠️ detectBedenGroup hata:', e) console.warn('⚠️ detectBedenGroup hata:', e)
bedenGrpKey = null bedenGrpKey = null

View File

@@ -90,6 +90,7 @@
<div class="total-row"> <div class="total-row">
<div class="total-cell">ADET</div> <div class="total-cell">ADET</div>
<div class="total-cell">FOTO</div>
</div> </div>
</div> </div>
</div> </div>
@@ -176,6 +177,24 @@
<q-icon :name="isOpen(grp2.key) ? 'expand_less' : 'expand_more'" size="18px" /> <q-icon :name="isOpen(grp2.key) ? 'expand_less' : 'expand_more'" size="18px" />
</div> </div>
</div> </div>
<div class="sub-image level2-image">
<q-card flat bordered class="product-image-card">
<q-card-section class="q-pa-xs product-image-wrap">
<q-img
v-if="getProductImageUrl(grp1.productCode, grp2.colorCode)"
:src="getProductImageUrl(grp1.productCode, grp2.colorCode)"
fit="cover"
class="product-image"
loading="lazy"
@error="onProductImageError(grp1.productCode, grp2.colorCode)"
/>
<div v-else class="product-image-placeholder">
<q-icon name="image_not_supported" size="22px" color="grey-6" />
</div>
</q-card-section>
</q-card>
</div>
</div> </div>
<template v-if="isOpen(grp2.key)"> <template v-if="isOpen(grp2.key)">
@@ -200,6 +219,7 @@
</div> </div>
<div class="cell adet">{{ formatNumber(row.adet) }}</div> <div class="cell adet">{{ formatNumber(row.adet) }}</div>
<div class="cell img-placeholder"></div>
</div> </div>
</div> </div>
</template> </template>
@@ -217,7 +237,7 @@
</template> </template>
<script setup> <script setup>
import { computed, onMounted, ref } from 'vue' import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useQuasar } from 'quasar' import { useQuasar } from 'quasar'
import api from 'src/services/api' import api from 'src/services/api'
import { usePermission } from 'src/composables/usePermission' import { usePermission } from 'src/composables/usePermission'
@@ -240,6 +260,17 @@ const selectedProductCode = ref('')
const productOptions = ref([]) const productOptions = ref([])
const filteredProductOptions = ref([]) const filteredProductOptions = ref([])
const rawRows = ref([]) const rawRows = ref([])
const productImageCache = ref({})
const productImageLoading = ref({})
const productImageListByCode = ref({})
const productImageListLoading = ref({})
const productImageFallbackByKey = ref({})
const productImageContentLoading = ref({})
const productImageBlobUrls = ref([])
const productImageListBlockedUntil = ref(0)
const IMAGE_LIST_CONCURRENCY = 8
let imageListActiveRequests = 0
const imageListWaitQueue = []
const activeSchema = ref(storeSchemaByKey.tak) const activeSchema = ref(storeSchemaByKey.tak)
const activeGrpKey = ref('tak') const activeGrpKey = ref('tak')
const openState = ref({}) const openState = ref({})
@@ -280,6 +311,155 @@ function parseNumber(value) {
return Number.isFinite(n) ? n : 0 return Number.isFinite(n) ? n : 0
} }
function buildImageKey(code, color) {
return `${String(code || '').trim().toUpperCase()}::${String(color || '').trim().toUpperCase()}`
}
function normalizeUploadsPath(storagePath) {
const raw = String(storagePath || '').trim()
if (!raw) return ''
const normalized = raw.replace(/\\/g, '/')
const idx = normalized.toLowerCase().indexOf('/uploads/')
if (idx >= 0) return normalized.slice(idx)
if (normalized.toLowerCase().startsWith('uploads/')) return `/${normalized}`
return ''
}
function resolveProductImageUrl(item) {
if (!item || typeof item !== 'object') {
return { contentUrl: '', publicUrl: '' }
}
let contentUrl = ''
const imageId = Number(item.id || item.ID || 0)
if (Number.isFinite(imageId) && imageId > 0) {
contentUrl = `/api/product-images/${imageId}/content`
} else {
const contentURL = String(item.content_url || item.ContentURL || '').trim()
if (contentURL.startsWith('/api/')) contentUrl = contentURL
else if (contentURL.startsWith('/')) contentUrl = `/api${contentURL}`
}
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage || '')
let publicUrl = ''
const thumbFolder = (typeof window !== 'undefined' && window.devicePixelRatio > 1.5) ? 't600' : 't300'
if (uploadsPath) {
if (uploadsPath.includes('/uploads/image/') && !uploadsPath.includes('/uploads/image/t300/') && !uploadsPath.includes('/uploads/image/t600/')) {
publicUrl = uploadsPath.replace('/uploads/image/', `/uploads/image/${thumbFolder}/`)
} else {
publicUrl = uploadsPath
}
} else {
const fileName = String(item.file_name || item.FileName || '').trim()
if (fileName) publicUrl = `/uploads/image/${thumbFolder}/${fileName}`
}
return { contentUrl, publicUrl }
}
function getProductImageUrl(code, color) {
const key = buildImageKey(code, color)
const existing = productImageCache.value[key]
if (existing !== undefined) return existing || ''
void ensureProductImage(code, color)
return ''
}
async function onProductImageError(code, color) {
const key = buildImageKey(code, color)
const current = String(productImageCache.value[key] || '')
const fallback = String(productImageFallbackByKey.value[key] || '')
if (fallback && current !== fallback && !productImageContentLoading.value[key]) {
productImageContentLoading.value[key] = true
try {
const blobRes = await api.get(fallback, { baseURL: '', responseType: 'blob' })
const blob = blobRes?.data
if (blob instanceof Blob) {
const objectUrl = URL.createObjectURL(blob)
productImageBlobUrls.value.push(objectUrl)
productImageCache.value[key] = objectUrl
return
}
} catch {
// no-op
} finally {
delete productImageContentLoading.value[key]
}
}
productImageCache.value[key] = ''
}
async function ensureProductImage(code, color) {
const key = buildImageKey(code, color)
const codeTrim = String(code || '').trim().toUpperCase()
const colorTrim = String(color || '').trim().toUpperCase()
if (!codeTrim) {
productImageCache.value[key] = ''
return ''
}
if (Date.now() < Number(productImageListBlockedUntil.value || 0)) {
productImageCache.value[key] = ''
return ''
}
if (productImageCache.value[key] !== undefined) return productImageCache.value[key] || ''
if (productImageLoading.value[key]) return ''
productImageLoading.value[key] = true
try {
if (!productImageListByCode.value[codeTrim]) {
if (!productImageListLoading.value[codeTrim]) {
productImageListLoading.value[codeTrim] = true
try {
if (imageListActiveRequests >= IMAGE_LIST_CONCURRENCY) {
await new Promise((resolve) => imageListWaitQueue.push(resolve))
}
imageListActiveRequests++
const res = await api.get('/product-images', { params: { code: codeTrim } })
productImageListByCode.value[codeTrim] = Array.isArray(res?.data) ? res.data : []
} catch (err) {
productImageListByCode.value[codeTrim] = []
const status = Number(err?.response?.status || 0)
if (status >= 500 || status === 403 || status === 0) {
productImageListBlockedUntil.value = Date.now() + 30 * 1000
}
console.warn('[ProductStockQuery] product image list fetch failed', { code: codeTrim, err })
} finally {
imageListActiveRequests = Math.max(0, imageListActiveRequests - 1)
const nextInQueue = imageListWaitQueue.shift()
if (nextInQueue) nextInQueue()
delete productImageListLoading.value[codeTrim]
}
} else {
while (productImageListLoading.value[codeTrim]) {
await new Promise((resolve) => setTimeout(resolve, 25))
}
}
}
const list = productImageListByCode.value[codeTrim] || []
let first = null
if (colorTrim) {
const needle = `-${colorTrim.toLowerCase()}-`
first = list.find((item) =>
String(item?.file_name || item?.FileName || '').toLowerCase().includes(needle)
) || null
}
if (!first) first = list[0] || null
const resolved = resolveProductImageUrl(first)
productImageCache.value[key] = resolved.publicUrl || resolved.contentUrl || ''
productImageFallbackByKey.value[key] = resolved.contentUrl || ''
} catch (err) {
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
productImageCache.value[key] = ''
productImageFallbackByKey.value[key] = ''
} finally {
delete productImageLoading.value[key]
}
return productImageCache.value[key] || ''
}
function formatNumber(v) { function formatNumber(v) {
return parseNumber(v).toLocaleString('tr-TR', { minimumFractionDigits: 0, maximumFractionDigits: 2 }) return parseNumber(v).toLocaleString('tr-TR', { minimumFractionDigits: 0, maximumFractionDigits: 2 })
} }
@@ -534,6 +714,13 @@ async function fetchStockByCode() {
activeSchema.value = schemaMap?.[grpKey] || storeSchemaByKey.tak activeSchema.value = schemaMap?.[grpKey] || storeSchemaByKey.tak
rawRows.value = list rawRows.value = list
productImageCache.value = {}
productImageLoading.value = {}
productImageListByCode.value = {}
productImageListLoading.value = {}
productImageFallbackByKey.value = {}
productImageContentLoading.value = {}
productImageListBlockedUntil.value = 0
initOpenState(level1Groups.value) initOpenState(level1Groups.value)
} catch (err) { } catch (err) {
console.error('fetchStockByCode error:', err) console.error('fetchStockByCode error:', err)
@@ -550,8 +737,11 @@ async function fetchStockByCode() {
} }
} }
function onLevel2Click(_productCode, grp2) { function onLevel2Click(productCode, grp2) {
toggleOpen(grp2.key) toggleOpen(grp2.key)
if (isOpen(grp2.key)) {
void ensureProductImage(productCode, grp2.colorCode)
}
} }
function resetForm() { function resetForm() {
@@ -560,11 +750,25 @@ function resetForm() {
errorMessage.value = '' errorMessage.value = ''
openState.value = {} openState.value = {}
activeSchema.value = storeSchemaByKey.tak activeSchema.value = storeSchemaByKey.tak
productImageCache.value = {}
productImageLoading.value = {}
productImageListByCode.value = {}
productImageListLoading.value = {}
productImageFallbackByKey.value = {}
productImageContentLoading.value = {}
productImageListBlockedUntil.value = 0
} }
onMounted(() => { onMounted(() => {
loadProductOptions() loadProductOptions()
}) })
onUnmounted(() => {
for (const url of productImageBlobUrls.value) {
try { URL.revokeObjectURL(url) } catch {}
}
productImageBlobUrls.value = []
})
</script> </script>
<style scoped> <style scoped>
@@ -573,6 +777,7 @@ onMounted(() => {
--grp-title-w: 44px; --grp-title-w: 44px;
--psq-header-h: 56px; --psq-header-h: 56px;
--psq-col-adet: calc(var(--col-adet) + var(--beden-w)); --psq-col-adet: calc(var(--col-adet) + var(--beden-w));
--psq-col-img: 126px;
--psq-l1-lift: 42px; --psq-l1-lift: 42px;
} }
@@ -585,7 +790,8 @@ onMounted(() => {
var(--col-alt) var(--col-alt)
var(--col-aciklama) var(--col-aciklama)
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count))) calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
var(--psq-col-adet) !important; var(--psq-col-adet)
var(--psq-col-img) !important;
} }
.order-grid-header .col-fixed, .order-grid-header .col-fixed,
@@ -631,7 +837,7 @@ onMounted(() => {
} }
.order-grid-header .total-cell:last-child { .order-grid-header .total-cell:last-child {
width: var(--psq-col-adet) !important; width: var(--psq-col-img) !important;
} }
.order-sub-header { .order-sub-header {
@@ -642,7 +848,8 @@ onMounted(() => {
var(--col-alt) var(--col-alt)
var(--col-aciklama) var(--col-aciklama)
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count))) calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w)*var(--beden-count)))
var(--psq-col-adet) !important; var(--psq-col-adet)
var(--psq-col-img) !important;
top: calc( top: calc(
var(--header-h) var(--header-h)
+ var(--filter-h) + var(--filter-h)
@@ -897,6 +1104,41 @@ onMounted(() => {
border-left: 1px solid #d4c79f; border-left: 1px solid #d4c79f;
} }
.order-sub-header.level-2 .sub-image.level2-image {
grid-column: 8 / 9;
display: flex;
align-items: center;
justify-content: center;
padding: 6px;
border-left: 1px solid #d4c79f;
}
.product-image-card {
width: 110px;
height: 68px;
border-radius: 8px;
overflow: hidden;
}
.product-image-wrap {
width: 100%;
height: 100%;
}
.product-image {
width: 100%;
height: 100%;
}
.product-image-placeholder {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: #f5f6f7;
}
.order-sub-header.level-2 .sub-right .top-total { .order-sub-header.level-2 .sub-right .top-total {
grid-column: 1 / 2; grid-column: 1 / 2;
grid-row: 1; grid-row: 1;
@@ -931,7 +1173,8 @@ onMounted(() => {
var(--col-alt) var(--col-alt)
var(--col-aciklama) var(--col-aciklama)
calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count))) calc(var(--grp-title-w) + var(--grp-title-gap) + (var(--beden-w) * var(--beden-count)))
var(--psq-col-adet) !important; var(--psq-col-adet)
var(--psq-col-img) !important;
min-height: 56px; min-height: 56px;
height: 56px; height: 56px;
background: #fff; background: #fff;
@@ -1048,6 +1291,11 @@ onMounted(() => {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.order-grid-body .summary-row .cell.img-placeholder {
justify-content: center;
text-align: center;
}
.order-grid-body { .order-grid-body {
margin-top: 0 !important; margin-top: 0 !important;
padding-top: 0 !important; padding-top: 0 !important;

View File

@@ -422,7 +422,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
const detail = await extractApiErrorDetail(err) const detail = await extractApiErrorDetail(err)
const orderId = id || this.header?.OrderHeaderID || '-' const orderId = id || this.header?.OrderHeaderID || '-'
const status = err?.status || err?.response?.status || '-' const status = err?.status || err?.response?.status || '-'
console.error(`? PDF a<EFBFBD>ma hatas<EFBFBD> [${status}] order=${orderId}: ${detail}`) console.error(`? PDF a<>ma hatas<61> [${status}] order=${orderId}: ${detail}`)
throw new Error(detail) throw new Error(detail)
} }
} }
@@ -2206,8 +2206,33 @@ export const useOrderEntryStore = defineStore('orderentry', {
// ❗ BEDEN YOK → bu SADECE üst seviye grup anahtarı // ❗ BEDEN YOK → bu SADECE üst seviye grup anahtarı
const modelKey = `${model}||${renk}||${renk2}` const modelKey = `${model}||${renk}||${renk2}`
const grpKey = raw.grpKey || 'tak' // grouped map: { [grpKey]: { beden: qty } } or flat map: { beden: qty }
const srcMap = raw.bedenMap[grpKey] || {} const groupedKey =
raw.grpKey ||
(raw.bedenMap && typeof raw.bedenMap === 'object'
? Object.keys(raw.bedenMap).find(k => raw.bedenMap[k] && typeof raw.bedenMap[k] === 'object')
: null)
const srcMap =
groupedKey && raw.bedenMap?.[groupedKey] && typeof raw.bedenMap[groupedKey] === 'object'
? raw.bedenMap[groupedKey]
: raw.bedenMap
const grpKey =
groupedKey ||
detectBedenGroup(
Object.keys(srcMap || {}),
raw.urunAnaGrubu || raw.UrunAnaGrubu || '',
raw.kategori || raw.Kategori || raw.urunAltGrubu || raw.UrunAltGrubu || '',
raw.yetiskinGarson ||
raw.YETISKIN_GARSON ||
raw.YetiskinGarson ||
raw.AskiliYan ||
raw.ASKILIYAN ||
raw.askiliyan ||
''
) ||
'tak'
const adet = Object.values(srcMap).reduce((a, b) => a + (Number(b) || 0), 0) const adet = Object.values(srcMap).reduce((a, b) => a + (Number(b) || 0), 0)
const fiyat = Number(raw.fiyat || 0) const fiyat = Number(raw.fiyat || 0)
@@ -2264,7 +2289,16 @@ export const useOrderEntryStore = defineStore('orderentry', {
urunAnaGrubu: raw.UrunAnaGrubu || 'GENEL', urunAnaGrubu: raw.UrunAnaGrubu || 'GENEL',
urunAltGrubu: raw.UrunAltGrubu || '', urunAltGrubu: raw.UrunAltGrubu || '',
kategori: raw.Kategori || '', kategori: raw.Kategori || raw.UrunAltGrubu || '',
urunAltGrubu: raw.UrunAltGrubu || '',
yetiskinGarson:
raw.YETISKIN_GARSON ||
raw.YetiskinGarson ||
raw.yetiskinGarson ||
raw.AskiliYan ||
raw.ASKILIYAN ||
raw.askiliyan ||
'',
aciklama: raw.LineDescription || '', aciklama: raw.LineDescription || '',
fiyat: Number(raw.Price || 0), fiyat: Number(raw.Price || 0),
@@ -2321,7 +2355,8 @@ export const useOrderEntryStore = defineStore('orderentry', {
const grpKey = detectBedenGroup( const grpKey = detectBedenGroup(
bedenList, bedenList,
row.urunAnaGrubu, row.urunAnaGrubu,
row.kategori row.kategori || row.urunAltGrubu,
row.yetiskinGarson
) )
const cleanedMap = { ...row.__tmpMap } const cleanedMap = { ...row.__tmpMap }
@@ -2516,7 +2551,7 @@ export const useOrderEntryStore = defineStore('orderentry', {
return detectBedenGroup( return detectBedenGroup(
null, null,
row?.urunAnaGrubu || '', row?.urunAnaGrubu || '',
row?.kategori || '', row?.kategori || row?.urunAltGrubu || '',
row?.YETISKIN_GARSON || row?.yetiskinGarson || '' row?.YETISKIN_GARSON || row?.yetiskinGarson || ''
) )
}, },
@@ -3442,6 +3477,15 @@ export function detectBedenGroup(bedenList, urunAnaGrubu = '', urunKategori = ''
? bedenList.map(v => (v || '').toString().trim().toUpperCase()) ? bedenList.map(v => (v || '').toString().trim().toUpperCase())
: [' '] : [' ']
// Beden seti çocuk yaş formatındaysa metadata beklemeden "yas" aç.
// Örn: 2,4,6,8,10,12,14 veya 2Y,4Y,6Y...
const yasNums = new Set(['2', '4', '6', '8', '10', '12', '14'])
const yasParsed = list
.map(v => v.replace(/\s+/g, '').replace(/YAS$/i, '').replace(/Y$/i, ''))
if (yasParsed.length > 0 && yasParsed.every(v => yasNums.has(v))) {
return 'yas'
}
const rawAna = normalizeTextForMatch(urunAnaGrubu || '') const rawAna = normalizeTextForMatch(urunAnaGrubu || '')
const rawKat = normalizeTextForMatch(urunKategori || '') const rawKat = normalizeTextForMatch(urunKategori || '')
const rawYetiskinGarson = normalizeTextForMatch(yetiskinGarson || '') const rawYetiskinGarson = normalizeTextForMatch(yetiskinGarson || '')