ui: update ProductPerformanceProfitability page to enhance table views and tabs management
This commit is contained in:
@@ -53,11 +53,9 @@ func productPerformanceStockQuery() string {
|
||||
;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')
|
||||
('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')
|
||||
) W(WarehouseCode)
|
||||
),
|
||||
Raw AS (
|
||||
@@ -72,8 +70,8 @@ Raw AS (
|
||||
END,
|
||||
InQty = SUM(S.In_Qty1),
|
||||
OutQty = SUM(S.Out_Qty1),
|
||||
NetQty = SUM(S.InventoryQty1)
|
||||
FROM StockWithCost S WITH(NOLOCK)
|
||||
NetQty = SUM(S.In_Qty1 - S.Out_Qty1)
|
||||
FROM trStock S WITH(NOLOCK)
|
||||
INNER JOIN ActiveWarehouses W
|
||||
ON W.WarehouseCode = LTRIM(RTRIM(S.WarehouseCode))
|
||||
WHERE S.ItemTypeCode = 1
|
||||
@@ -185,6 +183,103 @@ Running AS (
|
||||
ConsumptionOutQty,
|
||||
CountDiffQty
|
||||
FROM Collapsed
|
||||
),
|
||||
CurrentStock AS (
|
||||
SELECT
|
||||
ProductCode = LTRIM(RTRIM(S.ItemCode)),
|
||||
ColorCode = LTRIM(RTRIM(ISNULL(S.ColorCode, ''))),
|
||||
YakaKodu = LTRIM(RTRIM(ISNULL(S.ItemDim2Code, ''))),
|
||||
InventoryQty1 = SUM(S.In_Qty1 - S.Out_Qty1)
|
||||
FROM trStock S WITH(NOLOCK)
|
||||
INNER JOIN ActiveWarehouses W
|
||||
ON W.WarehouseCode = LTRIM(RTRIM(S.WarehouseCode))
|
||||
WHERE S.ItemTypeCode = 1
|
||||
AND (
|
||||
LEN(LTRIM(RTRIM(S.ItemCode))) = 13
|
||||
AND (
|
||||
S.ItemCode LIKE 'S%'
|
||||
OR S.ItemCode LIKE 'O%'
|
||||
OR S.ItemCode LIKE 'N%'
|
||||
OR S.ItemCode LIKE 'X%'
|
||||
OR S.ItemCode LIKE 'I%'
|
||||
OR S.ItemCode LIKE 'A%'
|
||||
)
|
||||
)
|
||||
AND (@p3 = '' OR S.ItemCode LIKE @p3 + '%')
|
||||
AND S.DocumentDate < DATEADD(DAY, 1, @p2)
|
||||
GROUP BY S.ItemCode, S.ColorCode, S.ItemDim2Code
|
||||
),
|
||||
CurrentPick AS (
|
||||
SELECT
|
||||
ProductCode = LTRIM(RTRIM(P.ItemCode)),
|
||||
ColorCode = LTRIM(RTRIM(ISNULL(P.ColorCode, ''))),
|
||||
YakaKodu = LTRIM(RTRIM(ISNULL(P.ItemDim2Code, ''))),
|
||||
PickingQty1 = SUM(P.Qty1)
|
||||
FROM PickingStates P WITH(NOLOCK)
|
||||
INNER JOIN ActiveWarehouses W
|
||||
ON W.WarehouseCode = LTRIM(RTRIM(P.WarehouseCode))
|
||||
WHERE P.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(P.ItemCode))) = 13
|
||||
AND (@p3 = '' OR P.ItemCode LIKE @p3 + '%')
|
||||
GROUP BY P.ItemCode, P.ColorCode, P.ItemDim2Code
|
||||
),
|
||||
CurrentReserve AS (
|
||||
SELECT
|
||||
ProductCode = LTRIM(RTRIM(R.ItemCode)),
|
||||
ColorCode = LTRIM(RTRIM(ISNULL(R.ColorCode, ''))),
|
||||
YakaKodu = LTRIM(RTRIM(ISNULL(R.ItemDim2Code, ''))),
|
||||
ReserveQty1 = SUM(R.Qty1)
|
||||
FROM ReserveStates R WITH(NOLOCK)
|
||||
INNER JOIN ActiveWarehouses W
|
||||
ON W.WarehouseCode = LTRIM(RTRIM(R.WarehouseCode))
|
||||
WHERE R.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(R.ItemCode))) = 13
|
||||
AND (@p3 = '' OR R.ItemCode LIKE @p3 + '%')
|
||||
GROUP BY R.ItemCode, R.ColorCode, R.ItemDim2Code
|
||||
),
|
||||
CurrentDisp AS (
|
||||
SELECT
|
||||
ProductCode = LTRIM(RTRIM(D.ItemCode)),
|
||||
ColorCode = LTRIM(RTRIM(ISNULL(D.ColorCode, ''))),
|
||||
YakaKodu = LTRIM(RTRIM(ISNULL(D.ItemDim2Code, ''))),
|
||||
DispOrderQty1 = SUM(D.Qty1)
|
||||
FROM DispOrderStates D WITH(NOLOCK)
|
||||
INNER JOIN ActiveWarehouses W
|
||||
ON W.WarehouseCode = LTRIM(RTRIM(D.WarehouseCode))
|
||||
WHERE D.ItemTypeCode = 1
|
||||
AND LEN(LTRIM(RTRIM(D.ItemCode))) = 13
|
||||
AND (@p3 = '' OR D.ItemCode LIKE @p3 + '%')
|
||||
GROUP BY D.ItemCode, D.ColorCode, D.ItemDim2Code
|
||||
),
|
||||
CurrentAvailable AS (
|
||||
SELECT
|
||||
StockDate = @p2,
|
||||
S.ProductCode,
|
||||
S.ColorCode,
|
||||
S.YakaKodu,
|
||||
StockQty = CAST(ROUND(
|
||||
ISNULL(S.InventoryQty1, 0)
|
||||
- ISNULL(PK.PickingQty1, 0)
|
||||
- ISNULL(RS.ReserveQty1, 0)
|
||||
- ISNULL(DP.DispOrderQty1, 0),
|
||||
2
|
||||
) AS decimal(18,4)),
|
||||
InQty = CAST(0 AS decimal(18,4)),
|
||||
OutQty = CAST(0 AS decimal(18,4)),
|
||||
KpiInQty = CAST(0 AS decimal(18,4)),
|
||||
KpiOutQty = CAST(0 AS decimal(18,4)),
|
||||
SalesMovementQty = CAST(0 AS decimal(18,4)),
|
||||
ProductionInQty = CAST(0 AS decimal(18,4)),
|
||||
PurchaseInQty = CAST(0 AS decimal(18,4)),
|
||||
ConsumptionOutQty = CAST(0 AS decimal(18,4)),
|
||||
CountDiffQty = CAST(0 AS decimal(18,4))
|
||||
FROM CurrentStock S
|
||||
LEFT JOIN CurrentPick PK
|
||||
ON PK.ProductCode = S.ProductCode AND PK.ColorCode = S.ColorCode AND PK.YakaKodu = S.YakaKodu
|
||||
LEFT JOIN CurrentReserve RS
|
||||
ON RS.ProductCode = S.ProductCode AND RS.ColorCode = S.ColorCode AND RS.YakaKodu = S.YakaKodu
|
||||
LEFT JOIN CurrentDisp DP
|
||||
ON DP.ProductCode = S.ProductCode AND DP.ColorCode = S.ColorCode AND DP.YakaKodu = S.YakaKodu
|
||||
)
|
||||
SELECT
|
||||
StockDate,
|
||||
@@ -201,8 +296,11 @@ SELECT
|
||||
PurchaseInQty,
|
||||
ConsumptionOutQty,
|
||||
CountDiffQty
|
||||
FROM Running
|
||||
WHERE StockDate BETWEEN @p1 AND @p2
|
||||
FROM (
|
||||
SELECT * FROM Running WHERE StockDate BETWEEN @p1 AND DATEADD(DAY, -1, @p2)
|
||||
UNION ALL
|
||||
SELECT * FROM CurrentAvailable
|
||||
) X
|
||||
ORDER BY StockDate, ProductCode, ColorCode, YakaKodu
|
||||
`
|
||||
}
|
||||
@@ -2165,6 +2263,326 @@ LIMIT $1
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, breakdown string, limit int) ([]models.ProductPerformanceSalesBreakdownRow, error) {
|
||||
if err := EnsureProductPerformanceTables(pg); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if limit <= 0 || limit > 1000 {
|
||||
limit = 500
|
||||
}
|
||||
mode := strings.ToLower(strings.TrimSpace(breakdown))
|
||||
fields := map[string]string{
|
||||
"product_code": "s.product_code",
|
||||
"color_code": "s.color_code",
|
||||
"yaka_kodu": "s.yaka_kodu",
|
||||
"item_description": "MAX(s.item_description)",
|
||||
"market_key": "s.market_key",
|
||||
"country": "s.customer_country",
|
||||
"customer_segment": "s.customer_segment",
|
||||
"customer_code": "s.customer_code",
|
||||
"customer_name": "MAX(s.customer_name)",
|
||||
}
|
||||
selected := map[string]bool{}
|
||||
groupCols := []string{}
|
||||
switch mode {
|
||||
case "color_yaka_market_customer":
|
||||
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["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"}
|
||||
case "market_customer_product":
|
||||
selected["market_key"] = true
|
||||
selected["customer_code"] = true
|
||||
selected["customer_name"] = true
|
||||
selected["product_code"] = true
|
||||
selected["color_code"] = true
|
||||
selected["yaka_kodu"] = true
|
||||
selected["item_description"] = 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
|
||||
selected["customer_segment"] = true
|
||||
selected["market_key"] = true
|
||||
selected["customer_code"] = true
|
||||
selected["customer_name"] = true
|
||||
selected["product_code"] = true
|
||||
selected["color_code"] = true
|
||||
selected["yaka_kodu"] = true
|
||||
selected["item_description"] = 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["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"}
|
||||
}
|
||||
|
||||
selectExpr := func(name string) string {
|
||||
if selected[name] {
|
||||
return fields[name] + " AS " + name
|
||||
}
|
||||
return "''::text AS " + name
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
WITH Agg AS (
|
||||
SELECT
|
||||
$2::text AS breakdown,
|
||||
%s,
|
||||
%s,
|
||||
%s,
|
||||
%s,
|
||||
%s,
|
||||
%s,
|
||||
%s,
|
||||
%s,
|
||||
%s,
|
||||
COUNT(DISTINCT product_code) AS product_count,
|
||||
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,
|
||||
COALESCE(SUM(sales_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0) AS sales_usd_90d,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)
|
||||
END AS avg_price_usd_90d,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_qty * base_price_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)
|
||||
END AS base_price_usd_90d,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_qty * cost_price_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '89 days'),0)
|
||||
END AS cost_price_usd_90d,
|
||||
COUNT(DISTINCT customer_code) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days' AND COALESCE(customer_code,'') <> '')::integer AS customer_count_180d,
|
||||
COALESCE(SUM(invoice_count) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)::integer AS invoice_count_180d,
|
||||
COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0) AS sales_qty_180d,
|
||||
COALESCE(SUM(sales_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0) AS sales_usd_180d,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)
|
||||
END AS avg_price_usd_180d,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_qty * base_price_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)
|
||||
END AS base_price_usd_180d,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_qty * cost_price_usd) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '179 days'),0)
|
||||
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,
|
||||
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)
|
||||
END AS avg_price_usd_365d,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_qty * base_price_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)
|
||||
END AS base_price_usd_365d,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= current_date - INTERVAL '359 days'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_qty * cost_price_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)
|
||||
END AS cost_price_usd_365d,
|
||||
COUNT(DISTINCT customer_code) FILTER (WHERE sales_date >= DATE '2022-01-01' AND COALESCE(customer_code,'') <> '')::integer AS customer_count_total,
|
||||
COALESCE(SUM(invoice_count) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)::integer AS invoice_count_total,
|
||||
COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= DATE '2022-01-01'),0) AS sales_qty_total,
|
||||
COALESCE(SUM(sales_usd) FILTER (WHERE sales_date >= DATE '2022-01-01'),0) AS sales_usd_total,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_usd) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)
|
||||
END AS avg_price_usd_total,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_qty * base_price_usd) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)
|
||||
END AS base_price_usd_total,
|
||||
CASE WHEN COALESCE(SUM(sales_qty) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)=0 THEN 0
|
||||
ELSE COALESCE(SUM(sales_qty * cost_price_usd) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)
|
||||
/ NULLIF(SUM(sales_qty) FILTER (WHERE sales_date >= DATE '2022-01-01'),0)
|
||||
END AS cost_price_usd_total,
|
||||
COALESCE(to_char(MAX(sales_date),'YYYY-MM-DD'),'') AS last_sale_date
|
||||
FROM (
|
||||
SELECT
|
||||
s.*,
|
||||
COALESCE(pd.base_price_usd,0) AS base_price_usd,
|
||||
COALESCE(pd.cost_price_usd,0) AS cost_price_usd
|
||||
FROM mk_product_performance_sales_daily s
|
||||
LEFT JOIN mk_product_performance_price_dim pd ON pd.product_code = s.product_code
|
||||
) s
|
||||
WHERE sales_date >= DATE '2022-01-01'
|
||||
GROUP BY %s
|
||||
),
|
||||
Enriched AS (
|
||||
SELECT
|
||||
Agg.*,
|
||||
sales_usd_90d - (sales_qty_90d * base_price_usd_90d) AS gross_profit_base_usd_90d,
|
||||
sales_usd_90d - (sales_qty_90d * cost_price_usd_90d) AS gross_profit_cost_usd_90d,
|
||||
CASE WHEN sales_usd_90d <= 0 THEN 0 ELSE (sales_usd_90d - (sales_qty_90d * base_price_usd_90d)) / NULLIF(sales_usd_90d,0) END AS gross_margin_base_90d,
|
||||
CASE WHEN sales_usd_90d <= 0 THEN 0 ELSE (sales_usd_90d - (sales_qty_90d * cost_price_usd_90d)) / NULLIF(sales_usd_90d,0) END AS gross_margin_cost_90d,
|
||||
sales_usd_180d - (sales_qty_180d * base_price_usd_180d) AS gross_profit_base_usd_180d,
|
||||
sales_usd_180d - (sales_qty_180d * cost_price_usd_180d) AS gross_profit_cost_usd_180d,
|
||||
CASE WHEN sales_usd_180d <= 0 THEN 0 ELSE (sales_usd_180d - (sales_qty_180d * base_price_usd_180d)) / NULLIF(sales_usd_180d,0) END AS gross_margin_base_180d,
|
||||
CASE WHEN sales_usd_180d <= 0 THEN 0 ELSE (sales_usd_180d - (sales_qty_180d * cost_price_usd_180d)) / NULLIF(sales_usd_180d,0) END AS gross_margin_cost_180d,
|
||||
sales_usd_365d - (sales_qty_365d * base_price_usd_365d) AS gross_profit_base_usd_365d,
|
||||
sales_usd_365d - (sales_qty_365d * cost_price_usd_365d) AS gross_profit_cost_usd_365d,
|
||||
CASE WHEN sales_usd_365d <= 0 THEN 0 ELSE (sales_usd_365d - (sales_qty_365d * base_price_usd_365d)) / NULLIF(sales_usd_365d,0) END AS gross_margin_base_365d,
|
||||
CASE WHEN sales_usd_365d <= 0 THEN 0 ELSE (sales_usd_365d - (sales_qty_365d * cost_price_usd_365d)) / NULLIF(sales_usd_365d,0) END AS gross_margin_cost_365d,
|
||||
sales_usd_total - (sales_qty_total * base_price_usd_total) AS gross_profit_base_usd_total,
|
||||
sales_usd_total - (sales_qty_total * cost_price_usd_total) AS gross_profit_cost_usd_total,
|
||||
CASE WHEN sales_usd_total <= 0 THEN 0 ELSE (sales_usd_total - (sales_qty_total * base_price_usd_total)) / NULLIF(sales_usd_total,0) END AS gross_margin_base_total,
|
||||
CASE WHEN sales_usd_total <= 0 THEN 0 ELSE (sales_usd_total - (sales_qty_total * cost_price_usd_total)) / NULLIF(sales_usd_total,0) END AS gross_margin_cost_total,
|
||||
(base_price_usd_total > 0 AND cost_price_usd_total > 0) AS has_cost
|
||||
FROM Agg
|
||||
),
|
||||
Scored AS (
|
||||
SELECT
|
||||
Enriched.*,
|
||||
CASE WHEN AVG(NULLIF(sales_usd_90d,0)) OVER () IS NULL THEN 0
|
||||
ELSE sales_usd_90d / NULLIF(AVG(NULLIF(sales_usd_90d,0)) OVER (),0)
|
||||
END AS sales_index_90d
|
||||
FROM Enriched
|
||||
)
|
||||
SELECT
|
||||
breakdown,
|
||||
product_code,
|
||||
color_code,
|
||||
yaka_kodu,
|
||||
item_description,
|
||||
market_key,
|
||||
country,
|
||||
customer_segment,
|
||||
customer_code,
|
||||
customer_name,
|
||||
product_count,
|
||||
customer_count_90d,
|
||||
invoice_count_90d,
|
||||
sales_qty_90d,
|
||||
sales_usd_90d,
|
||||
avg_price_usd_90d,
|
||||
base_price_usd_90d,
|
||||
cost_price_usd_90d,
|
||||
gross_profit_base_usd_90d,
|
||||
gross_profit_cost_usd_90d,
|
||||
gross_margin_base_90d,
|
||||
gross_margin_cost_90d,
|
||||
customer_count_180d,
|
||||
invoice_count_180d,
|
||||
sales_qty_180d,
|
||||
sales_usd_180d,
|
||||
avg_price_usd_180d,
|
||||
base_price_usd_180d,
|
||||
cost_price_usd_180d,
|
||||
gross_profit_base_usd_180d,
|
||||
gross_profit_cost_usd_180d,
|
||||
gross_margin_base_180d,
|
||||
gross_margin_cost_180d,
|
||||
sales_qty_365d,
|
||||
sales_usd_365d,
|
||||
avg_price_usd_365d,
|
||||
base_price_usd_365d,
|
||||
cost_price_usd_365d,
|
||||
gross_profit_base_usd_365d,
|
||||
gross_profit_cost_usd_365d,
|
||||
gross_margin_base_365d,
|
||||
gross_margin_cost_365d,
|
||||
customer_count_total,
|
||||
invoice_count_total,
|
||||
sales_qty_total,
|
||||
sales_usd_total,
|
||||
avg_price_usd_total,
|
||||
base_price_usd_total,
|
||||
cost_price_usd_total,
|
||||
gross_profit_base_usd_total,
|
||||
gross_profit_cost_usd_total,
|
||||
gross_margin_base_total,
|
||||
gross_margin_cost_total,
|
||||
has_cost,
|
||||
sales_index_90d,
|
||||
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,
|
||||
4
|
||||
)
|
||||
END AS performance_score,
|
||||
CASE
|
||||
WHEN NOT has_cost THEN 'MALIYET_YOK'
|
||||
WHEN sales_index_90d >= 1.25 AND customer_count_90d >= 2 THEN 'YILDIZ_URUN'
|
||||
WHEN sales_qty_90d = 0 AND sales_qty_365d > 0 THEN 'STOK_RISKI'
|
||||
WHEN gross_margin_cost_90d < 0 THEN 'FIYAT_BASKISI'
|
||||
WHEN sales_index_90d < 0.75 THEN 'TAKIP'
|
||||
ELSE 'TAKIP'
|
||||
END AS performance_bucket,
|
||||
CASE
|
||||
WHEN sales_index_90d >= 1.25 AND customer_count_90d >= 2 THEN 'Güçlü satış kırılımı'
|
||||
WHEN sales_qty_90d = 0 AND sales_qty_365d > 0 THEN 'Son 90 gün zayıf, kontrol et'
|
||||
ELSE 'Takip'
|
||||
END AS recommendation,
|
||||
last_sale_date
|
||||
FROM Scored
|
||||
ORDER BY sales_usd_90d DESC, sales_qty_90d DESC
|
||||
LIMIT $1
|
||||
`,
|
||||
selectExpr("product_code"),
|
||||
selectExpr("color_code"),
|
||||
selectExpr("yaka_kodu"),
|
||||
selectExpr("item_description"),
|
||||
selectExpr("market_key"),
|
||||
selectExpr("country"),
|
||||
selectExpr("customer_segment"),
|
||||
selectExpr("customer_code"),
|
||||
selectExpr("customer_name"),
|
||||
strings.Join(groupCols, ", "),
|
||||
)
|
||||
|
||||
rows, err := pg.QueryContext(ctx, query, limit, mode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make([]models.ProductPerformanceSalesBreakdownRow, 0, limit)
|
||||
for rows.Next() {
|
||||
var r models.ProductPerformanceSalesBreakdownRow
|
||||
if err := rows.Scan(
|
||||
&r.Breakdown, &r.ProductCode, &r.ColorCode, &r.YakaKodu, &r.ItemDescription,
|
||||
&r.MarketKey, &r.Country, &r.CustomerSegment, &r.CustomerCode, &r.CustomerName,
|
||||
&r.ProductCount, &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.AvgPriceUSDTotal, &r.BasePriceUSDTotal, &r.CostPriceUSDTotal, &r.GrossProfitBaseTotal,
|
||||
&r.GrossProfitCostTotal, &r.GrossMarginBaseTotal, &r.GrossMarginCostTotal, &r.HasCost,
|
||||
&r.SalesIndex90, &r.PerformanceScore,
|
||||
&r.PerformanceBucket, &r.Recommendation, &r.LastSaleDate,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, r)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func ListProductPerformanceSalesDetails(ctx context.Context, pg *sql.DB, productCode, colorCode, yakaKodu string, limit int) ([]models.ProductPerformanceSalesDetailRow, error) {
|
||||
if limit <= 0 || limit > 500 {
|
||||
limit = 100
|
||||
@@ -2211,26 +2629,83 @@ func ListProductPerformanceStockSizes(ctx context.Context, productCode, colorCod
|
||||
;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')
|
||||
('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')
|
||||
) W(WarehouseCode)
|
||||
),
|
||||
Stock AS (
|
||||
SELECT
|
||||
SizeCode = LTRIM(RTRIM(ISNULL(S.ItemDim1Code, ''))),
|
||||
InventoryQty1 = SUM(S.In_Qty1 - S.Out_Qty1)
|
||||
FROM trStock 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
|
||||
AND LTRIM(RTRIM(ISNULL(S.ItemDim2Code, ''))) = @p3
|
||||
GROUP BY LTRIM(RTRIM(ISNULL(S.ItemDim1Code, '')))
|
||||
),
|
||||
Pick AS (
|
||||
SELECT
|
||||
SizeCode = LTRIM(RTRIM(ISNULL(P.ItemDim1Code, ''))),
|
||||
PickingQty1 = SUM(P.Qty1)
|
||||
FROM PickingStates P WITH(NOLOCK)
|
||||
INNER JOIN ActiveWarehouses W
|
||||
ON W.WarehouseCode = LTRIM(RTRIM(P.WarehouseCode))
|
||||
WHERE P.ItemTypeCode = 1
|
||||
AND LTRIM(RTRIM(P.ItemCode)) = @p1
|
||||
AND LTRIM(RTRIM(ISNULL(P.ColorCode, ''))) = @p2
|
||||
AND LTRIM(RTRIM(ISNULL(P.ItemDim2Code, ''))) = @p3
|
||||
GROUP BY LTRIM(RTRIM(ISNULL(P.ItemDim1Code, '')))
|
||||
),
|
||||
Reserve AS (
|
||||
SELECT
|
||||
SizeCode = LTRIM(RTRIM(ISNULL(R.ItemDim1Code, ''))),
|
||||
ReserveQty1 = SUM(R.Qty1)
|
||||
FROM ReserveStates R WITH(NOLOCK)
|
||||
INNER JOIN ActiveWarehouses W
|
||||
ON W.WarehouseCode = LTRIM(RTRIM(R.WarehouseCode))
|
||||
WHERE R.ItemTypeCode = 1
|
||||
AND LTRIM(RTRIM(R.ItemCode)) = @p1
|
||||
AND LTRIM(RTRIM(ISNULL(R.ColorCode, ''))) = @p2
|
||||
AND LTRIM(RTRIM(ISNULL(R.ItemDim2Code, ''))) = @p3
|
||||
GROUP BY LTRIM(RTRIM(ISNULL(R.ItemDim1Code, '')))
|
||||
),
|
||||
Disp AS (
|
||||
SELECT
|
||||
SizeCode = LTRIM(RTRIM(ISNULL(D.ItemDim1Code, ''))),
|
||||
DispOrderQty1 = SUM(D.Qty1)
|
||||
FROM DispOrderStates D WITH(NOLOCK)
|
||||
INNER JOIN ActiveWarehouses W
|
||||
ON W.WarehouseCode = LTRIM(RTRIM(D.WarehouseCode))
|
||||
WHERE D.ItemTypeCode = 1
|
||||
AND LTRIM(RTRIM(D.ItemCode)) = @p1
|
||||
AND LTRIM(RTRIM(ISNULL(D.ColorCode, ''))) = @p2
|
||||
AND LTRIM(RTRIM(ISNULL(D.ItemDim2Code, ''))) = @p3
|
||||
GROUP BY LTRIM(RTRIM(ISNULL(D.ItemDim1Code, '')))
|
||||
)
|
||||
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
|
||||
AND LTRIM(RTRIM(ISNULL(S.ItemDim2Code, ''))) = @p3
|
||||
GROUP BY LTRIM(RTRIM(ISNULL(S.ItemDim1Code, '')))
|
||||
HAVING SUM(S.InventoryQty1) <> 0
|
||||
ORDER BY LTRIM(RTRIM(ISNULL(S.ItemDim1Code, '')))
|
||||
S.SizeCode,
|
||||
StockQty = CAST(ROUND(
|
||||
ISNULL(S.InventoryQty1, 0)
|
||||
- ISNULL(P.PickingQty1, 0)
|
||||
- ISNULL(R.ReserveQty1, 0)
|
||||
- ISNULL(D.DispOrderQty1, 0),
|
||||
2
|
||||
) AS FLOAT)
|
||||
FROM Stock S
|
||||
LEFT JOIN Pick P ON P.SizeCode = S.SizeCode
|
||||
LEFT JOIN Reserve R ON R.SizeCode = S.SizeCode
|
||||
LEFT JOIN Disp D ON D.SizeCode = S.SizeCode
|
||||
WHERE (
|
||||
ISNULL(S.InventoryQty1, 0)
|
||||
- ISNULL(P.PickingQty1, 0)
|
||||
- ISNULL(R.ReserveQty1, 0)
|
||||
- ISNULL(D.DispOrderQty1, 0)
|
||||
) <> 0
|
||||
ORDER BY S.SizeCode
|
||||
`, strings.TrimSpace(productCode), strings.TrimSpace(colorCode), strings.TrimSpace(yakaKodu))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -47,20 +47,6 @@ func productPerformanceSalesSQL() string {
|
||||
AND cdItemAttributeDesc.LangCode = 'TR'
|
||||
), SPACE(0))),
|
||||
Kategori = dbo.HG_Temizlik(ISNULL((
|
||||
SELECT AttributeDescription
|
||||
FROM cdItemAttributeDesc WITH(NOLOCK)
|
||||
WHERE cdItemAttributeDesc.ItemTypeCode = I.ItemTypeCode
|
||||
AND cdItemAttributeDesc.AttributeTypeCode = 42
|
||||
AND cdItemAttributeDesc.AttributeCode = (
|
||||
SELECT TOP 1 AttributeCode
|
||||
FROM prItemAttribute WITH(NOLOCK)
|
||||
WHERE AttributeTypeCode = 42
|
||||
AND prItemAttribute.ItemTypeCode = I.ItemTypeCode
|
||||
AND prItemAttribute.ItemCode = I.ItemCode
|
||||
)
|
||||
AND cdItemAttributeDesc.LangCode = 'TR'
|
||||
), SPACE(0))),
|
||||
UrunIlkGrubu = dbo.HG_Temizlik(ISNULL((
|
||||
SELECT AttributeDescription
|
||||
FROM cdItemAttributeDesc WITH(NOLOCK)
|
||||
WHERE cdItemAttributeDesc.ItemTypeCode = I.ItemTypeCode
|
||||
@@ -74,10 +60,33 @@ func productPerformanceSalesSQL() string {
|
||||
)
|
||||
AND cdItemAttributeDesc.LangCode = 'TR'
|
||||
), SPACE(0))),
|
||||
UrunIlkGrubu = dbo.HG_Temizlik(ISNULL((
|
||||
SELECT AttributeDescription
|
||||
FROM cdItemAttributeDesc WITH(NOLOCK)
|
||||
WHERE cdItemAttributeDesc.ItemTypeCode = I.ItemTypeCode
|
||||
AND cdItemAttributeDesc.AttributeTypeCode = 42
|
||||
AND cdItemAttributeDesc.AttributeCode = (
|
||||
SELECT TOP 1 AttributeCode
|
||||
FROM prItemAttribute WITH(NOLOCK)
|
||||
WHERE AttributeTypeCode = 42
|
||||
AND prItemAttribute.ItemTypeCode = I.ItemTypeCode
|
||||
AND prItemAttribute.ItemCode = I.ItemCode
|
||||
)
|
||||
AND cdItemAttributeDesc.LangCode = 'TR'
|
||||
), SPACE(0))),
|
||||
AskiliYan = dbo.HG_Temizlik(ISNULL((
|
||||
SELECT TOP 1 ProductAtt45
|
||||
FROM ProductAttributesFilter WITH(NOLOCK)
|
||||
WHERE ProductAttributesFilter.ItemCode = I.ItemCode
|
||||
SELECT AttributeDescription
|
||||
FROM cdItemAttributeDesc WITH(NOLOCK)
|
||||
WHERE cdItemAttributeDesc.ItemTypeCode = I.ItemTypeCode
|
||||
AND cdItemAttributeDesc.AttributeTypeCode = 45
|
||||
AND cdItemAttributeDesc.AttributeCode = (
|
||||
SELECT TOP 1 AttributeCode
|
||||
FROM prItemAttribute WITH(NOLOCK)
|
||||
WHERE AttributeTypeCode = 45
|
||||
AND prItemAttribute.ItemTypeCode = I.ItemTypeCode
|
||||
AND prItemAttribute.ItemCode = I.ItemCode
|
||||
)
|
||||
AND cdItemAttributeDesc.LangCode = 'TR'
|
||||
), SPACE(0))),
|
||||
ChannelCode = CASE
|
||||
WHEN I.ProcessCode IN ('R') THEN dbo.HG_Temizlik(ISNULL((
|
||||
@@ -487,12 +496,12 @@ SalesAgg AS (
|
||||
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '29 days') AS sales_qty_30d,
|
||||
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '89 days') AS sales_qty_90d,
|
||||
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '179 days') AS sales_qty_180d,
|
||||
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '364 days') AS sales_qty_365d,
|
||||
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '359 days') AS sales_qty_365d,
|
||||
SUM(sales_qty) FILTER (WHERE sales_date >= $1::date - INTERVAL '729 days') AS sales_qty_730d,
|
||||
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '29 days') AS sales_usd_30d,
|
||||
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '89 days') AS sales_usd_90d,
|
||||
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '179 days') AS sales_usd_180d,
|
||||
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '364 days') AS sales_usd_365d,
|
||||
SUM(sales_usd) FILTER (WHERE sales_date >= $1::date - INTERVAL '359 days') AS sales_usd_365d,
|
||||
COUNT(DISTINCT NULLIF(customer_code, '-')) FILTER (WHERE sales_date >= $1::date - INTERVAL '89 days') AS customer_count_90d,
|
||||
MAX(sales_date) AS last_sale_date,
|
||||
MAX(last_ref_number) AS last_ref_number
|
||||
|
||||
Reference in New Issue
Block a user