Fix product performance grouped JSON build

This commit is contained in:
M_Kececi
2026-07-13 21:55:17 +03:00
parent 5eaf8be9e6
commit b80b5024f4
6 changed files with 59 additions and 59 deletions
+2 -2
View File
@@ -1289,12 +1289,12 @@ function normalizeUploadsPath (storagePath) {
function resolveProductImageUrl (item) {
if (!item || typeof item !== 'object') return ''
const contentUrl = toText(item.content_url || item.ContentURL)
if (contentUrl) return contentUrl.startsWith('/api/') ? contentUrl : contentUrl
const thumbUrl = toText(item.thumb_url || item.thumbUrl)
if (thumbUrl) return thumbUrl
const fullUrl = toText(item.full_url || item.fullUrl)
if (fullUrl) return fullUrl
const contentUrl = toText(item.content_url || item.ContentURL)
if (contentUrl) return contentUrl.startsWith('/api/') ? contentUrl : contentUrl
const uploadsPath = normalizeUploadsPath(item.storage_path || item.storage)
if (uploadsPath) return uploadsPath
const fileName = toText(item.file_name || item.FileName)
@@ -3535,6 +3535,7 @@ function weightFieldForMetric (field) {
function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
Object.assign(out, normalizeProductCostFields(out))
const isGroup = Boolean(String(groupField || '').trim())
const preservedCustomerScores = {}
const preservedProductScores = {}
for (const suffix of ['90d', '180d', '365d', 'total']) {
@@ -3556,7 +3557,7 @@ function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
const qty = Number(out[`sales_qty_${suffix}`] || 0)
const stockQty = Number(out.stock_qty || 0)
const avgStock = Number(out[`avg_stock_${suffix}`] || 0)
const turnoverBase = avgStock > 0 ? avgStock : stockQty
const turnoverBase = avgStock > 0 ? avgStock : (isGroup ? 0 : stockQty)
const days = productPerformancePeriodDays(out, suffix)
const avgDaily = days > 0 ? qty / days : Number(out[`avg_daily_sales_${suffix}`] || 0)
if (days > 0) out[`avg_daily_sales_${suffix}`] = avgDaily
@@ -3660,7 +3661,9 @@ function productPeriodMetricSource (row, suffix) {
const salesIndex = Number(row?.[`sales_index_${suffix}`] || 0)
const qty = Number(row?.[`sales_qty_${suffix}`] || 0)
const stockQty = Number(row?.stock_qty || 0)
const avgStock = Number(row?.[`avg_stock_${suffix}`] || 0) || stockQty
const avgStock = Number(row?.[`avg_stock_${suffix}`] || 0)
const isGroup = Boolean(row?.__group || String(row?.group_field || '').trim())
const turnoverBase = avgStock > 0 ? avgStock : (isGroup ? 0 : stockQty)
const turnoverKey = `stock_turnover_${suffix}`
const hasTurnover = Object.prototype.hasOwnProperty.call(row || {}, turnoverKey)
return {
@@ -3670,7 +3673,7 @@ function productPeriodMetricSource (row, suffix) {
gross_margin_cost_90d: Number(row?.[`gross_margin_cost_${suffix}`] ?? 0),
gross_margin_90d: Number(row?.[`gross_margin_${suffix}`] ?? 0),
sales_qty_90d: qty,
stock_turnover_90d: hasTurnover ? Number(row?.[turnoverKey] || 0) : annualizedStockTurnover(qty, avgStock, productPerformancePeriodDays(row, suffix)),
stock_turnover_90d: hasTurnover ? Number(row?.[turnoverKey] || 0) : annualizedStockTurnover(qty, turnoverBase, productPerformancePeriodDays(row, suffix)),
market_count_90d: periodCount(row, 'market_count', suffix),
customer_count_90d: periodCount(row, 'customer_count', suffix),
stock_qty: stockQty
@@ -4344,11 +4347,15 @@ function existingOrStockTurnover (value, salesQty, stockQty, periodDays) {
}
function groupedOrExistingStockTurnover (row, suffix, periodDays) {
const stockBase = Number(row?.[`avg_stock_${suffix}`] || row?.stock_qty || 0)
const salesQty = Number(row?.[`sales_qty_${suffix}`] || 0)
const avgStock = Number(row?.[`avg_stock_${suffix}`] || 0)
const stockQty = Number(row?.stock_qty || 0)
if (row?.__group) {
return stockTurnover(salesQty, stockBase, periodDays)
if (avgStock > 0) return stockTurnover(salesQty, avgStock, periodDays)
const current = Number(row?.[`stock_turnover_${suffix}`])
return Number.isFinite(current) ? current : 0
}
const stockBase = avgStock > 0 ? avgStock : stockQty
return existingOrStockTurnover(row?.[`stock_turnover_${suffix}`], salesQty, stockBase, periodDays)
}
+9 -25
View File
@@ -423,7 +423,6 @@ const productImageListByCode = ref({})
const productImageListLoading = ref({})
const productImageFallbackByKey = ref({})
const productImageContentLoading = ref({})
const productImageBlobUrls = ref([])
const productImageListBlockedUntil = ref(0)
const productCardDialog = ref(false)
const productCardData = ref({})
@@ -702,24 +701,13 @@ function clearGalleryQueryIndex() {
async function resolveProductImageUrlForCarousel(item) {
const resolved = resolveProductImageUrl(item)
const contentUrl = String(resolved.contentUrl || '').trim()
if (contentUrl) return contentUrl
const directUrl = String(resolved.fullUrl || resolved.thumbUrl || resolved.publicUrl || '').trim()
if (directUrl) return directUrl
const contentUrl = String(resolved.contentUrl || '').trim()
if (contentUrl) {
try {
const blobRes = await api.get(contentUrl, { baseURL: '', responseType: 'blob' })
const blob = blobRes?.data
if (blob instanceof Blob) {
const objectUrl = URL.createObjectURL(blob)
productImageBlobUrls.value.push(objectUrl)
return objectUrl
}
} catch {
// fall through to public url
}
}
return contentUrl
return ''
}
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
@@ -769,9 +757,9 @@ async function flushProductImageBatch() {
const list = Array.isArray(item.images) ? item.images : []
const first = list[0] || null
const resolved = resolveProductImageUrl(first)
const url = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || resolved.contentUrl || ''
const url = resolved.contentUrl || resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
productImageCache.value[key] = String(url || '').trim()
productImageFallbackByKey.value[key] = resolved.fullUrl || resolved.publicUrl || resolved.contentUrl || ''
productImageFallbackByKey.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
if (queued) productImageListByCode.value[queued.listKey] = list
delete productImageLoading.value[key]
}
@@ -860,11 +848,11 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
productImageCache.value[key] = String(
preferredCardUrl ||
primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || primaryResolved.contentUrl ||
secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || secondaryResolved.contentUrl ||
primaryResolved.contentUrl || primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl ||
secondaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl ||
''
).trim()
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.contentUrl || ''
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || ''
} catch (err) {
console.warn('[ProductStockByAttributes] product image fetch failed', { code, color, err })
productImageCache.value[key] = ''
@@ -1576,10 +1564,6 @@ onUnmounted(() => {
clearTimeout(filterOptionsDebounceTimer)
filterOptionsDebounceTimer = null
}
for (const url of productImageBlobUrls.value) {
try { URL.revokeObjectURL(url) } catch {}
}
productImageBlobUrls.value = []
})
</script>
+9 -25
View File
@@ -390,7 +390,6 @@ const productImageListByCode = ref({})
const productImageListLoading = ref({})
const productImageFallbackByKey = ref({})
const productImageContentLoading = ref({})
const productImageBlobUrls = ref([])
const productImageListBlockedUntil = ref(0)
const productCardDialog = ref(false)
const productCardData = ref({})
@@ -681,24 +680,13 @@ function clearGalleryQueryIndex() {
async function resolveProductImageUrlForCarousel(item) {
const resolved = resolveProductImageUrl(item)
const contentUrl = String(resolved.contentUrl || '').trim()
if (contentUrl) return contentUrl
const directUrl = String(resolved.fullUrl || resolved.thumbUrl || resolved.publicUrl || '').trim()
if (directUrl) return directUrl
const contentUrl = String(resolved.contentUrl || '').trim()
if (contentUrl) {
try {
const blobRes = await api.get(contentUrl, { baseURL: '', responseType: 'blob' })
const blob = blobRes?.data
if (blob instanceof Blob) {
const objectUrl = URL.createObjectURL(blob)
productImageBlobUrls.value.push(objectUrl)
return objectUrl
}
} catch {
// fall through to public url
}
}
return contentUrl
return ''
}
function getProductImageUrl(code, color, secondColor = '', dim1Id = '', dim3Id = '') {
@@ -748,9 +736,9 @@ async function flushProductImageBatch() {
const list = Array.isArray(item.images) ? item.images : []
const first = list[0] || null
const resolved = resolveProductImageUrl(first)
const url = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || resolved.contentUrl || ''
const url = resolved.contentUrl || resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
productImageCache.value[key] = String(url || '').trim()
productImageFallbackByKey.value[key] = resolved.fullUrl || resolved.publicUrl || resolved.contentUrl || ''
productImageFallbackByKey.value[key] = resolved.thumbUrl || resolved.fullUrl || resolved.publicUrl || ''
if (queued) productImageListByCode.value[queued.listKey] = list
delete productImageLoading.value[key]
}
@@ -836,11 +824,11 @@ async function ensureProductImage(code, color, secondColor = '', dim1Id = '', di
const secondaryResolved = resolveProductImageUrl(secondaryItem)
productImageCache.value[key] = String(
preferredCardUrl ||
primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || primaryResolved.contentUrl ||
secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || secondaryResolved.contentUrl ||
primaryResolved.contentUrl || primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl ||
secondaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl ||
''
).trim()
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.contentUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.contentUrl || ''
productImageFallbackByKey.value[key] = primaryResolved.fullUrl || primaryResolved.publicUrl || primaryResolved.thumbUrl || secondaryResolved.fullUrl || secondaryResolved.publicUrl || secondaryResolved.thumbUrl || ''
} catch (err) {
console.warn('[ProductStockQuery] product image fetch failed', { code, color, err })
productImageCache.value[key] = ''
@@ -1374,10 +1362,6 @@ function resetForm() {
onUnmounted(() => {
window.removeEventListener('mousemove', onFullscreenMouseMove)
window.removeEventListener('mouseup', onFullscreenMouseUp)
for (const url of productImageBlobUrls.value) {
try { URL.revokeObjectURL(url) } catch {}
}
productImageBlobUrls.value = []
})
onMounted(async () => {