|
|
|
@@ -1073,8 +1073,10 @@ func ListProductPerformanceGeneral(ctx context.Context, pg *sql.DB, limit int) (
|
|
|
|
|
if err := EnsureProductPerformanceTables(pg); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if limit <= 0 || limit > 1000 {
|
|
|
|
|
if limit <= 0 {
|
|
|
|
|
limit = 500
|
|
|
|
|
} else if limit > 50000 {
|
|
|
|
|
limit = 50000
|
|
|
|
|
}
|
|
|
|
|
rows, err := pg.QueryContext(ctx, `
|
|
|
|
|
WITH Bounds AS (
|
|
|
|
@@ -1350,8 +1352,10 @@ func ListProductPerformanceOrderAnalysis(ctx context.Context, pg *sql.DB, limit
|
|
|
|
|
if err := EnsureProductPerformanceTables(pg); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if limit <= 0 || limit > 1000 {
|
|
|
|
|
if limit <= 0 {
|
|
|
|
|
limit = 500
|
|
|
|
|
} else if limit > 50000 {
|
|
|
|
|
limit = 50000
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := db.MssqlDB.QueryContext(ctx, `
|
|
|
|
@@ -1592,9 +1596,22 @@ ORDER BY SUM(o.AmountUSD) DESC, SUM(o.Qty) DESC, o.ProductCode, o.ColorCode, o.Y
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
attrByKey, err := productPerformanceAttrLookup(ctx, pg, stockKeys)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
for i := range out {
|
|
|
|
|
row := &out[i]
|
|
|
|
|
price := priceByProduct[row.ProductCode]
|
|
|
|
|
attr := attrByKey[productPerformanceVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)]
|
|
|
|
|
if strings.TrimSpace(row.ItemDescription) == "" {
|
|
|
|
|
row.ItemDescription = attr.itemDescription
|
|
|
|
|
}
|
|
|
|
|
row.Kategori = attr.kategori
|
|
|
|
|
row.AskiliYan = attr.askiliYan
|
|
|
|
|
row.UrunIlkGrubu = attr.urunIlkGrubu
|
|
|
|
|
row.UrunAnaGrubu = attr.urunAnaGrubu
|
|
|
|
|
row.UrunAltGrubu = attr.urunAltGrubu
|
|
|
|
|
row.CostPriceUSD = price.cost
|
|
|
|
|
row.BasePriceUSD = price.base
|
|
|
|
|
row.StockQty = stockByKey[productPerformanceVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)]
|
|
|
|
@@ -1952,9 +1969,22 @@ ORDER BY SUM(AmountUSD) DESC, SUM(Qty) DESC
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
attrByKey, err := productPerformanceAttrLookup(ctx, pg, stockKeys)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
for i := range out {
|
|
|
|
|
row := &out[i]
|
|
|
|
|
price := priceByProduct[row.ProductCode]
|
|
|
|
|
attr := attrByKey[productPerformanceVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)]
|
|
|
|
|
if strings.TrimSpace(row.ItemDescription) == "" {
|
|
|
|
|
row.ItemDescription = attr.itemDescription
|
|
|
|
|
}
|
|
|
|
|
row.Kategori = attr.kategori
|
|
|
|
|
row.AskiliYan = attr.askiliYan
|
|
|
|
|
row.UrunIlkGrubu = attr.urunIlkGrubu
|
|
|
|
|
row.UrunAnaGrubu = attr.urunAnaGrubu
|
|
|
|
|
row.UrunAltGrubu = attr.urunAltGrubu
|
|
|
|
|
row.CostPriceUSD = price.cost
|
|
|
|
|
row.BasePriceUSD = price.base
|
|
|
|
|
row.StockQty = stockByKey[productPerformanceVariantKey(row.ProductCode, row.ColorCode, row.YakaKodu)]
|
|
|
|
@@ -1977,8 +2007,10 @@ func ListProductPerformanceOrderMarketDetails(ctx context.Context, pg *sql.DB, l
|
|
|
|
|
if err := EnsureProductPerformanceTables(pg); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if limit <= 0 || limit > 1500 {
|
|
|
|
|
if limit <= 0 {
|
|
|
|
|
limit = 800
|
|
|
|
|
} else if limit > 50000 {
|
|
|
|
|
limit = 50000
|
|
|
|
|
}
|
|
|
|
|
rows, err := db.MssqlDB.QueryContext(ctx, `
|
|
|
|
|
WITH OpenOrderLines AS (
|
|
|
|
@@ -2268,8 +2300,10 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
|
|
|
|
|
if err := EnsureProductPerformanceTables(pg); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
if limit <= 0 || limit > 1000 {
|
|
|
|
|
if limit <= 0 {
|
|
|
|
|
limit = 500
|
|
|
|
|
} else if limit > 50000 {
|
|
|
|
|
limit = 50000
|
|
|
|
|
}
|
|
|
|
|
mode := strings.ToLower(strings.TrimSpace(breakdown))
|
|
|
|
|
fields := map[string]string{
|
|
|
|
@@ -2277,6 +2311,11 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
|
|
|
|
|
"color_code": "s.color_code",
|
|
|
|
|
"yaka_kodu": "s.yaka_kodu",
|
|
|
|
|
"item_description": "MAX(s.item_description)",
|
|
|
|
|
"kategori": "MAX(s.kategori)",
|
|
|
|
|
"askili_yan": "MAX(s.askili_yan)",
|
|
|
|
|
"urun_ilk_grubu": "MAX(s.urun_ilk_grubu)",
|
|
|
|
|
"urun_ana_grubu": "MAX(s.urun_ana_grubu)",
|
|
|
|
|
"urun_alt_grubu": "MAX(s.urun_alt_grubu)",
|
|
|
|
|
"market_key": "s.market_key",
|
|
|
|
|
"country": "s.customer_country",
|
|
|
|
|
"customer_segment": "s.customer_segment",
|
|
|
|
@@ -2287,21 +2326,37 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
|
|
|
|
|
groupCols := []string{}
|
|
|
|
|
switch mode {
|
|
|
|
|
case "color_yaka_market_customer":
|
|
|
|
|
selected["product_code"] = true
|
|
|
|
|
selected["color_code"] = true
|
|
|
|
|
selected["yaka_kodu"] = true
|
|
|
|
|
selected["market_key"] = true
|
|
|
|
|
selected["customer_code"] = true
|
|
|
|
|
selected["customer_name"] = true
|
|
|
|
|
groupCols = []string{"s.color_code", "s.yaka_kodu", "s.market_key", "s.customer_code"}
|
|
|
|
|
case "product_country_segment_market_customer":
|
|
|
|
|
selected["product_code"] = true
|
|
|
|
|
selected["item_description"] = true
|
|
|
|
|
selected["kategori"] = true
|
|
|
|
|
selected["askili_yan"] = true
|
|
|
|
|
selected["urun_ilk_grubu"] = true
|
|
|
|
|
selected["urun_ana_grubu"] = true
|
|
|
|
|
selected["urun_alt_grubu"] = true
|
|
|
|
|
selected["country"] = true
|
|
|
|
|
selected["customer_segment"] = true
|
|
|
|
|
selected["market_key"] = true
|
|
|
|
|
selected["customer_code"] = true
|
|
|
|
|
selected["customer_name"] = true
|
|
|
|
|
groupCols = []string{"s.product_code", "s.customer_country", "s.customer_segment", "s.market_key", "s.customer_code"}
|
|
|
|
|
groupCols = []string{"s.color_code", "s.yaka_kodu", "s.askili_yan", "s.urun_ilk_grubu", "s.urun_ana_grubu", "s.urun_alt_grubu", "s.product_code", "s.customer_country", "s.market_key", "s.customer_segment", "s.customer_code"}
|
|
|
|
|
case "product_country_segment_market_customer":
|
|
|
|
|
selected["product_code"] = true
|
|
|
|
|
selected["item_description"] = true
|
|
|
|
|
selected["kategori"] = true
|
|
|
|
|
selected["askili_yan"] = true
|
|
|
|
|
selected["urun_ilk_grubu"] = true
|
|
|
|
|
selected["urun_ana_grubu"] = true
|
|
|
|
|
selected["urun_alt_grubu"] = true
|
|
|
|
|
selected["color_code"] = true
|
|
|
|
|
selected["yaka_kodu"] = true
|
|
|
|
|
selected["country"] = true
|
|
|
|
|
selected["customer_segment"] = true
|
|
|
|
|
selected["market_key"] = true
|
|
|
|
|
selected["customer_code"] = true
|
|
|
|
|
selected["customer_name"] = true
|
|
|
|
|
groupCols = []string{"s.market_key", "s.customer_code", "s.product_code", "s.color_code", "s.yaka_kodu", "s.customer_country", "s.customer_segment"}
|
|
|
|
|
case "market_customer_product":
|
|
|
|
|
selected["market_key"] = true
|
|
|
|
|
selected["customer_code"] = true
|
|
|
|
@@ -2310,6 +2365,11 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
|
|
|
|
|
selected["color_code"] = true
|
|
|
|
|
selected["yaka_kodu"] = true
|
|
|
|
|
selected["item_description"] = true
|
|
|
|
|
selected["kategori"] = true
|
|
|
|
|
selected["askili_yan"] = true
|
|
|
|
|
selected["urun_ilk_grubu"] = true
|
|
|
|
|
selected["urun_ana_grubu"] = true
|
|
|
|
|
selected["urun_alt_grubu"] = true
|
|
|
|
|
groupCols = []string{"s.market_key", "s.customer_code", "s.product_code", "s.color_code", "s.yaka_kodu"}
|
|
|
|
|
case "country_segment_market_customer_product":
|
|
|
|
|
selected["country"] = true
|
|
|
|
@@ -2321,15 +2381,29 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
|
|
|
|
|
selected["color_code"] = true
|
|
|
|
|
selected["yaka_kodu"] = true
|
|
|
|
|
selected["item_description"] = true
|
|
|
|
|
selected["kategori"] = true
|
|
|
|
|
selected["askili_yan"] = true
|
|
|
|
|
selected["urun_ilk_grubu"] = true
|
|
|
|
|
selected["urun_ana_grubu"] = true
|
|
|
|
|
selected["urun_alt_grubu"] = true
|
|
|
|
|
groupCols = []string{"s.customer_country", "s.customer_segment", "s.market_key", "s.customer_code", "s.product_code", "s.color_code", "s.yaka_kodu"}
|
|
|
|
|
default:
|
|
|
|
|
mode = "color_yaka_market_customer"
|
|
|
|
|
selected["product_code"] = true
|
|
|
|
|
selected["color_code"] = true
|
|
|
|
|
selected["yaka_kodu"] = true
|
|
|
|
|
selected["item_description"] = true
|
|
|
|
|
selected["kategori"] = true
|
|
|
|
|
selected["askili_yan"] = true
|
|
|
|
|
selected["urun_ilk_grubu"] = true
|
|
|
|
|
selected["urun_ana_grubu"] = true
|
|
|
|
|
selected["urun_alt_grubu"] = true
|
|
|
|
|
selected["country"] = true
|
|
|
|
|
selected["customer_segment"] = true
|
|
|
|
|
selected["market_key"] = true
|
|
|
|
|
selected["customer_code"] = true
|
|
|
|
|
selected["customer_name"] = true
|
|
|
|
|
groupCols = []string{"s.color_code", "s.yaka_kodu", "s.market_key", "s.customer_code"}
|
|
|
|
|
groupCols = []string{"s.color_code", "s.yaka_kodu", "s.askili_yan", "s.urun_ilk_grubu", "s.urun_ana_grubu", "s.urun_alt_grubu", "s.product_code", "s.customer_country", "s.market_key", "s.customer_segment", "s.customer_code"}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
selectExpr := func(name string) string {
|
|
|
|
@@ -2340,7 +2414,31 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
query := fmt.Sprintf(`
|
|
|
|
|
WITH Agg AS (
|
|
|
|
|
WITH LatestKPI AS (
|
|
|
|
|
SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
|
|
|
|
|
product_code,
|
|
|
|
|
color_code,
|
|
|
|
|
yaka_kodu,
|
|
|
|
|
kategori,
|
|
|
|
|
askili_yan,
|
|
|
|
|
COALESCE(NULLIF(urun_ilk_grubu,''), yas_grubu) AS urun_ilk_grubu,
|
|
|
|
|
COALESCE(NULLIF(urun_ana_grubu,''), seri) AS urun_ana_grubu,
|
|
|
|
|
urun_alt_grubu
|
|
|
|
|
FROM mk_product_performance_kpi_daily
|
|
|
|
|
WHERE kpi_date = (SELECT MAX(kpi_date) FROM mk_product_performance_kpi_daily)
|
|
|
|
|
ORDER BY product_code, color_code, yaka_kodu, performance_score DESC
|
|
|
|
|
),
|
|
|
|
|
StockAgg AS (
|
|
|
|
|
SELECT
|
|
|
|
|
product_code,
|
|
|
|
|
color_code,
|
|
|
|
|
yaka_kodu,
|
|
|
|
|
COALESCE(SUM(stock_qty),0) AS stock_qty
|
|
|
|
|
FROM mk_product_performance_stock_daily
|
|
|
|
|
WHERE stock_date = (SELECT MAX(stock_date) FROM mk_product_performance_stock_daily)
|
|
|
|
|
GROUP BY product_code, color_code, yaka_kodu
|
|
|
|
|
),
|
|
|
|
|
Agg AS (
|
|
|
|
|
SELECT
|
|
|
|
|
$2::text AS breakdown,
|
|
|
|
|
%s,
|
|
|
|
@@ -2352,7 +2450,13 @@ WITH Agg AS (
|
|
|
|
|
%s,
|
|
|
|
|
%s,
|
|
|
|
|
%s,
|
|
|
|
|
%s,
|
|
|
|
|
%s,
|
|
|
|
|
%s,
|
|
|
|
|
%s,
|
|
|
|
|
%s,
|
|
|
|
|
COUNT(DISTINCT product_code) AS product_count,
|
|
|
|
|
COALESCE(MAX(stock_qty),0) AS stock_qty,
|
|
|
|
|
COUNT(DISTINCT customer_code) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days' AND COALESCE(customer_code,'') <> '')::integer AS customer_count_90d,
|
|
|
|
|
COALESCE(SUM(invoice_count) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)::integer AS invoice_count_90d,
|
|
|
|
|
COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0) AS sales_qty_90d,
|
|
|
|
@@ -2387,6 +2491,8 @@ WITH Agg AS (
|
|
|
|
|
END AS cost_price_usd_180d,
|
|
|
|
|
COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days'),0) AS sales_qty_365d,
|
|
|
|
|
COALESCE(SUM(sales_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days'),0) AS sales_usd_365d,
|
|
|
|
|
COUNT(DISTINCT customer_code) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days' AND COALESCE(customer_code,'') <> '')::integer AS customer_count_365d,
|
|
|
|
|
COALESCE(SUM(invoice_count) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days'),0)::integer AS invoice_count_365d,
|
|
|
|
|
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days'),0)=0 THEN 0
|
|
|
|
|
ELSE COALESCE(SUM(sales_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days'),0)
|
|
|
|
|
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days'),0)
|
|
|
|
@@ -2419,10 +2525,18 @@ WITH Agg AS (
|
|
|
|
|
FROM (
|
|
|
|
|
SELECT
|
|
|
|
|
s.*,
|
|
|
|
|
COALESCE(k.kategori,'') AS kategori,
|
|
|
|
|
COALESCE(k.askili_yan,'') AS askili_yan,
|
|
|
|
|
COALESCE(k.urun_ilk_grubu,'') AS urun_ilk_grubu,
|
|
|
|
|
COALESCE(k.urun_ana_grubu,'') AS urun_ana_grubu,
|
|
|
|
|
COALESCE(k.urun_alt_grubu,'') AS urun_alt_grubu,
|
|
|
|
|
COALESCE(pd.base_price_usd,0) AS base_price_usd,
|
|
|
|
|
COALESCE(pd.cost_price_usd,0) AS cost_price_usd
|
|
|
|
|
COALESCE(pd.cost_price_usd,0) AS cost_price_usd,
|
|
|
|
|
COALESCE(st.stock_qty,0) AS stock_qty
|
|
|
|
|
FROM mk_product_performance_sales_daily s
|
|
|
|
|
LEFT JOIN mk_product_performance_price_dim pd ON pd.product_code = s.product_code
|
|
|
|
|
LEFT JOIN LatestKPI k ON k.product_code = s.product_code AND k.color_code = s.color_code AND k.yaka_kodu = s.yaka_kodu
|
|
|
|
|
LEFT JOIN StockAgg st ON st.product_code = s.product_code AND st.color_code = s.color_code AND st.yaka_kodu = s.yaka_kodu
|
|
|
|
|
) s
|
|
|
|
|
WHERE sales_date >= DATE '2022-01-01'
|
|
|
|
|
GROUP BY %s
|
|
|
|
@@ -2463,12 +2577,18 @@ SELECT
|
|
|
|
|
color_code,
|
|
|
|
|
yaka_kodu,
|
|
|
|
|
item_description,
|
|
|
|
|
kategori,
|
|
|
|
|
askili_yan,
|
|
|
|
|
urun_ilk_grubu,
|
|
|
|
|
urun_ana_grubu,
|
|
|
|
|
urun_alt_grubu,
|
|
|
|
|
market_key,
|
|
|
|
|
country,
|
|
|
|
|
customer_segment,
|
|
|
|
|
customer_code,
|
|
|
|
|
customer_name,
|
|
|
|
|
product_count,
|
|
|
|
|
stock_qty,
|
|
|
|
|
customer_count_90d,
|
|
|
|
|
invoice_count_90d,
|
|
|
|
|
sales_qty_90d,
|
|
|
|
@@ -2500,6 +2620,8 @@ SELECT
|
|
|
|
|
gross_profit_cost_usd_365d,
|
|
|
|
|
gross_margin_base_365d,
|
|
|
|
|
gross_margin_cost_365d,
|
|
|
|
|
customer_count_365d,
|
|
|
|
|
invoice_count_365d,
|
|
|
|
|
customer_count_total,
|
|
|
|
|
invoice_count_total,
|
|
|
|
|
sales_qty_total,
|
|
|
|
@@ -2511,15 +2633,55 @@ SELECT
|
|
|
|
|
gross_profit_cost_usd_total,
|
|
|
|
|
gross_margin_base_total,
|
|
|
|
|
gross_margin_cost_total,
|
|
|
|
|
CASE WHEN stock_qty > 0 THEN sales_qty_90d / NULLIF(stock_qty,0) ELSE 0 END AS stock_turnover_90d,
|
|
|
|
|
CASE WHEN stock_qty > 0 THEN sales_qty_180d / NULLIF(stock_qty,0) ELSE 0 END AS stock_turnover_180d,
|
|
|
|
|
CASE WHEN stock_qty > 0 THEN sales_qty_365d / NULLIF(stock_qty,0) ELSE 0 END AS stock_turnover_365d,
|
|
|
|
|
CASE WHEN stock_qty > 0 THEN sales_qty_total / NULLIF(stock_qty,0) ELSE 0 END AS stock_turnover_total,
|
|
|
|
|
has_cost,
|
|
|
|
|
sales_index_90d,
|
|
|
|
|
CASE WHEN NOT has_cost THEN 0 ELSE
|
|
|
|
|
ROUND((
|
|
|
|
|
LEAST(35, sales_usd_90d / 1000)
|
|
|
|
|
+ LEAST(25, GREATEST(gross_margin_cost_90d,0) * 55)
|
|
|
|
|
+ LEAST(20, invoice_count_90d * 2)
|
|
|
|
|
+ LEAST(10, sales_qty_90d / 10)
|
|
|
|
|
+ LEAST(10, product_count)
|
|
|
|
|
)::numeric, 4)
|
|
|
|
|
END AS customer_score_90d,
|
|
|
|
|
CASE WHEN NOT has_cost THEN 0 ELSE
|
|
|
|
|
ROUND((
|
|
|
|
|
LEAST(35, sales_usd_180d / 1000)
|
|
|
|
|
+ LEAST(25, GREATEST(gross_margin_cost_180d,0) * 55)
|
|
|
|
|
+ LEAST(20, invoice_count_180d * 2)
|
|
|
|
|
+ LEAST(10, sales_qty_180d / 10)
|
|
|
|
|
+ LEAST(10, product_count)
|
|
|
|
|
)::numeric, 4)
|
|
|
|
|
END AS customer_score_180d,
|
|
|
|
|
CASE WHEN NOT has_cost THEN 0 ELSE
|
|
|
|
|
ROUND((
|
|
|
|
|
LEAST(35, sales_usd_365d / 1000)
|
|
|
|
|
+ LEAST(25, GREATEST(gross_margin_cost_365d,0) * 55)
|
|
|
|
|
+ LEAST(20, invoice_count_365d * 2)
|
|
|
|
|
+ LEAST(10, sales_qty_365d / 10)
|
|
|
|
|
+ LEAST(10, product_count)
|
|
|
|
|
)::numeric, 4)
|
|
|
|
|
END AS customer_score_365d,
|
|
|
|
|
CASE WHEN NOT has_cost THEN 0 ELSE
|
|
|
|
|
ROUND((
|
|
|
|
|
LEAST(35, sales_usd_total / 1000)
|
|
|
|
|
+ LEAST(25, GREATEST(gross_margin_cost_total,0) * 55)
|
|
|
|
|
+ LEAST(20, invoice_count_total * 2)
|
|
|
|
|
+ LEAST(10, sales_qty_total / 10)
|
|
|
|
|
+ LEAST(10, product_count)
|
|
|
|
|
)::numeric, 4)
|
|
|
|
|
END AS customer_score_total,
|
|
|
|
|
CASE WHEN NOT has_cost THEN 0 ELSE
|
|
|
|
|
ROUND(
|
|
|
|
|
LEAST(35, sales_index_90d * 18)
|
|
|
|
|
+ LEAST(30, GREATEST(gross_margin_cost_90d,0) * 60)
|
|
|
|
|
+ LEAST(15, invoice_count_90d * 1.5)
|
|
|
|
|
+ LEAST(10, sales_qty_90d / 10)
|
|
|
|
|
+ CASE WHEN last_sale_date >= to_char(current_date - INTERVAL '30 days','YYYY-MM-DD') THEN 10 ELSE 0 END,
|
|
|
|
|
+ LEAST(10, (CASE WHEN stock_qty > 0 THEN sales_qty_90d / NULLIF(stock_qty,0) ELSE 0 END) * 5),
|
|
|
|
|
4
|
|
|
|
|
)
|
|
|
|
|
END AS performance_score,
|
|
|
|
@@ -2545,6 +2707,11 @@ LIMIT $1
|
|
|
|
|
selectExpr("color_code"),
|
|
|
|
|
selectExpr("yaka_kodu"),
|
|
|
|
|
selectExpr("item_description"),
|
|
|
|
|
selectExpr("kategori"),
|
|
|
|
|
selectExpr("askili_yan"),
|
|
|
|
|
selectExpr("urun_ilk_grubu"),
|
|
|
|
|
selectExpr("urun_ana_grubu"),
|
|
|
|
|
selectExpr("urun_alt_grubu"),
|
|
|
|
|
selectExpr("market_key"),
|
|
|
|
|
selectExpr("country"),
|
|
|
|
|
selectExpr("customer_segment"),
|
|
|
|
@@ -2563,18 +2730,20 @@ LIMIT $1
|
|
|
|
|
var r models.ProductPerformanceSalesBreakdownRow
|
|
|
|
|
if err := rows.Scan(
|
|
|
|
|
&r.Breakdown, &r.ProductCode, &r.ColorCode, &r.YakaKodu, &r.ItemDescription,
|
|
|
|
|
&r.Kategori, &r.AskiliYan, &r.UrunIlkGrubu, &r.UrunAnaGrubu, &r.UrunAltGrubu,
|
|
|
|
|
&r.MarketKey, &r.Country, &r.CustomerSegment, &r.CustomerCode, &r.CustomerName,
|
|
|
|
|
&r.ProductCount, &r.CustomerCount90, &r.InvoiceCount90, &r.SalesQty90, &r.SalesUSD90,
|
|
|
|
|
&r.ProductCount, &r.StockQty, &r.CustomerCount90, &r.InvoiceCount90, &r.SalesQty90, &r.SalesUSD90,
|
|
|
|
|
&r.AvgPriceUSD90, &r.BasePriceUSD90, &r.CostPriceUSD90, &r.GrossProfitBase90,
|
|
|
|
|
&r.GrossProfitCost90, &r.GrossMarginBase90, &r.GrossMarginCost90, &r.CustomerCount180,
|
|
|
|
|
&r.InvoiceCount180, &r.SalesQty180, &r.SalesUSD180, &r.AvgPriceUSD180, &r.BasePriceUSD180,
|
|
|
|
|
&r.CostPriceUSD180, &r.GrossProfitBase180, &r.GrossProfitCost180, &r.GrossMarginBase180,
|
|
|
|
|
&r.GrossMarginCost180, &r.SalesQty365, &r.SalesUSD365, &r.AvgPriceUSD365, &r.BasePriceUSD365,
|
|
|
|
|
&r.CostPriceUSD365, &r.GrossProfitBase365, &r.GrossProfitCost365, &r.GrossMarginBase365,
|
|
|
|
|
&r.GrossMarginCost365, &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.GrossProfitCostTotal, &r.GrossMarginBaseTotal, &r.GrossMarginCostTotal, &r.HasCost,
|
|
|
|
|
&r.SalesIndex90, &r.PerformanceScore,
|
|
|
|
|
&r.GrossProfitCostTotal, &r.GrossMarginBaseTotal, &r.GrossMarginCostTotal,
|
|
|
|
|
&r.StockTurnover90, &r.StockTurnover180, &r.StockTurnover365, &r.StockTurnoverTotal, &r.HasCost,
|
|
|
|
|
&r.SalesIndex90, &r.CustomerScore90, &r.CustomerScore180, &r.CustomerScore365, &r.CustomerScoreTotal, &r.PerformanceScore,
|
|
|
|
|
&r.PerformanceBucket, &r.Recommendation, &r.LastSaleDate,
|
|
|
|
|
); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
@@ -2883,7 +3052,7 @@ WITH Source AS (
|
|
|
|
|
|
|
|
|
|
func productPerformanceSQLGroupExpr(field string) (string, bool) {
|
|
|
|
|
switch field {
|
|
|
|
|
case "kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "color_code", "yaka_kodu":
|
|
|
|
|
case "kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu":
|
|
|
|
|
return field, true
|
|
|
|
|
case "market_key":
|
|
|
|
|
return "btrim(regexp_replace(COALESCE(market_key,''), '^.*\\|', ''))", true
|
|
|
|
@@ -2921,7 +3090,7 @@ func productPerformanceGroupedSourceRows(ctx context.Context, pg *sql.DB, mode s
|
|
|
|
|
}
|
|
|
|
|
return productPerformanceIdleSourceRows(structsToMaps(rows)), nil
|
|
|
|
|
case "sales_color_yaka_market_customer", "sales_product_country_segment_market_customer", "sales_market_customer_product", "sales_country_segment_market_customer_product":
|
|
|
|
|
rows, err := ListProductPerformanceSalesBreakdown(ctx, pg, mode, limit)
|
|
|
|
|
rows, err := ListProductPerformanceSalesBreakdown(ctx, pg, productPerformanceSalesBreakdownMode(mode), limit)
|
|
|
|
|
return structsToMaps(rows), err
|
|
|
|
|
case "order_product_customers":
|
|
|
|
|
rows, err := ListProductPerformanceOrderProductCustomers(ctx, pg, limit)
|
|
|
|
@@ -2935,6 +3104,21 @@ func productPerformanceGroupedSourceRows(ctx context.Context, pg *sql.DB, mode s
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceSalesBreakdownMode(mode string) string {
|
|
|
|
|
switch strings.TrimSpace(mode) {
|
|
|
|
|
case "sales_color_yaka_market_customer":
|
|
|
|
|
return "color_yaka_market_customer"
|
|
|
|
|
case "sales_product_country_segment_market_customer":
|
|
|
|
|
return "product_country_segment_market_customer"
|
|
|
|
|
case "sales_market_customer_product":
|
|
|
|
|
return "market_customer_product"
|
|
|
|
|
case "sales_country_segment_market_customer_product":
|
|
|
|
|
return "country_segment_market_customer_product"
|
|
|
|
|
default:
|
|
|
|
|
return mode
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceIdleSourceRows(rows []map[string]any) []map[string]any {
|
|
|
|
|
out := make([]map[string]any, 0, len(rows))
|
|
|
|
|
for _, row := range rows {
|
|
|
|
@@ -2987,7 +3171,7 @@ func appendProductPerformanceGroupedRows(out *[]map[string]any, sourceRows []map
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func makeProductPerformanceGroupedRow(key string, level int, field, value string, rows []map[string]any) map[string]any {
|
|
|
|
|
row := aggregateProductPerformanceRows(rows)
|
|
|
|
|
row := aggregateProductPerformanceRows(rows, field)
|
|
|
|
|
row["__group"] = true
|
|
|
|
|
row["row_key"] = "group|" + key
|
|
|
|
|
row["key"] = key
|
|
|
|
@@ -3006,7 +3190,7 @@ func makeProductPerformanceGroupedRow(key string, level int, field, value string
|
|
|
|
|
return row
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func aggregateProductPerformanceRows(rows []map[string]any) map[string]any {
|
|
|
|
|
func aggregateProductPerformanceRows(rows []map[string]any, groupField string) map[string]any {
|
|
|
|
|
out := map[string]any{}
|
|
|
|
|
for _, row := range rows {
|
|
|
|
|
for key, value := range row {
|
|
|
|
@@ -3027,12 +3211,12 @@ func aggregateProductPerformanceRows(rows []map[string]any) map[string]any {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
deriveProductPerformanceGroupMetrics(out)
|
|
|
|
|
deriveProductPerformanceGroupMetrics(out, groupField)
|
|
|
|
|
out["performance_bucket"] = dominantProductPerformanceValue(rows, "performance_bucket")
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func deriveProductPerformanceGroupMetrics(out map[string]any) {
|
|
|
|
|
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
|
|
|
|
for _, suffix := range []string{"90d", "180d", "365d", "total"} {
|
|
|
|
|
sales := floatFromMap(out, "sales_usd_"+suffix)
|
|
|
|
|
qty := floatFromMap(out, "sales_qty_"+suffix)
|
|
|
|
@@ -3064,13 +3248,138 @@ func deriveProductPerformanceGroupMetrics(out map[string]any) {
|
|
|
|
|
if orderQty > 0 {
|
|
|
|
|
out["avg_order_price_usd"] = orderUSD / orderQty
|
|
|
|
|
}
|
|
|
|
|
out["customer_score_90d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "90d"))
|
|
|
|
|
out["customer_score_180d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "180d"))
|
|
|
|
|
out["customer_score_365d"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "365d"))
|
|
|
|
|
out["customer_score_total"] = productPerformanceCustomerSalesPeriodScore(rowPeriodMetricMap(out, "total"))
|
|
|
|
|
out["performance_score"] = productPerformanceGroupScore(out, groupField)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceGroupScore(row map[string]any, groupField string) float64 {
|
|
|
|
|
if floatFromMap(row, "order_qty") > 0 || floatFromMap(row, "order_usd") > 0 {
|
|
|
|
|
return productPerformanceOrderGroupScore(row)
|
|
|
|
|
}
|
|
|
|
|
if isProductPerformanceCustomerGroup(groupField) {
|
|
|
|
|
return productPerformanceCustomerSalesGroupScore(row)
|
|
|
|
|
}
|
|
|
|
|
return productPerformanceSalesGroupScore(row)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isProductPerformanceCustomerGroup(groupField string) bool {
|
|
|
|
|
switch strings.TrimSpace(groupField) {
|
|
|
|
|
case "customer_code", "customer_name":
|
|
|
|
|
return true
|
|
|
|
|
default:
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceSalesGroupScore(row map[string]any) float64 {
|
|
|
|
|
salesIndex := floatFromMap(row, "sales_index_90d")
|
|
|
|
|
margin := floatFromMap(row, "gross_margin_cost_90d")
|
|
|
|
|
if margin == 0 {
|
|
|
|
|
margin = floatFromMap(row, "gross_margin_90d")
|
|
|
|
|
}
|
|
|
|
|
invoiceCount := floatFromMap(row, "invoice_count_90d")
|
|
|
|
|
salesQty := floatFromMap(row, "sales_qty_90d")
|
|
|
|
|
stockTurnover := floatFromMap(row, "stock_turnover_90d")
|
|
|
|
|
if stockTurnover == 0 {
|
|
|
|
|
if stockQty := floatFromMap(row, "stock_qty"); stockQty > 0 {
|
|
|
|
|
stockTurnover = salesQty / stockQty
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
score := minFloat(35, maxFloat(0, salesIndex)*18) +
|
|
|
|
|
minFloat(30, maxFloat(0, margin)*60) +
|
|
|
|
|
minFloat(15, invoiceCount*1.5) +
|
|
|
|
|
minFloat(10, salesQty/10) +
|
|
|
|
|
minFloat(10, maxFloat(0, stockTurnover)*5)
|
|
|
|
|
return score
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceCustomerSalesGroupScore(row map[string]any) float64 {
|
|
|
|
|
scores := []float64{
|
|
|
|
|
floatFromMap(row, "customer_score_90d"),
|
|
|
|
|
floatFromMap(row, "customer_score_180d"),
|
|
|
|
|
floatFromMap(row, "customer_score_365d"),
|
|
|
|
|
floatFromMap(row, "customer_score_total"),
|
|
|
|
|
}
|
|
|
|
|
total := 0.0
|
|
|
|
|
count := 0.0
|
|
|
|
|
for _, score := range scores {
|
|
|
|
|
if score <= 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
total += score
|
|
|
|
|
count++
|
|
|
|
|
}
|
|
|
|
|
if count == 0 {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
return total / count
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceCustomerSalesPeriodScore(row map[string]any) float64 {
|
|
|
|
|
salesUSD := floatFromMap(row, "sales_usd")
|
|
|
|
|
margin := floatFromMap(row, "gross_margin_cost")
|
|
|
|
|
if margin == 0 {
|
|
|
|
|
margin = floatFromMap(row, "gross_margin")
|
|
|
|
|
}
|
|
|
|
|
invoiceCount := floatFromMap(row, "invoice_count")
|
|
|
|
|
salesQty := floatFromMap(row, "sales_qty")
|
|
|
|
|
productCount := floatFromMap(row, "product_count")
|
|
|
|
|
return minFloat(35, salesUSD/1000) +
|
|
|
|
|
minFloat(25, maxFloat(0, margin)*55) +
|
|
|
|
|
minFloat(20, invoiceCount*2) +
|
|
|
|
|
minFloat(10, salesQty/10) +
|
|
|
|
|
minFloat(10, productCount)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func rowPeriodMetricMap(row map[string]any, suffix string) map[string]any {
|
|
|
|
|
return map[string]any{
|
|
|
|
|
"sales_usd": floatFromMap(row, "sales_usd_"+suffix),
|
|
|
|
|
"gross_margin_cost": floatFromMap(row, "gross_margin_cost_"+suffix),
|
|
|
|
|
"gross_margin": floatFromMap(row, "gross_margin_"+suffix),
|
|
|
|
|
"invoice_count": floatFromMap(row, "invoice_count_"+suffix),
|
|
|
|
|
"sales_qty": floatFromMap(row, "sales_qty_"+suffix),
|
|
|
|
|
"product_count": floatFromMap(row, "product_count"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceOrderGroupScore(row map[string]any) float64 {
|
|
|
|
|
orderUSD := floatFromMap(row, "order_usd")
|
|
|
|
|
margin := floatFromMap(row, "expected_margin_cost")
|
|
|
|
|
orderCount := floatFromMap(row, "order_count")
|
|
|
|
|
orderQty := floatFromMap(row, "order_qty")
|
|
|
|
|
netStockAfterOrder := floatFromMap(row, "net_stock_after_order")
|
|
|
|
|
score := minFloat(35, orderUSD/1000) +
|
|
|
|
|
minFloat(25, maxFloat(0, margin)*60) +
|
|
|
|
|
minFloat(15, orderCount*2) +
|
|
|
|
|
minFloat(15, orderQty/10)
|
|
|
|
|
if netStockAfterOrder >= 0 {
|
|
|
|
|
score += 10
|
|
|
|
|
}
|
|
|
|
|
return score
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func minFloat(a, b float64) float64 {
|
|
|
|
|
if a < b {
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func maxFloat(a, b float64) float64 {
|
|
|
|
|
if a > b {
|
|
|
|
|
return a
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sanitizeProductPerformanceGroupLevels(levels []string) []string {
|
|
|
|
|
allowed := map[string]bool{
|
|
|
|
|
"kategori": true, "askili_yan": true, "urun_ilk_grubu": true, "urun_ana_grubu": true, "urun_alt_grubu": true,
|
|
|
|
|
"product_code": true, "color_code": true, "yaka_kodu": true, "market_key": true, "customer_code": true,
|
|
|
|
|
"country": true, "customer_segment": true,
|
|
|
|
|
"product_code": true, "item_description": true, "color_code": true, "yaka_kodu": true, "market_key": true, "customer_code": true,
|
|
|
|
|
"customer_name": true, "country": true, "customer_segment": true,
|
|
|
|
|
}
|
|
|
|
|
out := make([]string, 0, len(levels))
|
|
|
|
|
seen := map[string]bool{}
|
|
|
|
@@ -3087,21 +3396,21 @@ func sanitizeProductPerformanceGroupLevels(levels []string) []string {
|
|
|
|
|
func defaultProductPerformanceGroupLevels(mode string) []string {
|
|
|
|
|
switch mode {
|
|
|
|
|
case "sales_color_yaka_market_customer":
|
|
|
|
|
return []string{"color_code", "yaka_kodu", "market_key", "customer_code"}
|
|
|
|
|
return []string{"color_code", "yaka_kodu", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "country", "market_key", "customer_segment", "customer_code", "customer_name"}
|
|
|
|
|
case "idle":
|
|
|
|
|
return []string{"kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code"}
|
|
|
|
|
return []string{"askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"}
|
|
|
|
|
case "sales_product_country_segment_market_customer":
|
|
|
|
|
return []string{"product_code", "country", "customer_segment", "market_key", "customer_code"}
|
|
|
|
|
return []string{"market_key", "customer_code", "customer_name", "kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"}
|
|
|
|
|
case "sales_market_customer_product":
|
|
|
|
|
return []string{"market_key", "customer_code", "product_code", "color_code", "yaka_kodu"}
|
|
|
|
|
return []string{"market_key", "customer_code", "customer_name", "kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"}
|
|
|
|
|
case "sales_country_segment_market_customer_product":
|
|
|
|
|
return []string{"country", "customer_segment", "market_key", "customer_code", "product_code", "color_code", "yaka_kodu"}
|
|
|
|
|
return []string{"market_key", "customer_code", "customer_name", "kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"}
|
|
|
|
|
case "order_product_customers":
|
|
|
|
|
return []string{"product_code", "market_key", "customer_code"}
|
|
|
|
|
return []string{"market_key", "customer_code", "customer_name", "kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"}
|
|
|
|
|
case "order_market_details":
|
|
|
|
|
return []string{"market_key", "customer_code", "product_code", "color_code", "yaka_kodu"}
|
|
|
|
|
return []string{"market_key", "customer_code", "customer_name", "kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu"}
|
|
|
|
|
default:
|
|
|
|
|
return []string{"kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "market_key", "product_code"}
|
|
|
|
|
return []string{"kategori", "askili_yan", "urun_ilk_grubu", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu", "market_key"}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -3525,6 +3834,15 @@ type productPerformancePriceInfo struct {
|
|
|
|
|
base float64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type productPerformanceAttrInfo struct {
|
|
|
|
|
itemDescription string
|
|
|
|
|
kategori string
|
|
|
|
|
askiliYan string
|
|
|
|
|
urunIlkGrubu string
|
|
|
|
|
urunAnaGrubu string
|
|
|
|
|
urunAltGrubu string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceVariantKey(productCode, colorCode, yakaKodu string) string {
|
|
|
|
|
return strings.TrimSpace(productCode) + "|" + strings.TrimSpace(colorCode) + "|" + strings.TrimSpace(yakaKodu)
|
|
|
|
|
}
|
|
|
|
@@ -3554,6 +3872,46 @@ WHERE product_code = ANY($1)
|
|
|
|
|
return out, rows.Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceAttrLookup(ctx context.Context, pg *sql.DB, variantKeys []string) (map[string]productPerformanceAttrInfo, error) {
|
|
|
|
|
out := make(map[string]productPerformanceAttrInfo, len(variantKeys))
|
|
|
|
|
if len(variantKeys) == 0 {
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
rows, err := pg.QueryContext(ctx, `
|
|
|
|
|
WITH Latest AS (
|
|
|
|
|
SELECT MAX(kpi_date) AS kpi_date
|
|
|
|
|
FROM mk_product_performance_kpi_daily
|
|
|
|
|
)
|
|
|
|
|
SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
|
|
|
|
|
product_code,
|
|
|
|
|
color_code,
|
|
|
|
|
yaka_kodu,
|
|
|
|
|
COALESCE(item_description,'') AS item_description,
|
|
|
|
|
COALESCE(kategori,'') AS kategori,
|
|
|
|
|
COALESCE(askili_yan,'') AS askili_yan,
|
|
|
|
|
COALESCE(NULLIF(urun_ilk_grubu,''), yas_grubu, '') AS urun_ilk_grubu,
|
|
|
|
|
COALESCE(NULLIF(urun_ana_grubu,''), seri, '') AS urun_ana_grubu,
|
|
|
|
|
COALESCE(urun_alt_grubu,'') AS urun_alt_grubu
|
|
|
|
|
FROM mk_product_performance_kpi_daily
|
|
|
|
|
WHERE kpi_date = (SELECT kpi_date FROM Latest)
|
|
|
|
|
AND product_code || '|' || color_code || '|' || yaka_kodu = ANY($1)
|
|
|
|
|
ORDER BY product_code, color_code, yaka_kodu, performance_score DESC
|
|
|
|
|
`, pq.Array(variantKeys))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
defer rows.Close()
|
|
|
|
|
for rows.Next() {
|
|
|
|
|
var productCode, colorCode, yakaKodu string
|
|
|
|
|
var p productPerformanceAttrInfo
|
|
|
|
|
if err := rows.Scan(&productCode, &colorCode, &yakaKodu, &p.itemDescription, &p.kategori, &p.askiliYan, &p.urunIlkGrubu, &p.urunAnaGrubu, &p.urunAltGrubu); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
out[productPerformanceVariantKey(productCode, colorCode, yakaKodu)] = p
|
|
|
|
|
}
|
|
|
|
|
return out, rows.Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func productPerformanceStockLookup(ctx context.Context, pg *sql.DB, variantKeys []string) (map[string]float64, error) {
|
|
|
|
|
out := make(map[string]float64, len(variantKeys))
|
|
|
|
|
if len(variantKeys) == 0 {
|
|
|
|
|