Fix product performance grouped JSON build

This commit is contained in:
M_Kececi
2026-07-06 01:28:44 +03:00
parent a0da3f2bed
commit 3993c363cb
4 changed files with 296 additions and 101 deletions
+83 -62
View File
@@ -1,68 +1,85 @@
package models package models
type ProductPerformanceRow struct { type ProductPerformanceRow struct {
KpiDate string `json:"kpi_date"` KpiDate string `json:"kpi_date"`
ProductCode string `json:"product_code"` ProductCode string `json:"product_code"`
ColorCode string `json:"color_code"` ColorCode string `json:"color_code"`
ColorDescription string `json:"color_description"` ColorDescription string `json:"color_description"`
YakaKodu string `json:"yaka_kodu"` YakaKodu string `json:"yaka_kodu"`
ItemDescription string `json:"item_description"` ItemDescription string `json:"item_description"`
Kategori string `json:"kategori"` Kategori string `json:"kategori"`
Seri string `json:"seri"` Seri string `json:"seri"`
YasGrubu string `json:"yas_grubu"` YasGrubu string `json:"yas_grubu"`
AskiliYan string `json:"askili_yan"` AskiliYan string `json:"askili_yan"`
UrunIlkGrubu string `json:"urun_ilk_grubu"` UrunIlkGrubu string `json:"urun_ilk_grubu"`
UrunAnaGrubu string `json:"urun_ana_grubu"` UrunAnaGrubu string `json:"urun_ana_grubu"`
UrunAltGrubu string `json:"urun_alt_grubu"` UrunAltGrubu string `json:"urun_alt_grubu"`
MarketKey string `json:"market_key"` MarketKey string `json:"market_key"`
StockQty float64 `json:"stock_qty"` StockQty float64 `json:"stock_qty"`
SalesQty30 float64 `json:"sales_qty_30d"` SalesQty30 float64 `json:"sales_qty_30d"`
SalesQty90 float64 `json:"sales_qty_90d"` SalesQty90 float64 `json:"sales_qty_90d"`
SalesQty180 float64 `json:"sales_qty_180d"` SalesQty180 float64 `json:"sales_qty_180d"`
SalesQty365 float64 `json:"sales_qty_365d"` SalesQty365 float64 `json:"sales_qty_365d"`
SalesQty730 float64 `json:"sales_qty_730d"` SalesQty730 float64 `json:"sales_qty_730d"`
SalesQtyTotal float64 `json:"sales_qty_total"` SalesQtyTotal float64 `json:"sales_qty_total"`
SalesUSD30 float64 `json:"sales_usd_30d"` SalesUSD30 float64 `json:"sales_usd_30d"`
SalesUSD90 float64 `json:"sales_usd_90d"` SalesUSD90 float64 `json:"sales_usd_90d"`
SalesUSD180 float64 `json:"sales_usd_180d"` SalesUSD180 float64 `json:"sales_usd_180d"`
SalesUSD365 float64 `json:"sales_usd_365d"` SalesUSD365 float64 `json:"sales_usd_365d"`
SalesUSDTotal float64 `json:"sales_usd_total"` SalesUSDTotal float64 `json:"sales_usd_total"`
AvgDailySales90 float64 `json:"avg_daily_sales_90d"` AvgDailySales90 float64 `json:"avg_daily_sales_90d"`
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"`
StockDays90 float64 `json:"stock_days_90d"` AvgStock90 float64 `json:"avg_stock_90d"`
StockDays180 float64 `json:"stock_days_180d"` AvgStock180 float64 `json:"avg_stock_180d"`
StockDays365 float64 `json:"stock_days_365d"` AvgStock365 float64 `json:"avg_stock_365d"`
StockDaysTotal float64 `json:"stock_days_total"` AvgStockTotal float64 `json:"avg_stock_total"`
StockTurnover90 float64 `json:"stock_turnover_90d"` StockDays90 float64 `json:"stock_days_90d"`
StockTurnover180 float64 `json:"stock_turnover_180d"` StockDays180 float64 `json:"stock_days_180d"`
StockTurnover365 float64 `json:"stock_turnover_365d"` StockDays365 float64 `json:"stock_days_365d"`
StockTurnoverTotal float64 `json:"stock_turnover_total"` StockDaysTotal float64 `json:"stock_days_total"`
AvgPriceUSD90 float64 `json:"avg_price_usd_90d"` StockTurnover90 float64 `json:"stock_turnover_90d"`
AvgPriceUSD180 float64 `json:"avg_price_usd_180d"` StockTurnover180 float64 `json:"stock_turnover_180d"`
CostPriceUSD float64 `json:"cost_price_usd"` StockTurnover365 float64 `json:"stock_turnover_365d"`
BasePriceUSD float64 `json:"base_price_usd"` StockTurnoverTotal float64 `json:"stock_turnover_total"`
BasePriceTRY float64 `json:"base_price_try"` AvgPriceUSD90 float64 `json:"avg_price_usd_90d"`
GrossProfitUSD90 float64 `json:"gross_profit_usd_90d"` AvgPriceUSD180 float64 `json:"avg_price_usd_180d"`
GrossProfitUSD180 float64 `json:"gross_profit_usd_180d"` CostPriceUSD float64 `json:"cost_price_usd"`
GrossMargin90 float64 `json:"gross_margin_90d"` BasePriceUSD float64 `json:"base_price_usd"`
GrossMargin180 float64 `json:"gross_margin_180d"` BasePriceTRY float64 `json:"base_price_try"`
UnitProfitCost90 float64 `json:"unit_profit_cost_90d"` GrossProfitUSD90 float64 `json:"gross_profit_usd_90d"`
UnitProfitCost180 float64 `json:"unit_profit_cost_180d"` GrossProfitUSD180 float64 `json:"gross_profit_usd_180d"`
UnitProfitBase90 float64 `json:"unit_profit_base_90d"` GrossMargin90 float64 `json:"gross_margin_90d"`
UnitProfitBase180 float64 `json:"unit_profit_base_180d"` GrossMargin180 float64 `json:"gross_margin_180d"`
MarketCount90 int `json:"market_count_90d"` UnitProfitCost90 float64 `json:"unit_profit_cost_90d"`
CustomerCount90 int `json:"customer_count_90d"` UnitProfitCost180 float64 `json:"unit_profit_cost_180d"`
SalesIndex90 float64 `json:"sales_index_90d"` UnitProfitBase90 float64 `json:"unit_profit_base_90d"`
PriceIndex90 float64 `json:"price_index_90d"` UnitProfitBase180 float64 `json:"unit_profit_base_180d"`
MarginIndex90 float64 `json:"margin_index_90d"` MarketCount90 int `json:"market_count_90d"`
PerformanceScore float64 `json:"performance_score"` MarketCount180 int `json:"market_count_180d"`
PerformanceBucket string `json:"performance_bucket"` MarketCount365 int `json:"market_count_365d"`
Recommendation string `json:"recommendation"` MarketCountTotal int `json:"market_count_total"`
LastSaleDate string `json:"last_sale_date"` CustomerCount90 int `json:"customer_count_90d"`
LastRefNumber string `json:"last_ref_number"` CustomerCount180 int `json:"customer_count_180d"`
UpdatedAt string `json:"updated_at"` CustomerCount365 int `json:"customer_count_365d"`
CustomerCountTotal int `json:"customer_count_total"`
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"`
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"`
PerformanceBucket string `json:"performance_bucket"`
Recommendation string `json:"recommendation"`
LastSaleDate string `json:"last_sale_date"`
LastRefNumber string `json:"last_ref_number"`
UpdatedAt string `json:"updated_at"`
} }
type ProductPerformanceSummary struct { type ProductPerformanceSummary struct {
@@ -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"`
+153 -19
View File
@@ -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,7 +5184,9 @@ func mergeProductPerformanceGeneralSnapshotMetrics(ctx context.Context, pg *sql.
} }
} }
if value, ok := general["performance_score"]; ok { 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 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)
+30 -7
View File
@@ -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) {