Compare commits
2
Commits
a05e6bf1e2
...
5288f19976
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5288f19976 | ||
|
|
60eef45769 |
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2921,7 +2942,7 @@ SELECT jsonb_build_object(
|
|||||||
) AS row_json
|
) AS row_json
|
||||||
FROM (
|
FROM (
|
||||||
SELECT
|
SELECT
|
||||||
COALESCE(NULLIF(%s,''), '-') AS group_value,
|
COALESCE(%s, '') AS group_value,
|
||||||
COUNT(*)::integer AS row_count,
|
COUNT(*)::integer AS row_count,
|
||||||
(ARRAY_AGG(product_code ORDER BY performance_score DESC NULLS LAST))[1] AS image_product_code,
|
(ARRAY_AGG(product_code ORDER BY performance_score DESC NULLS LAST))[1] AS image_product_code,
|
||||||
(ARRAY_AGG(color_code ORDER BY performance_score DESC NULLS LAST))[1] AS image_color_code,
|
(ARRAY_AGG(color_code ORDER BY performance_score DESC NULLS LAST))[1] AS image_color_code,
|
||||||
@@ -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,
|
||||||
@@ -2945,36 +2966,36 @@ FROM (
|
|||||||
COALESCE(SUM(sales_usd_365d),0) AS sales_usd_365d,
|
COALESCE(SUM(sales_usd_365d),0) AS sales_usd_365d,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(sales_usd_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS avg_price_usd_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(sales_usd_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS avg_price_usd_90d,
|
||||||
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(sales_usd_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE 0 END AS avg_price_usd_180d,
|
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(sales_usd_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE 0 END AS avg_price_usd_180d,
|
||||||
CASE WHEN SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0 THEN SUM(CASE WHEN stock_variant_rank = 1 THEN cost_price_usd * stock_qty ELSE 0 END) / NULLIF(SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0) ELSE AVG(cost_price_usd) END AS cost_price_usd,
|
CASE WHEN SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0 THEN SUM(CASE WHEN stock_variant_rank = 1 THEN cost_price_usd * stock_qty ELSE 0 END) / NULLIF(SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0) ELSE 0 END AS cost_price_usd,
|
||||||
CASE WHEN SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0 THEN SUM(CASE WHEN stock_variant_rank = 1 THEN base_price_usd * stock_qty ELSE 0 END) / NULLIF(SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0) ELSE AVG(base_price_usd) END AS base_price_usd,
|
CASE WHEN SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0 THEN SUM(CASE WHEN stock_variant_rank = 1 THEN base_price_usd * stock_qty ELSE 0 END) / NULLIF(SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0) ELSE 0 END AS base_price_usd,
|
||||||
CASE WHEN SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0 THEN SUM(CASE WHEN stock_variant_rank = 1 THEN base_price_try * stock_qty ELSE 0 END) / NULLIF(SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0) ELSE AVG(base_price_try) END AS base_price_try,
|
CASE WHEN SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END) > 0 THEN SUM(CASE WHEN stock_variant_rank = 1 THEN base_price_try * stock_qty ELSE 0 END) / NULLIF(SUM(CASE WHEN stock_variant_rank = 1 THEN stock_qty ELSE 0 END),0) ELSE 0 END AS base_price_try,
|
||||||
COALESCE(SUM(gross_profit_usd_90d),0) AS gross_profit_usd_90d,
|
COALESCE(SUM(gross_profit_usd_90d),0) AS gross_profit_usd_90d,
|
||||||
COALESCE(SUM(gross_profit_usd_180d),0) AS gross_profit_usd_180d,
|
COALESCE(SUM(gross_profit_usd_180d),0) AS gross_profit_usd_180d,
|
||||||
CASE WHEN SUM(sales_usd_90d) > 0 THEN SUM(gross_profit_usd_90d) / NULLIF(SUM(sales_usd_90d),0) ELSE 0 END AS gross_margin_90d,
|
CASE WHEN SUM(sales_usd_90d) > 0 THEN SUM(gross_profit_usd_90d) / NULLIF(SUM(sales_usd_90d),0) ELSE 0 END AS gross_margin_90d,
|
||||||
CASE WHEN SUM(sales_usd_180d) > 0 THEN SUM(gross_profit_usd_180d) / NULLIF(SUM(sales_usd_180d),0) ELSE 0 END AS gross_margin_180d,
|
CASE WHEN SUM(sales_usd_180d) > 0 THEN SUM(gross_profit_usd_180d) / NULLIF(SUM(sales_usd_180d),0) ELSE 0 END AS gross_margin_180d,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(unit_profit_cost_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE AVG(unit_profit_cost_90d) END AS unit_profit_cost_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(unit_profit_cost_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS unit_profit_cost_90d,
|
||||||
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(unit_profit_cost_180d * sales_qty_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE AVG(unit_profit_cost_180d) END AS unit_profit_cost_180d,
|
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(unit_profit_cost_180d * sales_qty_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE 0 END AS unit_profit_cost_180d,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(unit_profit_base_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE AVG(unit_profit_base_90d) END AS unit_profit_base_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(unit_profit_base_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS unit_profit_base_90d,
|
||||||
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(unit_profit_base_180d * sales_qty_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE AVG(unit_profit_base_180d) END AS unit_profit_base_180d,
|
CASE WHEN SUM(sales_qty_180d) > 0 THEN SUM(unit_profit_base_180d * sales_qty_180d) / NULLIF(SUM(sales_qty_180d),0) ELSE 0 END AS unit_profit_base_180d,
|
||||||
COALESCE(SUM(market_count_90d),0)::integer AS market_count_90d,
|
COALESCE(SUM(market_count_90d),0)::integer AS market_count_90d,
|
||||||
COALESCE(SUM(customer_count_90d),0)::integer AS customer_count_90d,
|
COALESCE(SUM(customer_count_90d),0)::integer AS customer_count_90d,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(sales_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE AVG(sales_index_90d) END AS sales_index_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(sales_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS sales_index_90d,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(price_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE AVG(price_index_90d) END AS price_index_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(price_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS price_index_90d,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(margin_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE AVG(margin_index_90d) END AS margin_index_90d,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(margin_index_90d * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS margin_index_90d,
|
||||||
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(performance_score * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE AVG(performance_score) END AS performance_score,
|
CASE WHEN SUM(sales_qty_90d) > 0 THEN SUM(performance_score * sales_qty_90d) / NULLIF(SUM(sales_qty_90d),0) ELSE 0 END AS performance_score,
|
||||||
MODE() WITHIN GROUP (ORDER BY performance_bucket) AS performance_bucket,
|
MODE() WITHIN GROUP (ORDER BY performance_bucket) AS performance_bucket,
|
||||||
COALESCE(SUM(CASE WHEN stock_variant_rank = 1 THEN idle_cost_usd ELSE 0 END),0) AS idle_cost_usd
|
COALESCE(SUM(CASE WHEN stock_variant_rank = 1 THEN idle_cost_usd ELSE 0 END),0) AS idle_cost_usd
|
||||||
FROM (
|
FROM (
|
||||||
SELECT
|
SELECT
|
||||||
Source.*,
|
Source.*,
|
||||||
ROW_NUMBER() OVER (
|
ROW_NUMBER() OVER (
|
||||||
PARTITION BY COALESCE(NULLIF(%s,''), '-'), product_code, color_code, yaka_kodu
|
PARTITION BY COALESCE(%s, ''), product_code, color_code, yaka_kodu
|
||||||
ORDER BY performance_score DESC NULLS LAST
|
ORDER BY performance_score DESC NULLS LAST
|
||||||
) AS stock_variant_rank
|
) AS stock_variant_rank
|
||||||
FROM Source
|
FROM Source
|
||||||
%s
|
%s
|
||||||
) s
|
) s
|
||||||
GROUP BY COALESCE(NULLIF(%s,''), '-')
|
GROUP BY COALESCE(%s, '')
|
||||||
) g
|
) g
|
||||||
ORDER BY group_value
|
ORDER BY group_value
|
||||||
`, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, groupExpr, groupExpr, whereSQL, groupExpr)
|
`, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, len(args)+1, groupExpr, groupExpr, whereSQL, groupExpr)
|
||||||
@@ -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
|
||||||
@@ -3107,7 +3140,7 @@ func productPerformanceSQLFilterWhere(filters []productPerformanceSQLGroupFilter
|
|||||||
return "", nil, fmt.Errorf("unsupported product performance filter field: %s", filter.Field)
|
return "", nil, fmt.Errorf("unsupported product performance filter field: %s", filter.Field)
|
||||||
}
|
}
|
||||||
args = append(args, normalizeProductPerformanceGroupValue(filter.Value))
|
args = append(args, normalizeProductPerformanceGroupValue(filter.Value))
|
||||||
parts = append(parts, fmt.Sprintf("COALESCE(NULLIF(%s,''), '-') = $%d", expr, len(args)))
|
parts = append(parts, fmt.Sprintf("COALESCE(%s, '') = $%d", expr, len(args)))
|
||||||
}
|
}
|
||||||
return "WHERE " + strings.Join(parts, " AND "), args, nil
|
return "WHERE " + strings.Join(parts, " AND "), args, 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3310,17 +3347,8 @@ func distinctProductPerformanceVariantStockCost(rows []map[string]any) float64 {
|
|||||||
|
|
||||||
func productPerformanceMapVariantKey(row map[string]any) string {
|
func productPerformanceMapVariantKey(row map[string]any) string {
|
||||||
productCode := stringFromMap(row, "product_code")
|
productCode := stringFromMap(row, "product_code")
|
||||||
if productCode == "" {
|
|
||||||
productCode = stringFromMap(row, "image_product_code")
|
|
||||||
}
|
|
||||||
colorCode := stringFromMap(row, "color_code")
|
colorCode := stringFromMap(row, "color_code")
|
||||||
if colorCode == "" {
|
|
||||||
colorCode = stringFromMap(row, "image_color_code")
|
|
||||||
}
|
|
||||||
yakaKodu := stringFromMap(row, "yaka_kodu")
|
yakaKodu := stringFromMap(row, "yaka_kodu")
|
||||||
if yakaKodu == "" {
|
|
||||||
yakaKodu = stringFromMap(row, "image_yaka_kodu")
|
|
||||||
}
|
|
||||||
if productCode == "" && colorCode == "" && yakaKodu == "" {
|
if productCode == "" && colorCode == "" && yakaKodu == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -3391,9 +3419,6 @@ func isProductPerformanceCustomerGroup(groupField string) bool {
|
|||||||
func productPerformanceSalesGroupScore(row map[string]any) float64 {
|
func productPerformanceSalesGroupScore(row map[string]any) float64 {
|
||||||
salesIndex := floatFromMap(row, "sales_index_90d")
|
salesIndex := floatFromMap(row, "sales_index_90d")
|
||||||
margin := floatFromMap(row, "gross_margin_cost_90d")
|
margin := floatFromMap(row, "gross_margin_cost_90d")
|
||||||
if margin == 0 {
|
|
||||||
margin = floatFromMap(row, "gross_margin_90d")
|
|
||||||
}
|
|
||||||
invoiceCount := floatFromMap(row, "invoice_count_90d")
|
invoiceCount := floatFromMap(row, "invoice_count_90d")
|
||||||
salesQty := floatFromMap(row, "sales_qty_90d")
|
salesQty := floatFromMap(row, "sales_qty_90d")
|
||||||
stockTurnover := floatFromMap(row, "stock_turnover_90d")
|
stockTurnover := floatFromMap(row, "stock_turnover_90d")
|
||||||
@@ -3435,9 +3460,6 @@ func productPerformanceCustomerSalesGroupScore(row map[string]any) float64 {
|
|||||||
func productPerformanceCustomerSalesPeriodScore(row map[string]any) float64 {
|
func productPerformanceCustomerSalesPeriodScore(row map[string]any) float64 {
|
||||||
salesUSD := floatFromMap(row, "sales_usd")
|
salesUSD := floatFromMap(row, "sales_usd")
|
||||||
margin := floatFromMap(row, "gross_margin_cost")
|
margin := floatFromMap(row, "gross_margin_cost")
|
||||||
if margin == 0 {
|
|
||||||
margin = floatFromMap(row, "gross_margin")
|
|
||||||
}
|
|
||||||
invoiceCount := floatFromMap(row, "invoice_count")
|
invoiceCount := floatFromMap(row, "invoice_count")
|
||||||
salesQty := floatFromMap(row, "sales_qty")
|
salesQty := floatFromMap(row, "sales_qty")
|
||||||
productCount := floatFromMap(row, "product_count")
|
productCount := floatFromMap(row, "product_count")
|
||||||
@@ -3589,8 +3611,7 @@ func productPerformanceMetricWeightField(field string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qtyField string) float64 {
|
func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qtyField string) float64 {
|
||||||
var weighted, qty, sum float64
|
var weighted, qty float64
|
||||||
var count int
|
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
value := floatFromMap(row, valueField)
|
value := floatFromMap(row, valueField)
|
||||||
weight := floatFromMap(row, qtyField)
|
weight := floatFromMap(row, qtyField)
|
||||||
@@ -3598,17 +3619,10 @@ func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qt
|
|||||||
weighted += value * weight
|
weighted += value * weight
|
||||||
qty += weight
|
qty += weight
|
||||||
}
|
}
|
||||||
if value != 0 {
|
|
||||||
sum += value
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if qty > 0 {
|
if qty > 0 {
|
||||||
return weighted / qty
|
return weighted / qty
|
||||||
}
|
}
|
||||||
if count > 0 {
|
|
||||||
return sum / float64(count)
|
|
||||||
}
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3667,11 +3681,7 @@ func cloneMap(row map[string]any) map[string]any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func normalizeProductPerformanceGroupValue(value string) string {
|
func normalizeProductPerformanceGroupValue(value string) string {
|
||||||
value = strings.TrimSpace(value)
|
return strings.TrimSpace(value)
|
||||||
if value == "" {
|
|
||||||
return "-"
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayProductPerformanceMarketName(value string) string {
|
func displayProductPerformanceMarketName(value string) string {
|
||||||
@@ -3695,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 {
|
||||||
@@ -3705,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])
|
||||||
}
|
}
|
||||||
@@ -3747,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 {
|
||||||
@@ -3926,9 +3947,9 @@ func productPerformanceOrderBy(sortBy string, desc bool) string {
|
|||||||
"unit_profit_base_180d": "unit_profit_base_180d",
|
"unit_profit_base_180d": "unit_profit_base_180d",
|
||||||
"unit_profit_cost_180d": "unit_profit_cost_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_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_cost_90d": "(CASE WHEN COALESCE(sales_usd_90d,0) <= 0 THEN 0 ELSE (sales_usd_90d - (sales_qty_90d * COALESCE(cost_price_usd,0))) / NULLIF(sales_usd_90d,0) END)",
|
||||||
"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_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_cost_180d": "(CASE WHEN COALESCE(sales_usd_180d,0) <= 0 THEN 0 ELSE (sales_usd_180d - (sales_qty_180d * COALESCE(cost_price_usd,0))) / NULLIF(sales_usd_180d,0) END)",
|
||||||
"gross_margin_90d": "gross_margin_90d",
|
"gross_margin_90d": "gross_margin_90d",
|
||||||
"gross_margin_180d": "gross_margin_180d",
|
"gross_margin_180d": "gross_margin_180d",
|
||||||
"market_count_90d": "market_count_90d",
|
"market_count_90d": "market_count_90d",
|
||||||
@@ -4009,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
|
||||||
@@ -4028,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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -2789,7 +2804,7 @@ function salesGroupPerformanceScore (row) {
|
|||||||
|
|
||||||
function productSalesPeriodScore (row) {
|
function productSalesPeriodScore (row) {
|
||||||
const salesIndex = Number(row?.sales_index_90d || 0)
|
const salesIndex = Number(row?.sales_index_90d || 0)
|
||||||
const margin = Number(row?.gross_margin_cost_90d ?? row?.gross_margin_90d ?? 0)
|
const margin = Number(row?.gross_margin_cost_90d ?? 0)
|
||||||
const invoices = Number(row?.invoice_count_90d || 0)
|
const invoices = Number(row?.invoice_count_90d || 0)
|
||||||
const qty = Number(row?.sales_qty_90d || 0)
|
const qty = Number(row?.sales_qty_90d || 0)
|
||||||
let stockTurnover = Number(row?.stock_turnover_90d || 0)
|
let stockTurnover = Number(row?.stock_turnover_90d || 0)
|
||||||
@@ -2803,8 +2818,7 @@ function productSalesPeriodScore (row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function productPeriodMetricSource (row, suffix) {
|
function productPeriodMetricSource (row, suffix) {
|
||||||
const salesUSD = Number(row?.[`sales_usd_${suffix}`] || 0)
|
const salesIndex = Number(row?.[`sales_index_${suffix}`] || 0)
|
||||||
const salesIndex = Number(row?.[`sales_index_${suffix}`] || 0) || (salesUSD > 0 ? salesUSD / 1000 : 0)
|
|
||||||
const qty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
const qty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
||||||
const stockQty = Number(row?.stock_qty || 0)
|
const stockQty = Number(row?.stock_qty || 0)
|
||||||
return {
|
return {
|
||||||
@@ -2833,7 +2847,7 @@ function customerSalesGroupPerformanceScore (row) {
|
|||||||
|
|
||||||
function customerSalesPeriodScore (row) {
|
function customerSalesPeriodScore (row) {
|
||||||
const salesUSD = Number(row?.sales_usd || 0)
|
const salesUSD = Number(row?.sales_usd || 0)
|
||||||
const margin = Number(row?.gross_margin_cost ?? row?.gross_margin ?? 0)
|
const margin = Number(row?.gross_margin_cost ?? 0)
|
||||||
const invoices = Number(row?.invoice_count || 0)
|
const invoices = Number(row?.invoice_count || 0)
|
||||||
const qty = Number(row?.sales_qty || 0)
|
const qty = Number(row?.sales_qty || 0)
|
||||||
const productCount = Number(row?.product_count || 0)
|
const productCount = Number(row?.product_count || 0)
|
||||||
@@ -2873,9 +2887,9 @@ function sumRows (sourceRows, field) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function productVariantStockKey (row) {
|
function productVariantStockKey (row) {
|
||||||
const productCode = String(row?.product_code || row?.image_product_code || '').trim()
|
const productCode = String(row?.product_code || '').trim()
|
||||||
const colorCode = String(row?.color_code || row?.image_color_code || '').trim()
|
const colorCode = String(row?.color_code || '').trim()
|
||||||
const yakaKodu = String(row?.yaka_kodu || row?.image_yaka_kodu || '').trim()
|
const yakaKodu = String(row?.yaka_kodu || '').trim()
|
||||||
if (!productCode && !colorCode && !yakaKodu) return ''
|
if (!productCode && !colorCode && !yakaKodu) return ''
|
||||||
return `${productCode}|${colorCode}|${yakaKodu}`
|
return `${productCode}|${colorCode}|${yakaKodu}`
|
||||||
}
|
}
|
||||||
@@ -2980,7 +2994,7 @@ function weightedAverageOrAverage (sourceRows, valueField, qtyField) {
|
|||||||
if (qty > 0) {
|
if (qty > 0) {
|
||||||
return weightedRows.reduce((sum, row) => sum + row.value * row.qty, 0) / qty
|
return weightedRows.reduce((sum, row) => sum + row.value * row.qty, 0) / qty
|
||||||
}
|
}
|
||||||
return averageRows(sourceRows, valueField)
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function marginFromSalesCost (salesUSD, qty, unitCost) {
|
function marginFromSalesCost (salesUSD, qty, unitCost) {
|
||||||
@@ -2999,8 +3013,14 @@ function marginFromRows (sourceRows, salesField, qtyField, unitCostField) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function normalizeGroupValue (value) {
|
function normalizeGroupValue (value) {
|
||||||
|
return String(value || '').trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldSkipGroupValue (field, value) {
|
||||||
const text = String(value || '').trim()
|
const text = String(value || '').trim()
|
||||||
return text || '-'
|
if (field === 'urun_ilk_grubu') return !text || text === '-'
|
||||||
|
if (field === 'askili_yan') return !text || text === '-'
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function isGroupExpanded (key) {
|
function isGroupExpanded (key) {
|
||||||
@@ -3065,11 +3085,11 @@ function hasProductVariantImageKey (row) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function canShowProductImage (row) {
|
function canShowProductImage (row) {
|
||||||
return activeTab.value === 'products' && !row?.__group && hasProductVariantImageKey(row)
|
return activeTab.value === 'products' && hasProductVariantImageKey(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupShowsImage () {
|
function groupShowsImage (row) {
|
||||||
return false
|
return activeTab.value === 'products' && row?.__group && hasProductVariantImageKey(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
const groupDimensionFields = new Set(dimensionColumnNames)
|
const groupDimensionFields = new Set(dimensionColumnNames)
|
||||||
@@ -3232,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')
|
||||||
@@ -3249,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)
|
||||||
@@ -3261,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)
|
||||||
@@ -3745,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ı' })
|
||||||
@@ -4886,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;
|
||||||
|
|||||||
Reference in New Issue
Block a user