@@ -8,6 +8,36 @@
< / div >
< / div >
< div class = "col-12 col-md-auto row items-center q-gutter-sm" >
< q-option-group
v - model = "selectedPeriods"
: options = "periodOptions"
type = "checkbox"
inline
dense
class = "period-selector bg-white q-px-sm q-py-xs"
/ >
< q-btn-dropdown
dense
outline
color = "primary"
: icon = "allProductGroupsExpanded ? 'unfold_less' : 'unfold_more'"
: label = "allProductGroupsExpanded ? 'Detayları Kapat' : 'Tüm Detayları Aç'"
@ click = "toggleAllProductGroups"
>
< div class = "detail-level-menu" >
< q-option-group
v - model = "activeSelectedExpandLevelKeys"
: options = "expandLevelOptions"
type = "checkbox"
dense
/ >
< q-separator class = "q-my-sm" / >
< div class = "row items-center justify-end q-gutter-xs" >
< q-btn dense flat color = "grey-7" label = "Kapat" @click ="expandedGroups = {}" / >
< q-btn dense color = "primary" label = "Seçileni Aç" @click ="expandSelectedProductGroups" / >
< / div >
< / div >
< / q-btn-dropdown >
< q-btn outline color = "secondary" icon = "refresh" label = "Yenile" :loading = "loading" @click ="reload" / >
< / div >
< / div >
@@ -32,13 +62,13 @@
align = "left"
>
< q-tab name = "products" icon = "inventory_2" label = "Ürün KPI" / >
< q-tab name = "general" icon = "analytics" label = "KPI Genel " / >
< q-tab name = "order_product_customers" icon = "assignment_ind" label = "Ürün > Müşteri Sipariş" / >
< q-tab name = "order_market_details" icon = "account_tree" label = "Piyasa > Sipariş Detay" / >
< q-tab name = "sales_color_yaka_market_customer" icon = "palette" label = "Renk > Yaka > Piyasa > Müşteri " / >
< q-tab name = "idle" icon = "warning" label = "Atı l Stok / Maliyet" / >
< q-tab name = "markets" icon = "query_stats" label = "Piyasa Grup Kı yası " / >
< q-tab name = "countries " icon = "public " label = "Ülke / Segment " / >
< q-tab name = "customers " icon = "groups " label = "Müşteri Kı rı lı mı " / >
< q-tab name = "sales_product_country_segment_market_customer" icon = "account_tree" label = "Ürün > Ülke > Segment > Piyasa > Müşteri " / >
< q-tab name = "sales_market_customer_product " icon = "groups " label = "Piyasa > Müşteri > Ürün Satış KPI " / >
< q-tab name = "sales_country_segment_market_customer_product " icon = "public " label = "Ülke > Segment > Piyasa > Müşteri > Ürün Satış KPI " / >
< q-tab name = "order_product_customers" icon = "assignment_ind" label = "Ürün > Piyasa > Müşteri Sipariş" / >
< q-tab name = "order_market_details" icon = "receipt_long" label = "Piyasa > Müşteri > Ürün Sipariş" / >
< / q-tabs >
< q-table
@@ -370,9 +400,9 @@
flat
bordered
row - key = "row_key"
class = "performance-table bg-white"
class = "performance-table product-breakdown-table bg-white"
: rows = "productTableRows"
: columns = "c olumns"
: columns = "productC olumns"
: loading = "loading"
: pagination = "pagination"
@ request = "onRequest"
@@ -455,8 +485,23 @@
: class = "['group-row', `group-row-level-${Math.min(Number(props.row.level || 0), 5)}`]"
>
< q-td v-for = "col in props.cols" :key="col.name" :props="props" :class="groupCellClass(col.name)" >
< template v-if = "col.name === 'image' && groupShowsImage(props.row)" >
< button type = "button" class = "product-thumb-button" @click.stop ="openProductImageDialog(props.row)" >
< q-img
v - if = "getCachedProductImageUrl(props.row)"
: src = "getCachedProductImageUrl(props.row)"
fit = "contain"
class = "product-thumb"
no - spinner
/ >
< div v-else class = "product-thumb-placeholder" >
< q-icon name = "image" size = "22px" / >
< / div >
< q-tooltip > Foto < / q-tooltip >
< / button >
< / template >
< div
v - if = "col.name === groupLabelColumnName(props.row)"
v - else - if = "col.name === groupLabelColumnName(props.row)"
class = "group-cell-label"
: style = "{ paddingLeft: `${props.row.level * 18}px` }"
>
@@ -710,6 +755,111 @@
< / template >
< / q-table >
< q-table
v - else - if = "salesBreakdownTabKeys.includes(activeTab)"
flat
bordered
row - key = "row_key"
class = "performance-table bg-white"
: rows = "filteredActiveSalesBreakdownRows"
: columns = "visibleSalesBreakdownColumns"
: loading = "loading"
: pagination = "{ rowsPerPage: 100, sortBy: 'performance_score', descending: true }"
>
< template # header -cell = " props " >
< q-th :props = "props" class = "filterable-header-cell" >
< div class = "header-with-filter" >
< span class = "header-label" :title = "props.col.label" > { { props . col . label } } < / span >
< q-btn
v - if = "isColumnFilterable(props.col.name)"
dense
flat
round
size = "8px"
icon = "filter_alt"
: color = "hasColumnFilter(activeTab, props.col.name) ? 'primary' : 'grey-7'"
class = "header-filter-btn"
@ click . stop
>
< q-badge v-if = "hasColumnFilter(activeTab, props.col.name)" color="primary" floating rounded >
{ { getColumnFilterBadgeValue ( activeTab , props . col . name ) } }
< / q-badge >
< q-menu anchor = "bottom right" self = "top right" : offset = "[0, 4]" >
< div class = "excel-filter-menu" >
< q-input
v - model = "columnFilterSearch[filterKey(activeTab, props.col.name)]"
dense
outlined
clearable
class = "excel-filter-select"
placeholder = "Ara"
/ >
< div class = "excel-filter-actions row items-center justify-between q-pt-xs" >
< q-btn flat dense size = "sm" label = "Tümünü Seç" @click ="selectAllColumnFilterOptions(activeTab, props.col.name)" / >
< q-btn flat dense size = "sm" label = "Temizle" @click ="clearColumnFilter(activeTab, props.col.name)" / >
< / div >
< q-virtual-scroll
v - if = "columnFilterOptions(activeTab, props.col.name).length > 0"
class = "excel-filter-options"
: items = "columnFilterOptions(activeTab, props.col.name)"
: virtual - scroll - item - size = "32"
separator
>
< template # default = "{ item: option }" >
< q-item
: key = "`${props.col.name}-${option.value}`"
dense
clickable
class = "excel-filter-option"
@ click = "toggleColumnFilterValue(activeTab, props.col.name, option.value)"
>
< q-item-section avatar >
< q-checkbox
dense
size = "sm"
: model - value = "isColumnFilterValueSelected(activeTab, props.col.name, option.value)"
@ update : model - value = "() => toggleColumnFilterValue(activeTab, props.col.name, option.value)"
@ click . stop
/ >
< / q-item-section >
< q-item-section >
< q-item-label > { { option . label } } < / q-item-label >
< / q-item-section >
< / q-item >
< / template >
< / q-virtual-scroll >
< div v-else class = "excel-filter-empty" > Sonuç yok < / div >
< / div >
< / q-menu >
< / q-btn >
< q-btn v-else dense flat round size = "8px" icon = "filter_alt" class = "header-filter-btn header-filter-ghost" tabindex = "-1" / >
< / div >
< / q-th >
< / template >
< template # body -cell -sales_usd_90d = " props " >
< q-td :props = "props" class = "text-right" >
{ { formatMoney ( props . row . sales _usd _90d , 'USD' ) } }
< / q-td >
< / template >
< template # body -cell -sales_usd_365d = " props " >
< q-td :props = "props" class = "text-right" >
{ { formatMoney ( props . row . sales _usd _365d , 'USD' ) } }
< / q-td >
< / template >
< template # body -cell -avg_price_usd_90d = " props " >
< q-td :props = "props" class = "text-right" >
{ { formatMoney ( props . row . avg _price _usd _90d , 'USD' ) } }
< / q-td >
< / template >
< template # body -cell -performance_bucket = " props " >
< q-td :props = "props" >
< q-badge :color = "bucketColor(props.row.performance_bucket)" >
{ { bucketLabel ( props . row . performance _bucket ) } }
< / q-badge >
< / q-td >
< / template >
< / q-table >
< div v-else-if = "activeTab === 'customers'" >
< q-card flat bordered class = "q-mb-sm" >
< q-card-section class = "row q-col-gutter-sm items-center" >
@@ -1034,9 +1184,16 @@ const orderAnalysisMode = ref('product')
const marketRows = ref ( [ ] )
const countryRows = ref ( [ ] )
const customerRows = ref ( [ ] )
const salesBreakdownRows = reactive ( {
sales _color _yaka _market _customer : [ ] ,
sales _product _country _segment _market _customer : [ ] ,
sales _market _customer _product : [ ] ,
sales _country _segment _market _customer _product : [ ]
} )
const summary = ref ( { } )
const activeTab = ref ( 'products' )
const customerBreakdown = ref ( 'market_customer' )
const selectedPeriods = ref ( [ '90' , '180' , '360' , 'total' ] )
const imageUrlByKey = ref ( { } )
const imageListByKey = ref ( { } )
const imageDialog = ref ( false )
@@ -1055,6 +1212,16 @@ const detailStockSizes = ref([])
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' ]
} )
const productKpiFetchLimit = 50000
const pagination = ref ( {
@@ -1071,15 +1238,32 @@ const bucketOptions = [
{ label : 'Stok Riski' , value : 'STOK_RISKI' } ,
{ label : 'Fiyat Baskı sı ' , value : 'FIYAT_BASKISI' } ,
{ label : 'Fiyat Fı rsatı ' , value : 'FIYAT_FIRSATI' } ,
{ label : 'Maliyet Yok' , value : 'MALIYET_YOK' } ,
{ label : 'Takip' , value : 'TAKIP' }
]
const periodOptions = [
{ label : '90G' , value : '90' } ,
{ label : '180G' , value : '180' } ,
{ label : '360G' , value : '360' } ,
{ label : 'Genel' , value : 'total' }
]
const customerBreakdownOptions = [
{ label : 'Piyasa > Müşteri' , value : 'market_customer' } ,
{ label : 'Ülke > Müşteri' , value : 'country_customer' } ,
{ label : 'Piyasa > Ülke > Müşteri' , value : 'market_country_customer' }
]
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'
}
const salesBreakdownTabKeys = Object . keys ( salesBreakdownModes )
const detailStockColumns = [
{ name : 'size_code' , label : 'Beden' , field : 'size_code' , align : 'left' , sortable : true } ,
{ name : 'stock_qty' , label : 'Stok' , field : row => formatNumber ( row . stock _qty , 0 ) , align : 'right' , sortable : true }
@@ -1088,7 +1272,7 @@ const detailStockColumns = [
const detailSalesColumns = [
{ name : 'sales_date' , label : 'Tarih' , field : 'sales_date' , align : 'left' , sortable : true } ,
{ name : 'ref_number' , label : 'Ref' , field : 'ref_number' , align : 'left' , sortable : true } ,
{ name : 'market_key' , label : 'Piyasa' , field : 'market_key' , align : 'left' , sortable : true } ,
{ name : 'market_key' , label : 'Piyasa' , field : row => displayMarketName ( row . market _key ) , align : 'left' , sortable : true } ,
{ name : 'country' , label : 'Ülke' , field : 'country' , 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 } ,
@@ -1104,40 +1288,75 @@ const columns = [
{ 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 : 'urun_ilk_grubu' , label : 'Ürün İlk Grubu' , field : row => row . urun _ilk _grubu || row . yas _grubu || '' , 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 : 'market_key' , label : 'Piyasa' , field : 'market_key' , align : 'left' , sortable : true } ,
{ 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 } ,
{ name : 'sales_qty_180d' , label : '180G Satış' , field : row => formatNumber ( row . sales _qty _180d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_qty_365d' , label : '360G Satış' , field : row => formatNumber ( row . sales _qty _365d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_qty_total' , label : 'Genel Satış' , field : row => formatNumber ( row . sales _qty _total , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_usd_90d' , label : '90G USD' , field : 'sales_usd_90d' , align : 'right' , sortable : true } ,
{ name : 'sales_usd_180d' , label : '180G USD' , field : 'sales_usd_180d' , align : 'right' , sortable : true } ,
{ name : 'sales_usd_365d' , label : '360G USD' , field : 'sales_usd_365d' , align : 'right' , sortable : true } ,
{ name : 'sales_usd_total' , label : 'Genel USD' , field : 'sales_usd_total' , align : 'right' , sortable : true } ,
{ name : 'stock_days_90d' , label : 'Stok Gün' , field : row => formatNumber ( row . stock _days _90d , 1 ) , align : 'right' , sortable : true } ,
{ name : 'stock_days_180d' , label : '180G Stok Gün' , field : row => formatNumber ( row . stock _days _180d , 1 ) , align : 'right' , sortable : true } ,
{ name : 'stock_days_total' , label : 'Genel Stok Gün' , field : row => formatNumber ( row . stock _days _total , 1 ) , align : 'right' , sortable : true } ,
{ name : 'avg_price_usd_90d' , label : 'Ort. USD' , field : row => formatMoney ( row . avg _price _usd _90d , 'USD' ) , align : 'right' } ,
{ name : 'avg_price_usd_180d' , label : '180G Ort. USD' , field : row => formatMoney ( row . avg _price _usd _180d , 'USD' ) , align : 'right' } ,
{ name : 'avg_price_usd_365d' , label : '360G Ort. USD' , field : row => formatMoney ( row . avg _price _usd _365d , 'USD' ) , align : 'right' } ,
{ name : 'avg_price_usd_total' , label : 'Genel Ort. USD' , field : row => formatMoney ( row . avg _price _usd _total , 'USD' ) , align : 'right' } ,
{ name : 'base_price_usd' , label : 'Ort. USD Taban Maliyeti' , field : row => formatMoney ( row . base _price _usd , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'cost_price_usd' , label : 'Ort. USD Çıplak Maliyeti' , field : row => formatMoney ( row . cost _price _usd , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'unit_profit_base_90d' , label : '90G P.Başı Taban K/Z' , field : row => formatMoney ( row . unit _profit _base _90d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'unit_profit_cost_90d' , label : '90G P.Başı Çıplak K/Z' , field : row => formatMoney ( row . unit _profit _cost _90d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'unit_profit_base_180d' , label : '180G P.Başı Taban K/Z' , field : row => formatMoney ( row . unit _profit _base _180d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'unit_profit_cost_180d' , label : '180G P.Başı Çıplak K/Z' , field : row => formatMoney ( row . unit _profit _cost _180d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'unit_profit_base_total' , label : 'Genel P.Başı Taban K/Z' , field : row => formatMoney ( row . unit _profit _base _total , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'unit_profit_cost_total' , label : 'Genel P.Başı Çıplak K/Z' , field : row => formatMoney ( row . unit _profit _cost _total , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'gross_profit_usd_90d' , label : '90G Toplam K/Z' , field : row => formatMoney ( row . gross _profit _usd _90d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'gross_profit_usd_180d' , label : '180G Toplam K/Z' , field : row => formatMoney ( row . gross _profit _usd _180d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'gross_profit_usd_total' , label : 'Genel Toplam K/Z' , field : row => formatMoney ( row . gross _profit _usd _total , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'gross_margin_base_90d' , label : '90G Taban Marj' , field : row => formatPercent ( row . gross _margin _base _90d ) , align : 'right' , sortable : true } ,
{ name : 'gross_margin_cost_90d' , label : '90G Çıplak Marj' , field : row => formatPercent ( row . gross _margin _cost _90d ) , 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 : '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 : 'market_count_90d' , label : '90G Piyasa' , field : row => formatNumber ( row . market _count _90d , 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 : 'market_count_total' , label : 'Genel Piyasa' , field : row => formatNumber ( row . market _count _total , 0 ) , 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 : 'sales_index_90d' , label : 'Piyasa End.' , field : row => formatNumber ( row . sales _index _90d , 2 ) , align : 'right' , sortable : true } ,
{ name : 'sales_index_total' , label : 'Genel Endeks' , field : row => formatNumber ( row . sales _index _total , 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' } ,
{ name : 'recommendation' , label : 'Öneri' , field : 'recommendation' , align : 'left' }
]
const periodColumnRules = [
{ period : '90' , match : name => name . includes ( '_90d' ) } ,
{ period : '180' , match : name => name . includes ( '_180d' ) } ,
{ period : '360' , match : name => name . includes ( '_365d' ) } ,
{ period : 'total' , match : name => name . includes ( '_total' ) }
]
const selectedPeriodSet = computed ( ( ) => new Set ( selectedPeriods . value ) )
function columnPeriod ( name ) {
return periodColumnRules . find ( rule => rule . match ( name ) ) ? . period || ''
}
function visiblePeriodColumn ( col ) {
const period = columnPeriod ( col . name )
return ! period || selectedPeriodSet . value . has ( period )
}
const productColumns = computed ( ( ) => columns . filter ( visiblePeriodColumn ) )
const generalColumns = [
{ name : 'image' , label : 'Foto' , field : 'image' , align : 'center' } ,
{ name : 'product_code' , label : 'Ürün' , field : 'product_code' , align : 'left' , sortable : true } ,
@@ -1145,11 +1364,11 @@ const generalColumns = [
{ 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 : 'kategori' , label : 'Kategori' , field : 'kategori' , 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 : '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 : 'market_key' , label : 'Piyasa' , field : 'market_key' , align : 'left' , sortable : true } ,
{ name : 'market_key' , label : 'Piyasa' , field : row => displayMarketName ( row . market _key ) , align : 'left' , sortable : true } ,
{ name : 'period_start' , label : 'Başlangıç' , field : 'period_start' , align : 'left' , sortable : true } ,
{ name : 'period_end' , label : 'Bitiş' , field : 'period_end' , align : 'left' , sortable : true } ,
{ name : 'stock_qty' , label : 'Stok' , field : row => formatNumber ( row . stock _qty , 0 ) , align : 'right' , sortable : true } ,
@@ -1183,11 +1402,11 @@ const orderAnalysisColumns = [
{ 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 : 'kategori' , label : 'Kategori' , field : 'kategori' , 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 : '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 : 'market_key' , label : 'Piyasa' , field : 'market_key' , align : 'left' , sortable : true } ,
{ name : 'market_key' , label : 'Piyasa' , field : row => displayMarketName ( row . market _key ) , 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. Sipariş USD' , field : row => formatMoney ( row . avg _order _price _usd , 'USD' ) , align : 'right' , sortable : true } ,
@@ -1212,7 +1431,7 @@ const orderAnalysisColumns = [
const orderGroupColumns = [
{ name : 'group_key' , label : 'Kı rı lı m' , field : row => orderGroupLabel ( row ) , align : 'left' , sortable : true } ,
{ name : 'market_key' , label : 'Piyasa' , field : 'market_key' , 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 : 'product_count' , label : 'Ürün' , field : row => formatNumber ( row . product _count , 0 ) , align : 'right' , sortable : true } ,
@@ -1242,7 +1461,7 @@ const orderProductCustomerColumns = [
{ 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 : 'market_key' , 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 : 'order_qty' , label : 'Açık Sipariş' , field : row => formatNumber ( row . order _qty , 0 ) , align : 'right' , sortable : true } ,
@@ -1264,7 +1483,7 @@ const orderProductCustomerColumns = [
]
const orderMarketDetailColumns = [
{ name : 'market_key' , label : 'Piyasa' , field : 'market_key' , 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 : 'order_number' , label : 'Sipariş No' , field : 'order_number' , align : 'left' , sortable : true } ,
@@ -1295,8 +1514,8 @@ const idleColumns = [
{ 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 : 'urun_ilk_grubu' , label : 'Ürün İlk Grubu' , field : row => row . urun _ilk _grubu || row . yas _grubu || '' , 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 : 'stock_qty' , label : 'Stok' , field : row => formatNumber ( row . stock _qty , 0 ) , align : 'right' , sortable : true } ,
@@ -1309,10 +1528,10 @@ const idleColumns = [
]
const marketColumns = [
{ name : 'market_key' , label : 'Piyasa Grubu ' , field : 'market_key' , align : 'left' , sortable : true } ,
{ name : 'market_key' , label : 'Piyasa' , field : row => displayMarketName ( row . market _key ) , align : 'left' , sortable : true } ,
{ name : 'kategori' , label : 'Kategori' , field : 'kategori' , align : 'left' } ,
{ name : 'urun_ilk_grubu' , label : 'Ürün İlk Grubu' , field : row => row . urun _ilk _grubu || row . yas _grubu || '' , 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_count' , label : 'Ürün' , field : row => formatNumber ( row . product _count , 0 ) , align : 'right' , sortable : true } ,
@@ -1330,19 +1549,19 @@ const marketColumns = [
const countryColumns = [
{ name : 'country' , label : 'Ülke' , field : 'country' , align : 'left' , sortable : true } ,
{ name : 'customer_segment' , label : 'Müşteri Segmenti' , field : 'customer_segment' , align : 'left' , sortable : true } ,
{ name : 'market_key' , label : 'Piyasa Grubu ' , field : 'market_key' , align : 'left' } ,
{ name : 'market_key' , label : 'Piyasa' , field : row => displayMarketName ( row . market _key ) , align : 'left' } ,
{ name : 'product_count' , label : 'Ürün' , field : row => formatNumber ( row . product _count , 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 : 'sales_usd_90d' , label : '90G USD' , field : row => formatMoney ( row . sales _usd _90d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'avg_price_usd_90d' , label : 'Ort. USD' , field : row => formatMoney ( row . avg _price _usd _90d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'customer_count_90d' , label : 'Müşteri' , field : row => formatNumber ( row . customer _count _90d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'invoice_count_90d' , label : 'Fatura' , field : row => formatNumber ( row . invoice _count _90d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_qty_365d' , label : '365 G Adet' , field : row => formatNumber ( row . sales _qty _365d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_usd_365d' , label : '365 G USD' , field : row => formatMoney ( row . sales _usd _365d , 'USD' ) , align : 'right' , sortable : true }
{ name : 'sales_qty_365d' , label : '360 G Adet' , field : row => formatNumber ( row . sales _qty _365d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_usd_365d' , label : '360 G USD' , field : row => formatMoney ( row . sales _usd _365d , 'USD' ) , align : 'right' , sortable : true }
]
const customerColumns = [
{ name : 'market_key' , label : 'Piyasa Grubu ' , field : 'market_key' , align : 'left' , sortable : true } ,
{ name : 'market_key' , label : 'Piyasa' , field : row => displayMarketName ( row . market _key ) , 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 : 'customer_code' , label : 'Müşteri Kodu' , field : 'customer_code' , align : 'left' , sortable : true } ,
@@ -1351,12 +1570,74 @@ 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 : '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 } ,
{ name : 'gross_profit_cost_usd_90d' , label : '90G Çıplak K/Z' , field : row => formatMoney ( row . gross _profit _cost _usd _90d , 'USD' ) , align : 'right' , sortable : true } ,
{ name : 'gross_margin_base_90d' , label : '90G Taban Marj' , field : row => formatPercent ( row . gross _margin _base _90d ) , align : 'right' , sortable : true } ,
{ name : 'gross_margin_cost_90d' , label : '90G Çıplak Marj' , field : row => formatPercent ( row . gross _margin _cost _90d ) , align : 'right' , sortable : true } ,
{ name : 'invoice_count_90d' , label : 'Fatura' , field : row => formatNumber ( row . invoice _count _90d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_qty_365d' , label : '365 G Adet' , field : row => formatNumber ( row . sales _qty _365d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_usd_365d' , label : '365 G USD' , field : 'sales_usd_365d' , align : 'right' , sortable : true } ,
{ name : 'sales_qty_365d' , label : '360 G Adet' , field : row => formatNumber ( row . sales _qty _365d , 0 ) , align : 'right' , sortable : true } ,
{ name : 'sales_usd_365d' , label : '360 G USD' , field : 'sales_usd_365d' , align : 'right' , sortable : true } ,
{ name : 'last_sale_date' , label : 'Son Satış' , field : 'last_sale_date' , align : 'left' , sortable : true }
]
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 : '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_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 : 'product_count' , label : 'Ürün Sayı sı ' , field : row => formatNumber ( row . product _count , 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 : '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 : '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 : '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 : '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_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 : '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 : '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 } ,
{ name : 'last_sale_date' , label : 'Son Satış' , field : 'last_sale_date' , align : 'left' , sortable : true } ,
{ name : 'recommendation' , label : 'Öneri' , field : 'recommendation' , align : 'left' }
]
const visibleSalesBreakdownColumns = computed ( ( ) => salesBreakdownColumns . filter ( visiblePeriodColumn ) )
const summaryCards = computed ( ( ) => [
{ key : 'date' , label : 'KPI Tarihi' , value : summary . value . kpi _date || '-' } ,
{ key : 'rows' , label : 'Satı r' , value : formatNumber ( summary . value . total _rows , 0 ) } ,
@@ -1448,26 +1729,110 @@ const performanceCards = computed(() => {
const groupLevels = [
{ key : 'kategori' , label : 'Kategori' } ,
{ key : 'urun_ilk_grubu' , label : 'Ürün İlk Grubu' , fallback : 'yas_grubu' } ,
{ 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 ' } ,
{ key : 'color _code' , label : 'Renk ' }
{ key : 'market_key ' , label : 'Piyasa ' } ,
{ key : 'product _code' , label : 'Ürün Kodu ' }
]
const tabGroupLevels = {
products : groupLevels ,
sales _color _yaka _market _customer : [
{ key : 'color_code' , label : 'Renk' } ,
{ key : 'yaka_kodu' , label : 'Yaka' } ,
{ key : 'market_key' , label : 'Piyasa' } ,
{ key : 'customer_code' , label : 'Müşteri' }
] ,
idle : [
{ 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' }
] ,
sales _market _customer _product : [
{ key : 'market_key' , label : 'Piyasa' } ,
{ key : 'customer_code' , label : 'Müşteri' } ,
{ key : 'product_code' , label : 'Ürün Kodu' } ,
{ 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 : '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' }
] ,
order _market _details : [
{ key : 'market_key' , label : 'Piyasa' } ,
{ key : 'customer_code' , label : 'Müşteri' } ,
{ key : 'product_code' , label : 'Ürün Kodu' } ,
{ key : 'color_code' , label : 'Renk' } ,
{ key : 'yaka_kodu' , label : 'Yaka' }
]
}
const activeGroupLevels = computed ( ( ) => tabGroupLevels [ activeTab . value ] || groupLevels )
const activeSelectedExpandLevelKeys = computed ( {
get : ( ) => selectedExpandLevelKeysByTab [ activeTab . value ] || [ ] ,
set : value => {
selectedExpandLevelKeysByTab [ activeTab . value ] = Array . isArray ( value ) ? value : [ ]
}
} )
const expandLevelOptions = computed ( ( ) => activeGroupLevels . value . map ( level => ( {
label : level . label ,
value : level . key
} ) ) )
const selectedExpandThroughLevel = computed ( ( ) => {
const selected = new Set ( activeSelectedExpandLevelKeys . value )
const indexes = activeGroupLevels . value
. map ( ( level , index ) => selected . has ( level . key ) ? index : - 1 )
. filter ( index => index >= 0 )
return indexes . length ? Math . max ( ... indexes ) : - 1
} )
const autoExpandThroughLevel = computed ( ( ) => {
const selectedLevel = selectedExpandThroughLevel . value
return selectedLevel >= 0 ? selectedLevel : activeGroupLevels . value . length - 1
} )
const tableFilterSourceMap = computed ( ( ) => ( {
products : { rows : rows . value , columns } ,
products : { rows : rows . value , columns : productColumns . value } ,
general : { rows : generalRows . value , columns : generalColumns } ,
order _product _customers : { rows : orderProductCustomerRows . value , columns : orderProductCustomerColumns } ,
order _market _details : { rows : orderMarketDetailRows . value , columns : orderMarketDetailColumns } ,
idle : { rows : idleRows . value , columns : idleColumns } ,
markets : { rows : marketRows . value , columns : marketColumns } ,
countries : { rows : countryRows . value , columns : countryColumns } ,
customers : { rows : customerRows . value , columns : customerColumns }
customers : { rows : customerRows . value , columns : customerColumns } ,
sales _color _yaka _market _customer : { rows : salesBreakdownRows . sales _color _yaka _market _customer , columns : visibleSalesBreakdownColumns . value } ,
sales _product _country _segment _market _customer : { rows : salesBreakdownRows . sales _product _country _segment _market _customer , columns : visibleSalesBreakdownColumns . value } ,
sales _market _customer _product : { rows : salesBreakdownRows . sales _market _customer _product , columns : visibleSalesBreakdownColumns . value } ,
sales _country _segment _market _customer _product : { rows : salesBreakdownRows . sales _country _segment _market _customer _product , columns : visibleSalesBreakdownColumns . value }
} ) )
const filteredProductRows = computed ( ( ) => filterRowsForTable ( 'products' , rows . value , columns ) )
const filteredProductRows = computed ( ( ) => filterRowsForTable ( 'products' , rows . value , productColumns . value ) )
const filteredGeneralRows = computed ( ( ) => filterRowsForTable ( 'general' , generalRows . value , generalColumns ) )
const filteredOrderProductCustomerRows = computed ( ( ) => filterRowsForTable ( 'order_product_customers' , orderProductCustomerRows . value , orderProductCustomerColumns ) )
const filteredOrderMarketDetailRows = computed ( ( ) => filterRowsForTable ( 'order_market_details' , orderMarketDetailRows . value , orderMarketDetailColumns ) )
@@ -1475,6 +1840,7 @@ const filteredIdleRows = computed(() => filterRowsForTable('idle', idleRows.valu
const filteredMarketRows = computed ( ( ) => filterRowsForTable ( 'markets' , marketRows . value , marketColumns ) )
const filteredCountryRows = computed ( ( ) => filterRowsForTable ( 'countries' , countryRows . value , countryColumns ) )
const filteredCustomerRows = computed ( ( ) => filterRowsForTable ( 'customers' , customerRows . value , customerColumns ) )
const filteredActiveSalesBreakdownRows = computed ( ( ) => filterRowsForTable ( activeTab . value , salesBreakdownRows [ activeTab . value ] || [ ] , visibleSalesBreakdownColumns . value ) )
const columnFilterOptionMap = computed ( ( ) => {
const out = { }
@@ -1502,17 +1868,34 @@ const columnFilterOptionMap = computed(() => {
const productTableRows = computed ( ( ) => {
const out = [ ]
appendGroupRows ( out , filteredProductRows . value , 0 , [ ] )
appendGroupRows ( out , filteredProductRows . value , 0 , [ ] , groupLevels )
return out
} )
function appendGroupRows ( out , sourceRows , level , parentKeys ) {
if ( level > = groupLevels . length ) {
out . push ( ... sourceRows )
const productGroupKeys = computed ( ( ) => {
const keys = [ ]
collectGroupKeys ( keys , filteredProductRows . value , 0 , [ ] , groupLevels )
return keys
} )
const productAutoExpandKeys = computed ( ( ) => {
const keys = [ ]
collectGroupKeys ( keys , filteredProductRows . value , 0 , [ ] , activeGroupLevels . value , autoExpandThroughLevel . value )
return keys
} )
const allProductGroupsExpanded = computed ( ( ) => {
const keys = productAutoExpandKeys . value
return keys . length > 0 && keys . every ( key => expandedGroups . value [ key ] === true )
} )
function appendGroupRows ( out , sourceRows , level , parentKeys , levels = groupLevels ) {
if ( level >= levels . length ) {
out . push ( ... sortProductLeafRows ( sourceRows ) )
return
}
const groupDef = groupLevels [ level ]
const groupDef = levels [ level ]
const grouped = new Map ( )
for ( const row of sourceRows ) {
const value = normalizeGroupValue ( rawProductCellValue ( row , groupDef . key ) || row [ groupDef . fallback ] )
@@ -1526,12 +1909,45 @@ function appendGroupRows (out, sourceRows, level, parentKeys) {
const key = [ ... parentKeys , ` ${ groupDef . key } : ${ value } ` ] . join ( '|' )
out . push ( makeGroupRow ( key , level , groupDef , value , groupRows ) )
if ( isGroupExpanded ( key ) ) {
appendGroupRows ( out , groupRows , level + 1 , [ ... parentKeys , ` ${ groupDef . key } : ${ value } ` ] )
appendGroupRows ( out , groupRows , level + 1 , [ ... parentKeys , ` ${ groupDef . key } : ${ value } ` ] , levels )
}
} )
}
function collectGroupKeys ( out , sourceRows , level , parentKeys , levels = groupLevels , maxLevel = levels . length - 1 ) {
if ( level >= levels . length ) return
const groupDef = levels [ level ]
const grouped = new Map ( )
for ( const row of sourceRows ) {
const value = normalizeGroupValue ( rawProductCellValue ( row , groupDef . key ) || row [ groupDef . fallback ] )
if ( ! grouped . has ( value ) ) grouped . set ( value , [ ] )
grouped . get ( value ) . push ( row )
}
Array . from ( grouped . entries ( ) )
. sort ( ( a , b ) => a [ 0 ] . localeCompare ( b [ 0 ] , 'tr' ) )
. forEach ( ( [ value , groupRows ] ) => {
const key = [ ... parentKeys , ` ${ groupDef . key } : ${ value } ` ] . join ( '|' )
out . push ( key )
if ( level < maxLevel ) {
collectGroupKeys ( out , groupRows , level + 1 , [ ... parentKeys , ` ${ groupDef . key } : ${ value } ` ] , levels , maxLevel )
}
} )
}
function sortProductLeafRows ( sourceRows ) {
return [ ... sourceRows ] . sort ( ( a , b ) => {
const colorCmp = String ( a ? . color _code || '' ) . localeCompare ( String ( b ? . color _code || '' ) , 'tr' , { numeric : true } )
if ( colorCmp !== 0 ) return colorCmp
const yakaCmp = String ( a ? . yaka _kodu || '' ) . localeCompare ( String ( b ? . yaka _kodu || '' ) , 'tr' , { numeric : true } )
if ( yakaCmp !== 0 ) return yakaCmp
return String ( a ? . item _description || '' ) . localeCompare ( String ( b ? . item _description || '' ) , 'tr' , { numeric : true } )
} )
}
function makeGroupRow ( key , level , groupDef , value , groupRows ) {
const imageSource = groupRows . find ( row => row ? . product _code ) || { }
return {
_ _group : true ,
row _key : ` group| ${ key } ` ,
@@ -1539,8 +1955,11 @@ function makeGroupRow (key, level, groupDef, value, groupRows) {
level ,
group _field : groupDef . key ,
group _value : value ,
label : ` ${ groupDef . label } : ${ value } ` ,
label : value ,
count : groupRows . length ,
image _product _code : imageSource . product _code || '' ,
image _color _code : imageSource . color _code || '' ,
image _yaka _kodu : imageSource . yaka _kodu || '' ,
product _code : groupDef . key === 'product_code' ? value : distinctSummary ( groupRows , 'product_code' ) ,
color _code : groupDef . key === 'color_code' ? value : distinctSummary ( groupRows , 'color_code' ) ,
yaka _kodu : groupDef . key === 'yaka_kodu' ? value : distinctSummary ( groupRows , 'yaka_kodu' ) ,
@@ -1550,33 +1969,48 @@ function makeGroupRow (key, level, groupDef, value, groupRows) {
askili _yan : groupDef . key === 'askili_yan' ? value : distinctSummary ( groupRows , 'askili_yan' ) ,
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 : distinctSummary ( groupRows , 'market_key' ) ,
market _key : groupDef . key === 'market_key' ? value : distinctSummary ( groupRows , 'market_key' ) ,
stock _qty : sumRows ( groupRows , 'stock_qty' ) ,
sales _qty _90d : sumRows ( groupRows , 'sales_qty_90d' ) ,
sales _qty _180d : sumRows ( groupRows , 'sales_qty_180d' ) ,
sales _qty _365d : sumRows ( groupRows , 'sales_qty_365d' ) ,
sales _qty _total : sumRows ( groupRows , 'sales_qty_total' ) ,
sales _usd _90d : sumRows ( groupRows , 'sales_usd_90d' ) ,
sales _usd _180d : sumRows ( groupRows , 'sales_usd_180d' ) ,
sales _usd _365d : sumRows ( groupRows , 'sales_usd_365d' ) ,
sales _usd _total : sumRows ( groupRows , 'sales_usd_total' ) ,
gross _profit _usd _90d : sumRows ( groupRows , 'gross_profit_usd_90d' ) ,
gross _profit _usd _180d : sumRows ( groupRows , 'gross_profit_usd_180d' ) ,
gross _profit _usd _total : sumRows ( groupRows , 'gross_profit_usd_total' ) ,
market _count _90d : distinctCount ( groupRows , 'market_key' ) ,
customer _count _90d : sumRows ( groupRows , 'customer_count_90d' ) ,
market _count _total : sumRows ( groupRows , 'market_count_total' ) ,
customer _count _total : sumRows ( groupRows , 'customer_count_total' ) ,
avg _price _usd _90d : weightedAverage ( groupRows , 'sales_usd_90d' , 'sales_qty_90d' ) ,
avg _price _usd _180d : weightedAverage ( groupRows , 'sales_usd_180d' , 'sales_qty_180d' ) ,
avg _price _usd _365d : weightedAverage ( groupRows , 'sales_usd_365d' , 'sales_qty_365d' ) ,
avg _price _usd _total : weightedAverage ( groupRows , 'sales_usd_total' , 'sales_qty_total' ) ,
base _price _usd : weightedAverageOrAverage ( groupRows , 'base_price_usd' , 'sales_qty_90d' ) ,
cost _price _usd : weightedAverageOrAverage ( groupRows , 'cost_price_usd' , 'sales_qty_90d' ) ,
unit _profit _base _90d : weightedAverageOrAverage ( groupRows , 'unit_profit_base_90d' , 'sales_qty_90d' ) ,
unit _profit _cost _90d : weightedAverageOrAverage ( groupRows , 'unit_profit_cost_90d' , 'sales_qty_90d' ) ,
unit _profit _base _180d : weightedAverageOrAverage ( groupRows , 'unit_profit_base_180d' , 'sales_qty_180d' ) ,
unit _profit _cost _180d : weightedAverageOrAverage ( groupRows , 'unit_profit_cost_180d' , 'sales_qty_180d' ) ,
unit _profit _base _total : weightedAverageOrAverage ( groupRows , 'unit_profit_base_total' , 'sales_qty_total' ) ,
unit _profit _cost _total : weightedAverageOrAverage ( groupRows , 'unit_profit_cost_total' , 'sales_qty_total' ) ,
gross _margin _base _90d : marginFromRows ( groupRows , 'sales_usd_90d' , 'sales_qty_90d' , 'base_price_usd' ) ,
gross _margin _cost _90d : marginFromRows ( groupRows , 'sales_usd_90d' , 'sales_qty_90d' , 'cost_price_usd' ) ,
gross _margin _base _180d : marginFromRows ( groupRows , 'sales_usd_180d' , 'sales_qty_180d' , 'base_price_usd' ) ,
gross _margin _cost _180d : marginFromRows ( groupRows , 'sales_usd_180d' , 'sales_qty_180d' , 'cost_price_usd' ) ,
gross _margin _base _total : marginFromRows ( groupRows , 'sales_usd_total' , 'sales_qty_total' , 'base_price_usd' ) ,
gross _margin _cost _total : marginFromRows ( groupRows , 'sales_usd_total' , 'sales_qty_total' , 'cost_price_usd' ) ,
gross _margin _90d : ratio ( sumRows ( groupRows , 'gross_profit_usd_90d' ) , sumRows ( groupRows , 'sales_usd_90d' ) ) ,
gross _margin _180d : ratio ( sumRows ( groupRows , 'gross_profit_usd_180d' ) , sumRows ( groupRows , 'sales_usd_180d' ) ) ,
stock _days _90d : weightedAverageOrAverage ( groupRows , 'stock_days_90d' , 'stock_qty' ) ,
stock _days _180d : weightedAverageOrAverage ( groupRows , 'stock_days_180d' , 'stock_qty' ) ,
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' ) ,
recommendation : ` ${ formatNumber ( groupRows . length , 0 ) } satı r `
@@ -1688,12 +2122,33 @@ function toggleGroup (key) {
}
}
function toggleAllProductGroups ( ) {
const keys = productAutoExpandKeys . value
if ( allProductGroupsExpanded . value ) {
expandedGroups . value = { }
return
}
expandSelectedProductGroups ( )
}
function expandSelectedProductGroups ( ) {
const keys = productAutoExpandKeys . value
const next = { ... expandedGroups . value }
for ( const key of keys ) next [ key ] = true
expandedGroups . value = next
}
function groupLabelColumnName ( row ) {
return row ? . group _field || 'product_code'
}
function groupShowsImage ( row ) {
return Number ( row ? . level ? ? 0 ) < autoExpandThroughLevel . value
}
function groupCellClass ( name ) {
return [ 'stock_qty' , 'sales_qty_90d' , 'sales_qty_180d' , 'sales_usd_90d' , 'sales_usd_180d' , 'stock_days_90d' , 'stock_days_180d' , 'avg_price_usd_90d' , 'avg_price_usd_180d' , 'base_price_usd' , 'cost_price_usd' , 'unit_profit_base_90d' , 'unit_profit_cost_90d' , 'unit_profit_base_180d' , 'unit_profit_cost_180d' , 'gross_profit_usd_90d' , 'gross_profit_usd_180d' , 'gross_margin_base_90d' , 'gross_margin_cost_90d' , 'gross_margin_base_180d' , 'gross_margin_cost_180d' , 'gross_margin_90d' , 'gross_margin_180d' , 'market_count_90d' , 'customer_count_90d' , 'sales_index_90d' , 'performance_score' ] . includes ( name )
if ( name === 'image' ) return 'product-image-cell'
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' , '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 )
? 'text-right'
: ''
}
@@ -1707,6 +2162,7 @@ function formatGroupCell (row, name) {
function withProductMargins ( row ) {
return {
... row ,
avg _price _usd _365d : Number ( row ? . sales _qty _365d || 0 ) > 0 ? Number ( row ? . sales _usd _365d || 0 ) / Number ( row ? . sales _qty _365d || 0 ) : 0 ,
gross _margin _base _90d : marginFromSalesCost ( row ? . sales _usd _90d , row ? . sales _qty _90d , row ? . base _price _usd ) ,
gross _margin _cost _90d : marginFromSalesCost ( row ? . sales _usd _90d , row ? . sales _qty _90d , row ? . cost _price _usd ) ,
gross _margin _base _180d : marginFromSalesCost ( row ? . sales _usd _180d , row ? . sales _qty _180d , row ? . base _price _usd ) ,
@@ -1793,6 +2249,7 @@ function rawProductCellValue (row, name) {
switch ( name ) {
case 'urun_ilk_grubu' : return row . urun _ilk _grubu || row . yas _grubu || ''
case 'urun_ana_grubu' : return row . urun _ana _grubu || row . seri || ''
case 'market_key' : return displayMarketName ( row . market _key )
case 'performance_bucket' : return bucketLabel ( row . performance _bucket )
default : return row [ name ]
}
@@ -1803,6 +2260,7 @@ function rawColumnCellValue (row, col) {
switch ( name ) {
case 'urun_ilk_grubu' : return row . urun _ilk _grubu || row . yas _grubu || ''
case 'urun_ana_grubu' : return row . urun _ana _grubu || row . seri || ''
case 'market_key' : return displayMarketName ( row . market _key )
case 'performance_bucket' : return bucketLabel ( row . performance _bucket )
case 'is_overdue' : return row . is _overdue ? 'Evet' : 'Hayı r'
default :
@@ -1823,26 +2281,39 @@ function formatProductCell (row, name) {
case 'stock_qty' :
case 'sales_qty_90d' :
case 'sales_qty_180d' :
case 'sales_qty_365d' :
case 'sales_qty_total' :
case 'market_count_90d' :
case 'customer_count_90d' :
case 'market_count_total' :
case 'customer_count_total' :
return formatNumber ( row [ name ] , 0 )
case 'stock_days_90d' :
case 'stock_days_180d' :
case 'stock_days_total' :
case 'sales_index_90d' :
case 'sales_index_total' :
case 'performance_score' :
return formatNumber ( row [ name ] , name === 'performance_score' ? 1 : 2 )
case 'sales_usd_90d' :
case 'sales_usd_180d' :
case 'sales_usd_365d' :
case 'sales_usd_total' :
case 'avg_price_usd_90d' :
case 'avg_price_usd_180d' :
case 'avg_price_usd_365d' :
case 'avg_price_usd_total' :
case 'base_price_usd' :
case 'cost_price_usd' :
case 'unit_profit_base_90d' :
case 'unit_profit_cost_90d' :
case 'unit_profit_base_180d' :
case 'unit_profit_cost_180d' :
case 'unit_profit_base_total' :
case 'unit_profit_cost_total' :
case 'gross_profit_usd_90d' :
case 'gross_profit_usd_180d' :
case 'gross_profit_usd_total' :
return formatMoney ( row [ name ] , 'USD' )
case 'gross_margin_base_90d' :
case 'gross_margin_cost_90d' :
@@ -1857,6 +2328,8 @@ function formatProductCell (row, name) {
return row . urun _ilk _grubu || row . yas _grubu || ''
case 'urun_ana_grubu' :
return row . urun _ana _grubu || row . seri || ''
case 'market_key' :
return displayMarketName ( row . market _key )
default :
return rawProductCellValue ( row , name ) || ''
}
@@ -1873,6 +2346,35 @@ function normalizeRow (row) {
} )
}
function performanceVariantKey ( row ) {
return [
String ( row ? . product _code || '' ) . trim ( ) ,
String ( row ? . color _code || '' ) . trim ( ) ,
String ( row ? . yaka _kodu || '' ) . trim ( ) ,
displayMarketName ( row ? . market _key )
] . join ( '|' )
}
function generalMetricFields ( row ) {
return {
period _start : row ? . period _start || '' ,
period _end : row ? . period _end || '' ,
sales _qty _total : Number ( row ? . sales _qty _total || 0 ) ,
sales _usd _total : Number ( row ? . sales _usd _total || 0 ) ,
avg _daily _sales _total : Number ( row ? . avg _daily _sales _total || 0 ) ,
stock _days _total : Number ( row ? . stock _days _total || 0 ) ,
avg _price _usd _total : Number ( row ? . avg _price _usd _total || 0 ) ,
gross _profit _usd _total : Number ( row ? . gross _profit _usd _total || 0 ) ,
gross _margin _total : Number ( row ? . gross _margin _total || 0 ) ,
unit _profit _cost _total : Number ( row ? . unit _profit _cost _total || 0 ) ,
unit _profit _base _total : Number ( row ? . unit _profit _base _total || 0 ) ,
market _count _total : Number ( row ? . market _count _total || 0 ) ,
customer _count _total : Number ( row ? . customer _count _total || 0 ) ,
invoice _count _total : Number ( row ? . invoice _count _total || 0 ) ,
sales _index _total : Number ( row ? . sales _index _total || 0 )
}
}
function normalizeGeneralRow ( row ) {
const product = String ( row ? . product _code || '' ) . trim ( )
const color = String ( row ? . color _code || '' ) . trim ( )
@@ -1923,6 +2425,13 @@ function normalizeOrderMarketDetailRow (row) {
}
}
function normalizeSalesBreakdownRow ( row ) {
return {
... row ,
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 || '' } `
}
}
function orderGroupLabel ( row ) {
if ( row ? . breakdown === 'customer' ) {
return [ row . customer _code , row . customer _name ] . filter ( Boolean ) . join ( ' - ' ) || '-'
@@ -1931,12 +2440,17 @@ function orderGroupLabel (row) {
}
function productImageKey ( row ) {
const product = String ( row ? . product _code || '' ) . trim ( )
const color = String ( row ? . color _code || '' ) . trim ( )
const yaka = String ( row ? . yaka _kodu || '' ) . trim ( )
const product = String ( row ? . image _product _code || row ? . product _code || '' ) . trim ( )
const color = String ( row ? . image _color _code || row ? . color _code || '' ) . trim ( )
const yaka = String ( row ? . image _yaka _kodu || row ? . yaka _kodu || '' ) . trim ( )
return ` ${ product } | ${ color } | ${ yaka } `
}
function displayMarketName ( value ) {
const parts = String ( value || '' ) . split ( '|' ) . map ( part => part . trim ( ) ) . filter ( Boolean )
return parts . length ? parts [ parts . length - 1 ] : ''
}
function normalizeUploadsPath ( storagePath ) {
const raw = String ( storagePath || '' ) . trim ( )
if ( ! raw ) return ''
@@ -1976,11 +2490,11 @@ async function fetchProductImagesForRow (row) {
return imageListByKey . value [ key ]
}
const code = String ( row ? . product _code || '' ) . trim ( )
const code = String ( row ? . image _product _code || row ? . product _code || '' ) . trim ( )
if ( ! code ) return [ ]
const color = String ( row ? . color _code || '' ) . trim ( )
const yaka = String ( row ? . yaka _kodu || '' ) . trim ( )
const color = String ( row ? . image _color _code || row ? . color _code || '' ) . trim ( )
const yaka = String ( row ? . image _yaka _kodu || row ? . yaka _kodu || '' ) . trim ( )
try {
const resp = await api . get ( '/product-images' , {
@@ -2024,11 +2538,11 @@ async function openProductImageDialog (row) {
const urls = await fetchProductImagesForRow ( row )
imageDialogUrls . value = urls
imageSlide . value = 0
imageDialogTitle . value = String ( row ? . product _code || '-' )
imageDialogTitle . value = String ( row ? . image _product _code || row ? . product _code || '-' )
imageDialogSubtitle . value = [
row ? . item _description ,
row ? . color _code ? ` Renk: ${ row . color _code } ` : '' ,
row ? . yaka _kodu ? ` Yaka: ${ row . yaka _kodu } ` : ''
row ? . image _color _code || row ? . color _code ,
row ? . image _yaka _kodu || row ? . yaka _kodu
] . filter ( Boolean ) . join ( ' | ' )
imageDialog . value = true
}
@@ -2038,9 +2552,9 @@ async function openPerformanceDialog (row) {
performanceDialogTitle . value = String ( row ? . product _code || '-' )
performanceDialogSubtitle . value = [
row ? . item _description ,
row ? . color _code ? ` Renk: ${ row . color _code } ` : '' ,
row ? . yaka _kodu ? ` Yaka: ${ row . yaka _kodu } ` : '' ,
row ? . market _key ? ` Piyasa: ${ row . market _key } ` : ''
row ? . color _code ,
row ? . yaka _kodu ,
row ? . market _key
] . filter ( Boolean ) . join ( ' | ' )
performanceDialog . value = true
detailLoading . value = true
@@ -2071,7 +2585,13 @@ async function openPerformanceDialog (row) {
async function reload ( ) {
loading . value = true
try {
const [ summaryResp , listResp , generalResp , marketResp , countryResp , customerResp ] = await Promise . all ( [
const salesBreakdownRequests = salesBreakdownTabKeys . map ( tabKey => {
return api . get ( '/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' , {
params : {
@@ -2088,11 +2608,17 @@ async function reload () {
api . get ( '/pricing/product-performance/customers' , {
params : { limit : 500 , breakdown : customerBreakdown . value } ,
timeout : 60000
} )
} ) ,
... salesBreakdownRequests
] )
summary . value = summaryResp ? . data || { }
rows . value = ( Array . isArray ( listResp ? . data ? . rows ) ? listResp . data . rows : [ ] ) . map ( normalizeRow )
generalRows . value = ( Array . isArray ( generalResp ? . data ) ? generalResp . data : [ ] ) . map ( normalizeGeneralRow )
const normalizedGeneralRows = ( Array . isArray ( generalResp ? . data ) ? generalResp . data : [ ] ) . map ( normalizeGeneralRow )
const generalByVariant = new Map ( normalizedGeneralRows . map ( row => [ performanceVariantKey ( row ) , generalMetricFields ( row ) ] ) )
rows . value = ( Array . isArray ( listResp ? . data ? . rows ) ? listResp . data . rows : [ ] ) . map ( row => {
const generalMetrics = generalByVariant . get ( performanceVariantKey ( row ) ) || { }
return normalizeRow ( { ... row , ... generalMetrics } )
} )
generalRows . value = normalizedGeneralRows
marketRows . value = Array . isArray ( marketResp ? . data ) ? marketResp . data : [ ]
countryRows . value = ( Array . isArray ( countryResp ? . data ) ? countryResp . data : [ ] ) . map ( row => ( {
... row ,
@@ -2102,6 +2628,9 @@ async function reload () {
... row ,
customer _key : ` ${ row . breakdown || '-' } | ${ row . market _key || '-' } | ${ row . country || '-' } | ${ row . customer _segment || '-' } | ${ row . customer _code || '-' } `
} ) )
salesBreakdownTabKeys . forEach ( ( tabKey , index ) => {
salesBreakdownRows [ tabKey ] = ( Array . isArray ( salesBreakdownResp [ index ] ? . data ) ? salesBreakdownResp [ index ] . data : [ ] ) . map ( normalizeSalesBreakdownRow )
} )
pagination . value . rowsNumber = Number ( listResp ? . data ? . total _count || 0 )
if ( activeTab . value === 'order_product_customers' ) {
await loadOrderProductCustomers ( false )
@@ -2109,7 +2638,7 @@ async function reload () {
if ( activeTab . value === 'order_market_details' ) {
await loadOrderMarketDetails ( false )
}
void primeProductImages ( [ ... rows . value , ... generalRows . value ] )
void primeProductImages ( rows . value )
} catch ( err ) {
Notify . create ( { type : 'negative' , message : err ? . response ? . data || err ? . message || 'Ürün performans verisi alı namadı ' } )
} finally {
@@ -2180,6 +2709,7 @@ function bucketColor (value) {
case 'STOK_RISKI' : return 'orange-8'
case 'FIYAT_BASKISI' : return 'red-7'
case 'FIYAT_FIRSATI' : return 'teal-7'
case 'MALIYET_YOK' : return 'blue-grey-7'
default : return 'grey-7'
}
}
@@ -2193,6 +2723,14 @@ watch(activeTab, tab => {
}
} )
watch ( productTableRows , tableRows => {
if ( activeTab . value !== 'products' ) return
const imageRows = tableRows
. filter ( row => ! row . _ _group || groupShowsImage ( row ) )
. slice ( 0 , 240 )
void primeProductImages ( imageRows )
} , { immediate : true } )
onMounted ( reload )
< / script >
@@ -2212,6 +2750,11 @@ onMounted(reload)
margin - top : 4 px ;
}
. detail - level - menu {
width : 260 px ;
padding : 10 px ;
}
. performance - table {
max - height : calc ( 100 vh - 260 px ) ;
}
@@ -2229,6 +2772,20 @@ onMounted(reload)
. performance - table : deep ( . q - table td ) {
border - right : 1 px solid # d5dce5 ;
border - bottom : 1 px solid # d5dce5 ;
vertical - align : middle ;
white - space : normal ;
}
. performance - table : deep ( . q - table tbody td ) {
max - width : 220 px ;
}
. performance - table : deep ( . q - table tbody td span : not ( . q - badge ) : not ( . q - icon ) ) {
display : - webkit - box ;
- webkit - line - clamp : 4 ;
- webkit - box - orient : vertical ;
overflow : hidden ;
line - height : 1.25 ;
}
. performance - table : deep ( . q - table th : first - child ) ,
@@ -2244,6 +2801,113 @@ onMounted(reload)
border - top : 1 px solid # d5dce5 ;
}
. product - breakdown - table : deep ( . q - table ) {
min - width : 2050 px ;
table - layout : fixed ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( - n + 10 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( - n + 10 ) ) {
position : sticky ;
z - index : 2 ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( - n + 10 ) ) {
z - index : 4 ;
background : # f8fbff ;
}
. product - breakdown - table : deep ( . q - table tbody tr : not ( . group - row ) td : nth - child ( - n + 10 ) ) {
background : # fff ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 1 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 1 ) ) {
left : 0 ;
width : 80 px ;
min - width : 80 px ;
max - width : 80 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 2 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 2 ) ) {
left : 80 px ;
width : 96 px ;
min - width : 96 px ;
max - width : 96 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 3 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 3 ) ) {
left : 176 px ;
width : 48 px ;
min - width : 48 px ;
max - width : 48 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 4 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 4 ) ) {
left : 224 px ;
width : 48 px ;
min - width : 48 px ;
max - width : 48 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 5 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 5 ) ) {
left : 272 px ;
width : 150 px ;
min - width : 150 px ;
max - width : 150 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 6 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 6 ) ) {
left : 422 px ;
width : 72 px ;
min - width : 72 px ;
max - width : 72 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 7 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 7 ) ) {
left : 494 px ;
width : 72 px ;
min - width : 72 px ;
max - width : 72 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 8 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 8 ) ) {
left : 566 px ;
width : 92 px ;
min - width : 92 px ;
max - width : 92 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 9 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 9 ) ) {
left : 658 px ;
width : 96 px ;
min - width : 96 px ;
max - width : 96 px ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 10 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 10 ) ) {
left : 754 px ;
width : 96 px ;
min - width : 96 px ;
max - width : 96 px ;
box - shadow : 8 px 0 10 px - 10 px rgba ( 17 , 24 , 39 , 0.45 ) ;
}
. product - breakdown - table : deep ( . q - table th : nth - child ( 11 ) ) ,
. product - breakdown - table : deep ( . q - table td : nth - child ( 11 ) ) {
width : 180 px ;
min - width : 140 px ;
}
. filterable - header - cell {
vertical - align : middle ;
}
@@ -2253,7 +2917,7 @@ onMounted(reload)
grid - template - columns : minmax ( 0 , 1 fr ) 20 px ;
align - items : center ;
gap : 2 px ;
min - width : 78 px ;
min - width : 42 px ;
}
. header - with - filter > span {
@@ -2305,47 +2969,47 @@ onMounted(reload)
}
. group - row td {
background : # eaf4ff ;
background : color - mix ( in srgb , var ( -- q - primary ) 8 % , # ffffff ) ;
font - weight : 600 ;
color : # 17324 f ;
border - color : # c3d4e6 ;
color : color - mix ( in srgb , var ( -- q - primary ) 55 % , # 1 f2937 ) ;
border - color : color - mix ( in srgb , var ( -- q - primary ) 18 % , # d5dce5 ) ;
white - space : nowrap ;
}
. group - row - level - 0 td {
background : # 1565 c0 ;
background : color - mix ( in srgb , var ( -- q - primary ) 82 % , # 1 f2937 ) ;
color : # fff ;
border - color : # 0 d47a1 ;
border - color : color - mix ( in srgb , var ( -- q - primary ) 75 % , # 111827 ) ;
}
. group - row - level - 1 td {
background : # 2 f7fd0 ;
background : color - mix ( in srgb , var ( -- q - primary ) 68 % , # ffffff ) ;
color : # fff ;
border - color : # 1565 c0 ;
border - color : color - mix ( in srgb , var ( -- q - primary ) 58 % , # d5dce5 ) ;
}
. group - row - level - 2 td {
background : # 6 da9df ;
color : # 0 c2f52 ;
border - color : # 3 c83c7 ;
background : color - mix ( in srgb , var ( -- q - primary ) 36 % , # ffffff ) ;
color : color - mix ( in srgb , var ( -- q - primary ) 65 % , # 111827 ) ;
border - color : color - mix ( in srgb , var ( -- q - primary ) 34 % , # d5dce5 ) ;
}
. group - row - level - 3 td {
background : # a9cdef ;
color : # 12385 c ;
border - color : # 78 addb ;
background : color - mix ( in srgb , var ( -- q - primary ) 22 % , # ffffff ) ;
color : color - mix ( in srgb , var ( -- q - primary ) 58 % , # 1 f2937 ) ;
border - color : color - mix ( in srgb , var ( -- q - primary ) 24 % , # d5dce5 ) ;
}
. group - row - level - 4 td {
background : # d1e5f8 ;
color : # 17324 f ;
border - color : # 9 fc5e8 ;
background : color - mix ( in srgb , var ( -- q - primary ) 14 % , # ffffff ) ;
color : color - mix ( in srgb , var ( -- q - primary ) 52 % , # 1 f2937 ) ;
border - color : color - mix ( in srgb , var ( -- q - primary ) 18 % , # d5dce5 ) ;
}
. group - row - level - 5 td {
background : # eaf4ff ;
color : # 17324 f ;
border - color : # c3d4e6 ;
background : color - mix ( in srgb , var ( -- q - primary ) 8 % , # ffffff ) ;
color : color - mix ( in srgb , var ( -- q - primary ) 48 % , # 1 f2937 ) ;
border - color : color - mix ( in srgb , var ( -- q - primary ) 14 % , # d5dce5 ) ;
}
. group - cell - label {
@@ -2360,24 +3024,27 @@ onMounted(reload)
}
. product - image - cell {
width : 72 px ;
min - width : 72 px ;
width : 80 px ;
min - width : 80 px ;
}
. product - thumb - button {
width : 56 px ;
height : 72 px ;
width : 68 px ;
height : 92 px ;
border : 1 px solid # d9dde3 ;
border - radius : 6 px ;
background : # fff ;
padding : 2 px ;
padding : 4 px ;
cursor : pointer ;
display : flex ;
align - items : center ;
justify - content : center ;
}
. product - thumb ,
. product - thumb - placeholder {
width : 50 px ;
height : 66 px ;
width : 58 px ;
height : 82 px ;
}
. product - thumb - placeholder {