Fix product performance grouped JSON build
This commit is contained in:
@@ -4361,8 +4361,12 @@ func listProductPerformanceGroupedSQL(ctx context.Context, pg *sql.DB, req Produ
|
|||||||
out := make([]map[string]any, 0, 512)
|
out := make([]map[string]any, 0, 512)
|
||||||
filters := productPerformanceGroupedBaseFilters(req)
|
filters := productPerformanceGroupedBaseFilters(req)
|
||||||
err := appendProductPerformanceGroupedSQLRows(ctx, pg, &out, mode, levels, 0, 0, []string{"tab:" + mode}, filters, req.ExpandedKeys, req.ExpandThroughLevel, req.Limit)
|
err := appendProductPerformanceGroupedSQLRows(ctx, pg, &out, mode, levels, 0, 0, []string{"tab:" + mode}, filters, req.ExpandedKeys, req.ExpandThroughLevel, req.Limit)
|
||||||
|
if err != nil {
|
||||||
return out, true, err
|
return out, true, err
|
||||||
}
|
}
|
||||||
|
out = sortProductPerformancePreparedGroupedRows(out, req.SortBy, req.Descending)
|
||||||
|
return out, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func productPerformanceGroupedBaseFilters(req ProductPerformanceGroupedRequest) []productPerformanceSQLGroupFilter {
|
func productPerformanceGroupedBaseFilters(req ProductPerformanceGroupedRequest) []productPerformanceSQLGroupFilter {
|
||||||
effectiveFilters := productPerformanceGroupedEffectiveFilters(req)
|
effectiveFilters := productPerformanceGroupedEffectiveFilters(req)
|
||||||
@@ -4439,6 +4443,7 @@ func appendProductPerformanceGroupedSQLRows(ctx context.Context, pg *sql.DB, out
|
|||||||
row["label"] = value
|
row["label"] = value
|
||||||
row["__group"] = true
|
row["__group"] = true
|
||||||
row[field] = value
|
row[field] = value
|
||||||
|
deriveProductPerformanceGroupMetrics(row, field)
|
||||||
*out = append(*out, row)
|
*out = append(*out, row)
|
||||||
if expandedKeys[key] || level <= expandThroughLevel {
|
if expandedKeys[key] || level <= expandThroughLevel {
|
||||||
nextFilters := append(append([]productPerformanceSQLGroupFilter{}, filters...), productPerformanceSQLGroupFilter{Field: field, Value: value})
|
nextFilters := append(append([]productPerformanceSQLGroupFilter{}, filters...), productPerformanceSQLGroupFilter{Field: field, Value: value})
|
||||||
@@ -4464,6 +4469,8 @@ SELECT (
|
|||||||
jsonb_build_object(
|
jsonb_build_object(
|
||||||
'group_value', group_value,
|
'group_value', group_value,
|
||||||
'label', group_value,
|
'label', group_value,
|
||||||
|
'period_start', '2022-01-01',
|
||||||
|
'period_end', COALESCE(to_char((SELECT kpi_date FROM LatestKPIDate),'YYYY-MM-DD'), ''),
|
||||||
'count', row_count,
|
'count', row_count,
|
||||||
'recommendation', CASE WHEN recommendation <> '' THEN recommendation ELSE row_count::text || ' satir' END,
|
'recommendation', CASE WHEN recommendation <> '' THEN recommendation ELSE row_count::text || ' satir' END,
|
||||||
'image_product_code', image_product_code,
|
'image_product_code', image_product_code,
|
||||||
@@ -4482,6 +4489,10 @@ SELECT (
|
|||||||
)
|
)
|
||||||
|| jsonb_build_object(
|
|| jsonb_build_object(
|
||||||
'stock_qty', stock_qty,
|
'stock_qty', stock_qty,
|
||||||
|
'avg_stock_90d', avg_stock_90d,
|
||||||
|
'avg_stock_180d', avg_stock_180d,
|
||||||
|
'avg_stock_365d', avg_stock_365d,
|
||||||
|
'avg_stock_total', avg_stock_total,
|
||||||
'sales_qty_90d', sales_qty_90d,
|
'sales_qty_90d', sales_qty_90d,
|
||||||
'sales_qty_180d', sales_qty_180d,
|
'sales_qty_180d', sales_qty_180d,
|
||||||
'sales_qty_365d', sales_qty_365d,
|
'sales_qty_365d', sales_qty_365d,
|
||||||
@@ -4506,8 +4517,17 @@ SELECT (
|
|||||||
'unit_profit_base_90d', unit_profit_base_90d,
|
'unit_profit_base_90d', unit_profit_base_90d,
|
||||||
'unit_profit_base_180d', unit_profit_base_180d,
|
'unit_profit_base_180d', unit_profit_base_180d,
|
||||||
'market_count_90d', market_count_90d,
|
'market_count_90d', market_count_90d,
|
||||||
|
'market_count_180d', market_count_180d,
|
||||||
|
'market_count_365d', market_count_365d,
|
||||||
|
'market_count_total', market_count_total,
|
||||||
'customer_count_90d', customer_count_90d,
|
'customer_count_90d', customer_count_90d,
|
||||||
|
'customer_count_180d', customer_count_180d,
|
||||||
|
'customer_count_365d', customer_count_365d,
|
||||||
|
'customer_count_total', customer_count_total,
|
||||||
'sales_index_90d', sales_index_90d,
|
'sales_index_90d', sales_index_90d,
|
||||||
|
'sales_index_180d', sales_index_180d,
|
||||||
|
'sales_index_365d', sales_index_365d,
|
||||||
|
'sales_index_total', sales_index_total,
|
||||||
'price_index_90d', price_index_90d,
|
'price_index_90d', price_index_90d,
|
||||||
'margin_index_90d', margin_index_90d,
|
'margin_index_90d', margin_index_90d,
|
||||||
'performance_score', performance_score,
|
'performance_score', performance_score,
|
||||||
@@ -4586,8 +4606,17 @@ FROM (
|
|||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(unit_profit_base_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS unit_profit_base_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(unit_profit_base_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS unit_profit_base_90d,
|
||||||
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(unit_profit_base_180d * sales_qty_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE 0 END AS unit_profit_base_180d,
|
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(unit_profit_base_180d * sales_qty_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE 0 END AS unit_profit_base_180d,
|
||||||
COUNT(DISTINCT NULLIF(NULLIF(market_key,'STOK'),'')) FILTER (WHERE COALESCE(sales_usd_90d,0) > 0)::integer AS market_count_90d,
|
COUNT(DISTINCT NULLIF(NULLIF(market_key,'STOK'),'')) FILTER (WHERE COALESCE(sales_usd_90d,0) > 0)::integer AS market_count_90d,
|
||||||
|
COUNT(DISTINCT NULLIF(NULLIF(market_key,'STOK'),'')) FILTER (WHERE COALESCE(sales_usd_180d,0) > 0)::integer AS market_count_180d,
|
||||||
|
COUNT(DISTINCT NULLIF(NULLIF(market_key,'STOK'),'')) FILTER (WHERE COALESCE(sales_usd_365d,0) > 0)::integer AS market_count_365d,
|
||||||
|
COUNT(DISTINCT NULLIF(NULLIF(market_key,'STOK'),'')) FILTER (WHERE COALESCE(sales_usd_total,0) > 0)::integer AS market_count_total,
|
||||||
COALESCE(SUM(CASE WHEN COALESCE(sales_usd_90d,0) > 0 THEN customer_count_90d ELSE 0 END),0)::integer AS customer_count_90d,
|
COALESCE(SUM(CASE WHEN COALESCE(sales_usd_90d,0) > 0 THEN customer_count_90d ELSE 0 END),0)::integer AS customer_count_90d,
|
||||||
|
COALESCE(SUM(CASE WHEN COALESCE(sales_usd_180d,0) > 0 THEN customer_count_180d ELSE 0 END),0)::integer AS customer_count_180d,
|
||||||
|
COALESCE(SUM(CASE WHEN COALESCE(sales_usd_365d,0) > 0 THEN customer_count_365d ELSE 0 END),0)::integer AS customer_count_365d,
|
||||||
|
COALESCE(SUM(CASE WHEN COALESCE(sales_usd_total,0) > 0 THEN customer_count_total ELSE 0 END),0)::integer AS customer_count_total,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(sales_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS sales_index_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(sales_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS sales_index_90d,
|
||||||
|
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(sales_index_180d * sales_qty_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE 0 END AS sales_index_180d,
|
||||||
|
CASE WHEN SUM(sales_qty_365d) > 0 THEN SUM(sales_index_365d * sales_qty_365d) / NULLIF(SUM(sales_qty_365d),0) ELSE 0 END AS sales_index_365d,
|
||||||
|
CASE WHEN SUM(sales_qty_total) > 0 THEN SUM(sales_index_total * sales_qty_total) / NULLIF(SUM(sales_qty_total),0) ELSE 0 END AS sales_index_total,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(price_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS price_index_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(price_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS price_index_90d,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(margin_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS margin_index_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(margin_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS margin_index_90d,
|
||||||
CASE
|
CASE
|
||||||
@@ -5966,15 +5995,8 @@ func productPerformanceMapVariantKey(row map[string]any) string {
|
|||||||
|
|
||||||
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
||||||
normalizeProductPerformanceCostFields(out)
|
normalizeProductPerformanceCostFields(out)
|
||||||
preservedPerformanceScores := map[string]float64{}
|
|
||||||
if score, ok := productPerformanceOptionalFloat(out, "performance_score"); ok {
|
|
||||||
preservedPerformanceScores["90d"] = score
|
|
||||||
}
|
|
||||||
preservedCustomerScores := map[string]float64{}
|
preservedCustomerScores := map[string]float64{}
|
||||||
for _, suffix := range productPerformancePeriodSuffixes() {
|
for _, suffix := range productPerformancePeriodSuffixes() {
|
||||||
if score, ok := productPerformanceOptionalFloat(out, "performance_score_"+suffix); ok {
|
|
||||||
preservedPerformanceScores[suffix] = score
|
|
||||||
}
|
|
||||||
if score, ok := productPerformanceOptionalFloat(out, "customer_score_"+suffix); ok {
|
if score, ok := productPerformanceOptionalFloat(out, "customer_score_"+suffix); ok {
|
||||||
preservedCustomerScores[suffix] = score
|
preservedCustomerScores[suffix] = score
|
||||||
}
|
}
|
||||||
@@ -6058,12 +6080,8 @@ func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string)
|
|||||||
} else {
|
} else {
|
||||||
out["customer_score_"+suffix] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, suffix))
|
out["customer_score_"+suffix] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, suffix))
|
||||||
}
|
}
|
||||||
if score, ok := preservedPerformanceScores[suffix]; ok {
|
|
||||||
out["performance_score_"+suffix] = score
|
|
||||||
} else {
|
|
||||||
out["performance_score_"+suffix] = productPerformanceSalesPeriodScore(out, suffix)
|
out["performance_score_"+suffix] = productPerformanceSalesPeriodScore(out, suffix)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
out["performance_score"] = out["performance_score_90d"]
|
out["performance_score"] = out["performance_score_90d"]
|
||||||
if floatFromMap(out, "order_qty") > 0 || floatFromMap(out, "order_usd") > 0 {
|
if floatFromMap(out, "order_qty") > 0 || floatFromMap(out, "order_usd") > 0 {
|
||||||
score := productPerformanceOrderGroupScore(out)
|
score := productPerformanceOrderGroupScore(out)
|
||||||
@@ -6282,8 +6300,8 @@ func productPerformanceProductScore(suffix string, salesUSD, salesIndex, margin,
|
|||||||
score := 0.30*productPerformanceMarginComponentScore(margin) +
|
score := 0.30*productPerformanceMarginComponentScore(margin) +
|
||||||
0.20*productPerformanceRatioScore(stockTurnover, productPerformanceStockTurnoverTarget(suffix)) +
|
0.20*productPerformanceRatioScore(stockTurnover, productPerformanceStockTurnoverTarget(suffix)) +
|
||||||
0.20*revenueScore +
|
0.20*revenueScore +
|
||||||
0.15*productPerformanceRatioScore(marketCount, productPerformanceMarketSpreadTarget(suffix)) +
|
0.05*productPerformanceRatioScore(marketCount, productPerformanceMarketSpreadTarget(suffix)) +
|
||||||
0.15*productPerformanceRatioScore(customerCount, productPerformanceCustomerSpreadTarget(suffix))
|
0.25*productPerformanceRatioScore(customerCount, productPerformanceCustomerSpreadTarget(suffix))
|
||||||
return productPerformanceRoundScore(score)
|
return productPerformanceRoundScore(score)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6296,11 +6314,11 @@ func productPerformanceMarketSpreadTarget(suffix string) float64 {
|
|||||||
case "90d":
|
case "90d":
|
||||||
return 3
|
return 3
|
||||||
case "180d":
|
case "180d":
|
||||||
return 5
|
return 3
|
||||||
case "365d":
|
case "365d":
|
||||||
return 6
|
return 6
|
||||||
default:
|
default:
|
||||||
return 8
|
return 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6309,11 +6327,11 @@ func productPerformanceCustomerSpreadTarget(suffix string) float64 {
|
|||||||
case "90d":
|
case "90d":
|
||||||
return 8
|
return 8
|
||||||
case "180d":
|
case "180d":
|
||||||
return 15
|
return 20
|
||||||
case "365d":
|
case "365d":
|
||||||
return 18
|
return 50
|
||||||
default:
|
default:
|
||||||
return 25
|
return 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -962,8 +962,8 @@ func productPerformanceExcelProductScore(suffix string, salesUSD, stockTurnover,
|
|||||||
score := 0.30*productPerformanceExcelMarginScore(margin) +
|
score := 0.30*productPerformanceExcelMarginScore(margin) +
|
||||||
0.20*productPerformanceExcelRatioScore(stockTurnover, 4) +
|
0.20*productPerformanceExcelRatioScore(stockTurnover, 4) +
|
||||||
0.20*revenueScore +
|
0.20*revenueScore +
|
||||||
0.15*productPerformanceExcelRatioScore(marketCount, productPerformanceExcelMarketSpreadTarget(suffix)) +
|
0.05*productPerformanceExcelRatioScore(marketCount, productPerformanceExcelMarketSpreadTarget(suffix)) +
|
||||||
0.15*productPerformanceExcelRatioScore(customerCount, productPerformanceExcelCustomerSpreadTarget(suffix))
|
0.25*productPerformanceExcelRatioScore(customerCount, productPerformanceExcelCustomerSpreadTarget(suffix))
|
||||||
return productPerformanceExcelRoundScore(score)
|
return productPerformanceExcelRoundScore(score)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -972,11 +972,11 @@ func productPerformanceExcelMarketSpreadTarget(suffix string) float64 {
|
|||||||
case "90d":
|
case "90d":
|
||||||
return 3
|
return 3
|
||||||
case "180d":
|
case "180d":
|
||||||
return 5
|
return 3
|
||||||
case "365d":
|
case "365d":
|
||||||
return 6
|
return 6
|
||||||
default:
|
default:
|
||||||
return 8
|
return 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -985,11 +985,11 @@ func productPerformanceExcelCustomerSpreadTarget(suffix string) float64 {
|
|||||||
case "90d":
|
case "90d":
|
||||||
return 8
|
return 8
|
||||||
case "180d":
|
case "180d":
|
||||||
return 15
|
return 20
|
||||||
case "365d":
|
case "365d":
|
||||||
return 18
|
return 50
|
||||||
default:
|
default:
|
||||||
return 25
|
return 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1850,7 +1850,6 @@ const columns = [
|
|||||||
{ name: 'customer_count_total', label: 'Genel Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
|
{ name: 'customer_count_total', label: 'Genel Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_index_90d', label: 'Piyasa End.', field: row => formatNumber(row.sales_index_90d, 2), align: 'right', sortable: true },
|
{ name: 'sales_index_90d', label: 'Piyasa End.', field: row => formatNumber(row.sales_index_90d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'sales_index_total', label: 'Genel Endeks', field: row => formatNumber(row.sales_index_total, 2), align: 'right', sortable: true },
|
{ name: 'sales_index_total', label: 'Genel Endeks', field: row => formatNumber(row.sales_index_total, 2), align: 'right', sortable: true },
|
||||||
{ name: 'performance_score', label: 'Skor', field: row => formatNumber(row.performance_score, 2), align: 'right', sortable: true },
|
|
||||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left' },
|
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left' },
|
||||||
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
|
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
|
||||||
]
|
]
|
||||||
@@ -2085,7 +2084,7 @@ const generalColumns = [
|
|||||||
{ name: 'customer_count_total', label: 'Toplam Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
|
{ name: 'customer_count_total', label: 'Toplam Müşteri', field: row => formatNumber(row.customer_count_total, 0), align: 'right', sortable: true },
|
||||||
{ name: 'invoice_count_total', label: 'Fatura', field: row => formatNumber(row.invoice_count_total, 0), align: 'right', sortable: true },
|
{ name: 'invoice_count_total', label: 'Fatura', field: row => formatNumber(row.invoice_count_total, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_index_total', label: 'Genel Endeks', field: row => formatNumber(row.sales_index_total, 2), align: 'right', sortable: true },
|
{ name: 'sales_index_total', label: 'Genel Endeks', field: row => formatNumber(row.sales_index_total, 2), align: 'right', sortable: true },
|
||||||
{ name: 'performance_score', label: 'Genel Skor', field: row => formatNumber(row.performance_score, 2), align: 'right', sortable: true },
|
{ name: 'performance_score_total', label: 'Genel Skor', field: row => formatNumber(row.performance_score_total ?? row.performance_score, 2), align: 'right', sortable: true },
|
||||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
||||||
{ name: 'first_sale_date', label: 'İlk Satış', field: 'first_sale_date', align: 'left', sortable: true },
|
{ name: 'first_sale_date', label: 'İlk Satış', field: 'first_sale_date', align: 'left', sortable: true },
|
||||||
{ name: 'last_sale_date', label: 'Son Satış', field: 'last_sale_date', align: 'left', sortable: true },
|
{ name: 'last_sale_date', label: 'Son Satış', field: 'last_sale_date', align: 'left', sortable: true },
|
||||||
@@ -2355,7 +2354,6 @@ const salesBreakdownColumns = [
|
|||||||
{ name: 'gross_margin_cost_total', label: 'Genel Çıplak Marj', field: row => formatPercent(row.gross_margin_cost_total), align: 'right', sortable: true },
|
{ name: 'gross_margin_cost_total', label: 'Genel Çıplak Marj', field: row => formatPercent(row.gross_margin_cost_total), align: 'right', sortable: true },
|
||||||
{ name: 'customer_score_total', label: 'Genel Müşteri Skor', field: row => formatNumber(row.customer_score_total, 2), align: 'right', sortable: true },
|
{ name: 'customer_score_total', label: 'Genel Müşteri Skor', field: row => formatNumber(row.customer_score_total, 2), align: 'right', sortable: true },
|
||||||
{ name: 'sales_index_90d', label: 'Endeks', field: row => formatNumber(row.sales_index_90d, 2), align: 'right', sortable: true },
|
{ name: 'sales_index_90d', label: 'Endeks', field: row => formatNumber(row.sales_index_90d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'performance_score', label: 'Skor', field: row => formatNumber(row.performance_score, 2), align: 'right', sortable: true },
|
|
||||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left', sortable: true },
|
||||||
{ name: 'last_sale_date', label: 'Son Satış', field: 'last_sale_date', align: 'left', sortable: true },
|
{ name: 'last_sale_date', label: 'Son Satış', field: 'last_sale_date', align: 'left', sortable: true },
|
||||||
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
|
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
|
||||||
@@ -2427,7 +2425,6 @@ const metricLabelOverrides = {
|
|||||||
gross_margin_cost_365d: '360G Çıplak Marj',
|
gross_margin_cost_365d: '360G Çıplak Marj',
|
||||||
gross_margin_base_total: 'Genel Taban Marj',
|
gross_margin_base_total: 'Genel Taban Marj',
|
||||||
gross_margin_cost_total: 'Genel Çıplak Marj',
|
gross_margin_cost_total: 'Genel Çıplak Marj',
|
||||||
performance_score: 'Skor',
|
|
||||||
performance_score_90d: '90G Ürün Skor',
|
performance_score_90d: '90G Ürün Skor',
|
||||||
performance_score_180d: '180G Ürün Skor',
|
performance_score_180d: '180G Ürün Skor',
|
||||||
performance_score_365d: '360G Ürün Skor',
|
performance_score_365d: '360G Ürün Skor',
|
||||||
@@ -2666,7 +2663,10 @@ const visibleOrderAnalysisColumns = computed(() => {
|
|||||||
function performanceCardsForRow (row) {
|
function performanceCardsForRow (row) {
|
||||||
const source = row || {}
|
const source = row || {}
|
||||||
return [
|
return [
|
||||||
{ key: 'score', label: 'Skor', value: formatNumber(source.performance_score, 2) },
|
{ key: 'score90', label: '90G Skor', value: formatNumber(source.performance_score_90d ?? source.performance_score, 2) },
|
||||||
|
{ key: 'score180', label: '180G Skor', value: formatNumber(source.performance_score_180d, 2) },
|
||||||
|
{ key: 'score360', label: '360G Skor', value: formatNumber(source.performance_score_365d, 2) },
|
||||||
|
{ key: 'scoreTotal', label: 'Genel Skor', value: formatNumber(source.performance_score_total ?? source.performance_score, 2) },
|
||||||
{ key: 'stock', label: 'Stok', value: formatNumber(source.stock_qty, 0) },
|
{ key: 'stock', label: 'Stok', value: formatNumber(source.stock_qty, 0) },
|
||||||
{ key: 'sales90', label: '90G Satış', value: formatNumber(source.sales_qty_90d, 0) },
|
{ key: 'sales90', label: '90G Satış', value: formatNumber(source.sales_qty_90d, 0) },
|
||||||
{ key: 'sales180', label: '180G Satış', value: formatNumber(source.sales_qty_180d, 0) },
|
{ key: 'sales180', label: '180G Satış', value: formatNumber(source.sales_qty_180d, 0) },
|
||||||
@@ -3285,6 +3285,9 @@ function compareGroupedBucketsForTable (tableKey, left, right, groupDef) {
|
|||||||
|
|
||||||
function groupedBucketSortValue (sourceRows, sortBy) {
|
function groupedBucketSortValue (sourceRows, sortBy) {
|
||||||
if (!sourceRows?.length) return 0
|
if (!sourceRows?.length) return 0
|
||||||
|
if (shouldUseDerivedGroupSortValue(sortBy)) {
|
||||||
|
return sortValueForColumnName(aggregateGroupFields(sourceRows), sortBy)
|
||||||
|
}
|
||||||
if (sortBy === 'stock_qty') return distinctVariantStockQty(sourceRows)
|
if (sortBy === 'stock_qty') return distinctVariantStockQty(sourceRows)
|
||||||
if (sortBy === 'idle_cost_usd') return distinctVariantStockCost(sourceRows)
|
if (sortBy === 'idle_cost_usd') return distinctVariantStockCost(sourceRows)
|
||||||
if (sortBy === 'base_price_usd' || sortBy === 'cost_price_usd') return weightedAverageProductCost(sourceRows, sortBy)
|
if (sortBy === 'base_price_usd' || sortBy === 'cost_price_usd') return weightedAverageProductCost(sourceRows, sortBy)
|
||||||
@@ -3293,6 +3296,10 @@ function groupedBucketSortValue (sourceRows, sortBy) {
|
|||||||
return sortValueForColumnName(sourceRows[0], sortBy)
|
return sortValueForColumnName(sourceRows[0], sortBy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldUseDerivedGroupSortValue (sortBy) {
|
||||||
|
return /^(performance_score|stock_turnover|stock_days|avg_price_usd|unit_profit|gross_profit|gross_margin|market_count|customer_count|sales_index)/i.test(String(sortBy || ''))
|
||||||
|
}
|
||||||
|
|
||||||
function sortProductGroupedTableRows (tableRows, sortBy, descending) {
|
function sortProductGroupedTableRows (tableRows, sortBy, descending) {
|
||||||
const source = Array.isArray(tableRows) ? tableRows : []
|
const source = Array.isArray(tableRows) ? tableRows : []
|
||||||
if (backendGroupedSupportedTab(activeTab.value)) return source
|
if (backendGroupedSupportedTab(activeTab.value)) return source
|
||||||
@@ -3528,15 +3535,8 @@ function weightFieldForMetric (field) {
|
|||||||
|
|
||||||
function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
||||||
Object.assign(out, normalizeProductCostFields(out))
|
Object.assign(out, normalizeProductCostFields(out))
|
||||||
const preservedPerformanceScores = {}
|
|
||||||
if (Object.prototype.hasOwnProperty.call(out, 'performance_score')) {
|
|
||||||
preservedPerformanceScores['90d'] = Number(out.performance_score || 0)
|
|
||||||
}
|
|
||||||
const preservedCustomerScores = {}
|
const preservedCustomerScores = {}
|
||||||
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||||
if (Object.prototype.hasOwnProperty.call(out, `performance_score_${suffix}`)) {
|
|
||||||
preservedPerformanceScores[suffix] = Number(out[`performance_score_${suffix}`] || 0)
|
|
||||||
}
|
|
||||||
if (Object.prototype.hasOwnProperty.call(out, `customer_score_${suffix}`)) {
|
if (Object.prototype.hasOwnProperty.call(out, `customer_score_${suffix}`)) {
|
||||||
preservedCustomerScores[suffix] = Number(out[`customer_score_${suffix}`] || 0)
|
preservedCustomerScores[suffix] = Number(out[`customer_score_${suffix}`] || 0)
|
||||||
}
|
}
|
||||||
@@ -3596,9 +3596,7 @@ function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
|||||||
out[`customer_score_${suffix}`] = Object.prototype.hasOwnProperty.call(preservedCustomerScores, suffix)
|
out[`customer_score_${suffix}`] = Object.prototype.hasOwnProperty.call(preservedCustomerScores, suffix)
|
||||||
? preservedCustomerScores[suffix]
|
? preservedCustomerScores[suffix]
|
||||||
: customerSalesPeriodScore(periodMetricSource(out, suffix))
|
: customerSalesPeriodScore(periodMetricSource(out, suffix))
|
||||||
out[`performance_score_${suffix}`] = Object.prototype.hasOwnProperty.call(preservedPerformanceScores, suffix)
|
out[`performance_score_${suffix}`] = productSalesPeriodScore(productPeriodMetricSource(out, suffix))
|
||||||
? preservedPerformanceScores[suffix]
|
|
||||||
: productSalesPeriodScore(productPeriodMetricSource(out, suffix))
|
|
||||||
}
|
}
|
||||||
out.performance_score = Number(out.performance_score_90d || 0)
|
out.performance_score = Number(out.performance_score_90d || 0)
|
||||||
}
|
}
|
||||||
@@ -3707,8 +3705,8 @@ function productScore100 ({ suffix = '90d', salesUSD = 0, margin = 0, stockTurno
|
|||||||
0.30 * marginScore(margin) +
|
0.30 * marginScore(margin) +
|
||||||
0.20 * ratioScore(stockTurnover, stockTurnoverTarget(suffix)) +
|
0.20 * ratioScore(stockTurnover, stockTurnoverTarget(suffix)) +
|
||||||
0.20 * revenue +
|
0.20 * revenue +
|
||||||
0.15 * ratioScore(marketCount, marketSpreadTarget(suffix)) +
|
0.05 * ratioScore(marketCount, marketSpreadTarget(suffix)) +
|
||||||
0.15 * ratioScore(customerCount, customerSpreadTarget(suffix))
|
0.25 * ratioScore(customerCount, customerSpreadTarget(suffix))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3777,16 +3775,16 @@ function stockTurnoverTarget () {
|
|||||||
|
|
||||||
function marketSpreadTarget (suffix) {
|
function marketSpreadTarget (suffix) {
|
||||||
if (suffix === '90d') return 3
|
if (suffix === '90d') return 3
|
||||||
if (suffix === '180d') return 5
|
if (suffix === '180d') return 3
|
||||||
if (suffix === '365d') return 6
|
if (suffix === '365d') return 6
|
||||||
return 8
|
return 6
|
||||||
}
|
}
|
||||||
|
|
||||||
function customerSpreadTarget (suffix) {
|
function customerSpreadTarget (suffix) {
|
||||||
if (suffix === '90d') return 8
|
if (suffix === '90d') return 8
|
||||||
if (suffix === '180d') return 15
|
if (suffix === '180d') return 20
|
||||||
if (suffix === '365d') return 18
|
if (suffix === '365d') return 50
|
||||||
return 25
|
return 50
|
||||||
}
|
}
|
||||||
|
|
||||||
function periodCount (row, prefix, suffix) {
|
function periodCount (row, prefix, suffix) {
|
||||||
|
|||||||
Reference in New Issue
Block a user