Fix product performance grouped JSON build
This commit is contained in:
@@ -31,6 +31,10 @@ type ProductPerformanceRow struct {
|
|||||||
AvgDailySales180 float64 `json:"avg_daily_sales_180d"`
|
AvgDailySales180 float64 `json:"avg_daily_sales_180d"`
|
||||||
AvgDailySales365 float64 `json:"avg_daily_sales_365d"`
|
AvgDailySales365 float64 `json:"avg_daily_sales_365d"`
|
||||||
AvgDailySalesTotal float64 `json:"avg_daily_sales_total"`
|
AvgDailySalesTotal float64 `json:"avg_daily_sales_total"`
|
||||||
|
AvgStock90 float64 `json:"avg_stock_90d"`
|
||||||
|
AvgStock180 float64 `json:"avg_stock_180d"`
|
||||||
|
AvgStock365 float64 `json:"avg_stock_365d"`
|
||||||
|
AvgStockTotal float64 `json:"avg_stock_total"`
|
||||||
StockDays90 float64 `json:"stock_days_90d"`
|
StockDays90 float64 `json:"stock_days_90d"`
|
||||||
StockDays180 float64 `json:"stock_days_180d"`
|
StockDays180 float64 `json:"stock_days_180d"`
|
||||||
StockDays365 float64 `json:"stock_days_365d"`
|
StockDays365 float64 `json:"stock_days_365d"`
|
||||||
@@ -53,10 +57,23 @@ type ProductPerformanceRow struct {
|
|||||||
UnitProfitBase90 float64 `json:"unit_profit_base_90d"`
|
UnitProfitBase90 float64 `json:"unit_profit_base_90d"`
|
||||||
UnitProfitBase180 float64 `json:"unit_profit_base_180d"`
|
UnitProfitBase180 float64 `json:"unit_profit_base_180d"`
|
||||||
MarketCount90 int `json:"market_count_90d"`
|
MarketCount90 int `json:"market_count_90d"`
|
||||||
|
MarketCount180 int `json:"market_count_180d"`
|
||||||
|
MarketCount365 int `json:"market_count_365d"`
|
||||||
|
MarketCountTotal int `json:"market_count_total"`
|
||||||
CustomerCount90 int `json:"customer_count_90d"`
|
CustomerCount90 int `json:"customer_count_90d"`
|
||||||
|
CustomerCount180 int `json:"customer_count_180d"`
|
||||||
|
CustomerCount365 int `json:"customer_count_365d"`
|
||||||
|
CustomerCountTotal int `json:"customer_count_total"`
|
||||||
SalesIndex90 float64 `json:"sales_index_90d"`
|
SalesIndex90 float64 `json:"sales_index_90d"`
|
||||||
|
SalesIndex180 float64 `json:"sales_index_180d"`
|
||||||
|
SalesIndex365 float64 `json:"sales_index_365d"`
|
||||||
|
SalesIndexTotal float64 `json:"sales_index_total"`
|
||||||
PriceIndex90 float64 `json:"price_index_90d"`
|
PriceIndex90 float64 `json:"price_index_90d"`
|
||||||
MarginIndex90 float64 `json:"margin_index_90d"`
|
MarginIndex90 float64 `json:"margin_index_90d"`
|
||||||
|
PerformanceScore90 float64 `json:"performance_score_90d"`
|
||||||
|
PerformanceScore180 float64 `json:"performance_score_180d"`
|
||||||
|
PerformanceScore365 float64 `json:"performance_score_365d"`
|
||||||
|
PerformanceScoreTotal float64 `json:"performance_score_total"`
|
||||||
PerformanceScore float64 `json:"performance_score"`
|
PerformanceScore float64 `json:"performance_score"`
|
||||||
PerformanceBucket string `json:"performance_bucket"`
|
PerformanceBucket string `json:"performance_bucket"`
|
||||||
Recommendation string `json:"recommendation"`
|
Recommendation string `json:"recommendation"`
|
||||||
@@ -378,6 +395,10 @@ type ProductPerformanceSalesBreakdownRow struct {
|
|||||||
GrossMarginBaseTotal float64 `json:"gross_margin_base_total"`
|
GrossMarginBaseTotal float64 `json:"gross_margin_base_total"`
|
||||||
GrossMarginCostTotal float64 `json:"gross_margin_cost_total"`
|
GrossMarginCostTotal float64 `json:"gross_margin_cost_total"`
|
||||||
StockQty float64 `json:"stock_qty"`
|
StockQty float64 `json:"stock_qty"`
|
||||||
|
AvgStock90 float64 `json:"avg_stock_90d"`
|
||||||
|
AvgStock180 float64 `json:"avg_stock_180d"`
|
||||||
|
AvgStock365 float64 `json:"avg_stock_365d"`
|
||||||
|
AvgStockTotal float64 `json:"avg_stock_total"`
|
||||||
StockTurnover90 float64 `json:"stock_turnover_90d"`
|
StockTurnover90 float64 `json:"stock_turnover_90d"`
|
||||||
StockTurnover180 float64 `json:"stock_turnover_180d"`
|
StockTurnover180 float64 `json:"stock_turnover_180d"`
|
||||||
StockTurnover365 float64 `json:"stock_turnover_365d"`
|
StockTurnover365 float64 `json:"stock_turnover_365d"`
|
||||||
|
|||||||
@@ -481,6 +481,10 @@ CREATE TABLE IF NOT EXISTS mk_product_performance_kpi_daily (
|
|||||||
sales_usd_365d NUMERIC(18,4) NOT NULL DEFAULT 0,
|
sales_usd_365d NUMERIC(18,4) NOT NULL DEFAULT 0,
|
||||||
avg_daily_sales_90d NUMERIC(18,6) 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_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_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||||
stock_days_180d 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,
|
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_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||||
unit_profit_base_180d 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_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_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_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,
|
price_index_90d NUMERIC(18,6) NOT NULL DEFAULT 0,
|
||||||
margin_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,
|
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_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_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_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_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_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`,
|
`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_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 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_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 (
|
CREATE TABLE IF NOT EXISTS mk_product_performance_report_snapshot (
|
||||||
report_key TEXT NOT NULL,
|
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_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,
|
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_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_days_90d, stock_days_180d, stock_days_365d, stock_days_total,
|
||||||
stock_turnover_90d, stock_turnover_180d, stock_turnover_365d, stock_turnover_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,
|
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,
|
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,
|
price_index_90d, margin_index_90d, performance_score, performance_bucket,
|
||||||
recommendation, COALESCE(to_char(last_sale_date,'YYYY-MM-DD'),''), last_ref_number,
|
recommendation, COALESCE(to_char(last_sale_date,'YYYY-MM-DD'),''), last_ref_number,
|
||||||
to_char(updated_at,'YYYY-MM-DD HH24:MI:SS')
|
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.SalesQty30, &r.SalesQty90, &r.SalesQty180, &r.SalesQty365, &r.SalesQty730, &r.SalesQtyTotal,
|
||||||
&r.SalesUSD30, &r.SalesUSD90, &r.SalesUSD180, &r.SalesUSD365, &r.SalesUSDTotal,
|
&r.SalesUSD30, &r.SalesUSD90, &r.SalesUSD180, &r.SalesUSD365, &r.SalesUSDTotal,
|
||||||
&r.AvgDailySales90, &r.AvgDailySales180, &r.AvgDailySales365, &r.AvgDailySalesTotal,
|
&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.StockDays90, &r.StockDays180, &r.StockDays365, &r.StockDaysTotal,
|
||||||
&r.StockTurnover90, &r.StockTurnover180, &r.StockTurnover365, &r.StockTurnoverTotal,
|
&r.StockTurnover90, &r.StockTurnover180, &r.StockTurnover365, &r.StockTurnoverTotal,
|
||||||
&r.AvgPriceUSD90, &r.AvgPriceUSD180, &r.CostPriceUSD, &r.BasePriceUSD, &r.BasePriceTRY,
|
&r.AvgPriceUSD90, &r.AvgPriceUSD180, &r.CostPriceUSD, &r.BasePriceUSD, &r.BasePriceTRY,
|
||||||
&r.GrossProfitUSD90, &r.GrossProfitUSD180, &r.GrossMargin90, &r.GrossMargin180,
|
&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.PriceIndex90, &r.MarginIndex90, &r.PerformanceScore, &r.PerformanceBucket,
|
||||||
&r.Recommendation, &r.LastSaleDate, &r.LastRefNumber, &r.UpdatedAt,
|
&r.Recommendation, &r.LastSaleDate, &r.LastRefNumber, &r.UpdatedAt,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -1824,37 +1858,104 @@ LIMIT $`+fmt.Sprint(len(args)-1)+` OFFSET $`+fmt.Sprint(len(args)), args...)
|
|||||||
func applyProductPerformanceProductRowScores(rows []models.ProductPerformanceRow) {
|
func applyProductPerformanceProductRowScores(rows []models.ProductPerformanceRow) {
|
||||||
avg := productPerformanceProductRowAverages(rows)
|
avg := productPerformanceProductRowAverages(rows)
|
||||||
for i := range 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",
|
"90d",
|
||||||
rows[i].SalesUSD90,
|
rows[i].SalesUSD90,
|
||||||
productPerformanceRelativeIndex(rows[i].SalesUSD90, avg.salesUSD90),
|
rows[i].SalesIndex90,
|
||||||
rows[i].GrossMargin90,
|
rows[i].GrossMargin90,
|
||||||
rows[i].StockTurnover90,
|
rows[i].StockTurnover90,
|
||||||
float64(rows[i].MarketCount90),
|
float64(rows[i].MarketCount90),
|
||||||
float64(rows[i].CustomerCount90),
|
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 {
|
type productPerformanceProductRowAverage struct {
|
||||||
salesUSD90 float64
|
salesUSD90 float64
|
||||||
|
salesUSD180 float64
|
||||||
|
salesUSD365 float64
|
||||||
|
salesUSDTotal float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func productPerformanceProductRowAverages(rows []models.ProductPerformanceRow) productPerformanceProductRowAverage {
|
func productPerformanceProductRowAverages(rows []models.ProductPerformanceRow) productPerformanceProductRowAverage {
|
||||||
var sum90, count90 float64
|
var sum90, count90, sum180, count180, sum365, count365, sumTotal, countTotal float64
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
if row.SalesUSD90 > 0 {
|
if row.SalesUSD90 > 0 {
|
||||||
sum90 += row.SalesUSD90
|
sum90 += row.SalesUSD90
|
||||||
count90++
|
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{}
|
out := productPerformanceProductRowAverage{}
|
||||||
if count90 > 0 {
|
if count90 > 0 {
|
||||||
out.salesUSD90 = sum90 / count90
|
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
|
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) {
|
func GetProductPerformanceSummary(ctx context.Context, pg *sql.DB) (models.ProductPerformanceSummary, error) {
|
||||||
if err := EnsureProductPerformanceTables(pg); err != nil {
|
if err := EnsureProductPerformanceTables(pg); err != nil {
|
||||||
return models.ProductPerformanceSummary{}, err
|
return models.ProductPerformanceSummary{}, err
|
||||||
@@ -3699,6 +3800,10 @@ SELECT
|
|||||||
gross_profit_cost_usd_total,
|
gross_profit_cost_usd_total,
|
||||||
gross_margin_base_total,
|
gross_margin_base_total,
|
||||||
gross_margin_cost_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_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_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,
|
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.GrossMarginCost365, &r.CustomerCount365, &r.InvoiceCount365, &r.CustomerCountTotal, &r.InvoiceCountTotal, &r.SalesQtyTotal, &r.SalesUSDTotal,
|
||||||
&r.AvgPriceUSDTotal, &r.BasePriceUSDTotal, &r.CostPriceUSDTotal, &r.GrossProfitBaseTotal,
|
&r.AvgPriceUSDTotal, &r.BasePriceUSDTotal, &r.CostPriceUSDTotal, &r.GrossProfitBaseTotal,
|
||||||
&r.GrossProfitCostTotal, &r.GrossMarginBaseTotal, &r.GrossMarginCostTotal,
|
&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.StockTurnover90, &r.StockTurnover180, &r.StockTurnover365, &r.StockTurnoverTotal, &r.HasCost,
|
||||||
&r.SalesIndex90, &r.CustomerScore90, &r.CustomerScore180, &r.CustomerScore365, &r.CustomerScoreTotal, &r.PerformanceScore,
|
&r.SalesIndex90, &r.CustomerScore90, &r.CustomerScore180, &r.CustomerScore365, &r.CustomerScoreTotal, &r.PerformanceScore,
|
||||||
&r.PerformanceBucket, &r.Recommendation, &r.LastSaleDate,
|
&r.PerformanceBucket, &r.Recommendation, &r.LastSaleDate,
|
||||||
@@ -4639,10 +4745,10 @@ Source AS (
|
|||||||
urun_alt_grubu,
|
urun_alt_grubu,
|
||||||
market_key,
|
market_key,
|
||||||
COALESCE(stock_qty,0) AS stock_qty,
|
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(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((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_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((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_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((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_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_90d,0) AS sales_qty_90d,
|
||||||
COALESCE(sales_qty_180d,0) AS sales_qty_180d,
|
COALESCE(sales_qty_180d,0) AS sales_qty_180d,
|
||||||
COALESCE(sales_qty_365d,0) AS sales_qty_365d,
|
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_90d,0) AS unit_profit_base_90d,
|
||||||
COALESCE(unit_profit_base_180d,0) AS unit_profit_base_180d,
|
COALESCE(unit_profit_base_180d,0) AS unit_profit_base_180d,
|
||||||
COALESCE(market_count_90d,0) AS market_count_90d,
|
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_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_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(price_index_90d,0) AS price_index_90d,
|
||||||
COALESCE(margin_index_90d,0) AS margin_index_90d,
|
COALESCE(margin_index_90d,0) AS margin_index_90d,
|
||||||
COALESCE(performance_score,0) AS performance_score,
|
COALESCE(performance_score,0) AS performance_score,
|
||||||
@@ -5069,9 +5184,11 @@ func mergeProductPerformanceGeneralSnapshotMetrics(ctx context.Context, pg *sql.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if value, ok := general["performance_score"]; ok {
|
if value, ok := general["performance_score"]; ok {
|
||||||
|
if _, exists := row["performance_score_total"]; !exists {
|
||||||
row["performance_score_total"] = value
|
row["performance_score_total"] = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return rows
|
return rows
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5849,6 +5966,19 @@ 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{}
|
||||||
|
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() {
|
for _, suffix := range productPerformancePeriodSuffixes() {
|
||||||
sales := floatFromMap(out, "sales_usd_"+suffix)
|
sales := floatFromMap(out, "sales_usd_"+suffix)
|
||||||
qty := floatFromMap(out, "sales_qty_"+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 {
|
if _, ok := out["net_stock_after_order"]; ok || orderQty > 0 {
|
||||||
out["net_stock_after_order"] = floatFromMap(out, "stock_qty") - orderQty
|
out["net_stock_after_order"] = floatFromMap(out, "stock_qty") - orderQty
|
||||||
}
|
}
|
||||||
out["customer_score_90d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "90d"))
|
for _, suffix := range productPerformancePeriodSuffixes() {
|
||||||
out["customer_score_180d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "180d"))
|
if score, ok := preservedCustomerScores[suffix]; ok {
|
||||||
out["customer_score_365d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "365d"))
|
out["customer_score_"+suffix] = score
|
||||||
out["customer_score_total"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "total"))
|
} else {
|
||||||
out["performance_score_90d"] = productPerformanceSalesPeriodScore(out, "90d")
|
out["customer_score_"+suffix] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, suffix))
|
||||||
out["performance_score_180d"] = productPerformanceSalesPeriodScore(out, "180d")
|
}
|
||||||
out["performance_score_365d"] = productPerformanceSalesPeriodScore(out, "365d")
|
if score, ok := preservedPerformanceScores[suffix]; ok {
|
||||||
out["performance_score_total"] = productPerformanceSalesPeriodScore(out, "total")
|
out["performance_score_"+suffix] = score
|
||||||
|
} else {
|
||||||
|
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)
|
||||||
|
|||||||
@@ -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 >= $1::date - INTERVAL '359 days') AS sales_usd_365d,
|
||||||
SUM(sales_usd) FILTER (WHERE sales_date >= DATE '2022-01-01') AS sales_usd_total,
|
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 '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(sales_date) AS last_sale_date,
|
||||||
MAX(last_ref_number) AS last_ref_number
|
MAX(last_ref_number) AS last_ref_number
|
||||||
FROM mk_product_performance_sales_daily
|
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,
|
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_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,
|
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
|
FROM SalesAgg
|
||||||
UNION ALL
|
UNION ALL
|
||||||
SELECT
|
SELECT
|
||||||
@@ -528,6 +531,9 @@ Scope AS (
|
|||||||
0 AS sales_usd_365d,
|
0 AS sales_usd_365d,
|
||||||
0 AS sales_usd_total,
|
0 AS sales_usd_total,
|
||||||
0 AS customer_count_90d,
|
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,
|
NULL::date AS last_sale_date,
|
||||||
'' AS last_ref_number
|
'' AS last_ref_number
|
||||||
FROM LatestStock ls
|
FROM LatestStock ls
|
||||||
@@ -602,15 +608,20 @@ Spread AS (
|
|||||||
product_code,
|
product_code,
|
||||||
color_code,
|
color_code,
|
||||||
yaka_kodu,
|
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
|
FROM SalesAgg
|
||||||
WHERE COALESCE(sales_usd_90d, 0) > 0
|
|
||||||
GROUP BY product_code, color_code, yaka_kodu
|
GROUP BY product_code, color_code, yaka_kodu
|
||||||
),
|
),
|
||||||
Market AS (
|
Market AS (
|
||||||
SELECT
|
SELECT
|
||||||
market_key,
|
market_key,
|
||||||
AVG(NULLIF(sales_qty_90d, 0)) AS market_avg_sales_qty_90d,
|
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(avg_price_usd_90d, 0)) AS market_avg_price_usd_90d,
|
||||||
AVG(NULLIF(gross_margin_90d, 0)) AS market_avg_margin_90d
|
AVG(NULLIF(gross_margin_90d, 0)) AS market_avg_margin_90d
|
||||||
FROM Base
|
FROM Base
|
||||||
@@ -620,6 +631,9 @@ Scored AS (
|
|||||||
SELECT
|
SELECT
|
||||||
b.*,
|
b.*,
|
||||||
COALESCE(sp.market_count_90d, 0) AS market_count_90d,
|
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_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_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,
|
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_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 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_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_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
|
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
|
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,
|
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_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,
|
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,
|
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,
|
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,
|
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,
|
unit_profit_cost_90d, unit_profit_cost_180d, unit_profit_base_90d, unit_profit_base_180d,
|
||||||
sales_index_90d, price_index_90d, margin_index_90d, performance_score,
|
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
|
performance_bucket, recommendation, last_sale_date, last_ref_number, updated_at
|
||||||
)
|
)
|
||||||
SELECT
|
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,
|
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_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(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(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(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(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(unit_profit_cost_90d, 0), COALESCE(unit_profit_cost_180d, 0), COALESCE(unit_profit_base_90d, 0), COALESCE(unit_profit_base_180d, 0),
|
||||||
COALESCE(sales_index_90d, 0), COALESCE(price_index_90d, 0), COALESCE(margin_index_90d, 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
|
CASE WHEN COALESCE(sales_usd_90d, 0) <= 0 THEN 1 ELSE
|
||||||
ROUND(
|
ROUND(
|
||||||
LEAST(35, COALESCE(sales_index_90d, 0) * 18)
|
LEAST(35, COALESCE(sales_index_90d, 0) * 18)
|
||||||
|
|||||||
@@ -3479,8 +3479,8 @@ function aggregateGroupFields (sourceRows, groupField = '') {
|
|||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
if (field === 'row_key' || field === 'key') continue
|
if (field === 'row_key' || field === 'key') continue
|
||||||
if (/^gross_margin(_base|_cost)?(_|$)/i.test(field)) continue
|
if (/^gross_margin(_base|_cost)?(_|$)/i.test(field)) continue
|
||||||
if (field === 'stock_qty') {
|
if (field === 'stock_qty' || /^avg_stock_/i.test(field)) {
|
||||||
out[field] = distinctVariantStockQty(sourceRows)
|
out[field] = distinctVariantNumber(sourceRows, field)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (field === 'idle_cost_usd') {
|
if (field === 'idle_cost_usd') {
|
||||||
@@ -3528,6 +3528,19 @@ 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 = {}
|
||||||
|
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}`)) {
|
||||||
|
preservedCustomerScores[suffix] = Number(out[`customer_score_${suffix}`] || 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||||
const sales = Number(out[`sales_usd_${suffix}`] || 0)
|
const sales = Number(out[`sales_usd_${suffix}`] || 0)
|
||||||
const qty = Number(out[`sales_qty_${suffix}`] || 0)
|
const qty = Number(out[`sales_qty_${suffix}`] || 0)
|
||||||
@@ -3579,14 +3592,14 @@ function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
|||||||
if (sourceRows.some(row => row.performance_bucket)) {
|
if (sourceRows.some(row => row.performance_bucket)) {
|
||||||
out.performance_bucket = dominantValue(sourceRows, 'performance_bucket')
|
out.performance_bucket = dominantValue(sourceRows, 'performance_bucket')
|
||||||
}
|
}
|
||||||
out.customer_score_90d = customerSalesPeriodScore(periodMetricSource(out, '90d'))
|
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||||
out.customer_score_180d = customerSalesPeriodScore(periodMetricSource(out, '180d'))
|
out[`customer_score_${suffix}`] = Object.prototype.hasOwnProperty.call(preservedCustomerScores, suffix)
|
||||||
out.customer_score_365d = customerSalesPeriodScore(periodMetricSource(out, '365d'))
|
? preservedCustomerScores[suffix]
|
||||||
out.customer_score_total = customerSalesPeriodScore(periodMetricSource(out, 'total'))
|
: customerSalesPeriodScore(periodMetricSource(out, suffix))
|
||||||
out.performance_score_90d = productSalesPeriodScore(productPeriodMetricSource(out, '90d'))
|
out[`performance_score_${suffix}`] = Object.prototype.hasOwnProperty.call(preservedPerformanceScores, suffix)
|
||||||
out.performance_score_180d = productSalesPeriodScore(productPeriodMetricSource(out, '180d'))
|
? preservedPerformanceScores[suffix]
|
||||||
out.performance_score_365d = productSalesPeriodScore(productPeriodMetricSource(out, '365d'))
|
: productSalesPeriodScore(productPeriodMetricSource(out, suffix))
|
||||||
out.performance_score_total = productSalesPeriodScore(productPeriodMetricSource(out, 'total'))
|
}
|
||||||
out.performance_score = Number(out.performance_score_90d || 0)
|
out.performance_score = Number(out.performance_score_90d || 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3779,19 +3792,23 @@ function productVariantStockKey (row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function distinctVariantStockQty (sourceRows) {
|
function distinctVariantStockQty (sourceRows) {
|
||||||
|
return distinctVariantNumber(sourceRows, 'stock_qty')
|
||||||
|
}
|
||||||
|
|
||||||
|
function distinctVariantNumber (sourceRows, field) {
|
||||||
const seen = new Set()
|
const seen = new Set()
|
||||||
let total = 0
|
let total = 0
|
||||||
let keyed = false
|
let keyed = false
|
||||||
for (const row of sourceRows) {
|
for (const row of sourceRows) {
|
||||||
const key = productVariantStockKey(row)
|
const key = productVariantStockKey(row)
|
||||||
const stockQty = Number(row?.stock_qty || 0)
|
const value = Number(row?.[field] || 0)
|
||||||
if (!key) continue
|
if (!key) continue
|
||||||
keyed = true
|
keyed = true
|
||||||
if (seen.has(key)) continue
|
if (seen.has(key)) continue
|
||||||
seen.add(key)
|
seen.add(key)
|
||||||
total += stockQty
|
total += value
|
||||||
}
|
}
|
||||||
return keyed ? total : sumRows(sourceRows, 'stock_qty')
|
return keyed ? total : sumRows(sourceRows, field)
|
||||||
}
|
}
|
||||||
|
|
||||||
function distinctVariantStockCost (sourceRows) {
|
function distinctVariantStockCost (sourceRows) {
|
||||||
|
|||||||
Reference in New Issue
Block a user