product performance grouped kpi improvements

This commit is contained in:
M_Kececi
2026-07-03 04:27:42 +03:00
parent 60eef45769
commit 5288f19976
3 changed files with 217 additions and 41 deletions
+79 -30
View File
@@ -349,7 +349,12 @@ CREATE TABLE IF NOT EXISTS mk_product_performance_sales_daily (
` `
UPDATE mk_product_performance_sales_daily UPDATE mk_product_performance_sales_daily
SET urun_ilk_grubu = '', updated_at = now() SET urun_ilk_grubu = '', updated_at = now()
WHERE upper(translate(btrim(urun_ilk_grubu), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON')`, WHERE btrim(urun_ilk_grubu) = '-'
OR upper(translate(btrim(urun_ilk_grubu), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON')`,
`
UPDATE mk_product_performance_sales_daily
SET askili_yan = '', updated_at = now()
WHERE btrim(askili_yan) = '-'`,
` `
DO $$ DO $$
BEGIN BEGIN
@@ -464,7 +469,12 @@ CREATE TABLE IF NOT EXISTS mk_product_performance_kpi_daily (
` `
UPDATE mk_product_performance_kpi_daily UPDATE mk_product_performance_kpi_daily
SET urun_ilk_grubu = '', updated_at = now() SET urun_ilk_grubu = '', updated_at = now()
WHERE upper(translate(btrim(urun_ilk_grubu), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON')`, WHERE btrim(urun_ilk_grubu) = '-'
OR upper(translate(btrim(urun_ilk_grubu), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON')`,
`
UPDATE mk_product_performance_kpi_daily
SET askili_yan = '', updated_at = now()
WHERE btrim(askili_yan) = '-'`,
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS sales_qty_180d NUMERIC(18,4) NOT NULL DEFAULT 0`, `ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS sales_qty_180d NUMERIC(18,4) NOT NULL DEFAULT 0`,
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS sales_usd_180d NUMERIC(18,4) NOT NULL DEFAULT 0`, `ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS sales_usd_180d NUMERIC(18,4) NOT NULL DEFAULT 0`,
`ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_daily_sales_180d NUMERIC(18,6) NOT NULL DEFAULT 0`, `ALTER TABLE mk_product_performance_kpi_daily ADD COLUMN IF NOT EXISTS avg_daily_sales_180d NUMERIC(18,6) NOT NULL DEFAULT 0`,
@@ -675,6 +685,7 @@ func refreshProductPerformanceSales(ctx context.Context, tx *sql.Tx, startDate,
); err != nil { ); err != nil {
return count, err return count, err
} }
r.AskiliYan = cleanProductPerformanceOptionalAttr(r.AskiliYan)
r.UrunIlkGrubu = cleanProductPerformanceFirstGroup(r.UrunIlkGrubu) r.UrunIlkGrubu = cleanProductPerformanceFirstGroup(r.UrunIlkGrubu)
if _, err := tx.ExecContext(ctx, ` if _, err := tx.ExecContext(ctx, `
INSERT INTO mk_product_performance_sales_daily ( INSERT INTO mk_product_performance_sales_daily (
@@ -1112,7 +1123,7 @@ SalesAgg AS (
MAX(kategori) AS kategori, MAX(kategori) AS kategori,
MAX(seri) AS seri, MAX(seri) AS seri,
MAX(yas_grubu) AS yas_grubu, MAX(yas_grubu) AS yas_grubu,
MAX(askili_yan) AS askili_yan, COALESCE(MAX(NULLIF(askili_yan, '-')), '') AS askili_yan,
MAX(urun_ilk_grubu) AS urun_ilk_grubu, MAX(urun_ilk_grubu) AS urun_ilk_grubu,
MAX(urun_ana_grubu) AS urun_ana_grubu, MAX(urun_ana_grubu) AS urun_ana_grubu,
MAX(urun_alt_grubu) AS urun_alt_grubu, MAX(urun_alt_grubu) AS urun_alt_grubu,
@@ -1146,7 +1157,7 @@ Dim AS (
COALESCE(kategori,'') AS kategori, COALESCE(kategori,'') AS kategori,
seri, seri,
yas_grubu, yas_grubu,
askili_yan, CASE WHEN btrim(COALESCE(askili_yan,'')) = '-' THEN '' ELSE COALESCE(askili_yan,'') END AS askili_yan,
urun_ilk_grubu, urun_ilk_grubu,
urun_ana_grubu, urun_ana_grubu,
urun_alt_grubu urun_alt_grubu
@@ -1175,7 +1186,7 @@ Dim AS (
MAX(kategori) AS kategori, MAX(kategori) AS kategori,
MAX(seri) AS seri, MAX(seri) AS seri,
MAX(yas_grubu) AS yas_grubu, MAX(yas_grubu) AS yas_grubu,
MAX(askili_yan) AS askili_yan, COALESCE(MAX(NULLIF(askili_yan, '-')), '') AS askili_yan,
MAX(urun_ilk_grubu) AS urun_ilk_grubu, MAX(urun_ilk_grubu) AS urun_ilk_grubu,
MAX(urun_ana_grubu) AS urun_ana_grubu, MAX(urun_ana_grubu) AS urun_ana_grubu,
MAX(urun_alt_grubu) AS urun_alt_grubu, MAX(urun_alt_grubu) AS urun_alt_grubu,
@@ -1195,7 +1206,7 @@ StockOnly AS (
COALESCE(MAX(d.kategori),'') AS kategori, COALESCE(MAX(d.kategori),'') AS kategori,
COALESCE(MAX(d.seri),'') AS seri, COALESCE(MAX(d.seri),'') AS seri,
COALESCE(MAX(d.yas_grubu),'') AS yas_grubu, COALESCE(MAX(d.yas_grubu),'') AS yas_grubu,
COALESCE(MAX(d.askili_yan),'') AS askili_yan, COALESCE(MAX(NULLIF(d.askili_yan, '-')),'') AS askili_yan,
COALESCE(MAX(d.urun_ilk_grubu),'') AS urun_ilk_grubu, COALESCE(MAX(d.urun_ilk_grubu),'') AS urun_ilk_grubu,
COALESCE(MAX(d.urun_ana_grubu),'') AS urun_ana_grubu, COALESCE(MAX(d.urun_ana_grubu),'') AS urun_ana_grubu,
COALESCE(MAX(d.urun_alt_grubu),'') AS urun_alt_grubu, COALESCE(MAX(d.urun_alt_grubu),'') AS urun_alt_grubu,
@@ -1542,7 +1553,7 @@ SELECT TOP (@p1)
Kategori = MAX(o.Kategori), Kategori = MAX(o.Kategori),
Seri = MAX(o.Seri), Seri = MAX(o.Seri),
YasGrubu = MAX(o.YasGrubu), YasGrubu = MAX(o.YasGrubu),
AskiliYan = MAX(o.AskiliYan), AskiliYan = ISNULL(NULLIF(MAX(o.AskiliYan), '-'), ''),
UrunIlkGrubu = MAX(o.UrunIlkGrubu), UrunIlkGrubu = MAX(o.UrunIlkGrubu),
UrunAnaGrubu = MAX(o.UrunAnaGrubu), UrunAnaGrubu = MAX(o.UrunAnaGrubu),
UrunAltGrubu = MAX(o.UrunAltGrubu), UrunAltGrubu = MAX(o.UrunAltGrubu),
@@ -1585,6 +1596,7 @@ ORDER BY SUM(o.AmountUSD) DESC, SUM(o.Qty) DESC, o.ProductCode, o.ColorCode, o.Y
); err != nil { ); err != nil {
return nil, err return nil, err
} }
r.AskiliYan = cleanProductPerformanceOptionalAttr(r.AskiliYan)
out = append(out, r) out = append(out, r)
if !seenProducts[r.ProductCode] { if !seenProducts[r.ProductCode] {
seenProducts[r.ProductCode] = true seenProducts[r.ProductCode] = true
@@ -1622,8 +1634,9 @@ ORDER BY SUM(o.AmountUSD) DESC, SUM(o.Qty) DESC, o.ProductCode, o.ColorCode, o.Y
if strings.TrimSpace(row.Kategori) == "" { if strings.TrimSpace(row.Kategori) == "" {
row.Kategori = attr.kategori row.Kategori = attr.kategori
} }
row.AskiliYan = cleanProductPerformanceOptionalAttr(row.AskiliYan)
if strings.TrimSpace(row.AskiliYan) == "" { if strings.TrimSpace(row.AskiliYan) == "" {
row.AskiliYan = attr.askiliYan row.AskiliYan = cleanProductPerformanceOptionalAttr(attr.askiliYan)
} }
if strings.TrimSpace(row.UrunIlkGrubu) == "" { if strings.TrimSpace(row.UrunIlkGrubu) == "" {
row.UrunIlkGrubu = attr.urunIlkGrubu row.UrunIlkGrubu = attr.urunIlkGrubu
@@ -2003,7 +2016,7 @@ ORDER BY SUM(AmountUSD) DESC, SUM(Qty) DESC
row.ItemDescription = attr.itemDescription row.ItemDescription = attr.itemDescription
} }
row.Kategori = attr.kategori row.Kategori = attr.kategori
row.AskiliYan = attr.askiliYan row.AskiliYan = cleanProductPerformanceOptionalAttr(attr.askiliYan)
row.UrunIlkGrubu = attr.urunIlkGrubu row.UrunIlkGrubu = attr.urunIlkGrubu
row.UrunAnaGrubu = attr.urunAnaGrubu row.UrunAnaGrubu = attr.urunAnaGrubu
row.UrunAltGrubu = attr.urunAltGrubu row.UrunAltGrubu = attr.urunAltGrubu
@@ -2147,7 +2160,7 @@ SELECT
MAX(kategori) AS kategori, MAX(kategori) AS kategori,
MAX(seri) AS seri, MAX(seri) AS seri,
MAX(yas_grubu) AS yas_grubu, MAX(yas_grubu) AS yas_grubu,
MAX(askili_yan) AS askili_yan, COALESCE(MAX(NULLIF(askili_yan, '-')), '') AS askili_yan,
MAX(urun_ilk_grubu) AS urun_ilk_grubu, MAX(urun_ilk_grubu) AS urun_ilk_grubu,
MAX(urun_ana_grubu) AS urun_ana_grubu, MAX(urun_ana_grubu) AS urun_ana_grubu,
MAX(urun_alt_grubu) AS urun_alt_grubu, MAX(urun_alt_grubu) AS urun_alt_grubu,
@@ -2335,8 +2348,8 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
"yaka_kodu": "s.yaka_kodu", "yaka_kodu": "s.yaka_kodu",
"item_description": "MAX(s.item_description)", "item_description": "MAX(s.item_description)",
"kategori": "COALESCE(MAX(s.kategori),'')", "kategori": "COALESCE(MAX(s.kategori),'')",
"askili_yan": "MAX(s.askili_yan)", "askili_yan": "COALESCE(MAX(CASE WHEN btrim(COALESCE(s.askili_yan,'')) = '-' THEN '' ELSE s.askili_yan END),'')",
"urun_ilk_grubu": "COALESCE(MAX(CASE WHEN upper(translate(btrim(COALESCE(s.urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' ELSE s.urun_ilk_grubu END),'')", "urun_ilk_grubu": "COALESCE(MAX(CASE WHEN btrim(COALESCE(s.urun_ilk_grubu,'')) = '-' THEN '' WHEN upper(translate(btrim(COALESCE(s.urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' ELSE s.urun_ilk_grubu END),'')",
"urun_ana_grubu": "COALESCE(MAX(s.urun_ana_grubu),'')", "urun_ana_grubu": "COALESCE(MAX(s.urun_ana_grubu),'')",
"urun_alt_grubu": "MAX(s.urun_alt_grubu)", "urun_alt_grubu": "MAX(s.urun_alt_grubu)",
"market_key": "s.market_key", "market_key": "s.market_key",
@@ -2363,7 +2376,7 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
selected["market_key"] = true selected["market_key"] = true
selected["customer_code"] = true selected["customer_code"] = true
selected["customer_name"] = true selected["customer_name"] = true
groupCols = []string{"CASE WHEN upper(translate(btrim(COALESCE(s.urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' ELSE s.urun_ilk_grubu END", "s.color_code", "s.yaka_kodu", "s.askili_yan", "s.kategori", "s.urun_ana_grubu", "s.urun_alt_grubu", "s.product_code", "s.customer_country", "s.market_key", "s.customer_segment", "s.customer_code"} groupCols = []string{"CASE WHEN btrim(COALESCE(s.urun_ilk_grubu,'')) = '-' THEN '' WHEN upper(translate(btrim(COALESCE(s.urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' ELSE s.urun_ilk_grubu END", "s.color_code", "s.yaka_kodu", "CASE WHEN btrim(COALESCE(s.askili_yan,'')) = '-' THEN '' ELSE s.askili_yan END", "s.kategori", "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": case "product_country_segment_market_customer":
selected["product_code"] = true selected["product_code"] = true
selected["item_description"] = true selected["item_description"] = true
@@ -2426,7 +2439,7 @@ func ListProductPerformanceSalesBreakdown(ctx context.Context, pg *sql.DB, break
selected["market_key"] = true selected["market_key"] = true
selected["customer_code"] = true selected["customer_code"] = true
selected["customer_name"] = true selected["customer_name"] = true
groupCols = []string{"CASE WHEN upper(translate(btrim(COALESCE(s.urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' ELSE s.urun_ilk_grubu END", "s.color_code", "s.yaka_kodu", "s.askili_yan", "s.kategori", "s.urun_ana_grubu", "s.urun_alt_grubu", "s.product_code", "s.customer_country", "s.market_key", "s.customer_segment", "s.customer_code"} groupCols = []string{"CASE WHEN btrim(COALESCE(s.urun_ilk_grubu,'')) = '-' THEN '' WHEN upper(translate(btrim(COALESCE(s.urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' ELSE s.urun_ilk_grubu END", "s.color_code", "s.yaka_kodu", "CASE WHEN btrim(COALESCE(s.askili_yan,'')) = '-' THEN '' ELSE s.askili_yan END", "s.kategori", "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 { selectExpr := func(name string) string {
@@ -2443,8 +2456,9 @@ WITH LatestKPI AS (
color_code, color_code,
yaka_kodu, yaka_kodu,
COALESCE(kategori,'') AS kategori, COALESCE(kategori,'') AS kategori,
askili_yan, CASE WHEN btrim(COALESCE(askili_yan,'')) = '-' THEN '' ELSE COALESCE(askili_yan,'') END AS askili_yan,
CASE CASE
WHEN btrim(COALESCE(urun_ilk_grubu,'')) = '-' THEN ''
WHEN upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' WHEN upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN ''
ELSE COALESCE(urun_ilk_grubu,'') ELSE COALESCE(urun_ilk_grubu,'')
END AS urun_ilk_grubu, END AS urun_ilk_grubu,
@@ -2799,7 +2813,7 @@ func ListProductPerformanceGrouped(ctx context.Context, pg *sql.DB, req ProductP
return nil, err return nil, err
} }
out := make([]map[string]any, 0, len(sourceRows)) out := make([]map[string]any, 0, len(sourceRows))
appendProductPerformanceGroupedRows(&out, sourceRows, levels, 0, []string{"tab:" + req.Mode}, req.ExpandedKeys) appendProductPerformanceGroupedRows(&out, sourceRows, levels, 0, 0, []string{"tab:" + req.Mode}, req.ExpandedKeys)
return out, nil return out, nil
} }
@@ -2817,11 +2831,11 @@ func listProductPerformanceGroupedSQL(ctx context.Context, pg *sql.DB, req Produ
return nil, true, err return nil, true, err
} }
out := make([]map[string]any, 0, 512) out := make([]map[string]any, 0, 512)
err := appendProductPerformanceGroupedSQLRows(ctx, pg, &out, mode, levels, 0, []string{"tab:" + mode}, nil, req.ExpandedKeys, req.Limit) err := appendProductPerformanceGroupedSQLRows(ctx, pg, &out, mode, levels, 0, 0, []string{"tab:" + mode}, nil, req.ExpandedKeys, req.Limit)
return out, true, err return out, true, err
} }
func appendProductPerformanceGroupedSQLRows(ctx context.Context, pg *sql.DB, out *[]map[string]any, mode string, levels []string, level int, parentKeys []string, filters []productPerformanceSQLGroupFilter, expandedKeys map[string]bool, limit int) error { func appendProductPerformanceGroupedSQLRows(ctx context.Context, pg *sql.DB, out *[]map[string]any, mode string, levels []string, level int, visualLevel int, parentKeys []string, filters []productPerformanceSQLGroupFilter, expandedKeys map[string]bool, limit int) error {
if level >= len(levels) { if level >= len(levels) {
rows, err := queryProductPerformanceSQLLeafRows(ctx, pg, mode, filters, limit) rows, err := queryProductPerformanceSQLLeafRows(ctx, pg, mode, filters, limit)
if err != nil { if err != nil {
@@ -2838,11 +2852,18 @@ func appendProductPerformanceGroupedSQLRows(ctx context.Context, pg *sql.DB, out
} }
for _, row := range rows { for _, row := range rows {
value := stringFromMap(row, "group_value") value := stringFromMap(row, "group_value")
if shouldSkipProductPerformanceGroupValue(field, value) {
nextFilters := append(append([]productPerformanceSQLGroupFilter{}, filters...), productPerformanceSQLGroupFilter{Field: field, Value: value})
if err := appendProductPerformanceGroupedSQLRows(ctx, pg, out, mode, levels, level+1, visualLevel, parentKeys, nextFilters, expandedKeys, limit); err != nil {
return err
}
continue
}
keyPart := field + ":" + value keyPart := field + ":" + value
key := strings.Join(append(parentKeys, keyPart), "|") key := strings.Join(append(parentKeys, keyPart), "|")
row["row_key"] = "group|" + key row["row_key"] = "group|" + key
row["key"] = key row["key"] = key
row["level"] = level row["level"] = visualLevel
row["group_field"] = field row["group_field"] = field
row["group_value"] = value row["group_value"] = value
row["label"] = value row["label"] = value
@@ -2851,7 +2872,7 @@ func appendProductPerformanceGroupedSQLRows(ctx context.Context, pg *sql.DB, out
*out = append(*out, row) *out = append(*out, row)
if expandedKeys[key] { if expandedKeys[key] {
nextFilters := append(append([]productPerformanceSQLGroupFilter{}, filters...), productPerformanceSQLGroupFilter{Field: field, Value: value}) nextFilters := append(append([]productPerformanceSQLGroupFilter{}, filters...), productPerformanceSQLGroupFilter{Field: field, Value: value})
if err := appendProductPerformanceGroupedSQLRows(ctx, pg, out, mode, levels, level+1, append(parentKeys, keyPart), nextFilters, expandedKeys, limit); err != nil { if err := appendProductPerformanceGroupedSQLRows(ctx, pg, out, mode, levels, level+1, visualLevel+1, append(parentKeys, keyPart), nextFilters, expandedKeys, limit); err != nil {
return err return err
} }
} }
@@ -2931,7 +2952,7 @@ FROM (
COALESCE((ARRAY_AGG(NULLIF(yaka_kodu,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS yaka_kodu, COALESCE((ARRAY_AGG(NULLIF(yaka_kodu,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS yaka_kodu,
COALESCE((ARRAY_AGG(NULLIF(item_description,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS item_description, COALESCE((ARRAY_AGG(NULLIF(item_description,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS item_description,
COALESCE((ARRAY_AGG(NULLIF(kategori,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS kategori, COALESCE((ARRAY_AGG(NULLIF(kategori,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS kategori,
COALESCE((ARRAY_AGG(NULLIF(askili_yan,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS askili_yan, COALESCE((ARRAY_AGG(NULLIF(NULLIF(askili_yan,''), '-') ORDER BY performance_score DESC NULLS LAST))[1], '') AS askili_yan,
COALESCE((ARRAY_AGG(NULLIF(urun_ilk_grubu,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS urun_ilk_grubu, COALESCE((ARRAY_AGG(NULLIF(urun_ilk_grubu,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS urun_ilk_grubu,
COALESCE((ARRAY_AGG(NULLIF(urun_ana_grubu,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS urun_ana_grubu, COALESCE((ARRAY_AGG(NULLIF(urun_ana_grubu,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS urun_ana_grubu,
COALESCE((ARRAY_AGG(NULLIF(urun_alt_grubu,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS urun_alt_grubu, COALESCE((ARRAY_AGG(NULLIF(urun_alt_grubu,'') ORDER BY performance_score DESC NULLS LAST))[1], '') AS urun_alt_grubu,
@@ -3041,8 +3062,9 @@ WITH Source AS (
kategori, kategori,
seri, seri,
yas_grubu, yas_grubu,
askili_yan, CASE WHEN btrim(COALESCE(askili_yan,'')) = '-' THEN '' ELSE COALESCE(askili_yan,'') END AS askili_yan,
CASE CASE
WHEN btrim(COALESCE(urun_ilk_grubu,'')) = '-' THEN ''
WHEN upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' WHEN upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN ''
ELSE COALESCE(urun_ilk_grubu,'') ELSE COALESCE(urun_ilk_grubu,'')
END AS urun_ilk_grubu, END AS urun_ilk_grubu,
@@ -3085,8 +3107,10 @@ WITH Source AS (
func productPerformanceSQLGroupExpr(field string) (string, bool) { func productPerformanceSQLGroupExpr(field string) (string, bool) {
switch field { switch field {
case "urun_ilk_grubu": case "urun_ilk_grubu":
return "CASE WHEN upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' ELSE COALESCE(urun_ilk_grubu,'') END", true return "CASE WHEN btrim(COALESCE(urun_ilk_grubu,'')) = '-' THEN '' WHEN upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), 'İŞĞÜÖÇ', 'ISGUOC')) IN ('YETISKIN', 'YETISKIN/GARSON', 'GARSON') THEN '' ELSE COALESCE(urun_ilk_grubu,'') END", true
case "kategori", "askili_yan", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu": case "askili_yan":
return "CASE WHEN btrim(COALESCE(askili_yan,'')) = '-' THEN '' ELSE COALESCE(askili_yan,'') END", true
case "kategori", "urun_ana_grubu", "urun_alt_grubu", "product_code", "item_description", "color_code", "yaka_kodu":
return field, true return field, true
case "market_key": case "market_key":
return "btrim(regexp_replace(COALESCE(market_key,''), '^.*\\|', ''))", true return "btrim(regexp_replace(COALESCE(market_key,''), '^.*\\|', ''))", true
@@ -3095,6 +3119,15 @@ func productPerformanceSQLGroupExpr(field string) (string, bool) {
} }
} }
func shouldSkipProductPerformanceGroupValue(field, value string) bool {
switch field {
case "urun_ilk_grubu", "askili_yan":
return strings.TrimSpace(value) == ""
default:
return false
}
}
func productPerformanceSQLFilterWhere(filters []productPerformanceSQLGroupFilter) (string, []any, error) { func productPerformanceSQLFilterWhere(filters []productPerformanceSQLGroupFilter) (string, []any, error) {
if len(filters) == 0 { if len(filters) == 0 {
return "", nil, nil return "", nil, nil
@@ -3172,7 +3205,7 @@ func productPerformanceIdleSourceRows(rows []map[string]any) []map[string]any {
return out return out
} }
func appendProductPerformanceGroupedRows(out *[]map[string]any, sourceRows []map[string]any, levels []string, level int, parentKeys []string, expandedKeys map[string]bool) { func appendProductPerformanceGroupedRows(out *[]map[string]any, sourceRows []map[string]any, levels []string, level int, visualLevel int, parentKeys []string, expandedKeys map[string]bool) {
if level >= len(levels) { if level >= len(levels) {
*out = append(*out, sourceRows...) *out = append(*out, sourceRows...)
return return
@@ -3194,12 +3227,16 @@ func appendProductPerformanceGroupedRows(out *[]map[string]any, sourceRows []map
}) })
for _, value := range values { for _, value := range values {
groupRows := grouped[value]
if shouldSkipProductPerformanceGroupValue(field, value) {
appendProductPerformanceGroupedRows(out, groupRows, levels, level+1, visualLevel, parentKeys, expandedKeys)
continue
}
keyPart := field + ":" + value keyPart := field + ":" + value
key := strings.Join(append(parentKeys, keyPart), "|") key := strings.Join(append(parentKeys, keyPart), "|")
groupRows := grouped[value] *out = append(*out, makeProductPerformanceGroupedRow(key, visualLevel, field, value, groupRows))
*out = append(*out, makeProductPerformanceGroupedRow(key, level, field, value, groupRows))
if expandedKeys[key] { if expandedKeys[key] {
appendProductPerformanceGroupedRows(out, groupRows, levels, level+1, append(parentKeys, keyPart), expandedKeys) appendProductPerformanceGroupedRows(out, groupRows, levels, level+1, visualLevel+1, append(parentKeys, keyPart), expandedKeys)
} }
} }
} }
@@ -3668,6 +3705,9 @@ func stringFromMap(row map[string]any, field string) string {
func cleanProductPerformanceFirstGroup(value string) string { func cleanProductPerformanceFirstGroup(value string) string {
value = strings.TrimSpace(value) value = strings.TrimSpace(value)
if value == "-" {
return ""
}
normalized := strings.ToUpper(value) normalized := strings.ToUpper(value)
normalized = strings.NewReplacer("İ", "I", "Ş", "S", "Ğ", "G", "Ü", "U", "Ö", "O", "Ç", "C").Replace(normalized) normalized = strings.NewReplacer("İ", "I", "Ş", "S", "Ğ", "G", "Ü", "U", "Ö", "O", "Ç", "C").Replace(normalized)
switch normalized { switch normalized {
@@ -3678,6 +3718,14 @@ func cleanProductPerformanceFirstGroup(value string) string {
} }
} }
func cleanProductPerformanceOptionalAttr(value string) string {
value = strings.TrimSpace(value)
if value == "-" {
return ""
}
return value
}
func floatFromMap(row map[string]any, field string) float64 { func floatFromMap(row map[string]any, field string) float64 {
return floatFromAny(row[field]) return floatFromAny(row[field])
} }
@@ -3720,7 +3768,7 @@ FROM mk_product_performance_sales_daily
WHERE product_code=$1 WHERE product_code=$1
AND color_code=$2 AND color_code=$2
AND yaka_kodu=$3 AND yaka_kodu=$3
ORDER BY sales_date DESC, sales_usd DESC ORDER BY sales_usd DESC, sales_date DESC
LIMIT $4 LIMIT $4
`, strings.TrimSpace(productCode), strings.TrimSpace(colorCode), strings.TrimSpace(yakaKodu), limit) `, strings.TrimSpace(productCode), strings.TrimSpace(colorCode), strings.TrimSpace(yakaKodu), limit)
if err != nil { if err != nil {
@@ -3982,7 +4030,7 @@ SELECT DISTINCT ON (product_code, color_code, yaka_kodu)
yaka_kodu, yaka_kodu,
COALESCE(item_description,'') AS item_description, COALESCE(item_description,'') AS item_description,
COALESCE(kategori,'') AS kategori, COALESCE(kategori,'') AS kategori,
COALESCE(askili_yan,'') AS askili_yan, CASE WHEN btrim(COALESCE(askili_yan,'')) = '-' THEN '' ELSE COALESCE(askili_yan,'') END AS askili_yan,
COALESCE(urun_ilk_grubu,'') AS urun_ilk_grubu, COALESCE(urun_ilk_grubu,'') AS urun_ilk_grubu,
COALESCE(urun_ana_grubu,'') AS urun_ana_grubu, COALESCE(urun_ana_grubu,'') AS urun_ana_grubu,
COALESCE(urun_alt_grubu,'') AS urun_alt_grubu COALESCE(urun_alt_grubu,'') AS urun_alt_grubu
@@ -4001,6 +4049,7 @@ ORDER BY product_code, color_code, yaka_kodu, performance_score DESC
if err := rows.Scan(&productCode, &colorCode, &yakaKodu, &p.itemDescription, &p.kategori, &p.askiliYan, &p.urunIlkGrubu, &p.urunAnaGrubu, &p.urunAltGrubu); err != nil { if err := rows.Scan(&productCode, &colorCode, &yakaKodu, &p.itemDescription, &p.kategori, &p.askiliYan, &p.urunIlkGrubu, &p.urunAnaGrubu, &p.urunAltGrubu); err != nil {
return nil, err return nil, err
} }
p.askiliYan = cleanProductPerformanceOptionalAttr(p.askiliYan)
p.urunIlkGrubu = cleanProductPerformanceFirstGroup(p.urunIlkGrubu) p.urunIlkGrubu = cleanProductPerformanceFirstGroup(p.urunIlkGrubu)
out[productPerformanceVariantKey(productCode, colorCode, yakaKodu)] = p out[productPerformanceVariantKey(productCode, colorCode, yakaKodu)] = p
} }
+3 -3
View File
@@ -196,11 +196,11 @@ SELECT
Kategori = MAX(Kategori), Kategori = MAX(Kategori),
Seri = '', Seri = '',
YasGrubu = '', YasGrubu = '',
AskiliYan = MAX(AskiliYan), AskiliYan = ISNULL(NULLIF(MAX(AskiliYan), '-'), ''),
UrunIlkGrubu = MAX(UrunIlkGrubu), UrunIlkGrubu = MAX(UrunIlkGrubu),
UrunAnaGrubu = MAX(UrunAnaGrubu), UrunAnaGrubu = MAX(UrunAnaGrubu),
UrunAltGrubu = MAX(UrunAltGrubu), UrunAltGrubu = MAX(UrunAltGrubu),
MarketKey = CONCAT(MAX(Kategori), '|', MAX(UrunIlkGrubu), '|', MAX(AskiliYan), '|', MAX(UrunAnaGrubu), '|', MAX(UrunAltGrubu), '|', ChannelCode), MarketKey = CONCAT(MAX(Kategori), '|', MAX(UrunIlkGrubu), '|', ISNULL(NULLIF(MAX(AskiliYan), '-'), ''), '|', MAX(UrunAnaGrubu), '|', MAX(UrunAltGrubu), '|', ChannelCode),
ChannelCode, ChannelCode,
CustomerCountry = ISNULL(NULLIF(CustomerCountry, ''), '-'), CustomerCountry = ISNULL(NULLIF(CustomerCountry, ''), '-'),
CustomerSegment = ISNULL(NULLIF(CustomerSegment, ''), '-'), CustomerSegment = ISNULL(NULLIF(CustomerSegment, ''), '-'),
@@ -489,7 +489,7 @@ SalesAgg AS (
MAX(kategori) AS kategori, MAX(kategori) AS kategori,
MAX(seri) AS seri, MAX(seri) AS seri,
MAX(yas_grubu) AS yas_grubu, MAX(yas_grubu) AS yas_grubu,
MAX(askili_yan) AS askili_yan, COALESCE(MAX(NULLIF(askili_yan, '-')), '') AS askili_yan,
MAX(urun_ilk_grubu) AS urun_ilk_grubu, MAX(urun_ilk_grubu) AS urun_ilk_grubu,
MAX(urun_ana_grubu) AS urun_ana_grubu, MAX(urun_ana_grubu) AS urun_ana_grubu,
MAX(urun_alt_grubu) AS urun_alt_grubu, MAX(urun_alt_grubu) AS urun_alt_grubu,
@@ -1390,12 +1390,13 @@
flat flat
bordered bordered
dense dense
class="dialog-detail-table"
title="Satış / Müşteri Özeti" title="Satış / Müşteri Özeti"
row-key="detail_key" row-key="detail_key"
:rows="detailSalesRows" :rows="detailSalesRows"
:columns="detailSalesColumns" :columns="detailSalesColumns"
:loading="detailLoading" :loading="detailLoading"
:pagination="{ rowsPerPage: 50 }" :pagination="{ rowsPerPage: 0, sortBy: 'sales_usd', descending: true }"
/> />
</section> </section>
</div> </div>
@@ -1431,12 +1432,13 @@
flat flat
bordered bordered
dense dense
class="dialog-stock-table"
title="Beden Stok" title="Beden Stok"
row-key="size_code" row-key="size_code"
:rows="detailStockSizes" :rows="detailStockSizes"
:columns="detailStockColumns" :columns="detailStockColumns"
:loading="detailLoading" :loading="detailLoading"
:pagination="{ rowsPerPage: 50 }" :pagination="{ rowsPerPage: 0 }"
virtual-scroll virtual-scroll
:virtual-scroll-item-size="48" :virtual-scroll-item-size="48"
/> />
@@ -1446,12 +1448,13 @@
flat flat
bordered bordered
dense dense
class="dialog-detail-table"
title="Satış / Müşteri Özeti" title="Satış / Müşteri Özeti"
row-key="detail_key" row-key="detail_key"
:rows="detailSalesRows" :rows="detailSalesRows"
:columns="detailSalesColumns" :columns="detailSalesColumns"
:loading="detailLoading" :loading="detailLoading"
:pagination="{ rowsPerPage: 50 }" :pagination="{ rowsPerPage: 0, sortBy: 'sales_usd', descending: true }"
virtual-scroll virtual-scroll
:virtual-scroll-item-size="48" :virtual-scroll-item-size="48"
/> />
@@ -2304,7 +2307,7 @@ const imageDialogInfoRows = computed(() => {
{ key: 'color', label: 'Renk', value: colorCode || '-' }, { key: 'color', label: 'Renk', value: colorCode || '-' },
{ key: 'yaka', label: 'Yaka', value: yakaCode || '-' }, { key: 'yaka', label: 'Yaka', value: yakaCode || '-' },
{ key: 'category', label: 'Kategori', value: productCategoryValue(row) || '-' }, { key: 'category', label: 'Kategori', value: productCategoryValue(row) || '-' },
{ key: 'hanger', label: 'Askılı/Yan', value: row.askili_yan || '-' }, { key: 'hanger', label: 'Askılı/Yan', value: cleanOptionalGroupValue(row.askili_yan) },
{ key: 'first', label: 'İlk Grup', value: productFirstGroupValue(row) || '-' }, { key: 'first', label: 'İlk Grup', value: productFirstGroupValue(row) || '-' },
{ key: 'main', label: 'Ana Grup', value: productMainGroupValue(row) || '-' }, { key: 'main', label: 'Ana Grup', value: productMainGroupValue(row) || '-' },
{ key: 'sub', label: 'Alt Grup', value: row.urun_alt_grubu || '-' }, { key: 'sub', label: 'Alt Grup', value: row.urun_alt_grubu || '-' },
@@ -2539,6 +2542,10 @@ const allProductGroupsExpanded = computed(() => {
}) })
function appendGroupRows (out, sourceRows, level, parentKeys, levels = groupLevels) { function appendGroupRows (out, sourceRows, level, parentKeys, levels = groupLevels) {
appendGroupRowsAt(out, sourceRows, level, 0, parentKeys, levels)
}
function appendGroupRowsAt (out, sourceRows, level, visualLevel, parentKeys, levels = groupLevels) {
if (level >= levels.length) { if (level >= levels.length) {
out.push(...sortProductLeafRows(sourceRows)) out.push(...sortProductLeafRows(sourceRows))
return return
@@ -2555,10 +2562,14 @@ function appendGroupRows (out, sourceRows, level, parentKeys, levels = groupLeve
Array.from(grouped.entries()) Array.from(grouped.entries())
.sort((a, b) => a[0].localeCompare(b[0], 'tr')) .sort((a, b) => a[0].localeCompare(b[0], 'tr'))
.forEach(([value, groupRows]) => { .forEach(([value, groupRows]) => {
if (shouldSkipGroupValue(groupDef.key, value)) {
appendGroupRowsAt(out, groupRows, level + 1, visualLevel, parentKeys, levels)
return
}
const key = [...parentKeys, `${groupDef.key}:${value}`].join('|') const key = [...parentKeys, `${groupDef.key}:${value}`].join('|')
out.push(makeGroupRow(key, level, groupDef, value, groupRows)) out.push(makeGroupRow(key, visualLevel, groupDef, value, groupRows))
if (isGroupExpanded(key)) { if (isGroupExpanded(key)) {
appendGroupRows(out, groupRows, level + 1, [...parentKeys, `${groupDef.key}:${value}`], levels) appendGroupRowsAt(out, groupRows, level + 1, visualLevel + 1, [...parentKeys, `${groupDef.key}:${value}`], levels)
} }
}) })
} }
@@ -2592,6 +2603,10 @@ function collectGroupKeys (out, sourceRows, level, parentKeys, levels = groupLev
.sort((a, b) => a[0].localeCompare(b[0], 'tr')) .sort((a, b) => a[0].localeCompare(b[0], 'tr'))
.forEach(([value, groupRows]) => { .forEach(([value, groupRows]) => {
if (out.length >= maxBulkExpandKeys) return if (out.length >= maxBulkExpandKeys) return
if (shouldSkipGroupValue(groupDef.key, value)) {
collectGroupKeys(out, groupRows, level + 1, parentKeys, levels, maxLevel)
return
}
const key = [...parentKeys, `${groupDef.key}:${value}`].join('|') const key = [...parentKeys, `${groupDef.key}:${value}`].join('|')
out.push(key) out.push(key)
if (level < maxLevel) { if (level < maxLevel) {
@@ -3001,6 +3016,13 @@ function normalizeGroupValue (value) {
return String(value || '').trim() return String(value || '').trim()
} }
function shouldSkipGroupValue (field, value) {
const text = String(value || '').trim()
if (field === 'urun_ilk_grubu') return !text || text === '-'
if (field === 'askili_yan') return !text || text === '-'
return false
}
function isGroupExpanded (key) { function isGroupExpanded (key) {
return expandedGroups.value[key] === true return expandedGroups.value[key] === true
} }
@@ -3230,8 +3252,14 @@ function productMainGroupValue (row) {
return row?.urun_ana_grubu || '' return row?.urun_ana_grubu || ''
} }
function cleanOptionalGroupValue (value) {
const text = String(value || '').trim()
return text === '-' ? '' : text
}
function cleanProductFirstGroupValue (value) { function cleanProductFirstGroupValue (value) {
const text = String(value || '').trim() const text = String(value || '').trim()
if (text === '-') return ''
const normalized = text const normalized = text
.toLocaleUpperCase('tr-TR') .toLocaleUpperCase('tr-TR')
.replaceAll('İ', 'I') .replaceAll('İ', 'I')
@@ -3247,6 +3275,7 @@ function rawProductCellValue (row, name) {
switch (name) { switch (name) {
case 'kategori': return productCategoryValue(row) case 'kategori': return productCategoryValue(row)
case 'urun_ilk_grubu': return productFirstGroupValue(row) case 'urun_ilk_grubu': return productFirstGroupValue(row)
case 'askili_yan': return cleanOptionalGroupValue(row?.askili_yan)
case 'urun_ana_grubu': return productMainGroupValue(row) case 'urun_ana_grubu': return productMainGroupValue(row)
case 'market_key': return displayMarketName(row.market_key) case 'market_key': return displayMarketName(row.market_key)
case 'performance_bucket': return bucketLabel(row.performance_bucket) case 'performance_bucket': return bucketLabel(row.performance_bucket)
@@ -3259,6 +3288,7 @@ function rawColumnCellValue (row, col) {
switch (name) { switch (name) {
case 'kategori': return productCategoryValue(row) case 'kategori': return productCategoryValue(row)
case 'urun_ilk_grubu': return productFirstGroupValue(row) case 'urun_ilk_grubu': return productFirstGroupValue(row)
case 'askili_yan': return cleanOptionalGroupValue(row?.askili_yan)
case 'urun_ana_grubu': return productMainGroupValue(row) case 'urun_ana_grubu': return productMainGroupValue(row)
case 'market_key': return displayMarketName(row.market_key) case 'market_key': return displayMarketName(row.market_key)
case 'performance_bucket': return bucketLabel(row.performance_bucket) case 'performance_bucket': return bucketLabel(row.performance_bucket)
@@ -3743,13 +3773,13 @@ async function loadProductDetailRows (row) {
} }
if (!params.product_code) return if (!params.product_code) return
const [salesResp, stockResp] = await Promise.all([ const [salesResp, stockResp] = await Promise.all([
api.get('/pricing/product-performance/sales-details', { params: { ...params, limit: 200 }, timeout: 60000 }), api.get('/pricing/product-performance/sales-details', { params: { ...params, limit: 500 }, timeout: 60000 }),
api.get('/pricing/product-performance/stock-sizes', { params, timeout: 60000 }) api.get('/pricing/product-performance/stock-sizes', { params, timeout: 60000 })
]) ])
detailSalesRows.value = (Array.isArray(salesResp?.data) ? salesResp.data : []).map((item, idx) => ({ detailSalesRows.value = (Array.isArray(salesResp?.data) ? salesResp.data : []).map((item, idx) => ({
...item, ...item,
detail_key: `${item.sales_date}|${item.ref_number}|${item.customer_code}|${idx}` detail_key: `${item.sales_date}|${item.ref_number}|${item.customer_code}|${idx}`
})) })).sort((a, b) => Number(b.sales_usd || 0) - Number(a.sales_usd || 0))
detailStockSizes.value = Array.isArray(stockResp?.data) ? stockResp.data : [] detailStockSizes.value = Array.isArray(stockResp?.data) ? stockResp.data : []
} catch (err) { } catch (err) {
Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Ürün detay analizi alınamadı' }) Notify.create({ type: 'negative', message: err?.response?.data || err?.message || 'Ürün detay analizi alınamadı' })
@@ -4884,6 +4914,103 @@ onMounted(reload)
font-size: 14px; font-size: 14px;
} }
.product-performance-dialog {
background: #f6f7f9;
}
.product-performance-dialog :deep(.q-table__container),
.product-image-dialog :deep(.q-table__container) {
width: 100%;
min-width: 0;
}
.product-performance-dialog :deep(.q-table__middle),
.product-image-dialog :deep(.q-table__middle) {
overflow-x: hidden;
}
.dialog-detail-table,
.dialog-stock-table {
width: 100%;
min-width: 0;
font-size: 11px;
}
.dialog-detail-table :deep(.q-table),
.dialog-stock-table :deep(.q-table) {
width: 100%;
table-layout: fixed;
}
.dialog-detail-table :deep(.q-table th),
.dialog-detail-table :deep(.q-table td),
.dialog-stock-table :deep(.q-table th),
.dialog-stock-table :deep(.q-table td) {
padding: 4px 5px;
white-space: normal;
overflow-wrap: anywhere;
word-break: break-word;
line-height: 1.18;
vertical-align: top;
}
.dialog-detail-table :deep(.q-table th) {
font-size: 10px;
}
.dialog-detail-table :deep(.q-table th:nth-child(1)),
.dialog-detail-table :deep(.q-table td:nth-child(1)) {
width: 9%;
}
.dialog-detail-table :deep(.q-table th:nth-child(2)),
.dialog-detail-table :deep(.q-table td:nth-child(2)) {
width: 12%;
}
.dialog-detail-table :deep(.q-table th:nth-child(3)),
.dialog-detail-table :deep(.q-table td:nth-child(3)) {
width: 11%;
}
.dialog-detail-table :deep(.q-table th:nth-child(4)),
.dialog-detail-table :deep(.q-table td:nth-child(4)) {
width: 9%;
}
.dialog-detail-table :deep(.q-table th:nth-child(5)),
.dialog-detail-table :deep(.q-table td:nth-child(5)) {
width: 12%;
}
.dialog-detail-table :deep(.q-table th:nth-child(6)),
.dialog-detail-table :deep(.q-table td:nth-child(6)) {
width: 23%;
}
.dialog-detail-table :deep(.q-table th:nth-child(7)),
.dialog-detail-table :deep(.q-table td:nth-child(7)),
.dialog-detail-table :deep(.q-table th:nth-child(8)),
.dialog-detail-table :deep(.q-table td:nth-child(8)),
.dialog-detail-table :deep(.q-table th:nth-child(9)),
.dialog-detail-table :deep(.q-table td:nth-child(9)) {
width: 8%;
white-space: nowrap;
overflow-wrap: normal;
word-break: normal;
}
.dialog-stock-table :deep(.q-table th:nth-child(1)),
.dialog-stock-table :deep(.q-table td:nth-child(1)) {
width: 58%;
}
.dialog-stock-table :deep(.q-table th:nth-child(2)),
.dialog-stock-table :deep(.q-table td:nth-child(2)) {
width: 42%;
white-space: nowrap;
}
@media (max-width: 1260px) { @media (max-width: 1260px) {
.product-image-dialog-body { .product-image-dialog-body {
overflow: auto; overflow: auto;