Fix product performance grouped JSON build

This commit is contained in:
M_Kececi
2026-07-05 02:58:06 +03:00
parent 480cad0d6f
commit 48bde09c2c
4 changed files with 562 additions and 70 deletions
@@ -1611,10 +1611,10 @@ const selectedExpandLevelKeysByTab = reactive({
product_detail: ['urun_alt_grubu'],
sales_color_yaka_market_customer: ['color_yaka'],
idle: ['urun_ilk_grubu', 'askili_yan', 'kategori'],
sales_product_country_segment_market_customer: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'],
sales_product_country_segment_market_customer: ['urun_ilk_grubu', 'askili_yan', 'kategori'],
sales_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'],
sales_country_segment_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'],
order_product_customers: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'],
sales_country_segment_market_customer_product: ['country', 'customer_segment', 'market_key'],
order_product_customers: ['urun_ilk_grubu', 'askili_yan', 'kategori'],
order_market_details: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan']
})
const productKpiFetchLimit = 50000
@@ -1623,6 +1623,7 @@ const maxBulkExpandKeys = 1000
const maxBackendExpandedKeys = 1000
const tableVirtualSliceSize = 160
const productThumbVirtualItemSize = 236
const maxRenderedGroupedRows = 2500
const maxAutoExpandLevelByTab = {
products: 8,
product_detail: 8,
@@ -2554,16 +2555,18 @@ const tabGroupLevels = {
{ key: 'color_yaka', label: 'Renk/Yaka' }
],
sales_product_country_segment_market_customer: [
{ key: 'market_key', label: 'Piyasa' },
{ key: 'customer_code', label: 'Müşteri Kodu' },
{ key: 'customer_name', label: 'Müşteri' },
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' },
{ key: 'askili_yan', label: 'Askılı/Yan' },
{ key: 'kategori', label: 'Kategori' },
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'color_yaka', label: 'Renk/Yaka' }
{ key: 'color_yaka', label: 'Renk/Yaka' },
{ key: 'country', label: 'Ülke' },
{ key: 'customer_segment', label: 'Segment' },
{ key: 'market_key', label: 'Piyasa' },
{ key: 'customer_code', label: 'Müşteri Kodu' },
{ key: 'customer_name', label: 'Müşteri' }
],
sales_market_customer_product: [
{ key: 'market_key', label: 'Piyasa' },
@@ -2578,6 +2581,8 @@ const tabGroupLevels = {
{ key: 'color_yaka', label: 'Renk/Yaka' }
],
sales_country_segment_market_customer_product: [
{ key: 'country', label: 'Ülke' },
{ key: 'customer_segment', label: 'Segment' },
{ key: 'market_key', label: 'Piyasa' },
{ key: 'customer_code', label: 'Müşteri Kodu' },
{ key: 'customer_name', label: 'Müşteri' },
@@ -2590,16 +2595,16 @@ const tabGroupLevels = {
{ key: 'color_yaka', label: 'Renk/Yaka' }
],
order_product_customers: [
{ key: 'market_key', label: 'Piyasa' },
{ key: 'customer_code', label: 'Müşteri Kodu' },
{ key: 'customer_name', label: 'Müşteri' },
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' },
{ key: 'askili_yan', label: 'Askılı/Yan' },
{ key: 'kategori', label: 'Kategori' },
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' },
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
{ key: 'product_code', label: 'Ürün' },
{ key: 'color_yaka', label: 'Renk/Yaka' }
{ key: 'color_yaka', label: 'Renk/Yaka' },
{ key: 'market_key', label: 'Piyasa' },
{ key: 'customer_code', label: 'Müşteri Kodu' },
{ key: 'customer_name', label: 'Müşteri' }
],
order_market_details: [
{ key: 'market_key', label: 'Piyasa' },
@@ -2660,22 +2665,6 @@ const fullExpandThroughLevel = computed(() => {
return Math.min(lastGroupLevel, maxAutoExpandThroughLevelForTab())
})
const tableFilterSourceMap = computed(() => ({
products: { rows: filterSourceRowsForTab('products', rows.value), columns: productColumns.value },
product_detail: { rows: filterSourceRowsForTab('product_detail', detailProductRows.value), columns: productDetailColumns.value },
general: { rows: generalRows.value, columns: generalColumns },
order_product_customers: { rows: filterSourceRowsForTab('order_product_customers', orderProductCustomerRows.value), columns: orderProductCustomerColumns },
order_market_details: { rows: filterSourceRowsForTab('order_market_details', orderMarketDetailRows.value), columns: orderMarketDetailColumns },
idle: { rows: filterSourceRowsForTab('idle', idleRows.value), columns: idleColumns },
markets: { rows: marketRows.value, columns: marketColumns },
countries: { rows: countryRows.value, columns: countryColumns },
customers: { rows: customerRows.value, columns: customerColumns },
sales_color_yaka_market_customer: { rows: filterSourceRowsForTab('sales_color_yaka_market_customer', salesBreakdownRows.sales_color_yaka_market_customer), columns: visibleSalesBreakdownColumns.value },
sales_product_country_segment_market_customer: { rows: filterSourceRowsForTab('sales_product_country_segment_market_customer', salesBreakdownRows.sales_product_country_segment_market_customer), columns: visibleSalesBreakdownColumns.value },
sales_market_customer_product: { rows: filterSourceRowsForTab('sales_market_customer_product', salesBreakdownRows.sales_market_customer_product), columns: visibleSalesBreakdownColumns.value },
sales_country_segment_market_customer_product: { rows: filterSourceRowsForTab('sales_country_segment_market_customer_product', salesBreakdownRows.sales_country_segment_market_customer_product), columns: visibleSalesBreakdownColumns.value }
}))
const filteredProductRows = computed(() => filterRowsForTable('products', rows.value, productColumns.value))
const isProductKpiTab = computed(() => activeTab.value === 'products' || activeTab.value === 'product_detail')
const activeProductTableKey = computed(() => activeTab.value === 'product_detail' ? 'product_detail' : 'products')
@@ -2706,7 +2695,7 @@ const activeGroupSourceRows = computed(() => {
const columnFilterOptionMap = computed(() => {
const out = {}
const tableKey = isProductKpiTab.value ? activeProductTableKey.value : activeTab.value
const source = tableFilterSourceMap.value[tableKey]
const source = tableFilterSource(tableKey)
if (!source) return out
out[tableKey] = {}
for (const col of source.columns) {
@@ -2730,7 +2719,7 @@ const columnFilterOptionMap = computed(() => {
const productTableRows = computed(() => {
const out = []
appendGroupRows(out, filteredProductRows.value, 0, ['tab:products'], groupLevels)
appendGroupRows(out, filteredProductRows.value, 0, ['tab:products'], groupLevels, 'products')
return out
})
const detailProductTableRows = computed(() => buildGroupedTableRows('product_detail', filteredDetailProductRows.value))
@@ -2813,13 +2802,14 @@ const allProductGroupsExpanded = computed(() => {
return selectedExpandThroughLevel.value >= fullExpandThroughLevel.value
})
function appendGroupRows (out, sourceRows, level, parentKeys, levels = groupLevels) {
appendGroupRowsAt(out, sourceRows, level, 0, parentKeys, levels)
function appendGroupRows (out, sourceRows, level, parentKeys, levels = groupLevels, tableKey = activeTab.value) {
appendGroupRowsAt(out, sourceRows, level, 0, parentKeys, levels, tableKey)
}
function appendGroupRowsAt (out, sourceRows, level, visualLevel, parentKeys, levels = groupLevels) {
function appendGroupRowsAt (out, sourceRows, level, visualLevel, parentKeys, levels = groupLevels, tableKey = activeTab.value) {
if (out.length >= maxRenderedGroupedRows) return
if (level >= levels.length) {
out.push(...sortProductLeafRows(sourceRows))
out.push(...sortLeafRowsForTable(tableKey, sourceRows).slice(0, Math.max(0, maxRenderedGroupedRows - out.length)))
return
}
@@ -2832,16 +2822,17 @@ function appendGroupRowsAt (out, sourceRows, level, visualLevel, parentKeys, lev
}
Array.from(grouped.entries())
.sort((a, b) => a[0].localeCompare(b[0], 'tr'))
.sort((a, b) => compareGroupedBucketsForTable(tableKey, a, b, groupDef))
.forEach(([value, groupRows]) => {
if (out.length >= maxRenderedGroupedRows) return
if (shouldSkipGroupValue(groupDef.key, value)) {
appendGroupRowsAt(out, groupRows, level + 1, visualLevel, parentKeys, levels)
appendGroupRowsAt(out, groupRows, level + 1, visualLevel, parentKeys, levels, tableKey)
return
}
const key = [...parentKeys, `${groupDef.key}:${value}`].join('|')
out.push(makeGroupRow(key, visualLevel, groupDef, value, groupRows))
if (isGroupExpanded(key)) {
appendGroupRowsAt(out, groupRows, level + 1, visualLevel + 1, [...parentKeys, `${groupDef.key}:${value}`], levels)
appendGroupRowsAt(out, groupRows, level + 1, visualLevel + 1, [...parentKeys, `${groupDef.key}:${value}`], levels, tableKey)
}
})
}
@@ -2849,7 +2840,7 @@ function appendGroupRowsAt (out, sourceRows, level, visualLevel, parentKeys, lev
function buildGroupedTableRows (tableKey, sourceRows) {
const levels = tabGroupLevels[tableKey] || groupLevels
const out = []
appendGroupRows(out, sourceRows, 0, [`tab:${tableKey}`], levels)
appendGroupRows(out, sourceRows, 0, [`tab:${tableKey}`], levels, tableKey)
return out
}
@@ -2863,6 +2854,22 @@ function filterSourceRowsForTab (tabKey, fallbackRows) {
return fallbackRows
}
function tableFilterSource (tableKey) {
if (tableKey === 'products') return { rows: filterSourceRowsForTab('products', rows.value), columns: productColumns.value }
if (tableKey === 'product_detail') return { rows: filterSourceRowsForTab('product_detail', detailProductRows.value), columns: productDetailColumns.value }
if (tableKey === 'general') return { rows: generalRows.value, columns: generalColumns }
if (tableKey === 'order_product_customers') return { rows: filterSourceRowsForTab('order_product_customers', orderProductCustomerRows.value), columns: orderProductCustomerColumns }
if (tableKey === 'order_market_details') return { rows: filterSourceRowsForTab('order_market_details', orderMarketDetailRows.value), columns: orderMarketDetailColumns }
if (tableKey === 'idle') return { rows: filterSourceRowsForTab('idle', idleRows.value), columns: idleColumns }
if (tableKey === 'markets') return { rows: marketRows.value, columns: marketColumns }
if (tableKey === 'countries') return { rows: countryRows.value, columns: countryColumns }
if (tableKey === 'customers') return { rows: customerRows.value, columns: customerColumns }
if (salesBreakdownTabKeys.includes(tableKey)) {
return { rows: filterSourceRowsForTab(tableKey, salesBreakdownRows[tableKey] || []), columns: visibleSalesBreakdownColumns.value }
}
return null
}
function columnsForTableKey (tabKey) {
if (tabKey === 'products') return productColumns.value
if (tabKey === 'product_detail') return productDetailColumns.value
@@ -2915,6 +2922,45 @@ function sortProductLeafRows (sourceRows) {
})
}
function sortLeafRowsForTable (tableKey, sourceRows) {
const { sortBy, descending } = tableSortState(tableKey)
if (!sortBy) return sortProductLeafRows(sourceRows)
return sortFlatTableRows(sourceRows, sortBy, descending)
}
function tableSortState (tableKey) {
const pagination = tablePagination[tableKey] || {}
return {
sortBy: pagination.sortBy || '',
descending: pagination.descending !== false
}
}
function compareGroupedBucketsForTable (tableKey, left, right, groupDef) {
const [leftLabel, leftRows] = left
const [rightLabel, rightRows] = right
const { sortBy, descending } = tableSortState(tableKey)
if (!sortBy || sortBy === groupDef.key) {
return String(leftLabel).localeCompare(String(rightLabel), 'tr', { numeric: true, sensitivity: 'base' })
}
const leftValue = groupedBucketSortValue(leftRows, sortBy)
const rightValue = groupedBucketSortValue(rightRows, sortBy)
const cmp = compareColumnSortValues(leftValue, rightValue)
if (cmp !== 0) return descending ? -cmp : cmp
return String(leftLabel).localeCompare(String(rightLabel), 'tr', { numeric: true, sensitivity: 'base' })
}
function groupedBucketSortValue (sourceRows, sortBy) {
if (!sourceRows?.length) return 0
if (sortBy === 'stock_qty') return distinctVariantStockQty(sourceRows)
if (sortBy === 'idle_cost_usd') return distinctVariantStockCost(sourceRows)
if (sortBy === 'base_price_usd' || sortBy === 'cost_price_usd') return weightedAverageProductCost(sourceRows, sortBy)
if (shouldSumField(sortBy)) return sumRows(sourceRows, sortBy)
if (shouldAverageField(sortBy)) return weightedAverageOrAverage(sourceRows, sortBy, weightFieldForMetric(sortBy))
return sortValueForColumnName(sourceRows[0], sortBy)
}
function sortProductGroupedTableRows (tableRows, sortBy, descending) {
const source = Array.isArray(tableRows) ? tableRows : []
if (!sortBy || !source.some(row => row?.__group)) {
@@ -3437,13 +3483,10 @@ function marginFromRows (sourceRows, salesField, qtyField, unitCostField) {
}
function normalizeGroupValue (value) {
return String(value || '').trim()
return String(value || '').trim() || '(Boş)'
}
function shouldSkipGroupValue (field, value) {
const text = String(value || '').trim()
if (field === 'urun_ilk_grubu') return !text || text === '-'
if (field === 'askili_yan') return !text || text === '-'
return false
}
@@ -3709,14 +3752,15 @@ function syncExpansionAfterFilterChange () {
}
function filterRowsForTable (tableKey, sourceRows, sourceColumns) {
return sourceRows.filter(row => {
return sourceColumns.every(col => {
if (!isColumnFilterable(col.name)) return true
const selected = selectedColumnFilters(tableKey, col.name)
if (!selected.length) return true
return selected.includes(columnFilterValue(row, col))
})
})
const activeFilters = sourceColumns
.filter(col => isColumnFilterable(col.name))
.map(col => ({
col,
selected: new Set(selectedColumnFilters(tableKey, col.name))
}))
.filter(item => item.selected.size > 0)
if (!activeFilters.length) return sourceRows
return sourceRows.filter(row => activeFilters.every(item => item.selected.has(columnFilterValue(row, item.col))))
}
function columnFilterValue (row, col) {
@@ -4465,7 +4509,6 @@ function setupTopScrollbarSync () {
function switchPerformanceTab (tabName) {
if (!tabName || tabName === activeTab.value || loading.value) return
backendGroupedLoading.value = true
activeTab.value = tabName
}
@@ -4887,7 +4930,7 @@ onBeforeUnmount(() => {
align-items: center;
justify-content: center;
gap: 12px;
background: rgba(246, 247, 249, 0.82);
background: #f6f7f9;
backdrop-filter: blur(2px);
pointer-events: all;
}