From a0dd832d2b68c4458f6d7729d74ecc13dd1d976c Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Fri, 3 Jul 2026 03:29:31 +0300 Subject: [PATCH] product performance grouped kpi improvements --- svc/queries/product_performance.go | 48 ++++------ svc/queries/product_performance_sql.go | 4 +- .../pages/ProductPerformanceProfitability.vue | 88 ++++++++++++------- 3 files changed, 77 insertions(+), 63 deletions(-) diff --git a/svc/queries/product_performance.go b/svc/queries/product_performance.go index bd56095..4701d93 100644 --- a/svc/queries/product_performance.go +++ b/svc/queries/product_performance.go @@ -1133,7 +1133,7 @@ Dim AS ( color_code, yaka_kodu, item_description, - COALESCE(NULLIF(kategori,''), yas_grubu, '') AS kategori, + COALESCE(kategori,'') AS kategori, seri, yas_grubu, askili_yan, @@ -2311,9 +2311,9 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break "color_code": "s.color_code", "yaka_kodu": "s.yaka_kodu", "item_description": "MAX(s.item_description)", - "kategori": "COALESCE(NULLIF(MAX(s.kategori),''), MAX(s.yas_grubu), '')", + "kategori": "COALESCE(MAX(s.kategori),'')", "askili_yan": "MAX(s.askili_yan)", - "urun_ilk_grubu": "COALESCE(NULLIF(MAX(s.seri),''), MAX(s.urun_ilk_grubu), '')", + "urun_ilk_grubu": "COALESCE(MAX(s.urun_ilk_grubu),'')", "urun_ana_grubu": "COALESCE(MAX(s.urun_ana_grubu),'')", "urun_alt_grubu": "MAX(s.urun_alt_grubu)", "market_key": "s.market_key", @@ -2340,7 +2340,7 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break selected["market_key"] = true selected["customer_code"] = true selected["customer_name"] = true - groupCols = []string{"s.color_code", "s.yaka_kodu", "s.askili_yan", "COALESCE(NULLIF(s.seri,''), s.urun_ilk_grubu, '')", "s.urun_ana_grubu", "s.urun_alt_grubu", "s.product_code", "s.customer_country", "s.market_key", "s.customer_segment", "s.customer_code"} + groupCols = []string{"s.color_code", "s.yaka_kodu", "s.urun_ilk_grubu", "s.askili_yan", "s.kategori", "s.urun_ana_grubu", "s.urun_alt_grubu", "s.product_code", "s.customer_country", "s.market_key", "s.customer_segment", "s.customer_code"} case "product_country_segment_market_customer": selected["product_code"] = true selected["item_description"] = true @@ -2403,7 +2403,7 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break selected["market_key"] = true selected["customer_code"] = true selected["customer_name"] = true - groupCols = []string{"s.color_code", "s.yaka_kodu", "s.askili_yan", "COALESCE(NULLIF(s.seri,''), s.urun_ilk_grubu, '')", "s.urun_ana_grubu", "s.urun_alt_grubu", "s.product_code", "s.customer_country", "s.market_key", "s.customer_segment", "s.customer_code"} + groupCols = []string{"s.color_code", "s.yaka_kodu", "s.urun_ilk_grubu", "s.askili_yan", "s.kategori", "s.urun_ana_grubu", "s.urun_alt_grubu", "s.product_code", "s.customer_country", "s.market_key", "s.customer_segment", "s.customer_code"} } selectExpr := func(name string) string { @@ -2419,9 +2419,9 @@ WITH LatestKPI AS ( product_code, color_code, yaka_kodu, - COALESCE(NULLIF(kategori,''), yas_grubu, '') AS kategori, + COALESCE(kategori,'') AS kategori, askili_yan, - COALESCE(NULLIF(seri,''), urun_ilk_grubu, '') AS urun_ilk_grubu, + COALESCE(urun_ilk_grubu,'') AS urun_ilk_grubu, COALESCE(urun_ana_grubu,'') AS urun_ana_grubu, urun_alt_grubu FROM mk_product_performance_kpi_daily @@ -3016,7 +3016,7 @@ WITH Source AS ( seri, yas_grubu, askili_yan, - COALESCE(NULLIF(seri,''), urun_ilk_grubu, '') AS urun_ilk_grubu, + COALESCE(urun_ilk_grubu,'') AS urun_ilk_grubu, COALESCE(urun_ana_grubu,'') AS urun_ana_grubu, urun_alt_grubu, market_key, @@ -3479,38 +3479,26 @@ func sanitizeProductPerformanceGroupLevels(levels []string) []string { func defaultProductPerformanceGroupLevels(mode string) []string { switch mode { case "sales_color_yaka_market_customer": - return []string{"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"} + return []string{"color_code", "yaka_kodu", "urun_ilk_grubu", "askili_yan", "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "country", "market_key", "customer_segment", "customer_code", "customer_name"} case "idle": - return []string{"askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"} + return []string{"urun_ilk_grubu", "askili_yan", "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"} case "sales_product_country_segment_market_customer": - return []string{"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"} + return []string{"market_key", "customer_code", "customer_name", "urun_ilk_grubu", "askili_yan", "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"} case "sales_market_customer_product": - return []string{"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"} + return []string{"market_key", "customer_code", "customer_name", "urun_ilk_grubu", "askili_yan", "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"} case "sales_country_segment_market_customer_product": - return []string{"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"} + return []string{"market_key", "customer_code", "customer_name", "urun_ilk_grubu", "askili_yan", "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"} case "order_product_customers": - return []string{"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"} + return []string{"market_key", "customer_code", "customer_name", "urun_ilk_grubu", "askili_yan", "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"} case "order_market_details": - return []string{"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"} + return []string{"market_key", "customer_code", "customer_name", "urun_ilk_grubu", "askili_yan", "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"} default: - return []string{"kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu", "market_key"} + return []string{"urun_ilk_grubu", "askili_yan", "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu", "market_key"} } } func mapGroupValue(row map[string]any, field string) string { switch field { - case "kategori": - if value := stringFromMap(row, "kategori"); value != "" { - return value - } - return stringFromMap(row, "yas_grubu") - case "urun_ilk_grubu": - if value := stringFromMap(row, "seri"); value != "" { - return value - } - return stringFromMap(row, "urun_ilk_grubu") - case "urun_ana_grubu": - return stringFromMap(row, "urun_ana_grubu") case "market_key": return displayProductPerformanceMarketName(stringFromMap(row, "market_key")) default: @@ -3972,9 +3960,9 @@ SELECT DISTINCT ON (product_code, color_code, yaka_kodu) color_code, yaka_kodu, COALESCE(item_description,'') AS item_description, - COALESCE(NULLIF(kategori,''), yas_grubu, '') AS kategori, + COALESCE(kategori,'') AS kategori, COALESCE(askili_yan,'') AS askili_yan, - COALESCE(NULLIF(seri,''), urun_ilk_grubu, '') AS urun_ilk_grubu, + COALESCE(urun_ilk_grubu,'') AS urun_ilk_grubu, COALESCE(urun_ana_grubu,'') AS urun_ana_grubu, COALESCE(urun_alt_grubu,'') AS urun_alt_grubu FROM mk_product_performance_kpi_daily diff --git a/svc/queries/product_performance_sql.go b/svc/queries/product_performance_sql.go index 11b2576..7886f97 100644 --- a/svc/queries/product_performance_sql.go +++ b/svc/queries/product_performance_sql.go @@ -194,8 +194,8 @@ SELECT YakaKodu, ItemDescription = MAX(ItemDescription), Kategori = MAX(Kategori), - Seri = MAX(Kategori), - YasGrubu = MAX(UrunIlkGrubu), + Seri = '', + YasGrubu = '', AskiliYan = MAX(AskiliYan), UrunIlkGrubu = MAX(UrunIlkGrubu), UrunAnaGrubu = MAX(UrunAnaGrubu), diff --git a/ui/src/pages/ProductPerformanceProfitability.vue b/ui/src/pages/ProductPerformanceProfitability.vue index 706d14e..602791a 100644 --- a/ui/src/pages/ProductPerformanceProfitability.vue +++ b/ui/src/pages/ProductPerformanceProfitability.vue @@ -1524,14 +1524,14 @@ const columnFilterSearch = reactive({}) const expandedGroups = ref({}) const lastManualExpandedGroupKey = ref('') const selectedExpandLevelKeysByTab = reactive({ - products: ['kategori', 'askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu'], - sales_color_yaka_market_customer: ['color_code', 'yaka_kodu', 'askili_yan', 'urun_ilk_grubu'], - idle: ['askili_yan', 'urun_ilk_grubu', 'urun_ana_grubu', 'urun_alt_grubu'], - sales_product_country_segment_market_customer: ['market_key', 'customer_code', 'customer_name', 'kategori'], - sales_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'kategori'], - sales_country_segment_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'kategori'], - order_product_customers: ['market_key', 'customer_code', 'customer_name', 'kategori'], - order_market_details: ['market_key', 'customer_code', 'customer_name', 'kategori'] + products: ['urun_ilk_grubu', 'askili_yan', 'kategori', 'urun_ana_grubu', 'urun_alt_grubu'], + sales_color_yaka_market_customer: ['color_code', 'yaka_kodu', 'urun_ilk_grubu', 'askili_yan'], + idle: ['urun_ilk_grubu', 'askili_yan', 'kategori', 'urun_ana_grubu', 'urun_alt_grubu'], + sales_product_country_segment_market_customer: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'], + sales_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'], + sales_country_segment_market_customer_product: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'], + order_product_customers: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'], + order_market_details: ['market_key', 'customer_code', 'customer_name', 'urun_ilk_grubu', 'askili_yan'] }) const productKpiFetchLimit = 50000 const maxBulkExpandKeys = 1200 @@ -1603,9 +1603,9 @@ const detailSalesColumns = [ const columns = [ { name: 'image', label: 'Foto', field: 'image', align: 'center' }, - { 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 => productFirstGroupValue(row), align: 'left' }, + { name: 'askili_yan', label: 'Askılı/Yan', field: 'askili_yan', align: 'left' }, + { name: 'kategori', label: 'Kategori', field: row => productCategoryValue(row), align: 'left' }, { name: 'urun_ana_grubu', label: 'Ürün Ana Grubu', field: row => productMainGroupValue(row), 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 }, @@ -1697,6 +1697,14 @@ const dimensionColumnNames = [ 'customer_name' ] +const productHierarchyColumnOrder = [ + 'urun_ilk_grubu', + 'askili_yan', + 'kategori', + 'urun_ana_grubu', + 'urun_alt_grubu' +] + const metricOrderFragments = [ 'customer_score', 'performance_score', @@ -1767,7 +1775,7 @@ function nonPeriodColumnOrder (name) { } function orderedMetricColumns (sourceColumns) { - return sourceColumns.filter(col => !hiddenTableColumns.has(col.name)).sort((a, b) => { + const sorted = sourceColumns.filter(col => !hiddenTableColumns.has(col.name)).sort((a, b) => { const aPeriod = columnPeriod(a.name) const bPeriod = columnPeriod(b.name) const aTrailing = isTrailingInfoColumn(a.name) @@ -1783,6 +1791,22 @@ function orderedMetricColumns (sourceColumns) { const metricDiff = metricOrder(a.name) - metricOrder(b.name) return metricDiff || 0 }) + return normalizeProductHierarchyColumnOrder(sorted) +} + +function normalizeProductHierarchyColumnOrder (sourceColumns) { + const positions = sourceColumns + .map((col, index) => productHierarchyColumnOrder.includes(col.name) ? index : -1) + .filter(index => index >= 0) + if (positions.length < 2) return sourceColumns + const ordered = positions + .map(index => sourceColumns[index]) + .sort((a, b) => productHierarchyColumnOrder.indexOf(a.name) - productHierarchyColumnOrder.indexOf(b.name)) + const next = [...sourceColumns] + positions.forEach((position, index) => { + next[position] = ordered[index] + }) + return next } function reorderColumnsInPlace (targetColumns) { @@ -2292,9 +2316,9 @@ const imageDialogInfoRows = computed(() => { }) const groupLevels = [ - { key: 'kategori', label: 'Kategori', fallback: 'yas_grubu' }, + { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' }, { key: 'askili_yan', label: 'Askılı/Yan' }, - { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'seri' }, + { key: 'kategori', label: 'Kategori' }, { key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' }, { key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' }, { key: 'product_code', label: 'Ürün' }, @@ -2308,8 +2332,9 @@ const tabGroupLevels = { sales_color_yaka_market_customer: [ { key: 'color_code', label: 'Renk' }, { key: 'yaka_kodu', label: 'Yaka' }, + { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' }, { key: 'askili_yan', label: 'Askılı/Yan' }, - { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'seri' }, + { key: 'kategori', label: 'Kategori' }, { key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' }, { key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' }, { key: 'product_code', label: 'Ürün' }, @@ -2320,8 +2345,9 @@ const tabGroupLevels = { { key: 'customer_name', label: 'Müşteri' } ], idle: [ + { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' }, { key: 'askili_yan', label: 'Askılı/Yan' }, - { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'seri' }, + { key: 'kategori', label: 'Kategori' }, { key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' }, { key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' }, { key: 'product_code', label: 'Ürün' }, @@ -2332,9 +2358,9 @@ const tabGroupLevels = { { key: 'market_key', label: 'Piyasa' }, { key: 'customer_code', label: 'Müşteri Kodu' }, { key: 'customer_name', label: 'Müşteri' }, - { key: 'kategori', label: 'Kategori', fallback: 'yas_grubu' }, + { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' }, { key: 'askili_yan', label: 'Askılı/Yan' }, - { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'seri' }, + { key: 'kategori', label: 'Kategori' }, { key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' }, { key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' }, { key: 'product_code', label: 'Ürün' }, @@ -2345,9 +2371,9 @@ const tabGroupLevels = { { key: 'market_key', label: 'Piyasa' }, { key: 'customer_code', label: 'Müşteri Kodu' }, { key: 'customer_name', label: 'Müşteri' }, - { key: 'kategori', label: 'Kategori', fallback: 'yas_grubu' }, + { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' }, { key: 'askili_yan', label: 'Askılı/Yan' }, - { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'seri' }, + { key: 'kategori', label: 'Kategori' }, { key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' }, { key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' }, { key: 'product_code', label: 'Ürün' }, @@ -2358,9 +2384,9 @@ const tabGroupLevels = { { key: 'market_key', label: 'Piyasa' }, { key: 'customer_code', label: 'Müşteri Kodu' }, { key: 'customer_name', label: 'Müşteri' }, - { key: 'kategori', label: 'Kategori', fallback: 'yas_grubu' }, + { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' }, { key: 'askili_yan', label: 'Askılı/Yan' }, - { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'seri' }, + { key: 'kategori', label: 'Kategori' }, { key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' }, { key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' }, { key: 'product_code', label: 'Ürün' }, @@ -2371,9 +2397,9 @@ const tabGroupLevels = { { key: 'market_key', label: 'Piyasa' }, { key: 'customer_code', label: 'Müşteri Kodu' }, { key: 'customer_name', label: 'Müşteri' }, - { key: 'kategori', label: 'Kategori', fallback: 'yas_grubu' }, + { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' }, { key: 'askili_yan', label: 'Askılı/Yan' }, - { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'seri' }, + { key: 'kategori', label: 'Kategori' }, { key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' }, { key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' }, { key: 'product_code', label: 'Ürün' }, @@ -2384,9 +2410,9 @@ const tabGroupLevels = { { key: 'market_key', label: 'Piyasa' }, { key: 'customer_code', label: 'Müşteri Kodu' }, { key: 'customer_name', label: 'Müşteri' }, - { key: 'kategori', label: 'Kategori', fallback: 'yas_grubu' }, + { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu' }, { key: 'askili_yan', label: 'Askılı/Yan' }, - { key: 'urun_ilk_grubu', label: 'Ürün İlk Grubu', fallback: 'seri' }, + { key: 'kategori', label: 'Kategori' }, { key: 'urun_ana_grubu', label: 'Ürün Ana Grubu' }, { key: 'urun_alt_grubu', label: 'Ürün Alt Grubu' }, { key: 'product_code', label: 'Ürün' }, @@ -2521,7 +2547,7 @@ function appendGroupRows (out, sourceRows, level, parentKeys, levels = groupLeve const groupDef = levels[level] const grouped = new Map() for (const row of sourceRows) { - const value = normalizeGroupValue(rawProductCellValue(row, groupDef.key) || row[groupDef.fallback]) + const value = normalizeGroupValue(rawProductCellValue(row, groupDef.key)) if (!grouped.has(value)) grouped.set(value, []) grouped.get(value).push(row) } @@ -2557,7 +2583,7 @@ function collectGroupKeys (out, sourceRows, level, parentKeys, levels = groupLev const groupDef = levels[level] const grouped = new Map() for (const row of sourceRows) { - const value = normalizeGroupValue(rawProductCellValue(row, groupDef.key) || row[groupDef.fallback]) + const value = normalizeGroupValue(rawProductCellValue(row, groupDef.key)) if (!grouped.has(value)) grouped.set(value, []) grouped.get(value).push(row) } @@ -2603,8 +2629,8 @@ function makeGroupRow (key, level, groupDef, value, groupRows) { color_code: groupDef.key === 'color_code' ? value : distinctSummary(groupRows, 'color_code'), yaka_kodu: groupDef.key === 'yaka_kodu' ? value : distinctSummary(groupRows, 'yaka_kodu'), item_description: distinctSummary(groupRows, 'item_description'), - kategori: groupDef.key === 'kategori' ? value : distinctSummary(groupRows, 'kategori', 'yas_grubu'), - urun_ilk_grubu: groupDef.key === 'urun_ilk_grubu' ? value : distinctSummary(groupRows, 'seri', 'urun_ilk_grubu'), + kategori: groupDef.key === 'kategori' ? value : distinctSummary(groupRows, 'kategori'), + urun_ilk_grubu: groupDef.key === 'urun_ilk_grubu' ? value : distinctSummary(groupRows, 'urun_ilk_grubu'), 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'), urun_alt_grubu: groupDef.key === 'urun_alt_grubu' ? value : distinctSummary(groupRows, 'urun_alt_grubu'), @@ -3195,11 +3221,11 @@ function columnFilterLabel (row, col) { } function productCategoryValue (row) { - return row?.kategori || row?.yas_grubu || '' + return row?.kategori || '' } function productFirstGroupValue (row) { - return row?.seri || row?.urun_ilk_grubu || '' + return row?.urun_ilk_grubu || '' } function productMainGroupValue (row) {