ui: update ProductPerformanceProfitability page to enhance table views and tabs management

This commit is contained in:
M_Kececi
2026-07-02 20:02:19 +03:00
parent 593a7d4816
commit 2fc9699ff1
3 changed files with 359 additions and 103 deletions
+75 -37
View File
@@ -670,8 +670,10 @@ func ListProductPerformance(ctx context.Context, pg *sql.DB, f ProductPerformanc
return nil, 0, err
}
limit := f.Limit
if limit <= 0 || limit > 500 {
if limit <= 0 {
limit = 100
} else if limit > 50000 {
limit = 50000
}
page := f.Page
if page <= 0 {
@@ -741,6 +743,25 @@ KPI AS (
FROM mk_product_performance_kpi_daily
WHERE kpi_date = (SELECT kpi_date FROM Latest)
),
VariantStock AS (
SELECT
product_code,
color_code,
yaka_kodu,
MAX(stock_qty) AS stock_qty,
MAX(cost_price_usd) AS cost_price_usd,
BOOL_OR(performance_bucket IN ('STOK_RISKI','TAKIP')) AS has_risk_bucket,
SUM(COALESCE(sales_qty_90d,0)) AS sales_qty_90d
FROM KPI
GROUP BY product_code, color_code, yaka_kodu
),
StockTotals AS (
SELECT
COALESCE(SUM(stock_qty),0) AS total_stock,
COALESCE(SUM(stock_qty * cost_price_usd),0) AS stock_cost_usd,
COALESCE(SUM(CASE WHEN has_risk_bucket AND COALESCE(sales_qty_90d,0)=0 THEN stock_qty * cost_price_usd ELSE 0 END),0) AS risk_cost_usd
FROM VariantStock
),
Sales90 AS (
SELECT
COUNT(DISTINCT market_key) AS market_count_90d,
@@ -754,9 +775,9 @@ SELECT
COUNT(*) FILTER (WHERE performance_bucket='YILDIZ_URUN'),
COUNT(*) FILTER (WHERE performance_bucket='STOK_RISKI'),
COUNT(*) FILTER (WHERE performance_bucket='STOKSUZ_TALEP'),
COALESCE(SUM(stock_qty),0),
COALESCE(SUM(stock_qty * cost_price_usd),0),
COALESCE(SUM(CASE WHEN performance_bucket IN ('STOK_RISKI','TAKIP') AND COALESCE(sales_qty_90d,0)=0 THEN stock_qty * cost_price_usd ELSE 0 END),0),
COALESCE(MAX(StockTotals.total_stock),0),
COALESCE(MAX(StockTotals.stock_cost_usd),0),
COALESCE(MAX(StockTotals.risk_cost_usd),0),
COALESCE(SUM(sales_qty_90d),0),
COALESCE(SUM(sales_usd_90d),0),
COALESCE(SUM(gross_profit_usd_90d),0),
@@ -765,6 +786,7 @@ SELECT
COALESCE(to_char(MAX(updated_at),'YYYY-MM-DD HH24:MI:SS'),'')
FROM KPI
CROSS JOIN Sales90
CROSS JOIN StockTotals
`).Scan(&s.KpiDate, &s.TotalRows, &s.StarCount, &s.StockRisk, &s.NoStockDemand, &s.TotalStock, &s.StockCostUSD, &s.RiskCostUSD, &s.SalesQty90, &s.SalesUSD90, &s.GrossProfit90, &s.MarketCount90, &s.CustomerCount90, &s.UpdatedAt)
return s, err
}
@@ -2007,10 +2029,22 @@ func ListProductPerformanceStockSizes(ctx context.Context, productCode, colorCod
return nil, fmt.Errorf("mssql db nil")
}
rows, err := db.MssqlDB.QueryContext(ctx, `
;WITH ActiveWarehouses AS (
SELECT WarehouseCode
FROM (VALUES
('1-0-12'),('1.01.2014'),('1.02.2005'),('1.02.2004'),('1-0-43'),
('4.02.2001'),('1-0-55'),('1.01.2003'),('1-0-21'),('1-0-2'),
('1.01.2004'),('1-0-49'),('1-0-37'),('1-0-29'),('1-0-28'),
('1-0-10'),('100'),('1.02.2006'),('1-0-42'),('1.01.2002'),
('1-0-52')
) W(WarehouseCode)
)
SELECT
SizeCode = LTRIM(RTRIM(ISNULL(S.ItemDim1Code, ''))),
StockQty = SUM(S.InventoryQty1)
FROM StockWithCost S WITH(NOLOCK)
INNER JOIN ActiveWarehouses W
ON W.WarehouseCode = LTRIM(RTRIM(S.WarehouseCode))
WHERE S.ItemTypeCode = 1
AND LTRIM(RTRIM(S.ItemCode)) = @p1
AND LTRIM(RTRIM(ISNULL(S.ColorCode, ''))) = @p2
@@ -2068,39 +2102,43 @@ func productPerformanceWhere(f ProductPerformanceFilters) (string, []any) {
func productPerformanceOrderBy(sortBy string, desc bool) string {
allowed := map[string]string{
"product_code": "product_code",
"color_code": "color_code",
"yaka_kodu": "yaka_kodu",
"item_description": "item_description",
"kategori": "kategori",
"urun_ilk_grubu": "urun_ilk_grubu",
"askili_yan": "askili_yan",
"urun_ana_grubu": "urun_ana_grubu",
"urun_alt_grubu": "urun_alt_grubu",
"market_key": "market_key",
"stock_qty": "stock_qty",
"sales_qty_90d": "sales_qty_90d",
"sales_qty_180d": "sales_qty_180d",
"sales_usd_90d": "sales_usd_90d",
"sales_usd_180d": "sales_usd_180d",
"stock_days_90d": "stock_days_90d",
"stock_days_180d": "stock_days_180d",
"avg_price_usd_90d": "avg_price_usd_90d",
"avg_price_usd_180d": "avg_price_usd_180d",
"base_price_usd": "base_price_usd",
"cost_price_usd": "cost_price_usd",
"unit_profit_base_90d": "unit_profit_base_90d",
"unit_profit_cost_90d": "unit_profit_cost_90d",
"unit_profit_base_180d": "unit_profit_base_180d",
"unit_profit_cost_180d": "unit_profit_cost_180d",
"gross_margin_90d": "gross_margin_90d",
"gross_margin_180d": "gross_margin_180d",
"market_count_90d": "market_count_90d",
"customer_count_90d": "customer_count_90d",
"sales_index_90d": "sales_index_90d",
"performance_score": "performance_score",
"performance_bucket": "performance_bucket",
"last_sale_date": "last_sale_date",
"product_code": "product_code",
"color_code": "color_code",
"yaka_kodu": "yaka_kodu",
"item_description": "item_description",
"kategori": "kategori",
"urun_ilk_grubu": "urun_ilk_grubu",
"askili_yan": "askili_yan",
"urun_ana_grubu": "urun_ana_grubu",
"urun_alt_grubu": "urun_alt_grubu",
"market_key": "market_key",
"stock_qty": "stock_qty",
"sales_qty_90d": "sales_qty_90d",
"sales_qty_180d": "sales_qty_180d",
"sales_usd_90d": "sales_usd_90d",
"sales_usd_180d": "sales_usd_180d",
"stock_days_90d": "stock_days_90d",
"stock_days_180d": "stock_days_180d",
"avg_price_usd_90d": "avg_price_usd_90d",
"avg_price_usd_180d": "avg_price_usd_180d",
"base_price_usd": "base_price_usd",
"cost_price_usd": "cost_price_usd",
"unit_profit_base_90d": "unit_profit_base_90d",
"unit_profit_cost_90d": "unit_profit_cost_90d",
"unit_profit_base_180d": "unit_profit_base_180d",
"unit_profit_cost_180d": "unit_profit_cost_180d",
"gross_margin_base_90d": "(CASE WHEN COALESCE(sales_usd_90d,0) <= 0 THEN 0 ELSE (sales_usd_90d - (sales_qty_90d * COALESCE(base_price_usd,0))) / NULLIF(sales_usd_90d,0) END)",
"gross_margin_cost_90d": "gross_margin_90d",
"gross_margin_base_180d": "(CASE WHEN COALESCE(sales_usd_180d,0) <= 0 THEN 0 ELSE (sales_usd_180d - (sales_qty_180d * COALESCE(base_price_usd,0))) / NULLIF(sales_usd_180d,0) END)",
"gross_margin_cost_180d": "gross_margin_180d",
"gross_margin_90d": "gross_margin_90d",
"gross_margin_180d": "gross_margin_180d",
"market_count_90d": "market_count_90d",
"customer_count_90d": "customer_count_90d",
"sales_index_90d": "sales_index_90d",
"performance_score": "performance_score",
"performance_bucket": "performance_bucket",
"last_sale_date": "last_sale_date",
}
col := allowed[strings.TrimSpace(sortBy)]
if col == "" {
+5 -4
View File
@@ -216,10 +216,11 @@ func productPerformanceStockSQL() string {
;WITH ActiveWarehouses AS (
SELECT WarehouseCode
FROM (VALUES
('1-0-14'),('1-0-10'),('1-0-8'),('1-2-5'),('1-2-4'),('1-0-12'),
('100'),('1-0-28'),('1-0-24'),('1-2-6'),('1-1-14'),('1-0-2'),
('1-0-52'),('1-1-2'),('1-0-21'),('1-1-3'),('1-0-33'),('101'),
('1-014'),('1-0-49'),('1-0-36')
('1-0-12'),('1.01.2014'),('1.02.2005'),('1.02.2004'),('1-0-43'),
('4.02.2001'),('1-0-55'),('1.01.2003'),('1-0-21'),('1-0-2'),
('1.01.2004'),('1-0-49'),('1-0-37'),('1-0-29'),('1-0-28'),
('1-0-10'),('100'),('1.02.2006'),('1-0-42'),('1.01.2002'),
('1-0-52')
) W(WarehouseCode)
),
Raw AS (