Fix product performance grouped JSON build

This commit is contained in:
M_Kececi
2026-07-04 13:45:43 +03:00
parent 60b2f76537
commit 4c56062ef4
3 changed files with 73 additions and 30 deletions
+39 -12
View File
@@ -3239,16 +3239,16 @@ SELECT (
'image_product_code', image_product_code, 'image_product_code', image_product_code,
'image_color_code', image_color_code, 'image_color_code', image_color_code,
'image_yaka_kodu', image_yaka_kodu, 'image_yaka_kodu', image_yaka_kodu,
'product_code', CASE WHEN $%d = 'product_code' THEN group_value ELSE product_code END, 'product_code', CASE WHEN $%d = 'product_code' THEN group_value ELSE '' END,
'color_code', CASE WHEN $%d = 'color_code' THEN group_value ELSE color_code END, 'color_code', CASE WHEN $%d = 'color_code' THEN group_value ELSE '' END,
'yaka_kodu', CASE WHEN $%d = 'yaka_kodu' THEN group_value ELSE yaka_kodu END, 'yaka_kodu', CASE WHEN $%d = 'yaka_kodu' THEN group_value ELSE '' END,
'item_description', item_description, 'item_description', CASE WHEN $%d = 'item_description' THEN group_value ELSE '' END,
'kategori', CASE WHEN $%d = 'kategori' THEN group_value ELSE kategori END, 'kategori', CASE WHEN $%d = 'kategori' THEN group_value ELSE '' END,
'askili_yan', CASE WHEN $%d = 'askili_yan' THEN group_value ELSE askili_yan 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 urun_ilk_grubu 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 urun_ana_grubu 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 urun_alt_grubu 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 market_key END 'market_key', CASE WHEN $%d = 'market_key' THEN group_value ELSE '' END
) )
|| jsonb_build_object( || jsonb_build_object(
'stock_qty', stock_qty, 'stock_qty', stock_qty,
@@ -3377,7 +3377,7 @@ FROM (
GROUP BY COALESCE(%s, '') GROUP BY COALESCE(%s, '')
) g ) g
ORDER BY group_value 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) args = append(args, field)
return queryProductPerformanceJSONRows(ctx, pg, query, args...) 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 { func makeProductPerformanceGroupedRow(key string, level int, field, value string, rows []map[string]any) map[string]any {
row := aggregateProductPerformanceRows(rows, field) row := aggregateProductPerformanceRows(rows, field)
clearProductPerformanceGroupDimensions(row, field, value)
row["__group"] = true row["__group"] = true
row["row_key"] = "group|" + key row["row_key"] = "group|" + key
row["key"] = key row["key"] = key
@@ -3686,7 +3687,6 @@ func makeProductPerformanceGroupedRow(key string, level int, field, value string
row["group_value"] = value row["group_value"] = value
row["label"] = value row["label"] = value
row["count"] = len(rows) row["count"] = len(rows)
row[field] = value
row["recommendation"] = fmt.Sprintf("%d satır", len(rows)) row["recommendation"] = fmt.Sprintf("%d satır", len(rows))
image := firstProductPerformanceImageSource(rows) image := firstProductPerformanceImageSource(rows)
@@ -3696,6 +3696,33 @@ func makeProductPerformanceGroupedRow(key string, level int, field, value string
return row 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 { func aggregateProductPerformanceRows(rows []map[string]any, groupField string) map[string]any {
out := map[string]any{} out := map[string]any{}
for _, row := range rows { for _, row := range rows {
+1 -1
View File
@@ -273,7 +273,7 @@ func GetProductPerformanceGroupedHandler(pg *sql.DB) http.HandlerFunc {
groupLevels := splitCSVQuery(r.URL.Query().Get("group_levels")) groupLevels := splitCSVQuery(r.URL.Query().Get("group_levels"))
limit := intQuery(r, "limit", 50000) limit := intQuery(r, "limit", 50000)
expanded := map[string]bool{} expanded := map[string]bool{}
const maxExpandedGroupKeys = 200 const maxExpandedGroupKeys = 260
addExpandedKey := func(key string) { addExpandedKey := func(key string) {
key = strings.TrimSpace(key) key = strings.TrimSpace(key)
if key != "" && len(expanded) < maxExpandedGroupKeys { if key != "" && len(expanded) < maxExpandedGroupKeys {
@@ -35,6 +35,7 @@
class="period-selector bg-white q-px-xs q-py-none" class="period-selector bg-white q-px-xs q-py-none"
/> />
<q-btn-dropdown <q-btn-dropdown
v-model="detailLevelMenuOpen"
dense dense
outline outline
color="primary" color="primary"
@@ -1560,6 +1561,7 @@ const selectedPeriods = ref(['90', '180', '360', 'total'])
const selectedDetailMainGroup = ref('') const selectedDetailMainGroup = ref('')
const imageUrlByKey = ref({}) const imageUrlByKey = ref({})
const imageListByKey = ref({}) const imageListByKey = ref({})
const imageFailedKeys = ref(new Set())
const imageDialog = ref(false) const imageDialog = ref(false)
const imageDialogUrls = ref([]) const imageDialogUrls = ref([])
const imageDialogTitle = ref('') const imageDialogTitle = ref('')
@@ -1579,6 +1581,7 @@ const imagePrimeLoading = ref(false)
const filterBusy = ref(false) const filterBusy = ref(false)
const bulkExpandLoading = ref(false) const bulkExpandLoading = ref(false)
const generalRowsLoaded = ref(false) const generalRowsLoaded = ref(false)
const detailLevelMenuOpen = ref(false)
let backendGroupedTimer = null let backendGroupedTimer = null
let imagePrimeActiveRequests = 0 let imagePrimeActiveRequests = 0
@@ -2940,9 +2943,9 @@ function sortValueForColumnName (row, columnName) {
function makeGroupRow (key, level, groupDef, value, groupRows) { function makeGroupRow (key, level, groupDef, value, groupRows) {
const imageSource = groupRows.find(row => row?.product_code) || {} const imageSource = groupRows.find(row => row?.product_code) || {}
const aggregate = aggregateGroupFields(groupRows, groupDef.key) const aggregate = aggregateGroupFields(groupRows, groupDef.key)
const colorSummary = groupDef.key === 'color_code' ? value : distinctSummary(groupRows, 'color_code') const colorSummary = groupDef.key === 'color_code' ? value : ''
const yakaSummary = groupDef.key === 'yaka_kodu' ? value : distinctSummary(groupRows, 'yaka_kodu') const yakaSummary = groupDef.key === 'yaka_kodu' ? value : ''
const colorDescriptionSummary = distinctSummary(groupRows, 'color_description') const colorDescriptionSummary = groupDef.key === 'color_code' ? distinctSummary(groupRows, 'color_description') : ''
return { return {
__group: true, __group: true,
row_key: `group|${key}`, row_key: `group|${key}`,
@@ -2956,21 +2959,21 @@ function makeGroupRow (key, level, groupDef, value, groupRows) {
image_color_code: imageSource.color_code || '', image_color_code: imageSource.color_code || '',
image_color_description: imageSource.color_description || '', image_color_description: imageSource.color_description || '',
image_yaka_kodu: imageSource.yaka_kodu || '', 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_code: colorSummary,
color_description: colorDescriptionSummary, color_description: colorDescriptionSummary,
yaka_kodu: yakaSummary, yaka_kodu: yakaSummary,
item_description: distinctSummary(groupRows, 'item_description'), item_description: groupDef.key === 'item_description' ? value : '',
kategori: groupDef.key === 'kategori' ? value : distinctSummary(groupRows, 'kategori'), kategori: groupDef.key === 'kategori' ? value : '',
urun_ilk_grubu: groupDef.key === 'urun_ilk_grubu' ? value : distinctSummary(groupRows, 'urun_ilk_grubu'), urun_ilk_grubu: groupDef.key === 'urun_ilk_grubu' ? value : '',
askili_yan: groupDef.key === 'askili_yan' ? value : distinctSummary(groupRows, 'askili_yan'), askili_yan: groupDef.key === 'askili_yan' ? value : '',
urun_ana_grubu: groupDef.key === 'urun_ana_grubu' ? value : distinctSummary(groupRows, 'urun_ana_grubu'), urun_ana_grubu: groupDef.key === 'urun_ana_grubu' ? value : '',
urun_alt_grubu: groupDef.key === 'urun_alt_grubu' ? value : distinctSummary(groupRows, 'urun_alt_grubu'), urun_alt_grubu: groupDef.key === 'urun_alt_grubu' ? value : '',
market_key: groupDef.key === 'market_key' ? value : distinctSummary(groupRows, 'market_key'), market_key: groupDef.key === 'market_key' ? value : '',
country: groupDef.key === 'country' ? value : distinctSummary(groupRows, 'country'), country: groupDef.key === 'country' ? value : '',
customer_segment: groupDef.key === 'customer_segment' ? value : distinctSummary(groupRows, 'customer_segment'), customer_segment: groupDef.key === 'customer_segment' ? value : '',
customer_code: groupDef.key === 'customer_code' ? value : distinctSummary(groupRows, 'customer_code'), customer_code: groupDef.key === 'customer_code' ? value : '',
customer_name: groupDef.key === 'customer_name' ? value : distinctSummary(groupRows, 'customer_name'), customer_name: groupDef.key === 'customer_name' ? value : '',
stock_qty: sumRows(groupRows, 'stock_qty'), stock_qty: sumRows(groupRows, 'stock_qty'),
sales_qty_90d: sumRows(groupRows, 'sales_qty_90d'), sales_qty_90d: sumRows(groupRows, 'sales_qty_90d'),
sales_qty_180d: sumRows(groupRows, 'sales_qty_180d'), sales_qty_180d: sumRows(groupRows, 'sales_qty_180d'),
@@ -3392,6 +3395,8 @@ function toggleGroup (key) {
} }
async function collapseAllProductGroups () { async function collapseAllProductGroups () {
if (bulkExpandLoading.value) return
detailLevelMenuOpen.value = false
const tabPrefix = `tab:${activeTab.value}|` const tabPrefix = `tab:${activeTab.value}|`
expandedGroups.value = Object.fromEntries( expandedGroups.value = Object.fromEntries(
Object.entries(expandedGroups.value).filter(([key]) => !key.startsWith(tabPrefix)) Object.entries(expandedGroups.value).filter(([key]) => !key.startsWith(tabPrefix))
@@ -3407,6 +3412,8 @@ async function collapseAllProductGroups () {
} }
async function expandSelectedProductGroups () { async function expandSelectedProductGroups () {
if (bulkExpandLoading.value) return
detailLevelMenuOpen.value = false
const keys = productAutoExpandKeys.value const keys = productAutoExpandKeys.value
const maxKeys = Math.min(maxBulkExpandKeys, maxBackendExpandedKeysForTab(activeTab.value)) const maxKeys = Math.min(maxBulkExpandKeys, maxBackendExpandedKeysForTab(activeTab.value))
const expandableKeys = keys.slice(0, maxKeys) const expandableKeys = keys.slice(0, maxKeys)
@@ -4055,6 +4062,7 @@ function resolveProductImageUrl (item) {
async function fetchProductImagesForRow (row) { async function fetchProductImagesForRow (row) {
if (!hasProductVariantImageKey(row)) return [] if (!hasProductVariantImageKey(row)) return []
const key = productImageKey(row) const key = productImageKey(row)
if (imageFailedKeys.value.has(key)) return []
if (Object.prototype.hasOwnProperty.call(imageListByKey.value, key)) { if (Object.prototype.hasOwnProperty.call(imageListByKey.value, key)) {
return imageListByKey.value[key] return imageListByKey.value[key]
} }
@@ -4083,6 +4091,11 @@ async function fetchProductImagesForRow (row) {
const list = Array.isArray(resp?.data) ? resp.data : [] const list = Array.isArray(resp?.data) ? resp.data : []
const urls = list.map(resolveProductImageUrl).filter(Boolean) const urls = list.map(resolveProductImageUrl).filter(Boolean)
performanceStore.setImageCache(key, urls) 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 } imageListByKey.value = { ...imageListByKey.value, [key]: urls }
imageUrlByKey.value = { ...imageUrlByKey.value, [key]: urls[0] || '' } imageUrlByKey.value = { ...imageUrlByKey.value, [key]: urls[0] || '' }
return urls return urls
@@ -4097,12 +4110,14 @@ async function fetchProductImagesForRow (row) {
function getCachedProductImageUrl (row) { function getCachedProductImageUrl (row) {
if (!canShowProductImage(row)) return '' if (!canShowProductImage(row)) return ''
const key = productImageKey(row) const key = productImageKey(row)
if (imageFailedKeys.value.has(key)) return ''
return imageUrlByKey.value[key] || performanceStore.imageUrl(key) || '' return imageUrlByKey.value[key] || performanceStore.imageUrl(key) || ''
} }
function markProductImageFailed (row) { function markProductImageFailed (row) {
const key = productImageKey(row) const key = productImageKey(row)
if (!key) return if (!key) return
imageFailedKeys.value = new Set([...imageFailedKeys.value, key])
performanceStore.setImageCache(key, []) performanceStore.setImageCache(key, [])
imageListByKey.value = { ...imageListByKey.value, [key]: [] } imageListByKey.value = { ...imageListByKey.value, [key]: [] }
imageUrlByKey.value = { ...imageUrlByKey.value, [key]: '' } imageUrlByKey.value = { ...imageUrlByKey.value, [key]: '' }
@@ -4119,7 +4134,7 @@ async function primeProductImages (sourceRows) {
if (!canShowProductImage(row)) continue if (!canShowProductImage(row)) continue
const key = productImageKey(row) const key = productImageKey(row)
const code = String(row?.image_product_code || row?.product_code || '').trim() 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) seen.add(key)
items.push({ items.push({
key, key,
@@ -4136,8 +4151,9 @@ async function primeProductImages (sourceRows) {
const nextUrls = { ...imageUrlByKey.value } const nextUrls = { ...imageUrlByKey.value }
for (const item of items) { for (const item of items) {
const urls = batch?.lists?.[item.key] || performanceStore.imageList(item.key) || [] 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 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 imageListByKey.value = nextLists
imageUrlByKey.value = nextUrls imageUrlByKey.value = nextUrls