From 4c56062ef4b3fc6d660409b3d14789d326199692 Mon Sep 17 00:00:00 2001 From: M_Kececi Date: Sat, 4 Jul 2026 13:45:43 +0300 Subject: [PATCH] Fix product performance grouped JSON build --- svc/queries/product_performance.go | 51 ++++++++++++++----- svc/routes/product_performance.go | 2 +- .../pages/ProductPerformanceProfitability.vue | 50 +++++++++++------- 3 files changed, 73 insertions(+), 30 deletions(-) diff --git a/svc/queries/product_performance.go b/svc/queries/product_performance.go index 9721aba..a0e37cd 100644 --- a/svc/queries/product_performance.go +++ b/svc/queries/product_performance.go @@ -3239,16 +3239,16 @@ SELECT ( 'image_product_code', image_product_code, 'image_color_code', image_color_code, 'image_yaka_kodu', image_yaka_kodu, - 'product_code', CASE WHEN $%d = 'product_code' THEN group_value ELSE product_code END, - 'color_code', CASE WHEN $%d = 'color_code' THEN group_value ELSE color_code END, - 'yaka_kodu', CASE WHEN $%d = 'yaka_kodu' THEN group_value ELSE yaka_kodu END, - 'item_description', item_description, - 'kategori', CASE WHEN $%d = 'kategori' THEN group_value ELSE kategori END, - 'askili_yan', CASE WHEN $%d = 'askili_yan' THEN group_value ELSE askili_yan END, - 'urun_ilk_grubu', CASE WHEN $%d = 'urun_ilk_grubu' THEN group_value ELSE urun_ilk_grubu END, - 'urun_ana_grubu', CASE WHEN $%d = 'urun_ana_grubu' THEN group_value ELSE urun_ana_grubu END, - 'urun_alt_grubu', CASE WHEN $%d = 'urun_alt_grubu' THEN group_value ELSE urun_alt_grubu END, - 'market_key', CASE WHEN $%d = 'market_key' THEN group_value ELSE market_key END + 'product_code', CASE WHEN $%d = 'product_code' THEN group_value ELSE '' END, + 'color_code', CASE WHEN $%d = 'color_code' THEN group_value ELSE '' END, + 'yaka_kodu', CASE WHEN $%d = 'yaka_kodu' THEN group_value ELSE '' END, + 'item_description', CASE WHEN $%d = 'item_description' THEN group_value ELSE '' END, + 'kategori', CASE WHEN $%d = 'kategori' THEN group_value ELSE '' END, + 'askili_yan', CASE WHEN $%d = 'askili_yan' THEN group_value ELSE '' END, + 'urun_ilk_grubu', CASE WHEN $%d = 'urun_ilk_grubu' THEN group_value ELSE '' END, + 'urun_ana_grubu', CASE WHEN $%d = 'urun_ana_grubu' THEN group_value ELSE '' END, + 'urun_alt_grubu', CASE WHEN $%d = 'urun_alt_grubu' THEN group_value ELSE '' END, + 'market_key', CASE WHEN $%d = 'market_key' THEN group_value ELSE '' END ) || jsonb_build_object( 'stock_qty', stock_qty, @@ -3377,7 +3377,7 @@ FROM ( GROUP BY COALESCE(%s, '') ) g ORDER BY group_value -`, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, groupExpr, groupExpr, whereSQL, groupExpr) +`, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, groupExpr, groupExpr, whereSQL, groupExpr) args = append(args, field) return queryProductPerformanceJSONRows(ctx, pg, query, args...) } @@ -3678,6 +3678,7 @@ func appendProductPerformanceGroupedRows(out *[]map[string]any, sourceRows []map func makeProductPerformanceGroupedRow(key string, level int, field, value string, rows []map[string]any) map[string]any { row := aggregateProductPerformanceRows(rows, field) + clearProductPerformanceGroupDimensions(row, field, value) row["__group"] = true row["row_key"] = "group|" + key row["key"] = key @@ -3686,7 +3687,6 @@ func makeProductPerformanceGroupedRow(key string, level int, field, value string row["group_value"] = value row["label"] = value row["count"] = len(rows) - row[field] = value row["recommendation"] = fmt.Sprintf("%d satır", len(rows)) image := firstProductPerformanceImageSource(rows) @@ -3696,6 +3696,33 @@ func makeProductPerformanceGroupedRow(key string, level int, field, value string return row } +var productPerformanceGroupDimensionFields = map[string]bool{ + "product_code": true, + "color_yaka": true, + "color_code": true, + "yaka_kodu": true, + "item_description": true, + "kategori": true, + "askili_yan": true, + "urun_ilk_grubu": true, + "urun_ana_grubu": true, + "urun_alt_grubu": true, + "market_key": true, + "country": true, + "customer_segment": true, + "customer_code": true, + "customer_name": true, +} + +func clearProductPerformanceGroupDimensions(row map[string]any, groupField, groupValue string) { + for field := range productPerformanceGroupDimensionFields { + row[field] = "" + } + if productPerformanceGroupDimensionFields[groupField] { + row[groupField] = groupValue + } +} + func aggregateProductPerformanceRows(rows []map[string]any, groupField string) map[string]any { out := map[string]any{} for _, row := range rows { diff --git a/svc/routes/product_performance.go b/svc/routes/product_performance.go index 824768e..f453379 100644 --- a/svc/routes/product_performance.go +++ b/svc/routes/product_performance.go @@ -273,7 +273,7 @@ func GetProductPerformanceGroupedHandler(pg *sql.DB) http.HandlerFunc { groupLevels := splitCSVQuery(r.URL.Query().Get("group_levels")) limit := intQuery(r, "limit", 50000) expanded := map[string]bool{} - const maxExpandedGroupKeys = 200 + const maxExpandedGroupKeys = 260 addExpandedKey := func(key string) { key = strings.TrimSpace(key) if key != "" && len(expanded) < maxExpandedGroupKeys { diff --git a/ui/src/pages/ProductPerformanceProfitability.vue b/ui/src/pages/ProductPerformanceProfitability.vue index c6f919c..f6683d6 100644 --- a/ui/src/pages/ProductPerformanceProfitability.vue +++ b/ui/src/pages/ProductPerformanceProfitability.vue @@ -35,6 +35,7 @@ class="period-selector bg-white q-px-xs q-py-none" /> row?.product_code) || {} const aggregate = aggregateGroupFields(groupRows, groupDef.key) - const colorSummary = groupDef.key === 'color_code' ? value : distinctSummary(groupRows, 'color_code') - const yakaSummary = groupDef.key === 'yaka_kodu' ? value : distinctSummary(groupRows, 'yaka_kodu') - const colorDescriptionSummary = distinctSummary(groupRows, 'color_description') + const colorSummary = groupDef.key === 'color_code' ? value : '' + const yakaSummary = groupDef.key === 'yaka_kodu' ? value : '' + const colorDescriptionSummary = groupDef.key === 'color_code' ? distinctSummary(groupRows, 'color_description') : '' return { __group: true, row_key: `group|${key}`, @@ -2956,21 +2959,21 @@ function makeGroupRow (key, level, groupDef, value, groupRows) { image_color_code: imageSource.color_code || '', image_color_description: imageSource.color_description || '', image_yaka_kodu: imageSource.yaka_kodu || '', - product_code: groupDef.key === 'product_code' ? value : distinctSummary(groupRows, 'product_code'), + product_code: groupDef.key === 'product_code' ? value : '', color_code: colorSummary, color_description: colorDescriptionSummary, yaka_kodu: yakaSummary, - item_description: distinctSummary(groupRows, 'item_description'), - 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'), - market_key: groupDef.key === 'market_key' ? value : distinctSummary(groupRows, 'market_key'), - country: groupDef.key === 'country' ? value : distinctSummary(groupRows, 'country'), - customer_segment: groupDef.key === 'customer_segment' ? value : distinctSummary(groupRows, 'customer_segment'), - customer_code: groupDef.key === 'customer_code' ? value : distinctSummary(groupRows, 'customer_code'), - customer_name: groupDef.key === 'customer_name' ? value : distinctSummary(groupRows, 'customer_name'), + item_description: groupDef.key === 'item_description' ? value : '', + kategori: groupDef.key === 'kategori' ? value : '', + urun_ilk_grubu: groupDef.key === 'urun_ilk_grubu' ? value : '', + askili_yan: groupDef.key === 'askili_yan' ? value : '', + urun_ana_grubu: groupDef.key === 'urun_ana_grubu' ? value : '', + urun_alt_grubu: groupDef.key === 'urun_alt_grubu' ? value : '', + market_key: groupDef.key === 'market_key' ? value : '', + country: groupDef.key === 'country' ? value : '', + customer_segment: groupDef.key === 'customer_segment' ? value : '', + customer_code: groupDef.key === 'customer_code' ? value : '', + customer_name: groupDef.key === 'customer_name' ? value : '', stock_qty: sumRows(groupRows, 'stock_qty'), sales_qty_90d: sumRows(groupRows, 'sales_qty_90d'), sales_qty_180d: sumRows(groupRows, 'sales_qty_180d'), @@ -3392,6 +3395,8 @@ function toggleGroup (key) { } async function collapseAllProductGroups () { + if (bulkExpandLoading.value) return + detailLevelMenuOpen.value = false const tabPrefix = `tab:${activeTab.value}|` expandedGroups.value = Object.fromEntries( Object.entries(expandedGroups.value).filter(([key]) => !key.startsWith(tabPrefix)) @@ -3407,6 +3412,8 @@ async function collapseAllProductGroups () { } async function expandSelectedProductGroups () { + if (bulkExpandLoading.value) return + detailLevelMenuOpen.value = false const keys = productAutoExpandKeys.value const maxKeys = Math.min(maxBulkExpandKeys, maxBackendExpandedKeysForTab(activeTab.value)) const expandableKeys = keys.slice(0, maxKeys) @@ -4055,6 +4062,7 @@ function resolveProductImageUrl (item) { async function fetchProductImagesForRow (row) { if (!hasProductVariantImageKey(row)) return [] const key = productImageKey(row) + if (imageFailedKeys.value.has(key)) return [] if (Object.prototype.hasOwnProperty.call(imageListByKey.value, key)) { return imageListByKey.value[key] } @@ -4083,6 +4091,11 @@ async function fetchProductImagesForRow (row) { const list = Array.isArray(resp?.data) ? resp.data : [] const urls = list.map(resolveProductImageUrl).filter(Boolean) performanceStore.setImageCache(key, urls) + if (urls.length && imageFailedKeys.value.has(key)) { + const nextFailed = new Set(imageFailedKeys.value) + nextFailed.delete(key) + imageFailedKeys.value = nextFailed + } imageListByKey.value = { ...imageListByKey.value, [key]: urls } imageUrlByKey.value = { ...imageUrlByKey.value, [key]: urls[0] || '' } return urls @@ -4097,12 +4110,14 @@ async function fetchProductImagesForRow (row) { function getCachedProductImageUrl (row) { if (!canShowProductImage(row)) return '' const key = productImageKey(row) + if (imageFailedKeys.value.has(key)) return '' return imageUrlByKey.value[key] || performanceStore.imageUrl(key) || '' } function markProductImageFailed (row) { const key = productImageKey(row) if (!key) return + imageFailedKeys.value = new Set([...imageFailedKeys.value, key]) performanceStore.setImageCache(key, []) imageListByKey.value = { ...imageListByKey.value, [key]: [] } imageUrlByKey.value = { ...imageUrlByKey.value, [key]: '' } @@ -4119,7 +4134,7 @@ async function primeProductImages (sourceRows) { if (!canShowProductImage(row)) continue const key = productImageKey(row) const code = String(row?.image_product_code || row?.product_code || '').trim() - if (!key || !code || seen.has(key) || Object.prototype.hasOwnProperty.call(imageListByKey.value, key) || performanceStore.hasImageKey(key)) continue + if (!key || !code || imageFailedKeys.value.has(key) || seen.has(key) || Object.prototype.hasOwnProperty.call(imageListByKey.value, key) || performanceStore.hasImageKey(key)) continue seen.add(key) items.push({ key, @@ -4136,8 +4151,9 @@ async function primeProductImages (sourceRows) { const nextUrls = { ...imageUrlByKey.value } for (const item of items) { const urls = batch?.lists?.[item.key] || performanceStore.imageList(item.key) || [] + if (!urls.length) imageFailedKeys.value = new Set([...imageFailedKeys.value, item.key]) nextLists[item.key] = urls - nextUrls[item.key] = batch?.urls?.[item.key] || urls[0] || '' + nextUrls[item.key] = imageFailedKeys.value.has(item.key) ? '' : (batch?.urls?.[item.key] || urls[0] || '') } imageListByKey.value = nextLists imageUrlByKey.value = nextUrls