ui: add B2B olmayan stok (orphans) page

This commit is contained in:
M_Kececi
2026-07-02 15:33:49 +03:00
parent 810c0e5eff
commit cbb853d433
19 changed files with 3365 additions and 395 deletions
+562
View File
@@ -0,0 +1,562 @@
package queries
func productPerformanceSalesSQL() string {
return `
;WITH SalesLines AS (
SELECT
I.InvoiceLineID,
SalesDate = CAST(I.InvoiceDate AS date),
I.InvoiceHeaderID,
I.InvoiceNumber,
ProductCode = LTRIM(RTRIM(I.ItemCode)),
ColorCode = LTRIM(RTRIM(ISNULL(I.ColorCode, ''))),
YakaKodu = LTRIM(RTRIM(ISNULL(I.ItemDim2Code, ''))),
ItemDescription = dbo.HG_Temizlik(ISNULL((
SELECT ItemDescription
FROM cdItemDesc WITH(NOLOCK)
WHERE cdItemDesc.ItemTypeCode = I.ItemTypeCode
AND cdItemDesc.ItemCode = I.ItemCode
AND cdItemDesc.LangCode = 'TR'
), SPACE(0))),
UrunAnaGrubu = dbo.HG_Temizlik(ISNULL((
SELECT AttributeDescription
FROM cdItemAttributeDesc WITH(NOLOCK)
WHERE cdItemAttributeDesc.ItemTypeCode = I.ItemTypeCode
AND cdItemAttributeDesc.AttributeTypeCode = 1
AND cdItemAttributeDesc.AttributeCode = (
SELECT TOP 1 AttributeCode
FROM prItemAttribute WITH(NOLOCK)
WHERE AttributeTypeCode = 1
AND prItemAttribute.ItemTypeCode = I.ItemTypeCode
AND prItemAttribute.ItemCode = I.ItemCode
)
AND cdItemAttributeDesc.LangCode = 'TR'
), SPACE(0))),
UrunAltGrubu = dbo.HG_Temizlik(ISNULL((
SELECT AttributeDescription
FROM cdItemAttributeDesc WITH(NOLOCK)
WHERE cdItemAttributeDesc.ItemTypeCode = I.ItemTypeCode
AND cdItemAttributeDesc.AttributeTypeCode = 2
AND cdItemAttributeDesc.AttributeCode = (
SELECT TOP 1 AttributeCode
FROM prItemAttribute WITH(NOLOCK)
WHERE AttributeTypeCode = 2
AND prItemAttribute.ItemTypeCode = I.ItemTypeCode
AND prItemAttribute.ItemCode = I.ItemCode
)
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
AND cdItemAttributeDesc.AttributeTypeCode = 44
AND cdItemAttributeDesc.AttributeCode = (
SELECT TOP 1 AttributeCode
FROM prItemAttribute WITH(NOLOCK)
WHERE AttributeTypeCode = 44
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
), SPACE(0))),
ChannelCode = CASE
WHEN I.ProcessCode IN ('R') THEN dbo.HG_Temizlik(ISNULL((
SELECT OfficeDescription
FROM cdOfficeDesc WITH(NOLOCK)
WHERE cdOfficeDesc.OfficeCode = I.OfficeCode
AND cdOfficeDesc.LangCode = 'TR'
), SPACE(0)))
ELSE dbo.HG_Temizlik(ISNULL((
SELECT AttributeDescription
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
WHERE cdCurrAccAttributeDesc.CurrAccTypeCode = 3
AND AttributeTypeCode = 1
AND CAF.CustomerAtt01 = AttributeCode
AND cdCurrAccAttributeDesc.LangCode = 'TR'
), SPACE(0)))
END,
CustomerCountry = dbo.HG_Temizlik(ISNULL((
SELECT AttributeDescription
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
WHERE cdCurrAccAttributeDesc.CurrAccTypeCode = 3
AND AttributeTypeCode = 5
AND CAF.CustomerAtt05 = AttributeCode
AND cdCurrAccAttributeDesc.LangCode = 'TR'
), SPACE(0))),
CustomerSegment = dbo.HG_Temizlik(ISNULL((
SELECT AttributeDescription
FROM cdCurrAccAttributeDesc WITH(NOLOCK)
WHERE cdCurrAccAttributeDesc.CurrAccTypeCode = 3
AND AttributeTypeCode = 11
AND CAF.CustomerAtt11 = AttributeCode
AND cdCurrAccAttributeDesc.LangCode = 'TR'
), SPACE(0))),
CustomerCode = CASE
WHEN I.ProcessCode = 'R' THEN dbo.HG_Temizlik(ISNULL(I.StoreCode, ''))
ELSE dbo.HG_Temizlik(ISNULL(I.CurrAccCode, ''))
END,
CustomerDescription = CASE
WHEN I.ProcessCode = 'R' THEN dbo.HG_Temizlik(ISNULL((
SELECT CurrAccDescription
FROM cdCurrAccDesc WITH(NOLOCK)
WHERE cdCurrAccDesc.CurrAccTypeCode = 5
AND cdCurrAccDesc.CurrAccCode = I.StoreCode
AND cdCurrAccDesc.LangCode = 'TR'
), SPACE(0)))
ELSE dbo.HG_Temizlik(ISNULL((
SELECT CurrAccDescription
FROM cdCurrAccDesc WITH(NOLOCK)
WHERE cdCurrAccDesc.CurrAccTypeCode = I.CurrAccTypeCode
AND cdCurrAccDesc.CurrAccCode = I.CurrAccCode
AND cdCurrAccDesc.LangCode = 'TR'
), SPACE(0)))
END,
Qty1 = ISNULL(I.Qty1, 0),
TLAmount = ISNULL(I.Doc_Amount, 0) * ISNULL(I.Loc_ExchangeRate, 0),
USDAmount = (ISNULL(I.Doc_Amount, 0) * ISNULL(I.Loc_ExchangeRate, 0)) / ISNULL((
SELECT TOP 1 Rate
FROM AllExchangeRates WITH(NOLOCK)
WHERE CurrencyCode = 'USD'
AND RelationCurrencyCode = 'TRY'
AND ExchangeTypeCode = 6
AND Rate > 1
ORDER BY ABS(DATEDIFF(DAY, AllExchangeRates.Date, I.InvoiceDate)) ASC
), 1),
RN = ROW_NUMBER() OVER (
PARTITION BY I.InvoiceLineID
ORDER BY I.InvoiceDate DESC, I.InvoiceNumber
)
FROM AllInvoicesWithAttributes I WITH(NOLOCK)
LEFT OUTER JOIN CustomerAttributesFilter CAF WITH(NOLOCK)
ON CAF.CurrAccTypeCode = I.CurrAccTypeCode
AND CAF.CurrAccCode = I.CurrAccCode
WHERE I.InvoiceDate >= @p1
AND I.InvoiceDate < DATEADD(DAY, 1, @p2)
AND I.ItemTypeCode = 1
AND (I.ItemCode LIKE 'S%' OR I.ItemCode LIKE 'O%' OR I.ItemCode LIKE 'X%' OR I.ItemCode LIKE 'N%')
AND (
(I.CompanyCode = 1 AND I.ATAtt01 IN (1,2) AND I.ProcessCode IN ('WS','R'))
OR (I.CompanyCode = 4 AND I.ProcessCode = 'R')
)
AND EXISTS (
SELECT 1
FROM trDebitHeader DH WITH(NOLOCK)
INNER JOIN trDebitLine DL WITH(NOLOCK)
ON DL.DebitHeaderID = DH.DebitHeaderID
WHERE DH.ApplicationCode = 'Invoi'
AND DH.ApplicationID = I.InvoiceHeaderID
)
)
SELECT
SalesDate,
ProductCode,
ColorCode,
YakaKodu,
ItemDescription = MAX(ItemDescription),
Kategori = MAX(Kategori),
Seri = MAX(Kategori),
YasGrubu = MAX(UrunIlkGrubu),
AskiliYan = MAX(AskiliYan),
UrunIlkGrubu = MAX(UrunIlkGrubu),
UrunAnaGrubu = MAX(UrunAnaGrubu),
UrunAltGrubu = MAX(UrunAltGrubu),
MarketKey = CONCAT(MAX(Kategori), '|', MAX(UrunIlkGrubu), '|', MAX(AskiliYan), '|', MAX(UrunAnaGrubu), '|', MAX(UrunAltGrubu), '|', ChannelCode),
ChannelCode,
CustomerCountry = ISNULL(NULLIF(CustomerCountry, ''), '-'),
CustomerSegment = ISNULL(NULLIF(CustomerSegment, ''), '-'),
CustomerCode = ISNULL(NULLIF(CustomerCode, ''), '-'),
CustomerDescription = MAX(ISNULL(NULLIF(CustomerDescription, ''), '-')),
SalesQty = ISNULL(SUM(Qty1), 0),
SalesTL = ISNULL(SUM(TLAmount), 0),
SalesUSD = ISNULL(SUM(USDAmount), 0),
AvgPriceUSD = ISNULL(SUM(USDAmount) / NULLIF(SUM(Qty1), 0), 0),
InvoiceLineCount = COUNT(DISTINCT InvoiceLineID),
InvoiceCount = COUNT(DISTINCT InvoiceHeaderID),
CustomerCount = COUNT(DISTINCT CustomerCode),
LastRefNumber = MAX(InvoiceNumber)
FROM SalesLines
WHERE RN = 1
GROUP BY SalesDate, ProductCode, ColorCode, YakaKodu, ChannelCode, CustomerCountry, CustomerSegment, CustomerCode
`
}
func productPerformanceStockSQL() string {
return `
;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')
) W(WarehouseCode)
),
Raw AS (
SELECT
ProductCode = LTRIM(RTRIM(S.ItemCode)),
ColorCode = LTRIM(RTRIM(ISNULL(S.ColorCode, ''))),
YakaKodu = LTRIM(RTRIM(ISNULL(S.ItemDim2Code, ''))),
MovementDate = CAST(S.DocumentDate AS date),
ProcessCode = CASE
WHEN LTRIM(RTRIM(ISNULL(S.ProcessCode, ''))) <> '' THEN LTRIM(RTRIM(S.ProcessCode))
ELSE LTRIM(RTRIM(ISNULL(S.InnerProcessCode, '')))
END,
InQty = SUM(S.In_Qty1),
OutQty = SUM(S.Out_Qty1),
NetQty = SUM(S.InventoryQty1)
FROM StockWithCost S WITH(NOLOCK)
INNER JOIN ActiveWarehouses W
ON W.WarehouseCode = LTRIM(RTRIM(S.WarehouseCode))
WHERE S.ItemTypeCode = 1
AND (S.ItemCode LIKE 'S%' OR S.ItemCode LIKE 'O%' OR S.ItemCode LIKE 'X%' OR S.ItemCode LIKE 'N%')
AND S.DocumentDate < DATEADD(DAY, 1, @p2)
GROUP BY
S.ItemCode,
S.ColorCode,
S.ItemDim2Code,
CAST(S.DocumentDate AS date),
CASE
WHEN LTRIM(RTRIM(ISNULL(S.ProcessCode, ''))) <> '' THEN LTRIM(RTRIM(S.ProcessCode))
ELSE LTRIM(RTRIM(ISNULL(S.InnerProcessCode, '')))
END
),
Opening AS (
SELECT
StockDate = @p1,
ProductCode,
ColorCode,
YakaKodu,
NetQty = SUM(NetQty),
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 Raw
WHERE MovementDate < @p1
GROUP BY ProductCode, ColorCode, YakaKodu
),
Daily AS (
SELECT
StockDate = MovementDate,
ProductCode,
ColorCode,
YakaKodu,
NetQty = SUM(NetQty),
InQty = SUM(InQty),
OutQty = SUM(OutQty),
KpiInQty = SUM(CASE WHEN ProcessCode IN ('OP','BP','CI') THEN InQty ELSE 0 END),
KpiOutQty = SUM(CASE WHEN ProcessCode IN ('R','WS','OC','CO') THEN OutQty ELSE 0 END),
SalesMovementQty = SUM(CASE WHEN ProcessCode IN ('R','WS') THEN OutQty ELSE 0 END),
ProductionInQty = SUM(CASE WHEN ProcessCode = 'OP' THEN InQty ELSE 0 END),
PurchaseInQty = SUM(CASE WHEN ProcessCode = 'BP' THEN InQty ELSE 0 END),
ConsumptionOutQty = SUM(CASE WHEN ProcessCode = 'OC' THEN OutQty ELSE 0 END),
CountDiffQty = SUM(CASE WHEN ProcessCode IN ('CO','CI') THEN NetQty ELSE 0 END)
FROM Raw
WHERE MovementDate BETWEEN @p1 AND @p2
GROUP BY MovementDate, ProductCode, ColorCode, YakaKodu
),
Series AS (
SELECT * FROM Opening
UNION ALL
SELECT * FROM Daily
),
Running AS (
SELECT
StockDate,
ProductCode,
ColorCode,
YakaKodu,
StockQty = SUM(NetQty) OVER (
PARTITION BY ProductCode, ColorCode, YakaKodu
ORDER BY StockDate
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
),
InQty,
OutQty,
KpiInQty,
KpiOutQty,
SalesMovementQty,
ProductionInQty,
PurchaseInQty,
ConsumptionOutQty,
CountDiffQty
FROM Series
)
SELECT
StockDate,
ProductCode,
ColorCode,
YakaKodu,
StockQty,
InQty,
OutQty,
KpiInQty,
KpiOutQty,
SalesMovementQty,
ProductionInQty,
PurchaseInQty,
ConsumptionOutQty,
CountDiffQty
FROM Running
WHERE StockDate BETWEEN @p1 AND @p2
ORDER BY StockDate, ProductCode, ColorCode, YakaKodu
`
}
func productPerformancePriceSQL() string {
return `
;WITH ProductCodes AS (
SELECT DISTINCT ItemCode
FROM trPriceListLine WITH(NOLOCK)
WHERE ItemTypeCode = 1
AND (ItemCode LIKE 'S%' OR ItemCode LIKE 'O%' OR ItemCode LIKE 'X%' OR ItemCode LIKE 'N%')
UNION
SELECT DISTINCT ItemCode
FROM prItemBasePrice WITH(NOLOCK)
WHERE ItemTypeCode = 1
AND (ItemCode LIKE 'S%' OR ItemCode LIKE 'O%' OR ItemCode LIKE 'X%' OR ItemCode LIKE 'N%')
),
LatestPrice AS (
SELECT
p.ItemCode,
DocCurrencyCode = LTRIM(RTRIM(p.DocCurrencyCode)),
p.Price,
rn = ROW_NUMBER() OVER (
PARTITION BY p.ItemCode, LTRIM(RTRIM(p.DocCurrencyCode))
ORDER BY p.ValidDate DESC, p.ValidTime DESC, p.LastUpdatedDate DESC
)
FROM trPriceListLine p WITH(NOLOCK)
INNER JOIN ProductCodes pc ON pc.ItemCode = p.ItemCode
WHERE p.ItemTypeCode = 1
AND ISNULL(p.IsDisabled, 0) = 0
AND LTRIM(RTRIM(p.DocCurrencyCode)) IN ('USD', 'TRY')
AND (
(LTRIM(RTRIM(p.DocCurrencyCode)) = 'USD' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-USD')
OR (LTRIM(RTRIM(p.DocCurrencyCode)) = 'TRY' AND LTRIM(RTRIM(p.PriceGroupCode)) = 'TM-TRY')
)
AND p.Price > 0
),
BasePrice AS (
SELECT
ItemCode,
BasePriceUsd = MAX(CASE WHEN DocCurrencyCode = 'USD' THEN Price END),
BasePriceTry = MAX(CASE WHEN DocCurrencyCode = 'TRY' THEN Price END)
FROM LatestPrice
WHERE rn = 1
GROUP BY ItemCode
),
Cost AS (
SELECT
b.ItemCode,
CostPriceUsd = CAST(b.Price AS decimal(18,2)),
LastPricingDate = CAST(b.PriceDate AS date),
rn = ROW_NUMBER() OVER (
PARTITION BY b.ItemCode
ORDER BY b.PriceDate DESC, b.LastUpdatedDate DESC
)
FROM prItemBasePrice b WITH(NOLOCK)
INNER JOIN ProductCodes pc ON pc.ItemCode = b.ItemCode
WHERE b.ItemTypeCode = 1
AND b.BasePriceCode = 1
AND LTRIM(RTRIM(b.CurrencyCode)) = 'USD'
)
SELECT
pc.ItemCode,
CostPriceUsd = ISNULL(c.CostPriceUsd, 0),
BasePriceUsd = ISNULL(bp.BasePriceUsd, 0),
BasePriceTry = ISNULL(bp.BasePriceTry, 0),
c.LastPricingDate
FROM ProductCodes pc
LEFT JOIN BasePrice bp ON bp.ItemCode = pc.ItemCode
LEFT JOIN Cost c ON c.ItemCode = pc.ItemCode AND c.rn = 1
`
}
func productPerformanceKPISQL() string {
return `
WITH LatestStock AS (
SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
product_code, color_code, yaka_kodu, stock_qty
FROM mk_product_performance_stock_daily
WHERE stock_date <= $1
ORDER BY product_code, color_code, yaka_kodu, stock_date DESC
),
SalesAgg AS (
SELECT
product_code,
color_code,
yaka_kodu,
market_key,
MAX(item_description) AS item_description,
MAX(kategori) AS kategori,
MAX(seri) AS seri,
MAX(yas_grubu) AS yas_grubu,
MAX(askili_yan) AS askili_yan,
MAX(urun_ilk_grubu) AS urun_ilk_grubu,
MAX(urun_ana_grubu) AS urun_ana_grubu,
MAX(urun_alt_grubu) AS urun_alt_grubu,
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 '364 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 '364 days') AS sales_usd_365d,
SUM(customer_count) 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
FROM mk_product_performance_sales_daily
WHERE sales_date >= $1::date - INTERVAL '729 days'
AND sales_date <= $1::date
GROUP BY product_code, color_code, yaka_kodu, market_key
),
Scope AS (
SELECT
product_code, color_code, yaka_kodu, market_key,
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_365d, sales_qty_730d,
sales_usd_30d, sales_usd_90d, sales_usd_365d,
customer_count_90d, last_sale_date, last_ref_number
FROM SalesAgg
UNION ALL
SELECT
ls.product_code,
ls.color_code,
ls.yaka_kodu,
'STOK' AS market_key,
'' AS item_description,
'' AS kategori,
'' AS seri,
'' AS yas_grubu,
'' AS askili_yan,
'' AS urun_ilk_grubu,
'' AS urun_ana_grubu,
'' AS urun_alt_grubu,
0 AS sales_qty_30d,
0 AS sales_qty_90d,
0 AS sales_qty_365d,
0 AS sales_qty_730d,
0 AS sales_usd_30d,
0 AS sales_usd_90d,
0 AS sales_usd_365d,
0 AS customer_count_90d,
NULL::date AS last_sale_date,
'' AS last_ref_number
FROM LatestStock ls
WHERE COALESCE(ls.stock_qty, 0) <> 0
AND NOT EXISTS (
SELECT 1
FROM SalesAgg sa
WHERE sa.product_code = ls.product_code
AND sa.color_code = ls.color_code
AND sa.yaka_kodu = ls.yaka_kodu
)
),
Base AS (
SELECT
s.*,
COALESCE(ls.stock_qty, 0) AS stock_qty,
COALESCE(pd.cost_price_usd, 0) AS cost_price_usd,
COALESCE(pd.base_price_usd, 0) AS base_price_usd,
COALESCE(pd.base_price_try, 0) AS base_price_try,
COALESCE(s.sales_qty_90d, 0) / 90.0 AS avg_daily_sales_90d,
CASE WHEN COALESCE(s.sales_qty_90d, 0) = 0 THEN 0 ELSE COALESCE(s.sales_usd_90d, 0) / NULLIF(s.sales_qty_90d, 0) END AS avg_price_usd_90d,
COALESCE(s.sales_usd_90d, 0) - (COALESCE(s.sales_qty_90d, 0) * COALESCE(pd.cost_price_usd, 0)) AS gross_profit_usd_90d,
CASE WHEN COALESCE(s.sales_usd_90d, 0) = 0 THEN 0
ELSE (COALESCE(s.sales_usd_90d, 0) - (COALESCE(s.sales_qty_90d, 0) * COALESCE(pd.cost_price_usd, 0))) / NULLIF(s.sales_usd_90d, 0)
END AS gross_margin_90d
FROM Scope s
LEFT JOIN LatestStock ls ON ls.product_code=s.product_code AND ls.color_code=s.color_code AND ls.yaka_kodu=s.yaka_kodu
LEFT JOIN mk_product_performance_price_dim pd ON pd.product_code=s.product_code
),
Market AS (
SELECT
market_key,
AVG(NULLIF(sales_qty_90d, 0)) AS market_avg_sales_qty_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
FROM Base
GROUP BY market_key
),
Scored AS (
SELECT
b.*,
CASE WHEN b.avg_daily_sales_90d = 0 THEN 0 ELSE b.stock_qty / NULLIF(b.avg_daily_sales_90d, 0) END AS stock_days_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_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
FROM Base b
LEFT JOIN Market m ON m.market_key=b.market_key
)
INSERT INTO mk_product_performance_kpi_daily (
kpi_date, product_code, color_code, yaka_kodu, item_description,
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_365d, sales_qty_730d,
sales_usd_30d, sales_usd_90d, sales_usd_365d, avg_daily_sales_90d,
stock_days_90d, avg_price_usd_90d, cost_price_usd, base_price_usd, base_price_try,
gross_profit_usd_90d, gross_margin_90d, customer_count_90d,
sales_index_90d, price_index_90d, margin_index_90d, performance_score,
performance_bucket, recommendation, last_sale_date, last_ref_number, updated_at
)
SELECT
$1::date,
product_code, color_code, yaka_kodu, item_description,
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_365d, 0), COALESCE(sales_qty_730d, 0),
COALESCE(sales_usd_30d, 0), COALESCE(sales_usd_90d, 0), COALESCE(sales_usd_365d, 0), COALESCE(avg_daily_sales_90d, 0),
COALESCE(stock_days_90d, 0), COALESCE(avg_price_usd_90d, 0), COALESCE(cost_price_usd, 0), COALESCE(base_price_usd, 0), COALESCE(base_price_try, 0),
COALESCE(gross_profit_usd_90d, 0), COALESCE(gross_margin_90d, 0), COALESCE(customer_count_90d, 0),
COALESCE(sales_index_90d, 0), COALESCE(price_index_90d, 0), COALESCE(margin_index_90d, 0),
ROUND(
LEAST(40, COALESCE(sales_index_90d, 0) * 20)
+ LEAST(25, GREATEST(COALESCE(gross_margin_90d, 0), 0) * 50)
+ LEAST(20, COALESCE(customer_count_90d, 0) * 2)
+ CASE WHEN COALESCE(stock_days_90d, 0) BETWEEN 10 AND 90 THEN 15 WHEN COALESCE(stock_days_90d, 0) > 180 THEN -10 ELSE 0 END
, 4) AS performance_score,
CASE
WHEN COALESCE(sales_index_90d,0) >= 1.25 AND COALESCE(gross_margin_90d,0) >= 0.25 THEN 'YILDIZ_URUN'
WHEN COALESCE(stock_qty,0) <= 0 AND COALESCE(sales_qty_90d,0) > 0 THEN 'STOKSUZ_TALEP'
WHEN COALESCE(stock_days_90d,0) > 180 AND COALESCE(sales_index_90d,0) < 0.75 THEN 'STOK_RISKI'
WHEN COALESCE(price_index_90d,0) > 1.10 AND COALESCE(sales_index_90d,0) < 0.80 THEN 'FIYAT_BASKISI'
WHEN COALESCE(price_index_90d,0) < 0.90 AND COALESCE(sales_index_90d,0) > 1.00 THEN 'FIYAT_FIRSATI'
ELSE 'TAKIP'
END AS performance_bucket,
CASE
WHEN COALESCE(sales_index_90d,0) >= 1.25 AND COALESCE(gross_margin_90d,0) >= 0.25 THEN 'Piyasa üstü satış ve iyi marj: stok ve fiyat gücü takip edilmeli.'
WHEN COALESCE(stock_qty,0) <= 0 AND COALESCE(sales_qty_90d,0) > 0 THEN 'Talep var, stok yok: üretim/satın alma planına alınmalı.'
WHEN COALESCE(stock_days_90d,0) > 180 AND COALESCE(sales_index_90d,0) < 0.75 THEN 'Stok yüksek, satış piyasa altı: kampanya veya fiyat kontrolü gerekli.'
WHEN COALESCE(price_index_90d,0) > 1.10 AND COALESCE(sales_index_90d,0) < 0.80 THEN 'Fiyat piyasa üstünde ve satış zayıf: fiyat revizyonu değerlendirilmeli.'
WHEN COALESCE(price_index_90d,0) < 0.90 AND COALESCE(sales_index_90d,0) > 1.00 THEN 'Satış güçlü, fiyat piyasa altı: taban fiyat artışı değerlendirilebilir.'
ELSE 'Düzenli takip.'
END AS recommendation,
last_sale_date,
COALESCE(last_ref_number, ''),
now()
FROM Scored
`
}