} */ public function quote( Part $part, ?Vehicle $vehicle = null, ?Client $client = null, string $urgency = 'normal', ): array { $base = $this->basePrice($part); $ctx = [ 'class' => $this->vehicleClass($vehicle), 'age' => $this->vehicleAge($vehicle), 'vip' => (bool) ($client?->is_vip), 'urgency' => $urgency ?: 'normal', ]; $coefficients = PricingCoefficient::where('is_active', true) ->orderBy('priority') ->get() ->filter(fn (PricingCoefficient $c) => $c->matches($ctx)); $applied = []; $factor = 1.0; // Stackable: multiply all. foreach ($coefficients->where('stackable', true) as $c) { $factor *= (float) $c->multiplier; $applied[] = ['name' => $c->name, 'multiplier' => (float) $c->multiplier]; } // Non-stackable: take only the strongest one. $nonStack = $coefficients->where('stackable', false) ->sortByDesc(fn ($c) => (float) $c->multiplier) ->first(); if ($nonStack) { $factor *= (float) $nonStack->multiplier; $applied[] = ['name' => $nonStack->name, 'multiplier' => (float) $nonStack->multiplier]; } return [ 'base' => round($base, 2), 'final' => round($base * $factor, 2), 'applied' => $applied, ]; } private function basePrice(Part $part): float { $rule = MarkupRule::bestForPart($part); if ($rule) { return (float) $part->buy_price * (1 + (float) $rule->markup_pct / 100); } // Fall back to existing sell_price, or buy_price + 30%. if ((float) $part->sell_price > 0) return (float) $part->sell_price; return (float) $part->buy_price * 1.30; } /** Explicit vehicle_class, else inferred from fuel (hybrid/EV). */ private function vehicleClass(?Vehicle $vehicle): ?string { if (! $vehicle) return null; if ($vehicle->vehicle_class) return $vehicle->vehicle_class; $fuel = mb_strtolower((string) $vehicle->fuel); if (str_contains($fuel, 'hybrid') || str_contains($fuel, 'hibrid')) return 'hybrid'; if (str_contains($fuel, 'electric') || $fuel === 'ev' || str_contains($fuel, 'electr')) return 'ev'; return null; } private function vehicleAge(?Vehicle $vehicle): ?int { if (! $vehicle || ! $vehicle->year) return null; return max(0, (int) date('Y') - (int) $vehicle->year); } }