product performance grouped kpi improvements
This commit is contained in:
@@ -201,7 +201,7 @@
|
||||
flat
|
||||
bordered
|
||||
row-key="row_key"
|
||||
class="performance-table sticky-dim-table sticky-dim-10 bg-white"
|
||||
class="performance-table sticky-dim-table sticky-dim-8 bg-white"
|
||||
:rows="displayOrderProductCustomerTableRows"
|
||||
:columns="orderProductCustomerColumns"
|
||||
:loading="loading || backendGroupedLoading"
|
||||
@@ -676,7 +676,7 @@
|
||||
flat
|
||||
bordered
|
||||
row-key="row_key"
|
||||
class="performance-table sticky-dim-table sticky-dim-6 bg-white"
|
||||
class="performance-table sticky-dim-table sticky-dim-11 bg-white"
|
||||
:rows="displayIdleTableRows"
|
||||
:columns="idleColumns"
|
||||
:loading="loading || backendGroupedLoading"
|
||||
@@ -1515,14 +1515,14 @@ const columnFilters = reactive({})
|
||||
const columnFilterSearch = reactive({})
|
||||
const expandedGroups = ref({})
|
||||
const selectedExpandLevelKeysByTab = reactive({
|
||||
products: ['kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'market_key'],
|
||||
sales_color_yaka_market_customer: ['color_code', 'yaka_kodu', 'market_key', 'customer_code'],
|
||||
idle: ['kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code'],
|
||||
sales_product_country_segment_market_customer: ['product_code', 'country', 'customer_segment', 'market_key', 'customer_code'],
|
||||
sales_market_customer_product: ['market_key', 'customer_code', 'product_code', 'color_code', 'yaka_kodu'],
|
||||
sales_country_segment_market_customer_product: ['country', 'customer_segment', 'market_key', 'customer_code', 'product_code', 'color_code', 'yaka_kodu'],
|
||||
order_product_customers: ['product_code', 'market_key', 'customer_code'],
|
||||
order_market_details: ['market_key', 'customer_code', 'product_code', 'color_code', 'yaka_kodu']
|
||||
products: ['kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'item_description', 'color_code', 'yaka_kodu', 'market_key'],
|
||||
sales_color_yaka_market_customer: ['color_code', 'yaka_kodu', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'item_description', 'country', 'market_key', 'customer_segment', 'customer_code', 'customer_name'],
|
||||
idle: ['askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'item_description', 'color_code', 'yaka_kodu'],
|
||||
sales_product_country_segment_market_customer: ['market_key', 'customer_code', 'customer_name', 'kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'item_description', 'color_code', 'yaka_kodu'],
|
||||
sales_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'item_description', 'color_code', 'yaka_kodu'],
|
||||
sales_country_segment_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'item_description', 'color_code', 'yaka_kodu'],
|
||||
order_product_customers: ['market_key', 'customer_code', 'customer_name', 'kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'item_description', 'color_code', 'yaka_kodu'],
|
||||
order_market_details: ['market_key', 'customer_code', 'customer_name', 'kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu', 'product_code', 'item_description', 'color_code', 'yaka_kodu']
|
||||
})
|
||||
const productKpiFetchLimit = 50000
|
||||
const performanceTabs = [
|
||||
@@ -1530,7 +1530,6 @@ const performanceTabs = [
|
||||
{ name: 'sales_color_yaka_market_customer', icon: 'palette', label: 'Renk > Yaka > Piyasa > Müşteri' },
|
||||
{ name: 'idle', icon: 'warning', label: 'Atıl Stok / Maliyet' },
|
||||
{ name: 'sales_product_country_segment_market_customer', icon: 'account_tree', label: 'Ürün > Ülke > Segment > Piyasa > Müşteri' },
|
||||
{ name: 'sales_market_customer_product', icon: 'groups', label: 'Piyasa > Müşteri > Ürün Satış KPI' },
|
||||
{ name: 'sales_country_segment_market_customer_product', icon: 'public', label: 'Ülke > Segment > Piyasa > Müşteri > Ürün Satış KPI' },
|
||||
{ name: 'order_product_customers', icon: 'assignment_ind', label: 'Ürün > Piyasa > Müşteri Sipariş' },
|
||||
{ name: 'order_market_details', icon: 'receipt_long', label: 'Piyasa > Müşteri > Ürün Sipariş' }
|
||||
@@ -1571,7 +1570,6 @@ const customerBreakdownOptions = [
|
||||
const salesBreakdownModes = {
|
||||
sales_color_yaka_market_customer: 'color_yaka_market_customer',
|
||||
sales_product_country_segment_market_customer: 'product_country_segment_market_customer',
|
||||
sales_market_customer_product: 'market_customer_product',
|
||||
sales_country_segment_market_customer_product: 'country_segment_market_customer_product'
|
||||
}
|
||||
|
||||
@@ -1596,15 +1594,15 @@ const detailSalesColumns = [
|
||||
|
||||
const columns = [
|
||||
{ name: 'image', label: 'Foto', field: 'image', align: 'center' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left' },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left' },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left' },
|
||||
{ name: 'kategori', label: 'Kategori', field: 'kategori', align: 'left' },
|
||||
{ name: 'askili_yan', label: 'Askılı/Yan', field: 'askili_yan', align: 'left' },
|
||||
{ name: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', field: row => row.urun_ilk_grubu || row.yas_grubu || '', align: 'left' },
|
||||
{ name: 'urun_ana_grubu', label: 'Ürün Ana Grubu', field: row => row.urun_ana_grubu || row.seri || '', align: 'left' },
|
||||
{ name: 'urun_alt_grubu', label: 'Ürün Alt Grubu', field: 'urun_alt_grubu', align: 'left' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left' },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left' },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left' },
|
||||
{ name: 'market_key', label: 'Piyasa', field: row => displayMarketName(row.market_key), align: 'left', sortable: true },
|
||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_qty_90d', label: '90G Satış', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
||||
@@ -1773,14 +1771,19 @@ const orderGroupColumns = [
|
||||
]
|
||||
|
||||
const orderProductCustomerColumns = [
|
||||
{ name: 'image', label: 'Foto', field: 'image', align: 'center' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'market_key', label: 'Piyasa', field: row => displayMarketName(row.market_key), align: 'left', sortable: true },
|
||||
{ name: 'customer_code', label: 'Müşteri Kodu', field: 'customer_code', align: 'left', sortable: true },
|
||||
{ name: 'customer_name', label: 'Müşteri', field: 'customer_name', align: 'left', sortable: true },
|
||||
{ name: 'image', label: 'Foto', field: 'image', align: 'center' },
|
||||
{ name: 'kategori', label: 'Kategori', field: 'kategori', align: 'left', sortable: true },
|
||||
{ name: 'askili_yan', label: 'Askılı/Yan', field: 'askili_yan', align: 'left', sortable: true },
|
||||
{ name: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', field: row => row.urun_ilk_grubu || row.yas_grubu || '', align: 'left', sortable: true },
|
||||
{ name: 'urun_ana_grubu', label: 'Ürün Ana Grubu', field: row => row.urun_ana_grubu || row.seri || '', align: 'left', sortable: true },
|
||||
{ name: 'urun_alt_grubu', label: 'Ürün Alt Grubu', field: 'urun_alt_grubu', align: 'left', sortable: true },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'order_qty', label: 'Açık Sipariş', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_usd', label: 'Sipariş USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_order_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
@@ -1803,13 +1806,18 @@ const orderMarketDetailColumns = [
|
||||
{ name: 'market_key', label: 'Piyasa', field: row => displayMarketName(row.market_key), align: 'left', sortable: true },
|
||||
{ name: 'customer_code', label: 'Müşteri Kodu', field: 'customer_code', align: 'left', sortable: true },
|
||||
{ name: 'customer_name', label: 'Müşteri', field: 'customer_name', align: 'left', sortable: true },
|
||||
{ name: 'kategori', label: 'Kategori', field: 'kategori', align: 'left', sortable: true },
|
||||
{ name: 'askili_yan', label: 'Askılı/Yan', field: 'askili_yan', align: 'left', sortable: true },
|
||||
{ name: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', field: row => row.urun_ilk_grubu || row.yas_grubu || '', align: 'left', sortable: true },
|
||||
{ name: 'urun_ana_grubu', label: 'Ürün Ana Grubu', field: row => row.urun_ana_grubu || row.seri || '', align: 'left', sortable: true },
|
||||
{ name: 'urun_alt_grubu', label: 'Ürün Alt Grubu', field: 'urun_alt_grubu', align: 'left', sortable: true },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'order_number', label: 'Sipariş No', field: 'order_number', align: 'left', sortable: true },
|
||||
{ name: 'order_date', label: 'Sipariş Tarihi', field: 'order_date', align: 'left', sortable: true },
|
||||
{ name: 'due_date', label: 'Termin', field: 'due_date', align: 'left', sortable: true },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'order_qty', label: 'Adet', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'order_usd', label: 'USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_order_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
@@ -1826,15 +1834,16 @@ const orderMarketDetailColumns = [
|
||||
|
||||
const idleColumns = [
|
||||
{ name: 'image', label: 'Foto', field: 'image', align: 'center' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left' },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left' },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left' },
|
||||
{ name: 'kategori', label: 'Kategori', field: 'kategori', align: 'left' },
|
||||
{ name: 'askili_yan', label: 'Askılı/Yan', field: 'askili_yan', align: 'left' },
|
||||
{ name: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', field: row => row.urun_ilk_grubu || row.yas_grubu || '', align: 'left' },
|
||||
{ name: 'urun_ana_grubu', label: 'Ürün Ana Grubu', field: row => row.urun_ana_grubu || row.seri || '', align: 'left' },
|
||||
{ name: 'urun_alt_grubu', label: 'Ürün Alt Grubu', field: 'urun_alt_grubu', align: 'left' },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left' },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left' },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left' },
|
||||
{ name: 'market_key', label: 'Piyasa', field: row => displayMarketName(row.market_key), align: 'left' },
|
||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'cost_price_usd', label: 'Maliyet USD', field: row => formatMoney(row.cost_price_usd, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'idle_cost_usd', label: 'Stok Maliyeti USD', field: 'idle_cost_usd', align: 'right', sortable: true },
|
||||
@@ -1888,6 +1897,7 @@ const customerColumns = [
|
||||
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_usd_90d', label: '90G USD', field: 'sales_usd_90d', align: 'right', sortable: true },
|
||||
{ name: 'avg_price_usd_90d', label: 'Ort. USD', field: 'avg_price_usd_90d', align: 'right', sortable: true },
|
||||
{ name: 'customer_score_90d', label: '90G Müşteri Skor', field: row => formatNumber(row.customer_score_90d, 1), align: 'right', sortable: true },
|
||||
{ name: 'base_price_usd_90d', label: '90G Taban', field: row => formatMoney(row.base_price_usd_90d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'cost_price_usd_90d', label: '90G Çıplak', field: row => formatMoney(row.cost_price_usd_90d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_base_usd_90d', label: '90G Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_90d, 'USD'), align: 'right', sortable: true },
|
||||
@@ -1901,52 +1911,65 @@ const customerColumns = [
|
||||
]
|
||||
|
||||
const salesBreakdownColumns = [
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'color_code', label: 'Renk', field: 'color_code', align: 'left', sortable: true },
|
||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||
{ name: 'askili_yan', label: 'Askılı/Yan', field: 'askili_yan', align: 'left', sortable: true },
|
||||
{ name: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', field: row => row.urun_ilk_grubu || row.yas_grubu || '', align: 'left', sortable: true },
|
||||
{ name: 'urun_ana_grubu', label: 'Ürün Ana Grubu', field: row => row.urun_ana_grubu || row.seri || '', align: 'left', sortable: true },
|
||||
{ name: 'urun_alt_grubu', label: 'Ürün Alt Grubu', field: 'urun_alt_grubu', align: 'left', sortable: true },
|
||||
{ name: 'product_code', label: 'Ürün', field: 'product_code', align: 'left', sortable: true },
|
||||
{ name: 'item_description', label: 'Açıklama', field: 'item_description', align: 'left', sortable: true },
|
||||
{ name: 'country', label: 'Ülke', field: 'country', align: 'left', sortable: true },
|
||||
{ name: 'customer_segment', label: 'Segment', field: 'customer_segment', align: 'left', sortable: true },
|
||||
{ name: 'market_key', label: 'Piyasa', field: row => displayMarketName(row.market_key), align: 'left', sortable: true },
|
||||
{ name: 'customer_segment', label: 'Segment', field: 'customer_segment', align: 'left', sortable: true },
|
||||
{ name: 'customer_code', label: 'Müşteri Kodu', field: 'customer_code', align: 'left', sortable: true },
|
||||
{ name: 'customer_name', label: 'Müşteri', field: 'customer_name', align: 'left', sortable: true },
|
||||
{ name: 'kategori', label: 'Kategori', field: 'kategori', align: 'left', sortable: true },
|
||||
{ name: 'product_count', label: 'Ürün Sayısı', field: row => formatNumber(row.product_count, 0), align: 'right', sortable: true },
|
||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||
{ name: 'customer_count_90d', label: '90G Müşteri', field: row => formatNumber(row.customer_count_90d, 0), align: 'right', sortable: true },
|
||||
{ name: 'invoice_count_90d', label: '90G Fatura', field: row => formatNumber(row.invoice_count_90d, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_usd_90d', label: '90G USD', field: 'sales_usd_90d', align: 'right', sortable: true },
|
||||
{ name: 'avg_price_usd_90d', label: 'Ort. USD', field: 'avg_price_usd_90d', align: 'right', sortable: true },
|
||||
{ name: 'stock_turnover_90d', label: '90G Stok Devir', field: row => formatNumber(row.stock_turnover_90d, 2), align: 'right', sortable: true },
|
||||
{ name: 'customer_count_180d', label: '180G Müşteri', field: row => formatNumber(row.customer_count_180d, 0), align: 'right', sortable: true },
|
||||
{ name: 'invoice_count_180d', label: '180G Fatura', field: row => formatNumber(row.invoice_count_180d, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_qty_180d', label: '180G Adet', field: row => formatNumber(row.sales_qty_180d, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_usd_180d', label: '180G USD', field: row => formatMoney(row.sales_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_price_usd_180d', label: '180G Ort. USD', field: row => formatMoney(row.avg_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'stock_turnover_180d', label: '180G Stok Devir', field: row => formatNumber(row.stock_turnover_180d, 2), align: 'right', sortable: true },
|
||||
{ name: 'base_price_usd_180d', label: '180G Taban', field: row => formatMoney(row.base_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'cost_price_usd_180d', label: '180G Çıplak', field: row => formatMoney(row.cost_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_base_usd_180d', label: '180G Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_cost_usd_180d', label: '180G Çıplak K/Z', field: row => formatMoney(row.gross_profit_cost_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_base_180d', label: '180G Taban Marj', field: row => formatPercent(row.gross_margin_base_180d), align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_cost_180d', label: '180G Çıplak Marj', field: row => formatPercent(row.gross_margin_cost_180d), align: 'right', sortable: true },
|
||||
{ name: 'customer_score_180d', label: '180G Müşteri Skor', field: row => formatNumber(row.customer_score_180d, 1), align: 'right', sortable: true },
|
||||
{ name: 'sales_qty_365d', label: '360G Adet', field: row => formatNumber(row.sales_qty_365d, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_usd_365d', label: '360G USD', field: row => formatMoney(row.sales_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_price_usd_365d', label: '360G Ort. USD', field: row => formatMoney(row.avg_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'stock_turnover_365d', label: '360G Stok Devir', field: row => formatNumber(row.stock_turnover_365d, 2), align: 'right', sortable: true },
|
||||
{ name: 'base_price_usd_365d', label: '360G Taban', field: row => formatMoney(row.base_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'cost_price_usd_365d', label: '360G Çıplak', field: row => formatMoney(row.cost_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_base_usd_365d', label: '360G Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_cost_usd_365d', label: '360G Çıplak K/Z', field: row => formatMoney(row.gross_profit_cost_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_base_365d', label: '360G Taban Marj', field: row => formatPercent(row.gross_margin_base_365d), align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_cost_365d', label: '360G Çıplak Marj', field: row => formatPercent(row.gross_margin_cost_365d), align: 'right', sortable: true },
|
||||
{ name: 'customer_score_365d', label: '360G Müşteri Skor', field: row => formatNumber(row.customer_score_365d, 1), align: 'right', sortable: true },
|
||||
{ name: 'customer_count_total', label: 'Genel Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
|
||||
{ name: 'invoice_count_total', label: 'Genel Fatura', field: row => formatNumber(row.invoice_count_total, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_qty_total', label: 'Genel Adet', field: row => formatNumber(row.sales_qty_total, 0), align: 'right', sortable: true },
|
||||
{ name: 'sales_usd_total', label: 'Genel USD', field: row => formatMoney(row.sales_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'avg_price_usd_total', label: 'Genel Ort. USD', field: row => formatMoney(row.avg_price_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'stock_turnover_total', label: 'Genel Stok Devir', field: row => formatNumber(row.stock_turnover_total, 2), align: 'right', sortable: true },
|
||||
{ name: 'base_price_usd_total', label: 'Genel Taban', field: row => formatMoney(row.base_price_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'cost_price_usd_total', label: 'Genel Çıplak', field: row => formatMoney(row.cost_price_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_base_usd_total', label: 'Genel Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_profit_cost_usd_total', label: 'Genel Çıplak K/Z', field: row => formatMoney(row.gross_profit_cost_usd_total, 'USD'), align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_base_total', label: 'Genel Taban Marj', field: row => formatPercent(row.gross_margin_base_total), align: 'right', sortable: true },
|
||||
{ name: 'gross_margin_cost_total', label: 'Genel Çıplak Marj', field: row => formatPercent(row.gross_margin_cost_total), align: 'right', sortable: true },
|
||||
{ name: 'customer_score_total', label: 'Genel Müşteri Skor', field: row => formatNumber(row.customer_score_total, 1), align: 'right', sortable: true },
|
||||
{ name: 'sales_index_90d', label: 'Endeks', field: row => formatNumber(row.sales_index_90d, 2), align: 'right', sortable: true },
|
||||
{ name: 'performance_score', label: 'Skor', field: row => formatNumber(row.performance_score, 1), align: 'right', sortable: true },
|
||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
||||
@@ -2076,8 +2099,11 @@ const groupLevels = [
|
||||
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'yas_grubu' },
|
||||
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
|
||||
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
|
||||
{ key: 'market_key', label: 'Piyasa' },
|
||||
{ key: 'product_code', label: 'Ürün Kodu' }
|
||||
{ key: 'product_code', label: 'Ürün' },
|
||||
{ key: 'item_description', label: 'Açıklama' },
|
||||
{ key: 'color_code', label: 'Renk' },
|
||||
{ key: 'yaka_kodu', label: 'Yaka' },
|
||||
{ key: 'market_key', label: 'Piyasa' }
|
||||
]
|
||||
|
||||
const tabGroupLevels = {
|
||||
@@ -2085,49 +2111,95 @@ const tabGroupLevels = {
|
||||
sales_color_yaka_market_customer: [
|
||||
{ key: 'color_code', label: 'Renk' },
|
||||
{ key: 'yaka_kodu', label: 'Yaka' },
|
||||
{ key: 'askili_yan', label: 'Askılı/Yan' },
|
||||
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'yas_grubu' },
|
||||
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
|
||||
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
|
||||
{ key: 'product_code', label: 'Ürün' },
|
||||
{ key: 'item_description', label: 'Açıklama' },
|
||||
{ key: 'country', label: 'Ülke' },
|
||||
{ key: 'market_key', label: 'Piyasa' },
|
||||
{ key: 'customer_code', label: 'Müşteri' }
|
||||
{ key: 'customer_segment', label: 'Segment' },
|
||||
{ key: 'customer_code', label: 'Müşteri Kodu' },
|
||||
{ key: 'customer_name', label: 'Müşteri' }
|
||||
],
|
||||
idle: [
|
||||
{ key: 'askili_yan', label: 'Askılı/Yan' },
|
||||
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'yas_grubu' },
|
||||
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
|
||||
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
|
||||
{ key: 'product_code', label: 'Ürün' },
|
||||
{ key: 'item_description', label: 'Açıklama' },
|
||||
{ key: 'color_code', label: 'Renk' },
|
||||
{ key: 'yaka_kodu', label: '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: 'kategori', label: 'Kategori' },
|
||||
{ key: 'askili_yan', label: 'Askılı/Yan' },
|
||||
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'yas_grubu' },
|
||||
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
|
||||
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
|
||||
{ key: 'product_code', label: 'Ürün Kodu' }
|
||||
],
|
||||
sales_product_country_segment_market_customer: [
|
||||
{ key: 'product_code', label: 'Ürün Kodu' },
|
||||
{ key: 'country', label: 'Ülke' },
|
||||
{ key: 'customer_segment', label: 'Segment' },
|
||||
{ key: 'market_key', label: 'Piyasa' },
|
||||
{ key: 'customer_code', label: 'Müşteri' }
|
||||
{ key: 'product_code', label: 'Ürün' },
|
||||
{ key: 'item_description', label: 'Açıklama' },
|
||||
{ key: 'color_code', label: 'Renk' },
|
||||
{ key: 'yaka_kodu', label: 'Yaka' }
|
||||
],
|
||||
sales_market_customer_product: [
|
||||
{ key: 'market_key', label: 'Piyasa' },
|
||||
{ key: 'customer_code', label: 'Müşteri' },
|
||||
{ key: 'product_code', label: 'Ürün Kodu' },
|
||||
{ key: 'customer_code', label: 'Müşteri Kodu' },
|
||||
{ key: 'customer_name', label: 'Müşteri' },
|
||||
{ key: 'kategori', label: 'Kategori' },
|
||||
{ key: 'askili_yan', label: 'Askılı/Yan' },
|
||||
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'yas_grubu' },
|
||||
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
|
||||
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
|
||||
{ key: 'product_code', label: 'Ürün' },
|
||||
{ key: 'item_description', label: 'Açıklama' },
|
||||
{ key: 'color_code', label: 'Renk' },
|
||||
{ key: 'yaka_kodu', label: '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' },
|
||||
{ key: 'product_code', label: 'Ürün Kodu' },
|
||||
{ key: 'customer_code', label: 'Müşteri Kodu' },
|
||||
{ key: 'customer_name', label: 'Müşteri' },
|
||||
{ key: 'kategori', label: 'Kategori' },
|
||||
{ key: 'askili_yan', label: 'Askılı/Yan' },
|
||||
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'yas_grubu' },
|
||||
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
|
||||
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
|
||||
{ key: 'product_code', label: 'Ürün' },
|
||||
{ key: 'item_description', label: 'Açıklama' },
|
||||
{ key: 'color_code', label: 'Renk' },
|
||||
{ key: 'yaka_kodu', label: 'Yaka' }
|
||||
],
|
||||
order_product_customers: [
|
||||
{ key: 'product_code', label: 'Ürün Kodu' },
|
||||
{ key: 'market_key', label: 'Piyasa' },
|
||||
{ key: 'customer_code', label: 'Müşteri' }
|
||||
{ key: 'customer_code', label: 'Müşteri Kodu' },
|
||||
{ key: 'customer_name', label: 'Müşteri' },
|
||||
{ key: 'kategori', label: 'Kategori' },
|
||||
{ key: 'askili_yan', label: 'Askılı/Yan' },
|
||||
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'yas_grubu' },
|
||||
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
|
||||
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
|
||||
{ key: 'product_code', label: 'Ürün' },
|
||||
{ key: 'item_description', label: 'Açıklama' },
|
||||
{ key: 'color_code', label: 'Renk' },
|
||||
{ key: 'yaka_kodu', label: 'Yaka' }
|
||||
],
|
||||
order_market_details: [
|
||||
{ key: 'market_key', label: 'Piyasa' },
|
||||
{ key: 'customer_code', label: 'Müşteri' },
|
||||
{ key: 'product_code', label: 'Ürün Kodu' },
|
||||
{ key: 'customer_code', label: 'Müşteri Kodu' },
|
||||
{ key: 'customer_name', label: 'Müşteri' },
|
||||
{ key: 'kategori', label: 'Kategori' },
|
||||
{ key: 'askili_yan', label: 'Askılı/Yan' },
|
||||
{ key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'yas_grubu' },
|
||||
{ key: 'urun_ana_grubu', label: 'Ürün Ana Grubu', fallback: 'seri' },
|
||||
{ key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' },
|
||||
{ key: 'product_code', label: 'Ürün' },
|
||||
{ key: 'item_description', label: 'Açıklama' },
|
||||
{ key: 'color_code', label: 'Renk' },
|
||||
{ key: 'yaka_kodu', label: 'Yaka' }
|
||||
]
|
||||
@@ -2321,6 +2393,7 @@ function sortProductLeafRows (sourceRows) {
|
||||
|
||||
function makeGroupRow (key, level, groupDef, value, groupRows) {
|
||||
const imageSource = groupRows.find(row => row?.product_code) || {}
|
||||
const aggregate = aggregateGroupFields(groupRows, groupDef.key)
|
||||
return {
|
||||
__group: true,
|
||||
row_key: `group|${key}`,
|
||||
@@ -2343,6 +2416,10 @@ function makeGroupRow (key, level, groupDef, value, groupRows) {
|
||||
urun_ana_grubu: groupDef.key === 'urun_ana_grubu' ? value : distinctSummary(groupRows, 'urun_ana_grubu', 'seri'),
|
||||
urun_alt_grubu: groupDef.key === 'urun_alt_grubu' ? value : distinctSummary(groupRows, 'urun_alt_grubu'),
|
||||
market_key: groupDef.key === 'market_key' ? value : distinctSummary(groupRows, 'market_key'),
|
||||
country: groupDef.key === 'country' ? value : distinctSummary(groupRows, 'country'),
|
||||
customer_segment: groupDef.key === 'customer_segment' ? value : distinctSummary(groupRows, 'customer_segment'),
|
||||
customer_code: groupDef.key === 'customer_code' ? value : distinctSummary(groupRows, 'customer_code'),
|
||||
customer_name: groupDef.key === 'customer_name' ? value : distinctSummary(groupRows, 'customer_name'),
|
||||
stock_qty: sumRows(groupRows, 'stock_qty'),
|
||||
sales_qty_90d: sumRows(groupRows, 'sales_qty_90d'),
|
||||
sales_qty_180d: sumRows(groupRows, 'sales_qty_180d'),
|
||||
@@ -2384,14 +2461,14 @@ function makeGroupRow (key, level, groupDef, value, groupRows) {
|
||||
stock_days_total: weightedAverageOrAverage(groupRows, 'stock_days_total', 'stock_qty'),
|
||||
sales_index_90d: averageRows(groupRows, 'sales_index_90d'),
|
||||
sales_index_total: averageRows(groupRows, 'sales_index_total'),
|
||||
performance_score: weightedAverageOrAverage(groupRows, 'performance_score', 'sales_qty_90d'),
|
||||
performance_bucket: dominantValue(groupRows, 'performance_bucket'),
|
||||
...aggregateGroupFields(groupRows),
|
||||
...aggregate,
|
||||
performance_score: groupPerformanceScore(aggregate, groupDef.key),
|
||||
recommendation: `${formatNumber(groupRows.length, 0)} satır`
|
||||
}
|
||||
}
|
||||
|
||||
function aggregateGroupFields (sourceRows) {
|
||||
function aggregateGroupFields (sourceRows, groupField = '') {
|
||||
const out = {}
|
||||
const fields = new Set()
|
||||
for (const row of sourceRows) {
|
||||
@@ -2407,7 +2484,7 @@ function aggregateGroupFields (sourceRows) {
|
||||
}
|
||||
}
|
||||
|
||||
applyDerivedGroupMetrics(out, sourceRows)
|
||||
applyDerivedGroupMetrics(out, sourceRows, groupField)
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -2429,7 +2506,7 @@ function weightFieldForMetric (field) {
|
||||
return 'sales_qty_90d'
|
||||
}
|
||||
|
||||
function applyDerivedGroupMetrics (out, sourceRows) {
|
||||
function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
||||
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||
const sales = Number(out[`sales_usd_${suffix}`] || 0)
|
||||
const qty = Number(out[`sales_qty_${suffix}`] || 0)
|
||||
@@ -2459,6 +2536,82 @@ function applyDerivedGroupMetrics (out, sourceRows) {
|
||||
if (sourceRows.some(row => row.performance_bucket)) {
|
||||
out.performance_bucket = dominantValue(sourceRows, 'performance_bucket')
|
||||
}
|
||||
out.customer_score_90d = customerSalesPeriodScore(periodMetricSource(out, '90d'))
|
||||
out.customer_score_180d = customerSalesPeriodScore(periodMetricSource(out, '180d'))
|
||||
out.customer_score_365d = customerSalesPeriodScore(periodMetricSource(out, '365d'))
|
||||
out.customer_score_total = customerSalesPeriodScore(periodMetricSource(out, 'total'))
|
||||
out.performance_score = groupPerformanceScore(out, groupField)
|
||||
}
|
||||
|
||||
function groupPerformanceScore (row, groupField = '') {
|
||||
if (Number(row?.order_qty || 0) > 0 || Number(row?.order_usd || 0) > 0) return orderGroupPerformanceScore(row)
|
||||
if (['customer_code', 'customer_name'].includes(groupField)) return customerSalesGroupPerformanceScore(row)
|
||||
return salesGroupPerformanceScore(row)
|
||||
}
|
||||
|
||||
function salesGroupPerformanceScore (row) {
|
||||
const salesIndex = Number(row?.sales_index_90d || 0)
|
||||
const margin = Number(row?.gross_margin_cost_90d ?? row?.gross_margin_90d ?? 0)
|
||||
const invoices = Number(row?.invoice_count_90d || 0)
|
||||
const qty = Number(row?.sales_qty_90d || 0)
|
||||
let stockTurnover = Number(row?.stock_turnover_90d || 0)
|
||||
const stockQty = Number(row?.stock_qty || 0)
|
||||
if (!stockTurnover && stockQty > 0) stockTurnover = qty / stockQty
|
||||
return Math.min(35, Math.max(0, salesIndex) * 18) +
|
||||
Math.min(30, Math.max(0, margin) * 60) +
|
||||
Math.min(15, invoices * 1.5) +
|
||||
Math.min(10, qty / 10) +
|
||||
Math.min(10, Math.max(0, stockTurnover) * 5)
|
||||
}
|
||||
|
||||
function customerSalesGroupPerformanceScore (row) {
|
||||
const periodToScore = {
|
||||
90: Number(row?.customer_score_90d || 0),
|
||||
180: Number(row?.customer_score_180d || 0),
|
||||
360: Number(row?.customer_score_365d || 0),
|
||||
total: Number(row?.customer_score_total || 0)
|
||||
}
|
||||
const selected = selectedPeriods.value.length ? selectedPeriods.value : ['90', '180', '360', 'total']
|
||||
const scores = selected.map(period => periodToScore[period]).filter(score => Number.isFinite(score) && score > 0)
|
||||
if (!scores.length) return 0
|
||||
return scores.reduce((sum, score) => sum + score, 0) / scores.length
|
||||
}
|
||||
|
||||
function customerSalesPeriodScore (row) {
|
||||
const salesUSD = Number(row?.sales_usd || 0)
|
||||
const margin = Number(row?.gross_margin_cost ?? row?.gross_margin ?? 0)
|
||||
const invoices = Number(row?.invoice_count || 0)
|
||||
const qty = Number(row?.sales_qty || 0)
|
||||
const productCount = Number(row?.product_count || 0)
|
||||
return Math.min(35, salesUSD / 1000) +
|
||||
Math.min(25, Math.max(0, margin) * 55) +
|
||||
Math.min(20, invoices * 2) +
|
||||
Math.min(10, qty / 10) +
|
||||
Math.min(10, productCount)
|
||||
}
|
||||
|
||||
function periodMetricSource (row, suffix) {
|
||||
return {
|
||||
sales_usd: Number(row?.[`sales_usd_${suffix}`] || 0),
|
||||
gross_margin_cost: Number(row?.[`gross_margin_cost_${suffix}`] ?? 0),
|
||||
gross_margin: Number(row?.[`gross_margin_${suffix}`] ?? 0),
|
||||
invoice_count: Number(row?.[`invoice_count_${suffix}`] || 0),
|
||||
sales_qty: Number(row?.[`sales_qty_${suffix}`] || 0),
|
||||
product_count: Number(row?.product_count || 0)
|
||||
}
|
||||
}
|
||||
|
||||
function orderGroupPerformanceScore (row) {
|
||||
const orderUSD = Number(row?.order_usd || 0)
|
||||
const margin = Number(row?.expected_margin_cost || 0)
|
||||
const orders = Number(row?.order_count || 0)
|
||||
const qty = Number(row?.order_qty || 0)
|
||||
const stockBonus = Number(row?.net_stock_after_order || 0) >= 0 ? 10 : 0
|
||||
return Math.min(35, orderUSD / 1000) +
|
||||
Math.min(25, Math.max(0, margin) * 60) +
|
||||
Math.min(15, orders * 2) +
|
||||
Math.min(15, qty / 10) +
|
||||
stockBonus
|
||||
}
|
||||
|
||||
function sumRows (sourceRows, field) {
|
||||
@@ -2601,7 +2754,7 @@ function groupCellClass (colOrName) {
|
||||
const name = colOrName?.name || colOrName
|
||||
if (name === 'image') return 'product-image-cell'
|
||||
if (colOrName?.align === 'right') return 'text-right'
|
||||
return ['stock_qty', 'sales_qty_90d', 'sales_qty_180d', 'sales_qty_365d', 'sales_qty_total', 'sales_usd_90d', 'sales_usd_180d', 'sales_usd_365d', 'sales_usd_total', 'stock_days_90d', 'stock_days_180d', 'stock_days_total', 'stock_turnover_90d', 'stock_turnover_180d', 'stock_turnover_365d', 'stock_turnover_total', 'avg_price_usd_90d', 'avg_price_usd_180d', 'avg_price_usd_365d', 'avg_price_usd_total', 'base_price_usd', 'cost_price_usd', 'unit_profit_base_90d', 'unit_profit_cost_90d', 'unit_profit_base_180d', 'unit_profit_cost_180d', 'unit_profit_base_total', 'unit_profit_cost_total', 'gross_profit_usd_90d', 'gross_profit_usd_180d', 'gross_profit_usd_total', 'gross_margin_base_90d', 'gross_margin_cost_90d', 'gross_margin_base_180d', 'gross_margin_cost_180d', 'gross_margin_base_total', 'gross_margin_cost_total', 'gross_margin_90d', 'gross_margin_180d', 'market_count_90d', 'customer_count_90d', 'market_count_total', 'customer_count_total', 'sales_index_90d', 'sales_index_total', 'performance_score'].includes(name)
|
||||
return ['stock_qty', 'sales_qty_90d', 'sales_qty_180d', 'sales_qty_365d', 'sales_qty_total', 'sales_usd_90d', 'sales_usd_180d', 'sales_usd_365d', 'sales_usd_total', 'stock_days_90d', 'stock_days_180d', 'stock_days_total', 'stock_turnover_90d', 'stock_turnover_180d', 'stock_turnover_365d', 'stock_turnover_total', 'avg_price_usd_90d', 'avg_price_usd_180d', 'avg_price_usd_365d', 'avg_price_usd_total', 'base_price_usd', 'cost_price_usd', 'unit_profit_base_90d', 'unit_profit_cost_90d', 'unit_profit_base_180d', 'unit_profit_cost_180d', 'unit_profit_base_total', 'unit_profit_cost_total', 'gross_profit_usd_90d', 'gross_profit_usd_180d', 'gross_profit_usd_total', 'gross_margin_base_90d', 'gross_margin_cost_90d', 'gross_margin_base_180d', 'gross_margin_cost_180d', 'gross_margin_base_total', 'gross_margin_cost_total', 'gross_margin_90d', 'gross_margin_180d', 'market_count_90d', 'customer_count_90d', 'market_count_total', 'customer_count_total', 'sales_index_90d', 'sales_index_total', 'customer_score_90d', 'customer_score_180d', 'customer_score_365d', 'customer_score_total', 'performance_score'].includes(name)
|
||||
? 'text-right'
|
||||
: ''
|
||||
}
|
||||
@@ -2954,8 +3107,13 @@ function normalizeOrderMarketDetailRow (row) {
|
||||
}
|
||||
|
||||
function normalizeSalesBreakdownRow (row) {
|
||||
const next = { ...row }
|
||||
next.customer_score_90d = Number(next.customer_score_90d || customerSalesPeriodScore(periodMetricSource(next, '90d')))
|
||||
next.customer_score_180d = Number(next.customer_score_180d || customerSalesPeriodScore(periodMetricSource(next, '180d')))
|
||||
next.customer_score_365d = Number(next.customer_score_365d || customerSalesPeriodScore(periodMetricSource(next, '365d')))
|
||||
next.customer_score_total = Number(next.customer_score_total || customerSalesPeriodScore(periodMetricSource(next, 'total')))
|
||||
return {
|
||||
...row,
|
||||
...next,
|
||||
row_key: `sales-breakdown|${row?.breakdown || ''}|${row?.product_code || ''}|${row?.color_code || ''}|${row?.yaka_kodu || ''}|${row?.country || ''}|${row?.customer_segment || ''}|${row?.market_key || ''}|${row?.customer_code || ''}`
|
||||
}
|
||||
}
|
||||
@@ -3165,9 +3323,13 @@ function backendGroupedSupportedTab (tabKey = activeTab.value) {
|
||||
}
|
||||
|
||||
function activeExpandedGroupKeys () {
|
||||
const tabPrefix = `tab:${activeTab.value}|`
|
||||
const validFields = new Set(activeGroupLevels.value.map(level => level.key))
|
||||
return Object.entries(expandedGroups.value)
|
||||
.filter(([, value]) => value === true)
|
||||
.map(([key]) => key)
|
||||
.filter(key => key.startsWith(tabPrefix))
|
||||
.filter(key => key.split('|').slice(1).every(part => validFields.has(String(part).split(':')[0])))
|
||||
}
|
||||
|
||||
function scheduleLoadBackendGroupedRows () {
|
||||
@@ -3195,11 +3357,17 @@ async function loadBackendGroupedRows () {
|
||||
limit: tabKey === 'products' || tabKey === 'idle' ? productKpiFetchLimit : 50000
|
||||
}
|
||||
try {
|
||||
const startedAt = performance.now()
|
||||
const rows = await performanceStore.fetchGroupedRows(params)
|
||||
backendGroupedRows.value = {
|
||||
...backendGroupedRows.value,
|
||||
[tabKey]: Array.isArray(rows) ? rows : []
|
||||
}
|
||||
console.info('[ProductPerformance][ui] grouped applied', {
|
||||
tab: tabKey,
|
||||
rows: Array.isArray(rows) ? rows.length : 0,
|
||||
elapsedSec: Number(((performance.now() - startedAt) / 1000).toFixed(2))
|
||||
})
|
||||
} catch (err) {
|
||||
console.warn('product performance grouped rows failed', err)
|
||||
} finally {
|
||||
@@ -3207,18 +3375,50 @@ async function loadBackendGroupedRows () {
|
||||
}
|
||||
}
|
||||
|
||||
function responseRowCount (data) {
|
||||
if (Array.isArray(data)) return data.length
|
||||
if (Array.isArray(data?.rows)) return data.rows.length
|
||||
if (data && typeof data === 'object') return Object.keys(data).length
|
||||
return 0
|
||||
}
|
||||
|
||||
async function timedProductPerformanceGet (label, url, config = {}) {
|
||||
const startedAt = performance.now()
|
||||
console.info('[ProductPerformance][api] start', {
|
||||
label,
|
||||
url,
|
||||
params: config?.params || {}
|
||||
})
|
||||
try {
|
||||
const resp = await api.get(url, config)
|
||||
console.info('[ProductPerformance][api] done', {
|
||||
label,
|
||||
rows: responseRowCount(resp?.data),
|
||||
elapsedSec: Number(((performance.now() - startedAt) / 1000).toFixed(2))
|
||||
})
|
||||
return resp
|
||||
} catch (err) {
|
||||
console.warn('[ProductPerformance][api] failed', {
|
||||
label,
|
||||
elapsedSec: Number(((performance.now() - startedAt) / 1000).toFixed(2)),
|
||||
message: err?.response?.data || err?.message || err
|
||||
})
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async function reload () {
|
||||
loading.value = true
|
||||
try {
|
||||
const salesBreakdownRequests = salesBreakdownTabKeys.map(tabKey => {
|
||||
return api.get('/pricing/product-performance/sales-breakdown', {
|
||||
return timedProductPerformanceGet(`sales-breakdown:${tabKey}`, '/pricing/product-performance/sales-breakdown', {
|
||||
params: { limit: 800, mode: salesBreakdownModes[tabKey] },
|
||||
timeout: 60000
|
||||
})
|
||||
})
|
||||
const [summaryResp, listResp, generalResp, marketResp, countryResp, customerResp, ...salesBreakdownResp] = await Promise.all([
|
||||
api.get('/pricing/product-performance/summary', { timeout: 60000 }),
|
||||
api.get('/pricing/product-performance', {
|
||||
timedProductPerformanceGet('summary', '/pricing/product-performance/summary', { timeout: 60000 }),
|
||||
timedProductPerformanceGet('product-kpi', '/pricing/product-performance', {
|
||||
params: {
|
||||
page: 1,
|
||||
limit: productKpiFetchLimit,
|
||||
@@ -3227,10 +3427,10 @@ async function reload () {
|
||||
},
|
||||
timeout: 60000
|
||||
}),
|
||||
api.get('/pricing/product-performance/general', { params: { limit: 500 }, timeout: 60000 }),
|
||||
api.get('/pricing/product-performance/markets', { params: { limit: 500 }, timeout: 60000 }),
|
||||
api.get('/pricing/product-performance/countries', { params: { limit: 500 }, timeout: 60000 }),
|
||||
api.get('/pricing/product-performance/customers', {
|
||||
timedProductPerformanceGet('general', '/pricing/product-performance/general', { params: { limit: 500 }, timeout: 60000 }),
|
||||
timedProductPerformanceGet('markets', '/pricing/product-performance/markets', { params: { limit: 500 }, timeout: 60000 }),
|
||||
timedProductPerformanceGet('countries', '/pricing/product-performance/countries', { params: { limit: 500 }, timeout: 60000 }),
|
||||
timedProductPerformanceGet('customers', '/pricing/product-performance/customers', {
|
||||
params: { limit: 500, breakdown: customerBreakdown.value },
|
||||
timeout: 60000
|
||||
}),
|
||||
@@ -3275,7 +3475,7 @@ async function reload () {
|
||||
async function loadOrderProductCustomers (showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await api.get('/pricing/product-performance/orders/product-customers', { params: { limit: 500 }, timeout: 90000 })
|
||||
const resp = await timedProductPerformanceGet('orders:product-customers', '/pricing/product-performance/orders/product-customers', { params: { limit: 500 }, timeout: 90000 })
|
||||
orderProductCustomerRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeOrderProductCustomerRow)
|
||||
orderProductCustomersLoaded.value = true
|
||||
await primeProductImages(orderProductCustomerRows.value)
|
||||
@@ -3289,7 +3489,7 @@ async function loadOrderProductCustomers (showLoading = true) {
|
||||
async function loadOrderMarketDetails (showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const resp = await api.get('/pricing/product-performance/orders/market-details', { params: { limit: 800 }, timeout: 90000 })
|
||||
const resp = await timedProductPerformanceGet('orders:market-details', '/pricing/product-performance/orders/market-details', { params: { limit: 800 }, timeout: 90000 })
|
||||
orderMarketDetailRows.value = (Array.isArray(resp?.data) ? resp.data : []).map(normalizeOrderMarketDetailRow)
|
||||
orderMarketDetailsLoaded.value = true
|
||||
} catch (err) {
|
||||
@@ -3303,9 +3503,9 @@ async function loadOrderAnalysis (showLoading = true) {
|
||||
if (showLoading) loading.value = true
|
||||
try {
|
||||
const [productResp, marketResp, customerResp] = await Promise.all([
|
||||
api.get('/pricing/product-performance/orders', { params: { limit: 500 }, timeout: 90000 }),
|
||||
api.get('/pricing/product-performance/orders/groups', { params: { limit: 500, breakdown: 'market' }, timeout: 90000 }),
|
||||
api.get('/pricing/product-performance/orders/groups', { params: { limit: 500, breakdown: 'customer' }, timeout: 90000 })
|
||||
timedProductPerformanceGet('orders:products', '/pricing/product-performance/orders', { params: { limit: 500 }, timeout: 90000 }),
|
||||
timedProductPerformanceGet('orders:groups:market', '/pricing/product-performance/orders/groups', { params: { limit: 500, breakdown: 'market' }, timeout: 90000 }),
|
||||
timedProductPerformanceGet('orders:groups:customer', '/pricing/product-performance/orders/groups', { params: { limit: 500, breakdown: 'customer' }, timeout: 90000 })
|
||||
])
|
||||
orderAnalysisRows.value = (Array.isArray(productResp?.data) ? productResp.data : []).map(normalizeOrderAnalysisRow)
|
||||
orderMarketRows.value = (Array.isArray(marketResp?.data) ? marketResp.data : []).map(normalizeOrderGroupRow)
|
||||
@@ -3683,6 +3883,11 @@ onMounted(reload)
|
||||
left: 1170px;
|
||||
}
|
||||
|
||||
.sticky-dim-table :deep(.q-table th:nth-child(11)),
|
||||
.sticky-dim-table :deep(.q-table td:nth-child(11)) {
|
||||
left: 1300px;
|
||||
}
|
||||
|
||||
.sticky-dim-table.sticky-dim-3 :deep(.q-table th:nth-child(-n+3)),
|
||||
.sticky-dim-table.sticky-dim-3 :deep(.q-table td:nth-child(-n+3)),
|
||||
.sticky-dim-table.sticky-dim-5 :deep(.q-table th:nth-child(-n+5)),
|
||||
@@ -3694,7 +3899,9 @@ onMounted(reload)
|
||||
.sticky-dim-table.sticky-dim-9 :deep(.q-table th:nth-child(-n+9)),
|
||||
.sticky-dim-table.sticky-dim-9 :deep(.q-table td:nth-child(-n+9)),
|
||||
.sticky-dim-table.sticky-dim-10 :deep(.q-table th:nth-child(-n+10)),
|
||||
.sticky-dim-table.sticky-dim-10 :deep(.q-table td:nth-child(-n+10)) {
|
||||
.sticky-dim-table.sticky-dim-10 :deep(.q-table td:nth-child(-n+10)),
|
||||
.sticky-dim-table.sticky-dim-11 :deep(.q-table th:nth-child(-n+11)),
|
||||
.sticky-dim-table.sticky-dim-11 :deep(.q-table td:nth-child(-n+11)) {
|
||||
position: sticky;
|
||||
z-index: 2;
|
||||
}
|
||||
@@ -3704,12 +3911,13 @@ onMounted(reload)
|
||||
.sticky-dim-table.sticky-dim-6 :deep(.q-table th:nth-child(-n+6)),
|
||||
.sticky-dim-table.sticky-dim-7 :deep(.q-table th:nth-child(-n+7)),
|
||||
.sticky-dim-table.sticky-dim-9 :deep(.q-table th:nth-child(-n+9)),
|
||||
.sticky-dim-table.sticky-dim-10 :deep(.q-table th:nth-child(-n+10)) {
|
||||
.sticky-dim-table.sticky-dim-10 :deep(.q-table th:nth-child(-n+10)),
|
||||
.sticky-dim-table.sticky-dim-11 :deep(.q-table th:nth-child(-n+11)) {
|
||||
z-index: 4;
|
||||
background: #f8fbff;
|
||||
}
|
||||
|
||||
.sticky-dim-table :deep(.q-table tbody td:nth-child(-n+10)) {
|
||||
.sticky-dim-table :deep(.q-table tbody td:nth-child(-n+11)) {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
@@ -3724,7 +3932,9 @@ onMounted(reload)
|
||||
.sticky-dim-table.sticky-dim-9 :deep(.q-table th:nth-child(9)),
|
||||
.sticky-dim-table.sticky-dim-9 :deep(.q-table td:nth-child(9)),
|
||||
.sticky-dim-table.sticky-dim-10 :deep(.q-table th:nth-child(10)),
|
||||
.sticky-dim-table.sticky-dim-10 :deep(.q-table td:nth-child(10)) {
|
||||
.sticky-dim-table.sticky-dim-10 :deep(.q-table td:nth-child(10)),
|
||||
.sticky-dim-table.sticky-dim-11 :deep(.q-table th:nth-child(11)),
|
||||
.sticky-dim-table.sticky-dim-11 :deep(.q-table td:nth-child(11)) {
|
||||
box-shadow: 8px 0 10px -10px rgba(17, 24, 39, 0.45);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user