'Categorie', 'brand' => 'Brand', 'range' => 'Interval preț', ]; protected $fillable = [ 'company_id', 'type', 'key', 'range_from', 'range_to', 'markup_pct', 'priority', 'is_active', ]; protected $casts = [ 'range_from' => 'decimal:2', 'range_to' => 'decimal:2', 'markup_pct' => 'decimal:2', 'is_active' => 'boolean', ]; /** * Find best matching rule for a given Part. Priority order: * 1) brand match 2) category match 3) range (buy_price intersect) 4) default 30%. */ public static function bestForPart(Part $part): ?self { // Brand exact if ($part->brand) { $r = static::where('type', 'brand') ->where('is_active', true) ->where('key', $part->brand) ->orderBy('priority')->first(); if ($r) return $r; } // Category exact if ($part->category) { $r = static::where('type', 'category') ->where('is_active', true) ->where('key', $part->category) ->orderBy('priority')->first(); if ($r) return $r; } // Range (buy_price ∈ [from, to)) $price = (float) $part->buy_price; $r = static::where('type', 'range') ->where('is_active', true) ->where('range_from', '<=', $price) ->where(function ($q) use ($price) { $q->whereNull('range_to')->orWhere('range_to', '>', $price); }) ->orderBy('priority')->orderByDesc('range_from')->first(); return $r; } public static function applyToPart(Part $part): bool { $rule = static::bestForPart($part); $pct = $rule?->markup_pct ?? 30; $part->sell_price = round((float) $part->buy_price * (1 + (float) $pct / 100), 2); return $part->save(); } }