Fix product performance grouped JSON build
This commit is contained in:
@@ -481,6 +481,10 @@ CREATE TABLE IF NOT EXISTS mk_product_performance_kpi_daily (
|
||||
sales_usd_365d NUMERIC(18,4) NOT NULL DEFAULT 0,
|
||||
avg_daily_sales_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
avg_daily_sales_180d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
avg_stock_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
avg_stock_180d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
avg_stock_365d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
avg_stock_total NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
stock_days_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
stock_days_180d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
avg_price_usd_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
@@ -497,8 +501,17 @@ CREATE TABLE IF NOT EXISTS mk_product_performance_kpi_daily (
|
||||
unit_profit_base_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
unit_profit_base_180d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
market_count_90d INTEGER NOT NULL DEFAULT 0,
|
||||
market_count_180d INTEGER NOT NULL DEFAULT 0,
|
||||
market_count_365d INTEGER NOT NULL DEFAULT 0,
|
||||
market_count_total INTEGER NOT NULL DEFAULT 0,
|
||||
customer_count_90d INTEGER NOT NULL DEFAULT 0,
|
||||
customer_count_180d INTEGER NOT NULL DEFAULT 0,
|
||||
customer_count_365d INTEGER NOT NULL DEFAULT 0,
|
||||
customer_count_total INTEGER NOT NULL DEFAULT 0,
|
||||
sales_index_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
sales_index_180d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
sales_index_365d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
sales_index_total NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
price_index_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
margin_index_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
performance_score NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||
@@ -552,6 +565,10 @@ WHERE btrim(askili_yan) = '-'`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_daily_sales_180d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_daily_sales_365d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_daily_sales_total NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_stock_90d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_stock_180d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_stock_365d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_stock_total NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS stock_days_180d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS stock_days_365d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS stock_days_total NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
@@ -567,6 +584,15 @@ WHERE btrim(askili_yan) = '-'`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS unit_profit_base_90d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS unit_profit_base_180d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS market_count_90d INTEGER NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS market_count_180d INTEGER NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS market_count_365d INTEGER NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS market_count_total INTEGER NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS customer_count_180d INTEGER NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS customer_count_365d INTEGER NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS customer_count_total INTEGER NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS sales_index_180d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS sales_index_365d NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS sales_index_total NUMERIC(18,6) NOT NULL DEFAULT 0`,
|
||||
`
|
||||
CREATE TABLE IF NOT EXISTS mk_product_performance_report_snapshot (
|
||||
report_key TEXT NOT NULL,
|
||||
@@ -1770,11 +1796,15 @@ SELECT
|
||||
sales_qty_30d, sales_qty_90d, sales_qty_180d, sales_qty_365d, sales_qty_730d, sales_qty_total,
|
||||
sales_usd_30d, sales_usd_90d, sales_usd_180d, sales_usd_365d, sales_usd_total,
|
||||
avg_daily_sales_90d, avg_daily_sales_180d, avg_daily_sales_365d, avg_daily_sales_total,
|
||||
avg_stock_90d, avg_stock_180d, avg_stock_365d, avg_stock_total,
|
||||
stock_days_90d, stock_days_180d, stock_days_365d, stock_days_total,
|
||||
stock_turnover_90d, stock_turnover_180d, stock_turnover_365d, stock_turnover_total,
|
||||
avg_price_usd_90d, avg_price_usd_180d, cost_price_usd, base_price_usd, base_price_try,
|
||||
gross_profit_usd_90d, gross_profit_usd_180d, gross_margin_90d, gross_margin_180d,
|
||||
unit_profit_cost_90d, unit_profit_cost_180d, unit_profit_base_90d, unit_profit_base_180d, market_count_90d, customer_count_90d, sales_index_90d,
|
||||
unit_profit_cost_90d, unit_profit_cost_180d, unit_profit_base_90d, unit_profit_base_180d,
|
||||
market_count_90d, market_count_180d, market_count_365d, market_count_total,
|
||||
customer_count_90d, customer_count_180d, customer_count_365d, customer_count_total,
|
||||
sales_index_90d, sales_index_180d, sales_index_365d, sales_index_total,
|
||||
price_index_90d, margin_index_90d, performance_score, performance_bucket,
|
||||
recommendation, COALESCE(to_char(last_sale_date,'YYYY-MM-DD'),''), last_ref_number,
|
||||
to_char(updated_at,'YYYY-MM-DD HH24:MI:SS')
|
||||
@@ -1796,11 +1826,15 @@ LIMIT $`+fmt.Sprint(len(args)-1)+` OFFSET $`+fmt.Sprint(len(args)), args...)
|
||||
&r.SalesQty30, &r.SalesQty90, &r.SalesQty180, &r.SalesQty365, &r.SalesQty730, &r.SalesQtyTotal,
|
||||
&r.SalesUSD30, &r.SalesUSD90, &r.SalesUSD180, &r.SalesUSD365, &r.SalesUSDTotal,
|
||||
&r.AvgDailySales90, &r.AvgDailySales180, &r.AvgDailySales365, &r.AvgDailySalesTotal,
|
||||
&r.AvgStock90, &r.AvgStock180, &r.AvgStock365, &r.AvgStockTotal,
|
||||
&r.StockDays90, &r.StockDays180, &r.StockDays365, &r.StockDaysTotal,
|
||||
&r.StockTurnover90, &r.StockTurnover180, &r.StockTurnover365, &r.StockTurnoverTotal,
|
||||
&r.AvgPriceUSD90, &r.AvgPriceUSD180, &r.CostPriceUSD, &r.BasePriceUSD, &r.BasePriceTRY,
|
||||
&r.GrossProfitUSD90, &r.GrossProfitUSD180, &r.GrossMargin90, &r.GrossMargin180,
|
||||
&r.UnitProfitCost90, &r.UnitProfitCost180, &r.UnitProfitBase90, &r.UnitProfitBase180, &r.MarketCount90, &r.CustomerCount90, &r.SalesIndex90,
|
||||
&r.UnitProfitCost90, &r.UnitProfitCost180, &r.UnitProfitBase90, &r.UnitProfitBase180,
|
||||
&r.MarketCount90, &r.MarketCount180, &r.MarketCount365, &r.MarketCountTotal,
|
||||
&r.CustomerCount90, &r.CustomerCount180, &r.CustomerCount365, &r.CustomerCountTotal,
|
||||
&r.SalesIndex90, &r.SalesIndex180, &r.SalesIndex365, &r.SalesIndexTotal,
|
||||
&r.PriceIndex90, &r.MarginIndex90, &r.PerformanceScore, &r.PerformanceBucket,
|
||||
&r.Recommendation, &r.LastSaleDate, &r.LastRefNumber, &r.UpdatedAt,
|
||||
); err != nil {
|
||||
@@ -1824,37 +1858,104 @@ LIMIT $`+fmt.Sprint(len(args)-1)+` OFFSET $`+fmt.Sprint(len(args)), args...)
|
||||
func applyProductPerformanceProductRowScores(rows []models.ProductPerformanceRow) {
|
||||
avg := productPerformanceProductRowAverages(rows)
|
||||
for i := range rows {
|
||||
rows[i].PerformanceScore = productPerformanceProductScore(
|
||||
rows[i].SalesIndex90 = productPerformanceRelativeIndex(rows[i].SalesUSD90, avg.salesUSD90)
|
||||
rows[i].SalesIndex180 = productPerformanceRelativeIndex(rows[i].SalesUSD180, avg.salesUSD180)
|
||||
rows[i].SalesIndex365 = productPerformanceRelativeIndex(rows[i].SalesUSD365, avg.salesUSD365)
|
||||
rows[i].SalesIndexTotal = productPerformanceRelativeIndex(rows[i].SalesUSDTotal, avg.salesUSDTotal)
|
||||
|
||||
margin365 := productPerformanceMarginFromSales(rows[i].SalesUSD365, rows[i].SalesQty365, rows[i].CostPriceUSD)
|
||||
marginTotal := productPerformanceMarginFromSales(rows[i].SalesUSDTotal, rows[i].SalesQtyTotal, rows[i].CostPriceUSD)
|
||||
|
||||
rows[i].PerformanceScore90 = productPerformanceProductScore(
|
||||
"90d",
|
||||
rows[i].SalesUSD90,
|
||||
productPerformanceRelativeIndex(rows[i].SalesUSD90, avg.salesUSD90),
|
||||
rows[i].SalesIndex90,
|
||||
rows[i].GrossMargin90,
|
||||
rows[i].StockTurnover90,
|
||||
float64(rows[i].MarketCount90),
|
||||
float64(rows[i].CustomerCount90),
|
||||
)
|
||||
rows[i].PerformanceScore180 = productPerformanceProductScore(
|
||||
"180d",
|
||||
rows[i].SalesUSD180,
|
||||
rows[i].SalesIndex180,
|
||||
rows[i].GrossMargin180,
|
||||
rows[i].StockTurnover180,
|
||||
float64(rows[i].MarketCount180),
|
||||
float64(rows[i].CustomerCount180),
|
||||
)
|
||||
rows[i].PerformanceScore365 = productPerformanceProductScore(
|
||||
"365d",
|
||||
rows[i].SalesUSD365,
|
||||
rows[i].SalesIndex365,
|
||||
margin365,
|
||||
rows[i].StockTurnover365,
|
||||
float64(rows[i].MarketCount365),
|
||||
float64(rows[i].CustomerCount365),
|
||||
)
|
||||
rows[i].PerformanceScoreTotal = productPerformanceProductScore(
|
||||
"total",
|
||||
rows[i].SalesUSDTotal,
|
||||
rows[i].SalesIndexTotal,
|
||||
marginTotal,
|
||||
rows[i].StockTurnoverTotal,
|
||||
float64(rows[i].MarketCountTotal),
|
||||
float64(rows[i].CustomerCountTotal),
|
||||
)
|
||||
rows[i].PerformanceScore = rows[i].PerformanceScore90
|
||||
}
|
||||
}
|
||||
|
||||
type productPerformanceProductRowAverage struct {
|
||||
salesUSD90 float64
|
||||
salesUSD90 float64
|
||||
salesUSD180 float64
|
||||
salesUSD365 float64
|
||||
salesUSDTotal float64
|
||||
}
|
||||
|
||||
func productPerformanceProductRowAverages(rows []models.ProductPerformanceRow) productPerformanceProductRowAverage {
|
||||
var sum90, count90 float64
|
||||
var sum90, count90, sum180, count180, sum365, count365, sumTotal, countTotal float64
|
||||
for _, row := range rows {
|
||||
if row.SalesUSD90 > 0 {
|
||||
sum90 += row.SalesUSD90
|
||||
count90++
|
||||
}
|
||||
if row.SalesUSD180 > 0 {
|
||||
sum180 += row.SalesUSD180
|
||||
count180++
|
||||
}
|
||||
if row.SalesUSD365 > 0 {
|
||||
sum365 += row.SalesUSD365
|
||||
count365++
|
||||
}
|
||||
if row.SalesUSDTotal > 0 {
|
||||
sumTotal += row.SalesUSDTotal
|
||||
countTotal++
|
||||
}
|
||||
}
|
||||
out := productPerformanceProductRowAverage{}
|
||||
if count90 > 0 {
|
||||
out.salesUSD90 = sum90 / count90
|
||||
}
|
||||
if count180 > 0 {
|
||||
out.salesUSD180 = sum180 / count180
|
||||
}
|
||||
if count365 > 0 {
|
||||
out.salesUSD365 = sum365 / count365
|
||||
}
|
||||
if countTotal > 0 {
|
||||
out.salesUSDTotal = sumTotal / countTotal
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func productPerformanceMarginFromSales(salesUSD, salesQty, unitCost float64) float64 {
|
||||
if salesUSD <= 0 {
|
||||
return 0
|
||||
}
|
||||
return (salesUSD - (salesQty * unitCost)) / salesUSD
|
||||
}
|
||||
|
||||
func GetProductPerformanceSummary(ctx context.Context, pg *sql.DB) (models.ProductPerformanceSummary, error) {
|
||||
if err := EnsureProductPerformanceTables(pg); err != nil {
|
||||
return models.ProductPerformanceSummary{}, err
|
||||
@@ -3699,6 +3800,10 @@ SELECT
|
||||
gross_profit_cost_usd_total,
|
||||
gross_margin_base_total,
|
||||
gross_margin_cost_total,
|
||||
avg_stock_90d,
|
||||
avg_stock_180d,
|
||||
avg_stock_365d,
|
||||
avg_stock_total,
|
||||
CASE WHEN avg_stock_90d > 0 THEN sales_qty_90d / NULLIF(avg_stock_90d,0) ELSE 0 END AS stock_turnover_90d,
|
||||
CASE WHEN avg_stock_180d > 0 THEN sales_qty_180d / NULLIF(avg_stock_180d,0) ELSE 0 END AS stock_turnover_180d,
|
||||
CASE WHEN avg_stock_365d > 0 THEN sales_qty_365d / NULLIF(avg_stock_365d,0) ELSE 0 END AS stock_turnover_365d,
|
||||
@@ -3807,6 +3912,7 @@ LIMIT $1
|
||||
&r.GrossMarginCost365, &r.CustomerCount365, &r.InvoiceCount365, &r.CustomerCountTotal, &r.InvoiceCountTotal, &r.SalesQtyTotal, &r.SalesUSDTotal,
|
||||
&r.AvgPriceUSDTotal, &r.BasePriceUSDTotal, &r.CostPriceUSDTotal, &r.GrossProfitBaseTotal,
|
||||
&r.GrossProfitCostTotal, &r.GrossMarginBaseTotal, &r.GrossMarginCostTotal,
|
||||
&r.AvgStock90, &r.AvgStock180, &r.AvgStock365, &r.AvgStockTotal,
|
||||
&r.StockTurnover90, &r.StockTurnover180, &r.StockTurnover365, &r.StockTurnoverTotal, &r.HasCost,
|
||||
&r.SalesIndex90, &r.CustomerScore90, &r.CustomerScore180, &r.CustomerScore365, &r.CustomerScoreTotal, &r.PerformanceScore,
|
||||
&r.PerformanceBucket, &r.Recommendation, &r.LastSaleDate,
|
||||
@@ -4639,10 +4745,10 @@ Source AS (
|
||||
urun_alt_grubu,
|
||||
market_key,
|
||||
COALESCE(stock_qty,0) AS stock_qty,
|
||||
(COALESCE((SELECT s.stock_qty FROM Stock90Start s WHERE s.product_code = mk_product_performance_kpi_daily.product_code AND s.color_code = mk_product_performance_kpi_daily.color_code AND s.yaka_kodu = mk_product_performance_kpi_daily.yaka_kodu),0) + COALESCE(stock_qty,0)) / 2.0 AS avg_stock_90d,
|
||||
(COALESCE((SELECT s.stock_qty FROM Stock180Start s WHERE s.product_code = mk_product_performance_kpi_daily.product_code AND s.color_code = mk_product_performance_kpi_daily.color_code AND s.yaka_kodu = mk_product_performance_kpi_daily.yaka_kodu),0) + COALESCE(stock_qty,0)) / 2.0 AS avg_stock_180d,
|
||||
(COALESCE((SELECT s.stock_qty FROM Stock365Start s WHERE s.product_code = mk_product_performance_kpi_daily.product_code AND s.color_code = mk_product_performance_kpi_daily.color_code AND s.yaka_kodu = mk_product_performance_kpi_daily.yaka_kodu),0) + COALESCE(stock_qty,0)) / 2.0 AS avg_stock_365d,
|
||||
(COALESCE((SELECT s.stock_qty FROM StockTotalStart s WHERE s.product_code = mk_product_performance_kpi_daily.product_code AND s.color_code = mk_product_performance_kpi_daily.color_code AND s.yaka_kodu = mk_product_performance_kpi_daily.yaka_kodu),0) + COALESCE(stock_qty,0)) / 2.0 AS avg_stock_total,
|
||||
COALESCE(NULLIF(avg_stock_90d,0), (COALESCE((SELECT s.stock_qty FROM Stock90Start s WHERE s.product_code = mk_product_performance_kpi_daily.product_code AND s.color_code = mk_product_performance_kpi_daily.color_code AND s.yaka_kodu = mk_product_performance_kpi_daily.yaka_kodu),0) + COALESCE(stock_qty,0)) / 2.0) AS avg_stock_90d,
|
||||
COALESCE(NULLIF(avg_stock_180d,0), (COALESCE((SELECT s.stock_qty FROM Stock180Start s WHERE s.product_code = mk_product_performance_kpi_daily.product_code AND s.color_code = mk_product_performance_kpi_daily.color_code AND s.yaka_kodu = mk_product_performance_kpi_daily.yaka_kodu),0) + COALESCE(stock_qty,0)) / 2.0) AS avg_stock_180d,
|
||||
COALESCE(NULLIF(avg_stock_365d,0), (COALESCE((SELECT s.stock_qty FROM Stock365Start s WHERE s.product_code = mk_product_performance_kpi_daily.product_code AND s.color_code = mk_product_performance_kpi_daily.color_code AND s.yaka_kodu = mk_product_performance_kpi_daily.yaka_kodu),0) + COALESCE(stock_qty,0)) / 2.0) AS avg_stock_365d,
|
||||
COALESCE(NULLIF(avg_stock_total,0), (COALESCE((SELECT s.stock_qty FROM StockTotalStart s WHERE s.product_code = mk_product_performance_kpi_daily.product_code AND s.color_code = mk_product_performance_kpi_daily.color_code AND s.yaka_kodu = mk_product_performance_kpi_daily.yaka_kodu),0) + COALESCE(stock_qty,0)) / 2.0) AS avg_stock_total,
|
||||
COALESCE(sales_qty_90d,0) AS sales_qty_90d,
|
||||
COALESCE(sales_qty_180d,0) AS sales_qty_180d,
|
||||
COALESCE(sales_qty_365d,0) AS sales_qty_365d,
|
||||
@@ -4669,8 +4775,17 @@ Source AS (
|
||||
COALESCE(unit_profit_base_90d,0) AS unit_profit_base_90d,
|
||||
COALESCE(unit_profit_base_180d,0) AS unit_profit_base_180d,
|
||||
COALESCE(market_count_90d,0) AS market_count_90d,
|
||||
COALESCE(market_count_180d,0) AS market_count_180d,
|
||||
COALESCE(market_count_365d,0) AS market_count_365d,
|
||||
COALESCE(market_count_total,0) AS market_count_total,
|
||||
COALESCE(customer_count_90d,0) AS customer_count_90d,
|
||||
COALESCE(customer_count_180d,0) AS customer_count_180d,
|
||||
COALESCE(customer_count_365d,0) AS customer_count_365d,
|
||||
COALESCE(customer_count_total,0) AS customer_count_total,
|
||||
COALESCE(sales_index_90d,0) AS sales_index_90d,
|
||||
COALESCE(sales_index_180d,0) AS sales_index_180d,
|
||||
COALESCE(sales_index_365d,0) AS sales_index_365d,
|
||||
COALESCE(sales_index_total,0) AS sales_index_total,
|
||||
COALESCE(price_index_90d,0) AS price_index_90d,
|
||||
COALESCE(margin_index_90d,0) AS margin_index_90d,
|
||||
COALESCE(performance_score,0) AS performance_score,
|
||||
@@ -5069,7 +5184,9 @@ func mergeProductPerformanceGeneralSnapshotMetrics(ctx context.Context, pg *sql.
|
||||
}
|
||||
}
|
||||
if value, ok := general["performance_score"]; ok {
|
||||
row["performance_score_total"] = value
|
||||
if _, exists := row["performance_score_total"]; !exists {
|
||||
row["performance_score_total"] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
return rows
|
||||
@@ -5849,6 +5966,19 @@ func productPerformanceMapVariantKey(row map[string]any) string {
|
||||
|
||||
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
||||
normalizeProductPerformanceCostFields(out)
|
||||
preservedPerformanceScores := map[string]float64{}
|
||||
if score, ok := productPerformanceOptionalFloat(out, "performance_score"); ok {
|
||||
preservedPerformanceScores["90d"] = score
|
||||
}
|
||||
preservedCustomerScores := map[string]float64{}
|
||||
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 {
|
||||
preservedCustomerScores[suffix] = score
|
||||
}
|
||||
}
|
||||
for _, suffix := range productPerformancePeriodSuffixes() {
|
||||
sales := floatFromMap(out, "sales_usd_"+suffix)
|
||||
qty := floatFromMap(out, "sales_qty_"+suffix)
|
||||
@@ -5926,14 +6056,18 @@ func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string)
|
||||
if _, ok := out["net_stock_after_order"]; ok || orderQty > 0 {
|
||||
out["net_stock_after_order"] = floatFromMap(out, "stock_qty") - orderQty
|
||||
}
|
||||
out["customer_score_90d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "90d"))
|
||||
out["customer_score_180d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "180d"))
|
||||
out["customer_score_365d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "365d"))
|
||||
out["customer_score_total"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "total"))
|
||||
out["performance_score_90d"] = productPerformanceSalesPeriodScore(out, "90d")
|
||||
out["performance_score_180d"] = productPerformanceSalesPeriodScore(out, "180d")
|
||||
out["performance_score_365d"] = productPerformanceSalesPeriodScore(out, "365d")
|
||||
out["performance_score_total"] = productPerformanceSalesPeriodScore(out, "total")
|
||||
for _, suffix := range productPerformancePeriodSuffixes() {
|
||||
if score, ok := preservedCustomerScores[suffix]; ok {
|
||||
out["customer_score_"+suffix] = score
|
||||
} else {
|
||||
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"] = out["performance_score_90d"]
|
||||
if floatFromMap(out, "order_qty") > 0 || floatFromMap(out, "order_usd") > 0 {
|
||||
score := productPerformanceOrderGroupScore(out)
|
||||
|
||||
@@ -486,6 +486,9 @@ SalesAgg AS (
|
||||
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '359 days') AS sales_usd_365d,
|
||||
SUM(sales_usd) FILTER (WHERE sales_date >= DATE '2022-01-01') AS sales_usd_total,
|
||||
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= $1::date - INTERVAL '89 days' AND COALESCE(sales_usd, 0) > 0) AS customer_count_90d,
|
||||
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= $1::date - INTERVAL '179 days' AND COALESCE(sales_usd, 0) > 0) AS customer_count_180d,
|
||||
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= $1::date - INTERVAL '359 days' AND COALESCE(sales_usd, 0) > 0) AS customer_count_365d,
|
||||
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= DATE '2022-01-01' AND COALESCE(sales_usd, 0) > 0) AS customer_count_total,
|
||||
MAX(sales_date) AS last_sale_date,
|
||||
MAX(last_ref_number) AS last_ref_number
|
||||
FROM mk_product_performance_sales_daily
|
||||
@@ -500,7 +503,7 @@ Scope AS (
|
||||
item_description, kategori, seri, yas_grubu, askili_yan, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu,
|
||||
sales_qty_30d, sales_qty_90d, sales_qty_180d, sales_qty_365d, sales_qty_730d, sales_qty_total,
|
||||
sales_usd_30d, sales_usd_90d, sales_usd_180d, sales_usd_365d, sales_usd_total,
|
||||
customer_count_90d, last_sale_date, last_ref_number
|
||||
customer_count_90d, customer_count_180d, customer_count_365d, customer_count_total, last_sale_date, last_ref_number
|
||||
FROM SalesAgg
|
||||
UNION ALL
|
||||
SELECT
|
||||
@@ -528,6 +531,9 @@ Scope AS (
|
||||
0 AS sales_usd_365d,
|
||||
0 AS sales_usd_total,
|
||||
0 AS customer_count_90d,
|
||||
0 AS customer_count_180d,
|
||||
0 AS customer_count_365d,
|
||||
0 AS customer_count_total,
|
||||
NULL::date AS last_sale_date,
|
||||
'' AS last_ref_number
|
||||
FROM LatestStock ls
|
||||
@@ -602,15 +608,20 @@ Spread AS (
|
||||
product_code,
|
||||
color_code,
|
||||
yaka_kodu,
|
||||
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) AS market_count_90d
|
||||
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) FILTER (WHERE COALESCE(sales_usd_90d, 0) > 0) AS market_count_90d,
|
||||
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) FILTER (WHERE COALESCE(sales_usd_180d, 0) > 0) AS market_count_180d,
|
||||
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) FILTER (WHERE COALESCE(sales_usd_365d, 0) > 0) AS market_count_365d,
|
||||
COUNT(DISTINCT NULLIF(NULLIF(market_key, 'STOK'), '')) FILTER (WHERE COALESCE(sales_usd_total, 0) > 0) AS market_count_total
|
||||
FROM SalesAgg
|
||||
WHERE COALESCE(sales_usd_90d, 0) > 0
|
||||
GROUP BY product_code, color_code, yaka_kodu
|
||||
),
|
||||
Market AS (
|
||||
SELECT
|
||||
market_key,
|
||||
AVG(NULLIF(sales_qty_90d, 0)) AS market_avg_sales_qty_90d,
|
||||
AVG(NULLIF(sales_qty_180d, 0)) AS market_avg_sales_qty_180d,
|
||||
AVG(NULLIF(sales_qty_365d, 0)) AS market_avg_sales_qty_365d,
|
||||
AVG(NULLIF(sales_qty_total, 0)) AS market_avg_sales_qty_total,
|
||||
AVG(NULLIF(avg_price_usd_90d, 0)) AS market_avg_price_usd_90d,
|
||||
AVG(NULLIF(gross_margin_90d, 0)) AS market_avg_margin_90d
|
||||
FROM Base
|
||||
@@ -620,6 +631,9 @@ Scored AS (
|
||||
SELECT
|
||||
b.*,
|
||||
COALESCE(sp.market_count_90d, 0) AS market_count_90d,
|
||||
COALESCE(sp.market_count_180d, 0) AS market_count_180d,
|
||||
COALESCE(sp.market_count_365d, 0) AS market_count_365d,
|
||||
COALESCE(sp.market_count_total, 0) AS market_count_total,
|
||||
CASE WHEN b.avg_daily_sales_90d = 0 THEN 0 ELSE b.avg_stock_90d / NULLIF(b.avg_daily_sales_90d, 0) END AS stock_days_90d,
|
||||
CASE WHEN b.avg_daily_sales_180d = 0 THEN 0 ELSE b.avg_stock_180d / NULLIF(b.avg_daily_sales_180d, 0) END AS stock_days_180d,
|
||||
CASE WHEN b.avg_daily_sales_365d = 0 THEN 0 ELSE b.avg_stock_365d / NULLIF(b.avg_daily_sales_365d, 0) END AS stock_days_365d,
|
||||
@@ -629,6 +643,9 @@ Scored AS (
|
||||
CASE WHEN b.avg_stock_365d = 0 THEN 0 ELSE b.sales_qty_365d / NULLIF(b.avg_stock_365d, 0) END AS stock_turnover_365d,
|
||||
CASE WHEN b.avg_stock_total = 0 THEN 0 ELSE b.sales_qty_total / NULLIF(b.avg_stock_total, 0) END AS stock_turnover_total,
|
||||
CASE WHEN COALESCE(m.market_avg_sales_qty_90d, 0) = 0 THEN 0 ELSE b.sales_qty_90d / NULLIF(m.market_avg_sales_qty_90d, 0) END AS sales_index_90d,
|
||||
CASE WHEN COALESCE(m.market_avg_sales_qty_180d, 0) = 0 THEN 0 ELSE b.sales_qty_180d / NULLIF(m.market_avg_sales_qty_180d, 0) END AS sales_index_180d,
|
||||
CASE WHEN COALESCE(m.market_avg_sales_qty_365d, 0) = 0 THEN 0 ELSE b.sales_qty_365d / NULLIF(m.market_avg_sales_qty_365d, 0) END AS sales_index_365d,
|
||||
CASE WHEN COALESCE(m.market_avg_sales_qty_total, 0) = 0 THEN 0 ELSE b.sales_qty_total / NULLIF(m.market_avg_sales_qty_total, 0) END AS sales_index_total,
|
||||
CASE WHEN COALESCE(m.market_avg_price_usd_90d, 0) = 0 THEN 0 ELSE b.avg_price_usd_90d / NULLIF(m.market_avg_price_usd_90d, 0) END AS price_index_90d,
|
||||
CASE WHEN COALESCE(m.market_avg_margin_90d, 0) = 0 THEN 0 ELSE b.gross_margin_90d / NULLIF(m.market_avg_margin_90d, 0) END AS margin_index_90d
|
||||
FROM Base b
|
||||
@@ -640,11 +657,14 @@ INSERT INTO mk_product_performance_kpi_daily (
|
||||
kategori, seri, yas_grubu, askili_yan, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu, market_key, stock_qty,
|
||||
sales_qty_30d, sales_qty_90d, sales_qty_180d, sales_qty_365d, sales_qty_730d, sales_qty_total,
|
||||
sales_usd_30d, sales_usd_90d, sales_usd_180d, sales_usd_365d, sales_usd_total, avg_daily_sales_90d, avg_daily_sales_180d, avg_daily_sales_365d, avg_daily_sales_total,
|
||||
avg_stock_90d, avg_stock_180d, avg_stock_365d, avg_stock_total,
|
||||
stock_days_90d, stock_days_180d, stock_days_365d, stock_days_total, stock_turnover_90d, stock_turnover_180d, stock_turnover_365d, stock_turnover_total,
|
||||
avg_price_usd_90d, avg_price_usd_180d, cost_price_usd, base_price_usd, base_price_try,
|
||||
gross_profit_usd_90d, gross_profit_usd_180d, gross_margin_90d, gross_margin_180d,
|
||||
unit_profit_cost_90d, unit_profit_cost_180d, unit_profit_base_90d, unit_profit_base_180d, market_count_90d, customer_count_90d,
|
||||
sales_index_90d, price_index_90d, margin_index_90d, performance_score,
|
||||
unit_profit_cost_90d, unit_profit_cost_180d, unit_profit_base_90d, unit_profit_base_180d,
|
||||
market_count_90d, market_count_180d, market_count_365d, market_count_total,
|
||||
customer_count_90d, customer_count_180d, customer_count_365d, customer_count_total,
|
||||
sales_index_90d, sales_index_180d, sales_index_365d, sales_index_total, price_index_90d, margin_index_90d, performance_score,
|
||||
performance_bucket, recommendation, last_sale_date, last_ref_number, updated_at
|
||||
)
|
||||
SELECT
|
||||
@@ -653,11 +673,14 @@ SELECT
|
||||
kategori, seri, yas_grubu, askili_yan, urun_ilk_grubu, urun_ana_grubu, urun_alt_grubu, market_key, stock_qty,
|
||||
COALESCE(sales_qty_30d, 0), COALESCE(sales_qty_90d, 0), COALESCE(sales_qty_180d, 0), COALESCE(sales_qty_365d, 0), COALESCE(sales_qty_730d, 0), COALESCE(sales_qty_total, 0),
|
||||
COALESCE(sales_usd_30d, 0), COALESCE(sales_usd_90d, 0), COALESCE(sales_usd_180d, 0), COALESCE(sales_usd_365d, 0), COALESCE(sales_usd_total, 0), COALESCE(avg_daily_sales_90d, 0), COALESCE(avg_daily_sales_180d, 0), COALESCE(avg_daily_sales_365d, 0), COALESCE(avg_daily_sales_total, 0),
|
||||
COALESCE(avg_stock_90d, 0), COALESCE(avg_stock_180d, 0), COALESCE(avg_stock_365d, 0), COALESCE(avg_stock_total, 0),
|
||||
COALESCE(stock_days_90d, 0), COALESCE(stock_days_180d, 0), COALESCE(stock_days_365d, 0), COALESCE(stock_days_total, 0), COALESCE(stock_turnover_90d, 0), COALESCE(stock_turnover_180d, 0), COALESCE(stock_turnover_365d, 0), COALESCE(stock_turnover_total, 0),
|
||||
COALESCE(avg_price_usd_90d, 0), COALESCE(avg_price_usd_180d, 0), COALESCE(cost_price_usd, 0), COALESCE(base_price_usd, 0), COALESCE(base_price_try, 0),
|
||||
COALESCE(gross_profit_usd_90d, 0), COALESCE(gross_profit_usd_180d, 0), COALESCE(gross_margin_90d, 0), COALESCE(gross_margin_180d, 0),
|
||||
COALESCE(unit_profit_cost_90d, 0), COALESCE(unit_profit_cost_180d, 0), COALESCE(unit_profit_base_90d, 0), COALESCE(unit_profit_base_180d, 0), COALESCE(market_count_90d, 0), COALESCE(customer_count_90d, 0),
|
||||
COALESCE(sales_index_90d, 0), COALESCE(price_index_90d, 0), COALESCE(margin_index_90d, 0),
|
||||
COALESCE(unit_profit_cost_90d, 0), COALESCE(unit_profit_cost_180d, 0), COALESCE(unit_profit_base_90d, 0), COALESCE(unit_profit_base_180d, 0),
|
||||
COALESCE(market_count_90d, 0), COALESCE(market_count_180d, 0), COALESCE(market_count_365d, 0), COALESCE(market_count_total, 0),
|
||||
COALESCE(customer_count_90d, 0), COALESCE(customer_count_180d, 0), COALESCE(customer_count_365d, 0), COALESCE(customer_count_total, 0),
|
||||
COALESCE(sales_index_90d, 0), COALESCE(sales_index_180d, 0), COALESCE(sales_index_365d, 0), COALESCE(sales_index_total, 0), COALESCE(price_index_90d, 0), COALESCE(margin_index_90d, 0),
|
||||
CASE WHEN COALESCE(sales_usd_90d, 0) <= 0 THEN 1 ELSE
|
||||
ROUND(
|
||||
LEAST(35, COALESCE(sales_index_90d, 0) * 18)
|
||||
|
||||
Reference in New Issue
Block a user