diff --git a/svc/queries/product_performance.go b/svc/queries/product_performance.go index 7ffe5b2..6a80086 100644 --- a/svc/queries/product_performance.go +++ b/svc/queries/product_performance.go @@ -4677,25 +4677,16 @@ FROM ( CASE WHEN SUM(CASE WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d - WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d - WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd - WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty - ELSE 1 + ELSE 0 END) > 0 THEN SUM(performance_score * CASE WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d - WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d - WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd - WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty - ELSE 1 + ELSE 0 END) / NULLIF(SUM(CASE WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d - WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d - WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd - WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty - ELSE 1 + ELSE 0 END),0) - ELSE 0 + ELSE COALESCE(AVG(performance_score),0) END AS performance_score, MODE() WITHIN GROUP (ORDER BY performance_bucket) AS performance_bucket, COALESCE(MODE() WITHIN GROUP (ORDER BY NULLIF(recommendation,'')), '') AS recommendation, @@ -6841,20 +6832,7 @@ func productPerformanceScoreWeight(row map[string]any, suffix string) float64 { if weight := floatFromMap(row, "sales_qty_"+suffix); weight > 0 { return weight } - if weight := floatFromMap(row, "sales_usd_"+suffix); weight > 0 { - return weight - } - stockQty := floatFromMap(row, "stock_qty") - if stockQty <= 0 { - return 1 - } - if cost := floatFromMap(row, "cost_price_usd_"+suffix); cost > 0 { - return stockQty * cost - } - if cost := floatFromMap(row, "cost_price_usd"); cost > 0 { - return stockQty * cost - } - return stockQty + return 0 } func productPerformanceCostWeight(row map[string]any) float64 { diff --git a/ui/src/pages/ProductPerformanceProfitability.vue b/ui/src/pages/ProductPerformanceProfitability.vue index 5009402..2c5e7f5 100644 --- a/ui/src/pages/ProductPerformanceProfitability.vue +++ b/ui/src/pages/ProductPerformanceProfitability.vue @@ -4011,12 +4011,7 @@ function scoreSuffixForMetric (field) { function scoreWeight (row, suffix) { const salesQty = Number(row?.[`sales_qty_${suffix}`] || 0) if (salesQty > 0) return salesQty - const salesUsd = Number(row?.[`sales_usd_${suffix}`] || 0) - if (salesUsd > 0) return salesUsd - const stockQty = Number(row?.stock_qty || 0) - if (stockQty <= 0) return 1 - const costPrice = Number(row?.[`cost_price_usd_${suffix}`] || row?.cost_price_usd || 0) - return costPrice > 0 ? stockQty * costPrice : stockQty + return 0 } function weightedAverageScore (sourceRows, valueField, suffix) { @@ -4256,10 +4251,10 @@ function withProductMargins (row) { const out = { ...next, avg_price_usd_365d: Number(next?.sales_qty_365d || 0) > 0 ? Number(next?.sales_usd_365d || 0) / Number(next?.sales_qty_365d || 0) : 0, - stock_turnover_90d: existingOrStockTurnover(next?.stock_turnover_90d, next?.sales_qty_90d, next?.avg_stock_90d || next?.stock_qty, 90), - stock_turnover_180d: existingOrStockTurnover(next?.stock_turnover_180d, next?.sales_qty_180d, next?.avg_stock_180d || next?.stock_qty, 180), - stock_turnover_365d: existingOrStockTurnover(next?.stock_turnover_365d, next?.sales_qty_365d, next?.avg_stock_365d || next?.stock_qty, 360), - stock_turnover_total: existingOrStockTurnover(next?.stock_turnover_total, next?.sales_qty_total, next?.avg_stock_total || next?.stock_qty, productPerformancePeriodDays(next, 'total')) + stock_turnover_90d: groupedOrExistingStockTurnover(next, '90d', 90), + stock_turnover_180d: groupedOrExistingStockTurnover(next, '180d', 180), + stock_turnover_365d: groupedOrExistingStockTurnover(next, '365d', 360), + stock_turnover_total: groupedOrExistingStockTurnover(next, 'total', productPerformancePeriodDays(next, 'total')) } for (const suffix of ['90d', '180d', '365d', 'total']) { applyPeriodProfitFields(out, suffix) @@ -4272,7 +4267,7 @@ function withGeneralMargins (row) { applyPeriodProfitFields(out, 'total') return { ...out, - stock_turnover_total: existingOrStockTurnover(out?.stock_turnover_total, out?.sales_qty_total, out?.avg_stock_total || out?.stock_qty, productPerformancePeriodDays(out, 'total')) + stock_turnover_total: groupedOrExistingStockTurnover(out, 'total', productPerformancePeriodDays(out, 'total')) } } @@ -4348,6 +4343,15 @@ function existingOrStockTurnover (value, salesQty, stockQty, periodDays) { return value !== undefined && value !== null && Number.isFinite(current) ? current : stockTurnover(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) + if (row?.__group) { + return stockTurnover(salesQty, stockBase, periodDays) + } + return existingOrStockTurnover(row?.[`stock_turnover_${suffix}`], salesQty, stockBase, periodDays) +} + function filterKey (tableKey, name) { return `${tableKey}:${name}` }