Fix product performance grouped JSON build
This commit is contained in:
@@ -445,12 +445,12 @@ CREATE TABLE IF NOT EXISTS mk_product_performance_price_dim (
|
|||||||
`
|
`
|
||||||
UPDATE mk_product_performance_price_dim
|
UPDATE mk_product_performance_price_dim
|
||||||
SET
|
SET
|
||||||
cost_price_usd = GREATEST(cost_price_usd, base_price_usd),
|
cost_price_usd = LEAST(cost_price_usd, base_price_usd),
|
||||||
base_price_usd = LEAST(cost_price_usd, base_price_usd),
|
base_price_usd = GREATEST(cost_price_usd, base_price_usd),
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE cost_price_usd > 0
|
WHERE cost_price_usd > 0
|
||||||
AND base_price_usd > 0
|
AND base_price_usd > 0
|
||||||
AND cost_price_usd < base_price_usd`,
|
AND cost_price_usd > base_price_usd`,
|
||||||
`
|
`
|
||||||
DELETE FROM mk_product_performance_price_dim
|
DELETE FROM mk_product_performance_price_dim
|
||||||
WHERE upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), U&'\0130\015E\011E\00DC\00D6\00C7\0131\015F\011F\00FC\00F6\00E7', 'ISGUOCisguoc')) IN ('MALZEMELI FASON', 'MALZEMESIZ FASON', 'MAZLEMELI FASON', 'MAZEMESIZ FASON', 'DIGER')`,
|
WHERE upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), U&'\0130\015E\011E\00DC\00D6\00C7\0131\015F\011F\00FC\00F6\00E7', 'ISGUOCisguoc')) IN ('MALZEMELI FASON', 'MALZEMESIZ FASON', 'MAZLEMELI FASON', 'MAZEMESIZ FASON', 'DIGER')`,
|
||||||
@@ -527,20 +527,20 @@ WHERE upper(translate(btrim(COALESCE(urun_ilk_grubu,'')), U&'\0130\015E\011E\00D
|
|||||||
`
|
`
|
||||||
UPDATE mk_product_performance_kpi_daily
|
UPDATE mk_product_performance_kpi_daily
|
||||||
SET
|
SET
|
||||||
cost_price_usd = GREATEST(cost_price_usd, base_price_usd),
|
cost_price_usd = LEAST(cost_price_usd, base_price_usd),
|
||||||
base_price_usd = LEAST(cost_price_usd, base_price_usd),
|
base_price_usd = GREATEST(cost_price_usd, base_price_usd),
|
||||||
gross_profit_usd_90d = COALESCE(sales_usd_90d,0) - (COALESCE(sales_qty_90d,0) * GREATEST(cost_price_usd, base_price_usd)),
|
gross_profit_usd_90d = COALESCE(sales_usd_90d,0) - (COALESCE(sales_qty_90d,0) * LEAST(cost_price_usd, base_price_usd)),
|
||||||
gross_profit_usd_180d = COALESCE(sales_usd_180d,0) - (COALESCE(sales_qty_180d,0) * GREATEST(cost_price_usd, base_price_usd)),
|
gross_profit_usd_180d = COALESCE(sales_usd_180d,0) - (COALESCE(sales_qty_180d,0) * LEAST(cost_price_usd, base_price_usd)),
|
||||||
gross_margin_90d = CASE WHEN COALESCE(sales_usd_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) - (COALESCE(sales_qty_90d,0) * GREATEST(cost_price_usd, base_price_usd))) / NULLIF(sales_usd_90d,0) END,
|
gross_margin_90d = CASE WHEN COALESCE(sales_usd_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) - (COALESCE(sales_qty_90d,0) * LEAST(cost_price_usd, base_price_usd))) / NULLIF(sales_usd_90d,0) END,
|
||||||
gross_margin_180d = CASE WHEN COALESCE(sales_usd_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) - (COALESCE(sales_qty_180d,0) * GREATEST(cost_price_usd, base_price_usd))) / NULLIF(sales_usd_180d,0) END,
|
gross_margin_180d = CASE WHEN COALESCE(sales_usd_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) - (COALESCE(sales_qty_180d,0) * LEAST(cost_price_usd, base_price_usd))) / NULLIF(sales_usd_180d,0) END,
|
||||||
unit_profit_cost_90d = CASE WHEN COALESCE(sales_qty_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) / NULLIF(sales_qty_90d,0)) - GREATEST(cost_price_usd, base_price_usd) END,
|
unit_profit_cost_90d = CASE WHEN COALESCE(sales_qty_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) / NULLIF(sales_qty_90d,0)) - LEAST(cost_price_usd, base_price_usd) END,
|
||||||
unit_profit_cost_180d = CASE WHEN COALESCE(sales_qty_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) / NULLIF(sales_qty_180d,0)) - GREATEST(cost_price_usd, base_price_usd) END,
|
unit_profit_cost_180d = CASE WHEN COALESCE(sales_qty_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) / NULLIF(sales_qty_180d,0)) - LEAST(cost_price_usd, base_price_usd) END,
|
||||||
unit_profit_base_90d = CASE WHEN COALESCE(sales_qty_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) / NULLIF(sales_qty_90d,0)) - LEAST(cost_price_usd, base_price_usd) END,
|
unit_profit_base_90d = CASE WHEN COALESCE(sales_qty_90d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_90d,0) / NULLIF(sales_qty_90d,0)) - GREATEST(cost_price_usd, base_price_usd) END,
|
||||||
unit_profit_base_180d = CASE WHEN COALESCE(sales_qty_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) / NULLIF(sales_qty_180d,0)) - LEAST(cost_price_usd, base_price_usd) END,
|
unit_profit_base_180d = CASE WHEN COALESCE(sales_qty_180d,0) = 0 THEN 0 ELSE (COALESCE(sales_usd_180d,0) / NULLIF(sales_qty_180d,0)) - GREATEST(cost_price_usd, base_price_usd) END,
|
||||||
updated_at = now()
|
updated_at = now()
|
||||||
WHERE cost_price_usd > 0
|
WHERE cost_price_usd > 0
|
||||||
AND base_price_usd > 0
|
AND base_price_usd > 0
|
||||||
AND cost_price_usd < base_price_usd`,
|
AND cost_price_usd > base_price_usd`,
|
||||||
`
|
`
|
||||||
UPDATE mk_product_performance_kpi_daily
|
UPDATE mk_product_performance_kpi_daily
|
||||||
SET askili_yan = '', updated_at = now()
|
SET askili_yan = '', updated_at = now()
|
||||||
@@ -4486,7 +4486,29 @@ FROM (
|
|||||||
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(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 0 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 0 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 0 END AS performance_score,
|
CASE
|
||||||
|
WHEN SUM(CASE
|
||||||
|
WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d
|
||||||
|
WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d
|
||||||
|
WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd
|
||||||
|
WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty
|
||||||
|
ELSE 1
|
||||||
|
END) > 0
|
||||||
|
THEN SUM(performance_score * CASE
|
||||||
|
WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d
|
||||||
|
WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d
|
||||||
|
WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd
|
||||||
|
WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty
|
||||||
|
ELSE 1
|
||||||
|
END) / NULLIF(SUM(CASE
|
||||||
|
WHEN COALESCE(sales_usd_90d,0) > 0 THEN sales_usd_90d
|
||||||
|
WHEN COALESCE(sales_qty_90d,0) > 0 THEN sales_qty_90d
|
||||||
|
WHEN COALESCE(stock_qty,0) > 0 AND COALESCE(cost_price_usd,0) > 0 THEN stock_qty * cost_price_usd
|
||||||
|
WHEN COALESCE(stock_qty,0) > 0 THEN stock_qty
|
||||||
|
ELSE 1
|
||||||
|
END),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(MODE() WITHIN GROUP (ORDER BY NULLIF(recommendation,'')), '') AS recommendation,
|
COALESCE(MODE() WITHIN GROUP (ORDER BY NULLIF(recommendation,'')), '') AS recommendation,
|
||||||
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
|
||||||
@@ -5167,7 +5189,7 @@ type productPerformanceGroupedSnapshotNode struct {
|
|||||||
Count int
|
Count int
|
||||||
Children map[string]*productPerformanceGroupedSnapshotNode
|
Children map[string]*productPerformanceGroupedSnapshotNode
|
||||||
ChildOrder []string
|
ChildOrder []string
|
||||||
StockSeen map[string]bool
|
StockMetricSeen map[string]map[string]bool
|
||||||
IdleSeen map[string]bool
|
IdleSeen map[string]bool
|
||||||
Avg map[string]*productPerformanceGroupedSnapshotAvgState
|
Avg map[string]*productPerformanceGroupedSnapshotAvgState
|
||||||
BucketCounts map[string]int
|
BucketCounts map[string]int
|
||||||
@@ -5235,19 +5257,8 @@ func (n *productPerformanceGroupedSnapshotNode) add(row map[string]any) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case key == "stock_qty":
|
case isProductPerformanceDistinctVariantStockMetric(key):
|
||||||
variantKey := productPerformanceMapVariantKey(row)
|
n.addDistinctVariantMetric(row, key, value)
|
||||||
if variantKey == "" {
|
|
||||||
n.Row[key] = floatFromAny(n.Row[key]) + floatFromAny(value)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if n.StockSeen == nil {
|
|
||||||
n.StockSeen = map[string]bool{}
|
|
||||||
}
|
|
||||||
if !n.StockSeen[variantKey] {
|
|
||||||
n.StockSeen[variantKey] = true
|
|
||||||
n.Row[key] = floatFromAny(n.Row[key]) + floatFromAny(value)
|
|
||||||
}
|
|
||||||
case key == "idle_cost_usd":
|
case key == "idle_cost_usd":
|
||||||
variantKey := productPerformanceMapVariantKey(row)
|
variantKey := productPerformanceMapVariantKey(row)
|
||||||
if variantKey == "" {
|
if variantKey == "" {
|
||||||
@@ -5289,6 +5300,27 @@ func (n *productPerformanceGroupedSnapshotNode) add(row map[string]any) {
|
|||||||
n.addDistinctSpread(row)
|
n.addDistinctSpread(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *productPerformanceGroupedSnapshotNode) addDistinctVariantMetric(row map[string]any, key string, value any) {
|
||||||
|
variantKey := productPerformanceMapVariantKey(row)
|
||||||
|
if variantKey == "" {
|
||||||
|
n.Row[key] = floatFromAny(n.Row[key]) + floatFromAny(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if n.StockMetricSeen == nil {
|
||||||
|
n.StockMetricSeen = map[string]map[string]bool{}
|
||||||
|
}
|
||||||
|
seen := n.StockMetricSeen[key]
|
||||||
|
if seen == nil {
|
||||||
|
seen = map[string]bool{}
|
||||||
|
n.StockMetricSeen[key] = seen
|
||||||
|
}
|
||||||
|
if seen[variantKey] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
seen[variantKey] = true
|
||||||
|
n.Row[key] = floatFromAny(n.Row[key]) + floatFromAny(value)
|
||||||
|
}
|
||||||
|
|
||||||
func (n *productPerformanceGroupedSnapshotNode) addDistinctSpread(row map[string]any) {
|
func (n *productPerformanceGroupedSnapshotNode) addDistinctSpread(row map[string]any) {
|
||||||
market := displayProductPerformanceMarketName(stringFromMap(row, "market_key"))
|
market := displayProductPerformanceMarketName(stringFromMap(row, "market_key"))
|
||||||
if market != "" && market != "STOK" {
|
if market != "" && market != "STOK" {
|
||||||
@@ -5355,11 +5387,11 @@ func (n *productPerformanceGroupedSnapshotNode) snapshotRow() map[string]any {
|
|||||||
row[key] = state.Sum / state.Count
|
row[key] = state.Sum / state.Count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
applyProductPerformanceDistinctSpread(row, n.Market90Seen, n.MarketTotalSeen, n.Customer90Seen, n.CustomerTotalSeen)
|
||||||
deriveProductPerformanceGroupMetrics(row, n.Field)
|
deriveProductPerformanceGroupMetrics(row, n.Field)
|
||||||
if bucket := n.dominantBucket(); bucket != "" {
|
if bucket := n.dominantBucket(); bucket != "" {
|
||||||
row["performance_bucket"] = bucket
|
row["performance_bucket"] = bucket
|
||||||
}
|
}
|
||||||
applyProductPerformanceDistinctSpread(row, n.Market90Seen, n.MarketTotalSeen, n.Customer90Seen, n.CustomerTotalSeen)
|
|
||||||
clearProductPerformanceGroupDimensions(row, n.Field, n.Value)
|
clearProductPerformanceGroupDimensions(row, n.Field, n.Value)
|
||||||
row["__group"] = true
|
row["__group"] = true
|
||||||
row["row_key"] = "group|" + n.Key
|
row["row_key"] = "group|" + n.Key
|
||||||
@@ -5489,8 +5521,8 @@ func aggregateProductPerformanceRows(rows []map[string]any, groupField string) m
|
|||||||
if isProductPerformanceMarginField(key) {
|
if isProductPerformanceMarginField(key) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if key == "stock_qty" {
|
if isProductPerformanceDistinctVariantStockMetric(key) {
|
||||||
out[key] = distinctProductPerformanceVariantStockQty(rows)
|
out[key] = distinctProductPerformanceVariantNumber(rows, key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if key == "idle_cost_usd" {
|
if key == "idle_cost_usd" {
|
||||||
@@ -5517,9 +5549,9 @@ func aggregateProductPerformanceRows(rows []map[string]any, groupField string) m
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
applyProductPerformanceDistinctSpread(out, market90Seen, marketTotalSeen, customer90Seen, customerTotalSeen)
|
||||||
deriveProductPerformanceGroupMetrics(out, groupField)
|
deriveProductPerformanceGroupMetrics(out, groupField)
|
||||||
out["performance_bucket"] = dominantProductPerformanceValue(rows, "performance_bucket")
|
out["performance_bucket"] = dominantProductPerformanceValue(rows, "performance_bucket")
|
||||||
applyProductPerformanceDistinctSpread(out, market90Seen, marketTotalSeen, customer90Seen, customerTotalSeen)
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5613,7 +5645,11 @@ func isProductPerformanceMarginField(field string) bool {
|
|||||||
return strings.HasPrefix(field, "gross_margin")
|
return strings.HasPrefix(field, "gross_margin")
|
||||||
}
|
}
|
||||||
|
|
||||||
func distinctProductPerformanceVariantStockQty(rows []map[string]any) float64 {
|
func isProductPerformanceDistinctVariantStockMetric(field string) bool {
|
||||||
|
return field == "stock_qty" || strings.HasPrefix(field, "avg_stock_")
|
||||||
|
}
|
||||||
|
|
||||||
|
func distinctProductPerformanceVariantNumber(rows []map[string]any, field string) float64 {
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
total := 0.0
|
total := 0.0
|
||||||
hasKey := false
|
hasKey := false
|
||||||
@@ -5627,13 +5663,13 @@ func distinctProductPerformanceVariantStockQty(rows []map[string]any) float64 {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seen[key] = true
|
seen[key] = true
|
||||||
total += floatFromMap(row, "stock_qty")
|
total += floatFromMap(row, field)
|
||||||
}
|
}
|
||||||
if hasKey {
|
if hasKey {
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
total += floatFromMap(row, "stock_qty")
|
total += floatFromMap(row, field)
|
||||||
}
|
}
|
||||||
return total
|
return total
|
||||||
}
|
}
|
||||||
@@ -5674,36 +5710,57 @@ func productPerformanceMapVariantKey(row map[string]any) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string) {
|
||||||
|
normalizeProductPerformanceCostFields(out)
|
||||||
for _, suffix := range []string{"90d", "180d", "365d", "total"} {
|
for _, suffix := range []string{"90d", "180d", "365d", "total"} {
|
||||||
sales := floatFromMap(out, "sales_usd_"+suffix)
|
sales := floatFromMap(out, "sales_usd_"+suffix)
|
||||||
qty := floatFromMap(out, "sales_qty_"+suffix)
|
qty := floatFromMap(out, "sales_qty_"+suffix)
|
||||||
stockQty := floatFromMap(out, "stock_qty")
|
stockQty := floatFromMap(out, "stock_qty")
|
||||||
if _, exists := out["stock_turnover_"+suffix]; !exists && stockQty > 0 {
|
turnoverBase := floatFromMap(out, "avg_stock_"+suffix)
|
||||||
out["stock_turnover_"+suffix] = qty / stockQty
|
if turnoverBase <= 0 {
|
||||||
|
turnoverBase = stockQty
|
||||||
|
}
|
||||||
|
if turnoverBase > 0 {
|
||||||
|
out["stock_turnover_"+suffix] = qty / turnoverBase
|
||||||
|
} else {
|
||||||
|
out["stock_turnover_"+suffix] = 0
|
||||||
}
|
}
|
||||||
if qty > 0 {
|
if qty > 0 {
|
||||||
out["avg_price_usd_"+suffix] = sales / qty
|
out["avg_price_usd_"+suffix] = sales / qty
|
||||||
}
|
}
|
||||||
basePrice, hasBasePrice := productPerformanceUnitCostForSuffix(out, "base_price_usd", suffix)
|
basePrice, hasBasePrice := productPerformanceUnitCostForSuffix(out, "base_price_usd", suffix)
|
||||||
costPrice, hasCostPrice := productPerformanceUnitCostForSuffix(out, "cost_price_usd", suffix)
|
costPrice, hasCostPrice := productPerformanceUnitCostForSuffix(out, "cost_price_usd", suffix)
|
||||||
|
if hasBasePrice {
|
||||||
|
out["base_price_usd_"+suffix] = basePrice
|
||||||
|
}
|
||||||
|
if hasCostPrice {
|
||||||
|
out["cost_price_usd_"+suffix] = costPrice
|
||||||
|
}
|
||||||
if qty > 0 && hasBasePrice {
|
if qty > 0 && hasBasePrice {
|
||||||
out["unit_profit_base_"+suffix] = (sales / qty) - basePrice
|
avgPrice := sales / qty
|
||||||
|
out["unit_profit_base_"+suffix] = avgPrice - basePrice
|
||||||
out["gross_profit_base_usd_"+suffix] = sales - (qty * basePrice)
|
out["gross_profit_base_usd_"+suffix] = sales - (qty * basePrice)
|
||||||
|
} else {
|
||||||
|
out["unit_profit_base_"+suffix] = 0
|
||||||
|
out["gross_profit_base_usd_"+suffix] = 0
|
||||||
}
|
}
|
||||||
if qty > 0 && hasCostPrice {
|
if qty > 0 && hasCostPrice {
|
||||||
out["unit_profit_cost_"+suffix] = (sales / qty) - costPrice
|
avgPrice := sales / qty
|
||||||
|
out["unit_profit_cost_"+suffix] = avgPrice - costPrice
|
||||||
out["gross_profit_cost_usd_"+suffix] = sales - (qty * costPrice)
|
out["gross_profit_cost_usd_"+suffix] = sales - (qty * costPrice)
|
||||||
|
out["gross_profit_usd_"+suffix] = out["gross_profit_cost_usd_"+suffix]
|
||||||
|
} else {
|
||||||
|
out["unit_profit_cost_"+suffix] = 0
|
||||||
|
out["gross_profit_cost_usd_"+suffix] = 0
|
||||||
|
out["gross_profit_usd_"+suffix] = 0
|
||||||
}
|
}
|
||||||
if sales > 0 {
|
if sales > 0 {
|
||||||
if _, ok := out["gross_profit_base_usd_"+suffix]; ok {
|
|
||||||
out["gross_margin_base_"+suffix] = floatFromMap(out, "gross_profit_base_usd_"+suffix) / sales
|
out["gross_margin_base_"+suffix] = floatFromMap(out, "gross_profit_base_usd_"+suffix) / sales
|
||||||
}
|
|
||||||
if _, ok := out["gross_profit_cost_usd_"+suffix]; ok {
|
|
||||||
out["gross_margin_cost_"+suffix] = floatFromMap(out, "gross_profit_cost_usd_"+suffix) / sales
|
out["gross_margin_cost_"+suffix] = floatFromMap(out, "gross_profit_cost_usd_"+suffix) / sales
|
||||||
}
|
|
||||||
if _, ok := out["gross_profit_usd_"+suffix]; ok {
|
|
||||||
out["gross_margin_"+suffix] = floatFromMap(out, "gross_profit_usd_"+suffix) / sales
|
out["gross_margin_"+suffix] = floatFromMap(out, "gross_profit_usd_"+suffix) / sales
|
||||||
}
|
} else {
|
||||||
|
out["gross_margin_base_"+suffix] = 0
|
||||||
|
out["gross_margin_cost_"+suffix] = 0
|
||||||
|
out["gross_margin_"+suffix] = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
orderUSD := floatFromMap(out, "order_usd")
|
orderUSD := floatFromMap(out, "order_usd")
|
||||||
@@ -5743,24 +5800,65 @@ func deriveProductPerformanceGroupMetrics(out map[string]any, groupField string)
|
|||||||
out["performance_score_total"] = productPerformanceSalesPeriodScore(out, "total")
|
out["performance_score_total"] = productPerformanceSalesPeriodScore(out, "total")
|
||||||
}
|
}
|
||||||
if _, ok := out["performance_score"]; !ok {
|
if _, ok := out["performance_score"]; !ok {
|
||||||
|
if score, exists := productPerformanceOptionalFloat(out, "performance_score_90d"); exists {
|
||||||
|
out["performance_score"] = score
|
||||||
|
} else {
|
||||||
out["performance_score"] = productPerformanceGroupScore(out, groupField)
|
out["performance_score"] = productPerformanceGroupScore(out, groupField)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if floatFromMap(out, "order_qty") > 0 || floatFromMap(out, "order_usd") > 0 {
|
if floatFromMap(out, "order_qty") > 0 || floatFromMap(out, "order_usd") > 0 {
|
||||||
score := productPerformanceOrderGroupScore(out)
|
score := productPerformanceOrderGroupScore(out)
|
||||||
out["performance_score_90d"] = score
|
out["performance_score_90d"] = score
|
||||||
out["performance_score_180d"] = score
|
out["performance_score_180d"] = score
|
||||||
out["performance_score_365d"] = score
|
out["performance_score_365d"] = score
|
||||||
out["performance_score_total"] = score
|
out["performance_score_total"] = score
|
||||||
|
out["performance_score"] = score
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func productPerformanceUnitCostForSuffix(row map[string]any, baseField, suffix string) (float64, bool) {
|
func normalizeProductPerformanceCostFields(row map[string]any) {
|
||||||
if value, ok := row[baseField+"_"+suffix]; ok {
|
normalize := func(costField, baseField string) {
|
||||||
|
cost, hasCost := productPerformanceOptionalFloat(row, costField)
|
||||||
|
base, hasBase := productPerformanceOptionalFloat(row, baseField)
|
||||||
|
if !hasCost && !hasBase {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cost, base = normalizeProductPerformanceCostPair(cost, base)
|
||||||
|
if hasCost || cost > 0 {
|
||||||
|
row[costField] = cost
|
||||||
|
}
|
||||||
|
if hasBase || base > 0 {
|
||||||
|
row[baseField] = base
|
||||||
|
}
|
||||||
|
}
|
||||||
|
normalize("cost_price_usd", "base_price_usd")
|
||||||
|
for _, suffix := range []string{"90d", "180d", "365d", "total"} {
|
||||||
|
normalize("cost_price_usd_"+suffix, "base_price_usd_"+suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceOptionalFloat(row map[string]any, field string) (float64, bool) {
|
||||||
|
value, ok := row[field]
|
||||||
|
if !ok {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
return floatFromAny(value), true
|
return floatFromAny(value), true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func productPerformanceUnitCostForSuffix(row map[string]any, baseField, suffix string) (float64, bool) {
|
||||||
|
hasSuffix := false
|
||||||
|
if value, ok := row[baseField+"_"+suffix]; ok {
|
||||||
|
hasSuffix = true
|
||||||
|
if n := floatFromAny(value); n > 0 {
|
||||||
|
return n, true
|
||||||
|
}
|
||||||
|
}
|
||||||
if value, ok := row[baseField]; ok {
|
if value, ok := row[baseField]; ok {
|
||||||
return floatFromAny(value), true
|
return floatFromAny(value), true
|
||||||
}
|
}
|
||||||
|
if hasSuffix {
|
||||||
|
return 0, true
|
||||||
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6099,6 +6197,7 @@ func shouldAverageProductPerformanceField(field string) bool {
|
|||||||
strings.HasPrefix(field, "expected_margin") ||
|
strings.HasPrefix(field, "expected_margin") ||
|
||||||
strings.HasPrefix(field, "sales_index") ||
|
strings.HasPrefix(field, "sales_index") ||
|
||||||
strings.HasPrefix(field, "performance_score") ||
|
strings.HasPrefix(field, "performance_score") ||
|
||||||
|
strings.HasPrefix(field, "customer_score") ||
|
||||||
strings.HasPrefix(field, "stock_days") ||
|
strings.HasPrefix(field, "stock_days") ||
|
||||||
strings.HasPrefix(field, "stock_turnover")
|
strings.HasPrefix(field, "stock_turnover")
|
||||||
}
|
}
|
||||||
@@ -6136,6 +6235,9 @@ func productPerformanceUsesCostWeight(field string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func productPerformanceMetricWeight(row map[string]any, field string) float64 {
|
func productPerformanceMetricWeight(row map[string]any, field string) float64 {
|
||||||
|
if productPerformanceUsesScoreWeight(field) {
|
||||||
|
return productPerformanceScoreWeight(row, productPerformanceScoreSuffix(field))
|
||||||
|
}
|
||||||
weightField := productPerformanceMetricWeightField(field)
|
weightField := productPerformanceMetricWeightField(field)
|
||||||
if weightField == "__product_cost_weight" {
|
if weightField == "__product_cost_weight" {
|
||||||
return productPerformanceCostWeight(row)
|
return productPerformanceCostWeight(row)
|
||||||
@@ -6143,6 +6245,44 @@ func productPerformanceMetricWeight(row map[string]any, field string) float64 {
|
|||||||
return floatFromMap(row, weightField)
|
return floatFromMap(row, weightField)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func productPerformanceUsesScoreWeight(field string) bool {
|
||||||
|
return strings.HasPrefix(field, "performance_score") ||
|
||||||
|
strings.HasPrefix(field, "customer_score")
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceScoreSuffix(field string) string {
|
||||||
|
switch {
|
||||||
|
case strings.Contains(field, "_180d"):
|
||||||
|
return "180d"
|
||||||
|
case strings.Contains(field, "_365d"):
|
||||||
|
return "365d"
|
||||||
|
case strings.Contains(field, "_total"):
|
||||||
|
return "total"
|
||||||
|
default:
|
||||||
|
return "90d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func productPerformanceScoreWeight(row map[string]any, suffix string) float64 {
|
||||||
|
if weight := floatFromMap(row, "sales_usd_"+suffix); weight > 0 {
|
||||||
|
return weight
|
||||||
|
}
|
||||||
|
if weight := floatFromMap(row, "sales_qty_"+suffix); weight > 0 {
|
||||||
|
return weight
|
||||||
|
}
|
||||||
|
stockQty := floatFromMap(row, "stock_qty")
|
||||||
|
if stockQty <= 0 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if cost := floatFromMap(row, "cost_price_usd_"+suffix); cost > 0 {
|
||||||
|
return stockQty * cost
|
||||||
|
}
|
||||||
|
if cost := floatFromMap(row, "cost_price_usd"); cost > 0 {
|
||||||
|
return stockQty * cost
|
||||||
|
}
|
||||||
|
return stockQty
|
||||||
|
}
|
||||||
|
|
||||||
func productPerformanceCostWeight(row map[string]any) float64 {
|
func productPerformanceCostWeight(row map[string]any) float64 {
|
||||||
if weight := floatFromMap(row, "sales_qty_total"); weight > 0 {
|
if weight := floatFromMap(row, "sales_qty_total"); weight > 0 {
|
||||||
return weight
|
return weight
|
||||||
@@ -6156,6 +6296,9 @@ func productPerformanceCostWeight(row map[string]any) float64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qtyField string) float64 {
|
func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qtyField string) float64 {
|
||||||
|
if productPerformanceUsesScoreWeight(valueField) {
|
||||||
|
return weightedAverageProductPerformanceScoreRows(rows, valueField, productPerformanceScoreSuffix(valueField))
|
||||||
|
}
|
||||||
var weighted, qty float64
|
var weighted, qty float64
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
value := floatFromMap(row, valueField)
|
value := floatFromMap(row, valueField)
|
||||||
@@ -6177,6 +6320,30 @@ func weightedAverageProductPerformanceRows(rows []map[string]any, valueField, qt
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func weightedAverageProductPerformanceScoreRows(rows []map[string]any, valueField, suffix string) float64 {
|
||||||
|
var weighted, weight, sum, count float64
|
||||||
|
for _, row := range rows {
|
||||||
|
value, ok := productPerformanceOptionalFloat(row, valueField)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
rowWeight := productPerformanceScoreWeight(row, suffix)
|
||||||
|
if rowWeight > 0 {
|
||||||
|
weighted += value * rowWeight
|
||||||
|
weight += rowWeight
|
||||||
|
}
|
||||||
|
sum += value
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if weight > 0 {
|
||||||
|
return weighted / weight
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
return sum / count
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func averageProductPerformanceDistinctVariantField(rows []map[string]any, valueField string) float64 {
|
func averageProductPerformanceDistinctVariantField(rows []map[string]any, valueField string) float64 {
|
||||||
seen := map[string]bool{}
|
seen := map[string]bool{}
|
||||||
sum := 0.0
|
sum := 0.0
|
||||||
@@ -6642,7 +6809,7 @@ func productPerformanceVariantKey(productCode, colorCode, yakaKodu string) strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func normalizeProductPerformanceCostPair(costPriceUSD, basePriceUSD float64) (float64, float64) {
|
func normalizeProductPerformanceCostPair(costPriceUSD, basePriceUSD float64) (float64, float64) {
|
||||||
if costPriceUSD > 0 && basePriceUSD > 0 && costPriceUSD < basePriceUSD {
|
if costPriceUSD > 0 && basePriceUSD > 0 && costPriceUSD > basePriceUSD {
|
||||||
return basePriceUSD, costPriceUSD
|
return basePriceUSD, costPriceUSD
|
||||||
}
|
}
|
||||||
return costPriceUSD, basePriceUSD
|
return costPriceUSD, basePriceUSD
|
||||||
|
|||||||
@@ -549,11 +549,11 @@ PriceDim AS (
|
|||||||
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,
|
||||||
CASE
|
CASE
|
||||||
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd < base_price_usd THEN base_price_usd
|
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd > base_price_usd THEN base_price_usd
|
||||||
ELSE cost_price_usd
|
ELSE cost_price_usd
|
||||||
END AS cost_price_usd,
|
END AS cost_price_usd,
|
||||||
CASE
|
CASE
|
||||||
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd < base_price_usd THEN cost_price_usd
|
WHEN cost_price_usd > 0 AND base_price_usd > 0 AND cost_price_usd > base_price_usd THEN cost_price_usd
|
||||||
ELSE base_price_usd
|
ELSE base_price_usd
|
||||||
END AS base_price_usd,
|
END AS base_price_usd,
|
||||||
base_price_try
|
base_price_try
|
||||||
|
|||||||
@@ -233,6 +233,7 @@ func (v *productPerformanceExcelVariant) fillDimensions(kpiDate, productCode, co
|
|||||||
v.UrunAltGrubu = productPerformanceExcelFirstNonEmpty(v.UrunAltGrubu, urunAltGrubu)
|
v.UrunAltGrubu = productPerformanceExcelFirstNonEmpty(v.UrunAltGrubu, urunAltGrubu)
|
||||||
v.BasePriceUSD = productPerformanceExcelFirstNonZero(v.BasePriceUSD, basePrice)
|
v.BasePriceUSD = productPerformanceExcelFirstNonZero(v.BasePriceUSD, basePrice)
|
||||||
v.CostPriceUSD = productPerformanceExcelFirstNonZero(v.CostPriceUSD, costPrice)
|
v.CostPriceUSD = productPerformanceExcelFirstNonZero(v.CostPriceUSD, costPrice)
|
||||||
|
v.CostPriceUSD, v.BasePriceUSD = productPerformanceExcelNormalizeCostPair(v.CostPriceUSD, v.BasePriceUSD)
|
||||||
if stockQty > v.StockQty {
|
if stockQty > v.StockQty {
|
||||||
v.StockQty = stockQty
|
v.StockQty = stockQty
|
||||||
}
|
}
|
||||||
@@ -402,11 +403,11 @@ func productPerformanceExcelPeriodColumns(label, suffix string, values func(*pro
|
|||||||
_, usd, _, _ := values(v)
|
_, usd, _, _ := values(v)
|
||||||
return usd
|
return usd
|
||||||
}),
|
}),
|
||||||
numberExcelColumn(label+" Ciro/Adet USD", "number", func(v *productPerformanceExcelVariant) any {
|
numberExcelColumn(label+" Ort. Satış Fiyatı USD", "number", func(v *productPerformanceExcelVariant) any {
|
||||||
qty, usd, _, _ := values(v)
|
qty, usd, _, _ := values(v)
|
||||||
return productPerformanceExcelAvgPrice(usd, qty)
|
return productPerformanceExcelAvgPrice(usd, qty)
|
||||||
}),
|
}),
|
||||||
numberExcelColumn(label+" Satış/Stok Oranı", "number", func(v *productPerformanceExcelVariant) any {
|
numberExcelColumn(label+" Stok Devir Hızı", "number", func(v *productPerformanceExcelVariant) any {
|
||||||
qty, _, _, _ := values(v)
|
qty, _, _, _ := values(v)
|
||||||
return productPerformanceExcelStockTurnover(qty, v.StockQty)
|
return productPerformanceExcelStockTurnover(qty, v.StockQty)
|
||||||
}),
|
}),
|
||||||
@@ -866,6 +867,13 @@ func productPerformanceExcelFirstNonZero(values ...float64) float64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func productPerformanceExcelNormalizeCostPair(costPriceUSD, basePriceUSD float64) (float64, float64) {
|
||||||
|
if costPriceUSD > 0 && basePriceUSD > 0 && costPriceUSD > basePriceUSD {
|
||||||
|
return basePriceUSD, costPriceUSD
|
||||||
|
}
|
||||||
|
return costPriceUSD, basePriceUSD
|
||||||
|
}
|
||||||
|
|
||||||
func productPerformanceExcelAvgPrice(salesUSD, qty float64) float64 {
|
func productPerformanceExcelAvgPrice(salesUSD, qty float64) float64 {
|
||||||
if qty <= 0 {
|
if qty <= 0 {
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -1784,7 +1784,7 @@ const detailSalesColumns = [
|
|||||||
{ name: 'customer_name', label: 'Müşteri', field: 'customer_name', align: 'left', sortable: true },
|
{ name: 'customer_name', label: 'Müşteri', field: 'customer_name', align: 'left', sortable: true },
|
||||||
{ name: 'sales_qty', label: 'Adet', field: row => formatNumber(row.sales_qty, 2), align: 'right', sortable: true },
|
{ name: 'sales_qty', label: 'Adet', field: row => formatNumber(row.sales_qty, 2), align: 'right', sortable: true },
|
||||||
{ name: 'sales_usd', label: 'USD', field: row => formatMoney(row.sales_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'sales_usd', label: 'USD', field: row => formatMoney(row.sales_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_price_usd, 'USD'), align: 'right', sortable: true }
|
{ name: 'avg_price_usd', label: 'Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd, 'USD'), align: 'right', sortable: true }
|
||||||
]
|
]
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -1816,14 +1816,14 @@ const columns = [
|
|||||||
{ name: 'stock_days_180d', label: '180G Stok Gün', field: row => formatNumber(row.stock_days_180d, 1), align: 'right', sortable: true },
|
{ name: 'stock_days_180d', label: '180G Stok Gün', field: row => formatNumber(row.stock_days_180d, 1), align: 'right', sortable: true },
|
||||||
{ name: 'stock_days_365d', label: '360G Stok Gün', field: row => formatNumber(row.stock_days_365d, 1), align: 'right', sortable: true },
|
{ name: 'stock_days_365d', label: '360G Stok Gün', field: row => formatNumber(row.stock_days_365d, 1), align: 'right', sortable: true },
|
||||||
{ name: 'stock_days_total', label: 'Genel Stok Gün', field: row => formatNumber(row.stock_days_total, 1), align: 'right', sortable: true },
|
{ name: 'stock_days_total', label: 'Genel Stok Gün', field: row => formatNumber(row.stock_days_total, 1), align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_90d', label: '90G Satış/Stok', field: row => formatNumber(row.stock_turnover_90d, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_90d', label: '90G Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_90d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_180d', label: '180G Satış/Stok', field: row => formatNumber(row.stock_turnover_180d, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_180d', label: '180G Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_180d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_365d', label: '360G Satış/Stok', field: row => formatNumber(row.stock_turnover_365d, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_365d', label: '360G Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_365d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_total', label: 'Genel Satış/Stok', field: row => formatNumber(row.stock_turnover_total, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_total', label: 'Genel Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_total, 2), align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd_90d', label: '90G Ciro/Adet USD', field: row => formatMoney(row.avg_price_usd_90d, 'USD'), align: 'right' },
|
{ name: 'avg_price_usd_90d', label: '90G Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_90d, 'USD'), align: 'right' },
|
||||||
{ name: 'avg_price_usd_180d', label: '180G Ciro/Adet USD', field: row => formatMoney(row.avg_price_usd_180d, 'USD'), align: 'right' },
|
{ name: 'avg_price_usd_180d', label: '180G Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_180d, 'USD'), align: 'right' },
|
||||||
{ name: 'avg_price_usd_365d', label: '360G Ciro/Adet USD', field: row => formatMoney(row.avg_price_usd_365d, 'USD'), align: 'right' },
|
{ name: 'avg_price_usd_365d', label: '360G Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_365d, 'USD'), align: 'right' },
|
||||||
{ name: 'avg_price_usd_total', label: 'Genel Ciro/Adet USD', field: row => formatMoney(row.avg_price_usd_total, 'USD'), align: 'right' },
|
{ name: 'avg_price_usd_total', label: 'Genel Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_total, 'USD'), align: 'right' },
|
||||||
{ name: 'base_price_usd', label: 'USD Taban Maliyeti', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'base_price_usd', label: 'USD Taban Maliyeti', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'cost_price_usd', label: 'USD Çıplak Maliyeti', field: row => formatMoney(row.cost_price_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'cost_price_usd', label: 'USD Çıplak Maliyeti', field: row => formatMoney(row.cost_price_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'unit_profit_base_90d', label: '90G P.Başı Taban K/Z', field: row => formatMoney(row.unit_profit_base_90d, 'USD'), align: 'right', sortable: true },
|
{ name: 'unit_profit_base_90d', label: '90G P.Başı Taban K/Z', field: row => formatMoney(row.unit_profit_base_90d, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2045,7 +2045,7 @@ const generalColumns = [
|
|||||||
{ name: 'sales_usd_total', label: 'Toplam Ciro USD', field: row => formatMoney(row.sales_usd_total, 'USD'), align: 'right', sortable: true },
|
{ name: 'sales_usd_total', label: 'Toplam Ciro USD', field: row => formatMoney(row.sales_usd_total, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_daily_sales_total', label: 'Günlük Ort.', field: row => formatNumber(row.avg_daily_sales_total, 3), align: 'right', sortable: true },
|
{ name: 'avg_daily_sales_total', label: 'Günlük Ort.', field: row => formatNumber(row.avg_daily_sales_total, 3), align: 'right', sortable: true },
|
||||||
{ name: 'stock_days_total', label: 'Genel Stok Gün', field: row => formatNumber(row.stock_days_total, 1), align: 'right', sortable: true },
|
{ name: 'stock_days_total', label: 'Genel Stok Gün', field: row => formatNumber(row.stock_days_total, 1), align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd_total', label: 'Genel Ciro/Adet USD', field: row => formatMoney(row.avg_price_usd_total, 'USD'), align: 'right', sortable: true },
|
{ name: 'avg_price_usd_total', label: 'Genel Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_total, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'base_price_usd', label: 'USD Taban Maliyeti', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'base_price_usd', label: 'USD Taban Maliyeti', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'cost_price_usd', label: 'USD Çıplak Maliyeti', field: row => formatMoney(row.cost_price_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'cost_price_usd', label: 'USD Çıplak Maliyeti', field: row => formatMoney(row.cost_price_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'unit_profit_base_total', label: 'P.Başı Taban K/Z', field: row => formatMoney(row.unit_profit_base_total, 'USD'), align: 'right', sortable: true },
|
{ name: 'unit_profit_base_total', label: 'P.Başı Taban K/Z', field: row => formatMoney(row.unit_profit_base_total, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2108,7 +2108,7 @@ const orderGroupColumns = [
|
|||||||
{ name: 'product_count', label: 'Ürün', field: row => formatNumber(row.product_count, 0), align: 'right', sortable: true },
|
{ name: 'product_count', label: 'Ürün', field: row => formatNumber(row.product_count, 0), align: 'right', sortable: true },
|
||||||
{ name: 'order_qty', label: 'Açık Sipariş', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
{ name: 'order_qty', label: 'Açık Sipariş', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||||
{ name: 'order_usd', label: 'Sipariş USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'order_usd', label: 'Sipariş USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_order_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'avg_order_price_usd', label: 'Ort. Sipariş Fiyatı USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'base_cost_usd', label: 'Taban Maliyet USD', field: row => formatMoney(row.base_cost_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'base_cost_usd', label: 'Taban Maliyet USD', field: row => formatMoney(row.base_cost_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'cost_amount_usd', label: 'Çıplak Maliyet USD', field: row => formatMoney(row.cost_amount_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'cost_amount_usd', label: 'Çıplak Maliyet USD', field: row => formatMoney(row.cost_amount_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'expected_profit_base_usd', label: 'Brüt K/Z', field: row => formatMoney(row.expected_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'expected_profit_base_usd', label: 'Brüt K/Z', field: row => formatMoney(row.expected_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2142,7 +2142,7 @@ const orderProductCustomerColumns = [
|
|||||||
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
{ name: 'yaka_kodu', label: 'Yaka', field: 'yaka_kodu', align: 'left', sortable: true },
|
||||||
{ name: 'order_qty', label: 'Açık Sipariş', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
{ name: 'order_qty', label: 'Açık Sipariş', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||||
{ name: 'order_usd', label: 'Sipariş USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'order_usd', label: 'Sipariş USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_order_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'avg_order_price_usd', label: 'Ort. Sipariş Fiyatı USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||||
{ name: 'net_stock_after_order', label: 'Stok - Sipariş', field: 'net_stock_after_order', align: 'right', sortable: true },
|
{ name: 'net_stock_after_order', label: 'Stok - Sipariş', field: 'net_stock_after_order', align: 'right', sortable: true },
|
||||||
{ name: 'base_price_usd', label: 'Taban USD', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'base_price_usd', label: 'Taban USD', field: row => formatMoney(row.base_price_usd, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2176,7 +2176,7 @@ const orderMarketDetailColumns = [
|
|||||||
{ name: 'due_date', label: 'Termin', field: 'due_date', align: 'left', sortable: true },
|
{ name: 'due_date', label: 'Termin', field: 'due_date', align: 'left', sortable: true },
|
||||||
{ name: 'order_qty', label: 'Adet', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
{ name: 'order_qty', label: 'Adet', field: row => formatNumber(row.order_qty, 0), align: 'right', sortable: true },
|
||||||
{ name: 'order_usd', label: 'USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'order_usd', label: 'USD', field: row => formatMoney(row.order_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_order_price_usd', label: 'Ort. USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'avg_order_price_usd', label: 'Ort. Sipariş Fiyatı USD', field: row => formatMoney(row.avg_order_price_usd, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
{ name: 'stock_qty', label: 'Stok', field: row => formatNumber(row.stock_qty, 0), align: 'right', sortable: true },
|
||||||
{ name: 'net_stock_after_order', label: 'Stok - Sipariş', field: 'net_stock_after_order', align: 'right', sortable: true },
|
{ name: 'net_stock_after_order', label: 'Stok - Sipariş', field: 'net_stock_after_order', align: 'right', sortable: true },
|
||||||
{ name: 'expected_profit_base_usd', label: 'Brüt K/Z', field: row => formatMoney(row.expected_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
{ name: 'expected_profit_base_usd', label: 'Brüt K/Z', field: row => formatMoney(row.expected_profit_base_usd, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2205,7 +2205,7 @@ const idleColumns = [
|
|||||||
{ name: 'idle_cost_usd', label: 'Stok Maliyeti USD', field: 'idle_cost_usd', align: 'right', sortable: true },
|
{ name: 'idle_cost_usd', label: 'Stok Maliyeti USD', field: 'idle_cost_usd', align: 'right', sortable: true },
|
||||||
{ name: 'sales_qty_90d', label: '90G Satış', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
{ name: 'sales_qty_90d', label: '90G Satış', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'stock_days_90d', label: 'Stok Gün', field: row => formatNumber(row.stock_days_90d, 1), align: 'right', sortable: true },
|
{ name: 'stock_days_90d', label: 'Stok Gün', field: row => formatNumber(row.stock_days_90d, 1), align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_90d', label: '90G Stok Devir', field: row => formatNumber(row.stock_turnover_90d, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_90d', label: '90G Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_90d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left' },
|
{ name: 'performance_bucket', label: 'Durum', field: 'performance_bucket', align: 'left' },
|
||||||
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
|
{ name: 'recommendation', label: 'Öneri', field: 'recommendation', align: 'left' }
|
||||||
]
|
]
|
||||||
@@ -2236,7 +2236,7 @@ const countryColumns = [
|
|||||||
{ name: 'product_count', label: 'Ürün', field: row => formatNumber(row.product_count, 0), align: 'right', sortable: true },
|
{ name: 'product_count', label: 'Ürün', field: row => formatNumber(row.product_count, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_usd_90d', label: '90G USD', field: row => formatMoney(row.sales_usd_90d, 'USD'), align: 'right', sortable: true },
|
{ name: 'sales_usd_90d', label: '90G USD', field: row => formatMoney(row.sales_usd_90d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd_90d', label: 'Ort. USD', field: row => formatMoney(row.avg_price_usd_90d, 'USD'), align: 'right', sortable: true },
|
{ name: 'avg_price_usd_90d', label: 'Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_90d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'customer_count_90d', label: 'Müşteri', field: row => formatNumber(row.customer_count_90d, 0), align: 'right', sortable: true },
|
{ name: 'customer_count_90d', label: 'Müşteri', field: row => formatNumber(row.customer_count_90d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'invoice_count_90d', label: 'Fatura', field: row => formatNumber(row.invoice_count_90d, 0), align: 'right', sortable: true },
|
{ name: 'invoice_count_90d', label: 'Fatura', field: row => formatNumber(row.invoice_count_90d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_qty_365d', label: '360G Adet', field: row => formatNumber(row.sales_qty_365d, 0), align: 'right', sortable: true },
|
{ name: 'sales_qty_365d', label: '360G Adet', field: row => formatNumber(row.sales_qty_365d, 0), align: 'right', sortable: true },
|
||||||
@@ -2252,7 +2252,7 @@ const customerColumns = [
|
|||||||
{ name: 'product_count', label: 'Ürün', field: row => formatNumber(row.product_count, 0), align: 'right', sortable: true },
|
{ name: 'product_count', label: 'Ürün', field: row => formatNumber(row.product_count, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_usd_90d', label: '90G USD', field: 'sales_usd_90d', align: 'right', sortable: true },
|
{ name: 'sales_usd_90d', label: '90G USD', field: 'sales_usd_90d', align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd_90d', label: 'Ort. USD', field: 'avg_price_usd_90d', align: 'right', sortable: true },
|
{ name: 'avg_price_usd_90d', label: 'Ort. Satış Fiyatı USD', field: 'avg_price_usd_90d', align: 'right', sortable: true },
|
||||||
{ name: 'customer_score_90d', label: '90G Müşteri Skor', field: row => formatNumber(row.customer_score_90d, 2), align: 'right', sortable: true },
|
{ name: 'customer_score_90d', label: '90G Müşteri Skor', field: row => formatNumber(row.customer_score_90d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'base_price_usd_90d', label: '90G Taban', field: row => formatMoney(row.base_price_usd_90d, 'USD'), align: 'right', sortable: true },
|
{ name: 'base_price_usd_90d', label: '90G Taban', field: row => formatMoney(row.base_price_usd_90d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'cost_price_usd_90d', label: '90G Çıplak', field: row => formatMoney(row.cost_price_usd_90d, 'USD'), align: 'right', sortable: true },
|
{ name: 'cost_price_usd_90d', label: '90G Çıplak', field: row => formatMoney(row.cost_price_usd_90d, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2287,14 +2287,14 @@ const salesBreakdownColumns = [
|
|||||||
{ name: 'invoice_count_90d', label: '90G Fatura', field: row => formatNumber(row.invoice_count_90d, 0), align: 'right', sortable: true },
|
{ name: 'invoice_count_90d', label: '90G Fatura', field: row => formatNumber(row.invoice_count_90d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
{ name: 'sales_qty_90d', label: '90G Adet', field: row => formatNumber(row.sales_qty_90d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_usd_90d', label: '90G USD', field: 'sales_usd_90d', align: 'right', sortable: true },
|
{ name: 'sales_usd_90d', label: '90G USD', field: 'sales_usd_90d', align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd_90d', label: 'Ort. USD', field: 'avg_price_usd_90d', align: 'right', sortable: true },
|
{ name: 'avg_price_usd_90d', label: '90G Ort. Satış Fiyatı USD', field: 'avg_price_usd_90d', align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_90d', label: '90G Stok Devir', field: row => formatNumber(row.stock_turnover_90d, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_90d', label: '90G Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_90d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'customer_count_180d', label: '180G Müşteri', field: row => formatNumber(row.customer_count_180d, 0), align: 'right', sortable: true },
|
{ name: 'customer_count_180d', label: '180G Müşteri', field: row => formatNumber(row.customer_count_180d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'invoice_count_180d', label: '180G Fatura', field: row => formatNumber(row.invoice_count_180d, 0), align: 'right', sortable: true },
|
{ name: 'invoice_count_180d', label: '180G Fatura', field: row => formatNumber(row.invoice_count_180d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_qty_180d', label: '180G Adet', field: row => formatNumber(row.sales_qty_180d, 0), align: 'right', sortable: true },
|
{ name: 'sales_qty_180d', label: '180G Adet', field: row => formatNumber(row.sales_qty_180d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_usd_180d', label: '180G USD', field: row => formatMoney(row.sales_usd_180d, 'USD'), align: 'right', sortable: true },
|
{ name: 'sales_usd_180d', label: '180G USD', field: row => formatMoney(row.sales_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd_180d', label: '180G Ort. USD', field: row => formatMoney(row.avg_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
{ name: 'avg_price_usd_180d', label: '180G Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_180d', label: '180G Stok Devir', field: row => formatNumber(row.stock_turnover_180d, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_180d', label: '180G Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_180d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'base_price_usd_180d', label: '180G Taban', field: row => formatMoney(row.base_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
{ name: 'base_price_usd_180d', label: '180G Taban', field: row => formatMoney(row.base_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'cost_price_usd_180d', label: '180G Çıplak', field: row => formatMoney(row.cost_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
{ name: 'cost_price_usd_180d', label: '180G Çıplak', field: row => formatMoney(row.cost_price_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'gross_profit_base_usd_180d', label: '180G Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_180d, 'USD'), align: 'right', sortable: true },
|
{ name: 'gross_profit_base_usd_180d', label: '180G Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_180d, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2304,8 +2304,8 @@ const salesBreakdownColumns = [
|
|||||||
{ name: 'customer_score_180d', label: '180G Müşteri Skor', field: row => formatNumber(row.customer_score_180d, 2), align: 'right', sortable: true },
|
{ name: 'customer_score_180d', label: '180G Müşteri Skor', field: row => formatNumber(row.customer_score_180d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'sales_qty_365d', label: '360G Adet', field: row => formatNumber(row.sales_qty_365d, 0), align: 'right', sortable: true },
|
{ name: 'sales_qty_365d', label: '360G Adet', field: row => formatNumber(row.sales_qty_365d, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_usd_365d', label: '360G USD', field: row => formatMoney(row.sales_usd_365d, 'USD'), align: 'right', sortable: true },
|
{ name: 'sales_usd_365d', label: '360G USD', field: row => formatMoney(row.sales_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd_365d', label: '360G Ort. USD', field: row => formatMoney(row.avg_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
{ name: 'avg_price_usd_365d', label: '360G Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_365d', label: '360G Stok Devir', field: row => formatNumber(row.stock_turnover_365d, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_365d', label: '360G Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_365d, 2), align: 'right', sortable: true },
|
||||||
{ name: 'base_price_usd_365d', label: '360G Taban', field: row => formatMoney(row.base_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
{ name: 'base_price_usd_365d', label: '360G Taban', field: row => formatMoney(row.base_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'cost_price_usd_365d', label: '360G Çıplak', field: row => formatMoney(row.cost_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
{ name: 'cost_price_usd_365d', label: '360G Çıplak', field: row => formatMoney(row.cost_price_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'gross_profit_base_usd_365d', label: '360G Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_365d, 'USD'), align: 'right', sortable: true },
|
{ name: 'gross_profit_base_usd_365d', label: '360G Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_365d, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2317,8 +2317,8 @@ const salesBreakdownColumns = [
|
|||||||
{ name: 'invoice_count_total', label: 'Genel Fatura', field: row => formatNumber(row.invoice_count_total, 0), align: 'right', sortable: true },
|
{ name: 'invoice_count_total', label: 'Genel Fatura', field: row => formatNumber(row.invoice_count_total, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_qty_total', label: 'Genel Adet', field: row => formatNumber(row.sales_qty_total, 0), align: 'right', sortable: true },
|
{ name: 'sales_qty_total', label: 'Genel Adet', field: row => formatNumber(row.sales_qty_total, 0), align: 'right', sortable: true },
|
||||||
{ name: 'sales_usd_total', label: 'Genel USD', field: row => formatMoney(row.sales_usd_total, 'USD'), align: 'right', sortable: true },
|
{ name: 'sales_usd_total', label: 'Genel USD', field: row => formatMoney(row.sales_usd_total, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'avg_price_usd_total', label: 'Genel Ort. USD', field: row => formatMoney(row.avg_price_usd_total, 'USD'), align: 'right', sortable: true },
|
{ name: 'avg_price_usd_total', label: 'Genel Ort. Satış Fiyatı USD', field: row => formatMoney(row.avg_price_usd_total, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'stock_turnover_total', label: 'Genel Stok Devir', field: row => formatNumber(row.stock_turnover_total, 2), align: 'right', sortable: true },
|
{ name: 'stock_turnover_total', label: 'Genel Stok Devir Hızı', field: row => formatNumber(row.stock_turnover_total, 2), align: 'right', sortable: true },
|
||||||
{ name: 'base_price_usd_total', label: 'Genel Taban', field: row => formatMoney(row.base_price_usd_total, 'USD'), align: 'right', sortable: true },
|
{ name: 'base_price_usd_total', label: 'Genel Taban', field: row => formatMoney(row.base_price_usd_total, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'cost_price_usd_total', label: 'Genel Çıplak', field: row => formatMoney(row.cost_price_usd_total, 'USD'), align: 'right', sortable: true },
|
{ name: 'cost_price_usd_total', label: 'Genel Çıplak', field: row => formatMoney(row.cost_price_usd_total, 'USD'), align: 'right', sortable: true },
|
||||||
{ name: 'gross_profit_base_usd_total', label: 'Genel Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_total, 'USD'), align: 'right', sortable: true },
|
{ name: 'gross_profit_base_usd_total', label: 'Genel Taban K/Z', field: row => formatMoney(row.gross_profit_base_usd_total, 'USD'), align: 'right', sortable: true },
|
||||||
@@ -2359,11 +2359,11 @@ const metricLabelOverrides = {
|
|||||||
sales_usd_180d: '180G Toplam Ciro USD',
|
sales_usd_180d: '180G Toplam Ciro USD',
|
||||||
sales_usd_365d: '360G Toplam Ciro USD',
|
sales_usd_365d: '360G Toplam Ciro USD',
|
||||||
sales_usd_total: 'Genel Toplam Ciro USD',
|
sales_usd_total: 'Genel Toplam Ciro USD',
|
||||||
avg_price_usd: 'Ciro/Adet USD',
|
avg_price_usd: 'Ort. Satış Fiyatı USD',
|
||||||
avg_price_usd_90d: '90G Ciro/Adet USD',
|
avg_price_usd_90d: '90G Ort. Satış Fiyatı USD',
|
||||||
avg_price_usd_180d: '180G Ciro/Adet USD',
|
avg_price_usd_180d: '180G Ort. Satış Fiyatı USD',
|
||||||
avg_price_usd_365d: '360G Ciro/Adet USD',
|
avg_price_usd_365d: '360G Ort. Satış Fiyatı USD',
|
||||||
avg_price_usd_total: 'Genel Ciro/Adet USD',
|
avg_price_usd_total: 'Genel Ort. Satış Fiyatı USD',
|
||||||
base_price_usd: 'Taban Maliyet USD',
|
base_price_usd: 'Taban Maliyet USD',
|
||||||
cost_price_usd: 'Çıplak Maliyet USD',
|
cost_price_usd: 'Çıplak Maliyet USD',
|
||||||
base_price_usd_90d: '90G Taban Maliyet USD',
|
base_price_usd_90d: '90G Taban Maliyet USD',
|
||||||
@@ -2412,10 +2412,10 @@ const metricLabelOverrides = {
|
|||||||
customer_count_90d: '90G Tekil Müşteri',
|
customer_count_90d: '90G Tekil Müşteri',
|
||||||
market_count_total: 'Genel Tekil Piyasa',
|
market_count_total: 'Genel Tekil Piyasa',
|
||||||
customer_count_total: 'Genel Tekil Müşteri',
|
customer_count_total: 'Genel Tekil Müşteri',
|
||||||
stock_turnover_90d: '90G Satış/Stok',
|
stock_turnover_90d: '90G Stok Devir Hızı',
|
||||||
stock_turnover_180d: '180G Satış/Stok',
|
stock_turnover_180d: '180G Stok Devir Hızı',
|
||||||
stock_turnover_365d: '360G Satış/Stok',
|
stock_turnover_365d: '360G Stok Devir Hızı',
|
||||||
stock_turnover_total: 'Genel Satış/Stok',
|
stock_turnover_total: 'Genel Stok Devir Hızı',
|
||||||
sales_index_90d: '90G Endeks',
|
sales_index_90d: '90G Endeks',
|
||||||
sales_index_total: 'Genel Endeks',
|
sales_index_total: 'Genel Endeks',
|
||||||
stock_days_90d: '90G Stok Gün',
|
stock_days_90d: '90G Stok Gün',
|
||||||
@@ -2645,7 +2645,7 @@ function performanceCardsForRow (row) {
|
|||||||
{ key: 'sales180', label: '180G Satış', value: formatNumber(source.sales_qty_180d, 0) },
|
{ key: 'sales180', label: '180G Satış', value: formatNumber(source.sales_qty_180d, 0) },
|
||||||
{ key: 'markets', label: '90G Piyasa', value: formatNumber(source.market_count_90d, 0) },
|
{ key: 'markets', label: '90G Piyasa', value: formatNumber(source.market_count_90d, 0) },
|
||||||
{ key: 'customers', label: '90G Müşteri', value: formatNumber(source.customer_count_90d, 0) },
|
{ key: 'customers', label: '90G Müşteri', value: formatNumber(source.customer_count_90d, 0) },
|
||||||
{ key: 'avg', label: 'Ort. USD Satış', value: formatMoney(source.avg_price_usd_90d, 'USD') },
|
{ key: 'avg', label: 'Ort. Satış Fiyatı', value: formatMoney(source.avg_price_usd_90d, 'USD') },
|
||||||
{ key: 'base', label: 'Taban Maliyet', value: formatMoney(source.base_price_usd, 'USD') },
|
{ key: 'base', label: 'Taban Maliyet', value: formatMoney(source.base_price_usd, 'USD') },
|
||||||
{ key: 'cost', label: 'Çıplak Maliyet', value: formatMoney(source.cost_price_usd, 'USD') },
|
{ key: 'cost', label: 'Çıplak Maliyet', value: formatMoney(source.cost_price_usd, 'USD') },
|
||||||
{ key: 'profitBase', label: 'P.Başı Taban K/Z', value: formatMoney(source.unit_profit_base_90d, 'USD') },
|
{ key: 'profitBase', label: 'P.Başı Taban K/Z', value: formatMoney(source.unit_profit_base_90d, 'USD') },
|
||||||
@@ -3470,7 +3470,7 @@ function shouldSumField (field) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function shouldAverageField (field) {
|
function shouldAverageField (field) {
|
||||||
return /^(avg_|unit_|base_price|cost_price|gross_margin|expected_margin|sales_index|performance_score|stock_days|stock_turnover)/i.test(field) ||
|
return /^(avg_|unit_|base_price|cost_price|gross_margin|expected_margin|sales_index|performance_score|customer_score|stock_days|stock_turnover)/i.test(field) ||
|
||||||
/(_price_usd|_margin|_index|_days)$/i.test(field)
|
/(_price_usd|_margin|_index|_days)$/i.test(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3484,21 +3484,35 @@ function weightFieldForMetric (field) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
||||||
|
Object.assign(out, normalizeProductCostFields(out))
|
||||||
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||||
const sales = Number(out[`sales_usd_${suffix}`] || 0)
|
const sales = Number(out[`sales_usd_${suffix}`] || 0)
|
||||||
const qty = Number(out[`sales_qty_${suffix}`] || 0)
|
const qty = Number(out[`sales_qty_${suffix}`] || 0)
|
||||||
|
const stockQty = Number(out.stock_qty || 0)
|
||||||
|
const avgStock = Number(out[`avg_stock_${suffix}`] || 0)
|
||||||
|
const turnoverBase = avgStock > 0 ? avgStock : stockQty
|
||||||
|
out[`stock_turnover_${suffix}`] = turnoverBase > 0 ? qty / turnoverBase : 0
|
||||||
if (qty > 0) out[`avg_price_usd_${suffix}`] = sales / qty
|
if (qty > 0) out[`avg_price_usd_${suffix}`] = sales / qty
|
||||||
if (sales > 0) {
|
const { costPrice, basePrice } = periodCostPair(out, suffix)
|
||||||
if (out[`gross_profit_base_usd_${suffix}`] !== undefined) {
|
out[`base_price_usd_${suffix}`] = basePrice
|
||||||
out[`gross_margin_base_${suffix}`] = Number(out[`gross_profit_base_usd_${suffix}`] || 0) / sales
|
out[`cost_price_usd_${suffix}`] = costPrice
|
||||||
}
|
if (qty > 0) {
|
||||||
if (out[`gross_profit_cost_usd_${suffix}`] !== undefined) {
|
const avgPrice = sales / qty
|
||||||
out[`gross_margin_cost_${suffix}`] = Number(out[`gross_profit_cost_usd_${suffix}`] || 0) / sales
|
out[`unit_profit_base_${suffix}`] = avgPrice - basePrice
|
||||||
}
|
out[`unit_profit_cost_${suffix}`] = avgPrice - costPrice
|
||||||
if (out[`gross_profit_usd_${suffix}`] !== undefined) {
|
out[`gross_profit_base_usd_${suffix}`] = sales - qty * basePrice
|
||||||
out[`gross_margin_${suffix}`] = Number(out[`gross_profit_usd_${suffix}`] || 0) / sales
|
out[`gross_profit_cost_usd_${suffix}`] = sales - qty * costPrice
|
||||||
}
|
out[`gross_profit_usd_${suffix}`] = out[`gross_profit_cost_usd_${suffix}`]
|
||||||
|
} else {
|
||||||
|
out[`unit_profit_base_${suffix}`] = 0
|
||||||
|
out[`unit_profit_cost_${suffix}`] = 0
|
||||||
|
out[`gross_profit_base_usd_${suffix}`] = 0
|
||||||
|
out[`gross_profit_cost_usd_${suffix}`] = 0
|
||||||
|
out[`gross_profit_usd_${suffix}`] = 0
|
||||||
}
|
}
|
||||||
|
out[`gross_margin_base_${suffix}`] = sales > 0 ? Number(out[`gross_profit_base_usd_${suffix}`] || 0) / sales : 0
|
||||||
|
out[`gross_margin_cost_${suffix}`] = sales > 0 ? Number(out[`gross_profit_cost_usd_${suffix}`] || 0) / sales : 0
|
||||||
|
out[`gross_margin_${suffix}`] = sales > 0 ? Number(out[`gross_profit_usd_${suffix}`] || 0) / sales : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderUsd = Number(out.order_usd || 0)
|
const orderUsd = Number(out.order_usd || 0)
|
||||||
@@ -3524,7 +3538,11 @@ function applyDerivedGroupMetrics (out, sourceRows, groupField = '') {
|
|||||||
if (!Object.prototype.hasOwnProperty.call(out, 'performance_score_180d')) out.performance_score_180d = productSalesPeriodScore(productPeriodMetricSource(out, '180d'))
|
if (!Object.prototype.hasOwnProperty.call(out, 'performance_score_180d')) out.performance_score_180d = productSalesPeriodScore(productPeriodMetricSource(out, '180d'))
|
||||||
if (!Object.prototype.hasOwnProperty.call(out, 'performance_score_365d')) out.performance_score_365d = productSalesPeriodScore(productPeriodMetricSource(out, '365d'))
|
if (!Object.prototype.hasOwnProperty.call(out, 'performance_score_365d')) out.performance_score_365d = productSalesPeriodScore(productPeriodMetricSource(out, '365d'))
|
||||||
if (!Object.prototype.hasOwnProperty.call(out, 'performance_score_total')) out.performance_score_total = productSalesPeriodScore(productPeriodMetricSource(out, 'total'))
|
if (!Object.prototype.hasOwnProperty.call(out, 'performance_score_total')) out.performance_score_total = productSalesPeriodScore(productPeriodMetricSource(out, 'total'))
|
||||||
if (!Object.prototype.hasOwnProperty.call(out, 'performance_score')) out.performance_score = groupPerformanceScore(out, groupField)
|
if (!Object.prototype.hasOwnProperty.call(out, 'performance_score')) {
|
||||||
|
out.performance_score = Object.prototype.hasOwnProperty.call(out, 'performance_score_90d')
|
||||||
|
? Number(out.performance_score_90d || 0)
|
||||||
|
: groupPerformanceScore(out, groupField)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupPerformanceScore (row, groupField = '') {
|
function groupPerformanceScore (row, groupField = '') {
|
||||||
@@ -3837,6 +3855,9 @@ function weightedAverage (sourceRows, amountField, qtyField) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function weightedAverageOrAverage (sourceRows, valueField, qtyField) {
|
function weightedAverageOrAverage (sourceRows, valueField, qtyField) {
|
||||||
|
if (/^(performance_score|customer_score)/i.test(valueField)) {
|
||||||
|
return weightedAverageScore(sourceRows, valueField, scoreSuffixForMetric(valueField))
|
||||||
|
}
|
||||||
const weightedRows = sourceRows
|
const weightedRows = sourceRows
|
||||||
.map(row => ({
|
.map(row => ({
|
||||||
value: Number(row[valueField] || 0),
|
value: Number(row[valueField] || 0),
|
||||||
@@ -3850,6 +3871,39 @@ function weightedAverageOrAverage (sourceRows, valueField, qtyField) {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scoreSuffixForMetric (field) {
|
||||||
|
if (field.includes('_180d')) return '180d'
|
||||||
|
if (field.includes('_365d')) return '365d'
|
||||||
|
if (field.includes('_total')) return 'total'
|
||||||
|
return '90d'
|
||||||
|
}
|
||||||
|
|
||||||
|
function scoreWeight (row, suffix) {
|
||||||
|
const salesUsd = Number(row?.[`sales_usd_${suffix}`] || 0)
|
||||||
|
if (salesUsd > 0) return salesUsd
|
||||||
|
const salesQty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
||||||
|
if (salesQty > 0) return salesQty
|
||||||
|
const stockQty = Number(row?.stock_qty || 0)
|
||||||
|
if (stockQty <= 0) return 1
|
||||||
|
const costPrice = Number(row?.[`cost_price_usd_${suffix}`] || row?.cost_price_usd || 0)
|
||||||
|
return costPrice > 0 ? stockQty * costPrice : stockQty
|
||||||
|
}
|
||||||
|
|
||||||
|
function weightedAverageScore (sourceRows, valueField, suffix) {
|
||||||
|
const rows = sourceRows
|
||||||
|
.filter(row => Object.prototype.hasOwnProperty.call(row || {}, valueField))
|
||||||
|
.map(row => ({
|
||||||
|
value: Number(row[valueField] || 0),
|
||||||
|
weight: scoreWeight(row, suffix)
|
||||||
|
}))
|
||||||
|
.filter(row => Number.isFinite(row.value) && Number.isFinite(row.weight))
|
||||||
|
const weight = rows.reduce((sum, row) => sum + Math.max(0, row.weight), 0)
|
||||||
|
if (weight > 0) {
|
||||||
|
return rows.reduce((sum, row) => sum + row.value * Math.max(0, row.weight), 0) / weight
|
||||||
|
}
|
||||||
|
return rows.length ? rows.reduce((sum, row) => sum + row.value, 0) / rows.length : 0
|
||||||
|
}
|
||||||
|
|
||||||
function weightedAverageProductCost (sourceRows, valueField) {
|
function weightedAverageProductCost (sourceRows, valueField) {
|
||||||
const salesRows = sourceRows
|
const salesRows = sourceRows
|
||||||
.map(row => ({
|
.map(row => ({
|
||||||
@@ -4068,53 +4122,93 @@ function formatGroupCell (row, colOrName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function withProductMargins (row) {
|
function withProductMargins (row) {
|
||||||
const salesQtyTotal = Number(row?.sales_qty_total || 0)
|
const next = normalizeProductCostFields(row)
|
||||||
const salesUSDTotal = Number(row?.sales_usd_total || 0)
|
const out = {
|
||||||
const basePrice = Number(row?.base_price_usd || 0)
|
...next,
|
||||||
const costPrice = Number(row?.cost_price_usd || 0)
|
avg_price_usd_365d: Number(next?.sales_qty_365d || 0) > 0 ? Number(next?.sales_usd_365d || 0) / Number(next?.sales_qty_365d || 0) : 0,
|
||||||
const grossProfitBaseTotal = salesQtyTotal > 0 ? salesUSDTotal - (salesQtyTotal * basePrice) : 0
|
stock_turnover_90d: existingOrStockTurnover(next?.stock_turnover_90d, next?.sales_qty_90d, next?.stock_qty),
|
||||||
const grossProfitCostTotal = salesQtyTotal > 0 ? salesUSDTotal - (salesQtyTotal * costPrice) : 0
|
stock_turnover_180d: existingOrStockTurnover(next?.stock_turnover_180d, next?.sales_qty_180d, next?.stock_qty),
|
||||||
return {
|
stock_turnover_365d: existingOrStockTurnover(next?.stock_turnover_365d, next?.sales_qty_365d, next?.stock_qty),
|
||||||
...row,
|
stock_turnover_total: existingOrStockTurnover(next?.stock_turnover_total, next?.sales_qty_total, next?.stock_qty)
|
||||||
avg_price_usd_365d: Number(row?.sales_qty_365d || 0) > 0 ? Number(row?.sales_usd_365d || 0) / Number(row?.sales_qty_365d || 0) : 0,
|
|
||||||
avg_price_usd_total: salesQtyTotal > 0 ? salesUSDTotal / salesQtyTotal : Number(row?.avg_price_usd_total || 0),
|
|
||||||
stock_turnover_90d: existingOrStockTurnover(row?.stock_turnover_90d, row?.sales_qty_90d, row?.stock_qty),
|
|
||||||
stock_turnover_180d: existingOrStockTurnover(row?.stock_turnover_180d, row?.sales_qty_180d, row?.stock_qty),
|
|
||||||
stock_turnover_365d: existingOrStockTurnover(row?.stock_turnover_365d, row?.sales_qty_365d, row?.stock_qty),
|
|
||||||
stock_turnover_total: existingOrStockTurnover(row?.stock_turnover_total, row?.sales_qty_total, row?.stock_qty),
|
|
||||||
gross_profit_base_usd_total: grossProfitBaseTotal,
|
|
||||||
gross_profit_cost_usd_total: grossProfitCostTotal,
|
|
||||||
gross_profit_usd_total: Number(row?.gross_profit_usd_total ?? grossProfitCostTotal),
|
|
||||||
gross_margin_base_90d: marginFromSalesCost(row?.sales_usd_90d, row?.sales_qty_90d, row?.base_price_usd),
|
|
||||||
gross_margin_cost_90d: marginFromSalesCost(row?.sales_usd_90d, row?.sales_qty_90d, row?.cost_price_usd),
|
|
||||||
gross_margin_base_180d: marginFromSalesCost(row?.sales_usd_180d, row?.sales_qty_180d, row?.base_price_usd),
|
|
||||||
gross_margin_cost_180d: marginFromSalesCost(row?.sales_usd_180d, row?.sales_qty_180d, row?.cost_price_usd),
|
|
||||||
gross_margin_base_365d: marginFromSalesCost(row?.sales_usd_365d, row?.sales_qty_365d, row?.base_price_usd),
|
|
||||||
gross_margin_cost_365d: marginFromSalesCost(row?.sales_usd_365d, row?.sales_qty_365d, row?.cost_price_usd),
|
|
||||||
gross_margin_base_total: marginFromSalesCost(salesUSDTotal, salesQtyTotal, basePrice),
|
|
||||||
gross_margin_cost_total: marginFromSalesCost(salesUSDTotal, salesQtyTotal, costPrice)
|
|
||||||
}
|
}
|
||||||
|
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||||
|
applyPeriodProfitFields(out, suffix)
|
||||||
|
}
|
||||||
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
function withGeneralMargins (row) {
|
function withGeneralMargins (row) {
|
||||||
const salesQtyTotal = Number(row?.sales_qty_total || 0)
|
const out = normalizeProductCostFields(row)
|
||||||
const salesUSDTotal = Number(row?.sales_usd_total || 0)
|
applyPeriodProfitFields(out, 'total')
|
||||||
const basePrice = Number(row?.base_price_usd || 0)
|
|
||||||
const costPrice = Number(row?.cost_price_usd || 0)
|
|
||||||
const grossProfitBaseTotal = salesQtyTotal > 0 ? salesUSDTotal - (salesQtyTotal * basePrice) : 0
|
|
||||||
const grossProfitCostTotal = salesQtyTotal > 0 ? salesUSDTotal - (salesQtyTotal * costPrice) : 0
|
|
||||||
return {
|
return {
|
||||||
...row,
|
...out,
|
||||||
avg_price_usd_total: salesQtyTotal > 0 ? salesUSDTotal / salesQtyTotal : Number(row?.avg_price_usd_total || 0),
|
stock_turnover_total: existingOrStockTurnover(out?.stock_turnover_total, out?.sales_qty_total, out?.stock_qty)
|
||||||
stock_turnover_total: existingOrStockTurnover(row?.stock_turnover_total, row?.sales_qty_total, row?.stock_qty),
|
|
||||||
gross_profit_base_usd_total: grossProfitBaseTotal,
|
|
||||||
gross_profit_cost_usd_total: grossProfitCostTotal,
|
|
||||||
gross_profit_usd_total: Number(row?.gross_profit_usd_total ?? grossProfitCostTotal),
|
|
||||||
gross_margin_base_total: marginFromSalesCost(salesUSDTotal, salesQtyTotal, basePrice),
|
|
||||||
gross_margin_cost_total: marginFromSalesCost(salesUSDTotal, salesQtyTotal, costPrice)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeProductCostFields (row) {
|
||||||
|
const next = { ...row }
|
||||||
|
normalizeProductCostPairFields(next, 'cost_price_usd', 'base_price_usd')
|
||||||
|
for (const suffix of ['90d', '180d', '365d', 'total']) {
|
||||||
|
normalizeProductCostPairFields(next, `cost_price_usd_${suffix}`, `base_price_usd_${suffix}`)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeProductCostPairFields (row, costField, baseField) {
|
||||||
|
const hasCost = Object.prototype.hasOwnProperty.call(row || {}, costField)
|
||||||
|
const hasBase = Object.prototype.hasOwnProperty.call(row || {}, baseField)
|
||||||
|
if (!hasCost && !hasBase) return
|
||||||
|
let costPrice = Number(row?.[costField] || 0)
|
||||||
|
let basePrice = Number(row?.[baseField] || 0)
|
||||||
|
if (costPrice > 0 && basePrice > 0 && costPrice > basePrice) {
|
||||||
|
const tmp = costPrice
|
||||||
|
costPrice = basePrice
|
||||||
|
basePrice = tmp
|
||||||
|
}
|
||||||
|
if (hasCost || costPrice > 0) row[costField] = costPrice
|
||||||
|
if (hasBase || basePrice > 0) row[baseField] = basePrice
|
||||||
|
}
|
||||||
|
|
||||||
|
function periodCostPair (row, suffix) {
|
||||||
|
const costField = `cost_price_usd_${suffix}`
|
||||||
|
const baseField = `base_price_usd_${suffix}`
|
||||||
|
let costPrice = Number(row?.[costField] || 0)
|
||||||
|
let basePrice = Number(row?.[baseField] || 0)
|
||||||
|
if (costPrice <= 0) costPrice = Number(row?.cost_price_usd || 0)
|
||||||
|
if (basePrice <= 0) basePrice = Number(row?.base_price_usd || 0)
|
||||||
|
if (costPrice > 0 && basePrice > 0 && costPrice > basePrice) {
|
||||||
|
const tmp = costPrice
|
||||||
|
costPrice = basePrice
|
||||||
|
basePrice = tmp
|
||||||
|
}
|
||||||
|
return { costPrice, basePrice }
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPeriodProfitFields (row, suffix) {
|
||||||
|
const qty = Number(row?.[`sales_qty_${suffix}`] || 0)
|
||||||
|
const salesUSD = Number(row?.[`sales_usd_${suffix}`] || 0)
|
||||||
|
const { costPrice, basePrice } = periodCostPair(row, suffix)
|
||||||
|
row[`base_price_usd_${suffix}`] = basePrice
|
||||||
|
row[`cost_price_usd_${suffix}`] = costPrice
|
||||||
|
if (qty > 0) {
|
||||||
|
row[`avg_price_usd_${suffix}`] = salesUSD / qty
|
||||||
|
row[`unit_profit_base_${suffix}`] = salesUSD / qty - basePrice
|
||||||
|
row[`unit_profit_cost_${suffix}`] = salesUSD / qty - costPrice
|
||||||
|
row[`gross_profit_base_usd_${suffix}`] = salesUSD - qty * basePrice
|
||||||
|
row[`gross_profit_cost_usd_${suffix}`] = salesUSD - qty * costPrice
|
||||||
|
row[`gross_profit_usd_${suffix}`] = row[`gross_profit_cost_usd_${suffix}`]
|
||||||
|
} else {
|
||||||
|
row[`unit_profit_base_${suffix}`] = 0
|
||||||
|
row[`unit_profit_cost_${suffix}`] = 0
|
||||||
|
row[`gross_profit_base_usd_${suffix}`] = 0
|
||||||
|
row[`gross_profit_cost_usd_${suffix}`] = 0
|
||||||
|
row[`gross_profit_usd_${suffix}`] = 0
|
||||||
|
}
|
||||||
|
row[`gross_margin_base_${suffix}`] = marginFromSalesCost(salesUSD, qty, basePrice)
|
||||||
|
row[`gross_margin_cost_${suffix}`] = marginFromSalesCost(salesUSD, qty, costPrice)
|
||||||
|
}
|
||||||
|
|
||||||
function stockTurnover (salesQty, stockQty) {
|
function stockTurnover (salesQty, stockQty) {
|
||||||
const stock = Number(stockQty || 0)
|
const stock = Number(stockQty || 0)
|
||||||
if (stock <= 0) return 0
|
if (stock <= 0) return 0
|
||||||
@@ -4461,11 +4555,21 @@ function formatProductCell (row, name) {
|
|||||||
case 'gross_profit_usd_90d':
|
case 'gross_profit_usd_90d':
|
||||||
case 'gross_profit_usd_180d':
|
case 'gross_profit_usd_180d':
|
||||||
case 'gross_profit_usd_total':
|
case 'gross_profit_usd_total':
|
||||||
|
case 'gross_profit_base_usd_90d':
|
||||||
|
case 'gross_profit_cost_usd_90d':
|
||||||
|
case 'gross_profit_base_usd_180d':
|
||||||
|
case 'gross_profit_cost_usd_180d':
|
||||||
|
case 'gross_profit_base_usd_365d':
|
||||||
|
case 'gross_profit_cost_usd_365d':
|
||||||
|
case 'gross_profit_base_usd_total':
|
||||||
|
case 'gross_profit_cost_usd_total':
|
||||||
return formatMoney(row[name], 'USD')
|
return formatMoney(row[name], 'USD')
|
||||||
case 'gross_margin_base_90d':
|
case 'gross_margin_base_90d':
|
||||||
case 'gross_margin_cost_90d':
|
case 'gross_margin_cost_90d':
|
||||||
case 'gross_margin_base_180d':
|
case 'gross_margin_base_180d':
|
||||||
case 'gross_margin_cost_180d':
|
case 'gross_margin_cost_180d':
|
||||||
|
case 'gross_margin_base_365d':
|
||||||
|
case 'gross_margin_cost_365d':
|
||||||
case 'gross_margin_base_total':
|
case 'gross_margin_base_total':
|
||||||
case 'gross_margin_cost_total':
|
case 'gross_margin_cost_total':
|
||||||
case 'gross_margin_90d':
|
case 'gross_margin_90d':
|
||||||
@@ -5045,7 +5149,7 @@ async function loadBackendGroupedRows (options = {}) {
|
|||||||
function normalizeGroupedDisplayRow (tabKey, row) {
|
function normalizeGroupedDisplayRow (tabKey, row) {
|
||||||
const nextRow = { ...row }
|
const nextRow = { ...row }
|
||||||
normalizeGroupedProductHierarchyValues(nextRow)
|
normalizeGroupedProductHierarchyValues(nextRow)
|
||||||
return nextRow
|
return withPeriodScores(withGeneralMargins(withProductMargins(nextRow)))
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeGroupedProductHierarchyValues (row) {
|
function normalizeGroupedProductHierarchyValues (row) {
|
||||||
|
|||||||
Reference in New Issue
Block a user