engine = app(PricingEngine::class); $this->makeCompany('pricing'); } public function test_base_markup_only_when_no_coefficients(): void { MarkupRule::create(['type' => 'category', 'key' => 'Frâne', 'markup_pct' => 50, 'priority' => 10, 'is_active' => true]); $part = Part::create(['name' => 'Disc', 'category' => 'Frâne', 'buy_price' => 100, 'sell_price' => 0, 'qty' => 1, 'unit' => 'buc', 'is_active' => true]); $q = $this->engine->quote($part); $this->assertEquals(150.0, $q['base']); $this->assertEquals(150.0, $q['final']); $this->assertEmpty($q['applied']); } public function test_old_vehicle_age_coefficient_applies(): void { $part = Part::create(['name' => 'Filtru', 'buy_price' => 100, 'sell_price' => 130, 'qty' => 1, 'unit' => 'buc', 'is_active' => true]); PricingCoefficient::create([ 'name' => 'Mașină veche', 'multiplier' => 1.20, 'priority' => 10, 'stackable' => true, 'is_active' => true, 'conditions' => ['age_min' => 10], ]); $oldCar = $this->makeVehicle(year: (int) date('Y') - 15); $q = $this->engine->quote($part, $oldCar); $this->assertEqualsWithDelta(156.0, $q['final'], 0.01); // 130 × 1.20 $newCar = $this->makeVehicle(year: (int) date('Y') - 2); $q2 = $this->engine->quote($part, $newCar); $this->assertEqualsWithDelta(130.0, $q2['final'], 0.01); // no coefficient } public function test_vip_client_coefficient(): void { $part = Part::create(['name' => 'P', 'buy_price' => 100, 'sell_price' => 100, 'qty' => 1, 'unit' => 'buc', 'is_active' => true]); PricingCoefficient::create([ 'name' => 'Discount VIP', 'multiplier' => 0.90, 'priority' => 10, 'stackable' => true, 'is_active' => true, 'conditions' => ['client_vip' => true], ]); $vip = Client::create(['name' => 'VIP', 'phone' => '+37360000001', 'type' => 'individual', 'status' => 'active', 'is_vip' => true]); $regular = Client::create(['name' => 'Reg', 'phone' => '+37360000002', 'type' => 'individual', 'status' => 'active', 'is_vip' => false]); $this->assertEqualsWithDelta(90.0, $this->engine->quote($part, null, $vip)['final'], 0.01); $this->assertEqualsWithDelta(100.0, $this->engine->quote($part, null, $regular)['final'], 0.01); } public function test_express_urgency_coefficient(): void { $part = Part::create(['name' => 'P', 'buy_price' => 100, 'sell_price' => 100, 'qty' => 1, 'unit' => 'buc', 'is_active' => true]); PricingCoefficient::create([ 'name' => 'Express', 'multiplier' => 1.30, 'priority' => 10, 'stackable' => true, 'is_active' => true, 'conditions' => ['urgency' => ['express']], ]); $this->assertEqualsWithDelta(130.0, $this->engine->quote($part, null, null, 'express')['final'], 0.01); $this->assertEqualsWithDelta(100.0, $this->engine->quote($part, null, null, 'normal')['final'], 0.01); } public function test_stackable_multiply_nonstackable_take_highest(): void { $part = Part::create(['name' => 'P', 'buy_price' => 100, 'sell_price' => 100, 'qty' => 1, 'unit' => 'buc', 'is_active' => true]); // Two stackable + two non-stackable, all matching (no conditions). PricingCoefficient::create(['name' => 'S1', 'multiplier' => 1.10, 'stackable' => true, 'is_active' => true, 'priority' => 1]); PricingCoefficient::create(['name' => 'S2', 'multiplier' => 1.20, 'stackable' => true, 'is_active' => true, 'priority' => 2]); PricingCoefficient::create(['name' => 'N1', 'multiplier' => 1.50, 'stackable' => false, 'is_active' => true, 'priority' => 3]); PricingCoefficient::create(['name' => 'N2', 'multiplier' => 1.30, 'stackable' => false, 'is_active' => true, 'priority' => 4]); // 100 × 1.10 × 1.20 × max(1.50,1.30) = 100 × 1.32 × 1.50 = 198 $q = $this->engine->quote($part); $this->assertEqualsWithDelta(198.0, $q['final'], 0.01); $this->assertCount(3, $q['applied']); // S1, S2, N1 (only strongest non-stackable) } public function test_hybrid_inferred_from_fuel(): void { $part = Part::create(['name' => 'P', 'buy_price' => 100, 'sell_price' => 100, 'qty' => 1, 'unit' => 'buc', 'is_active' => true]); PricingCoefficient::create([ 'name' => 'Hibrid', 'multiplier' => 1.25, 'stackable' => true, 'is_active' => true, 'priority' => 1, 'conditions' => ['classes' => ['hybrid']], ]); // No explicit vehicle_class, but fuel = Hybrid. $car = $this->makeVehicle(year: 2020, fuel: 'Hybrid'); $this->assertEqualsWithDelta(125.0, $this->engine->quote($part, $car)['final'], 0.01); } private function makeCompany(string $slug): Company { $plan = Plan::firstOrCreate(['slug' => 'test'], ['name' => 'T', 'price' => 0, 'features' => []]); $company = Company::create(['plan_id' => $plan->id, 'slug' => $slug, 'name' => 'P', 'status' => 'active']); app(TenantManager::class)->setCurrent($company); return $company; } private function makeVehicle(int $year, ?string $fuel = null): Vehicle { $client = Client::create(['name' => 'C', 'phone' => '+3736' . random_int(1000000, 9999999), 'type' => 'individual', 'status' => 'active']); return Vehicle::create([ 'client_id' => $client->id, 'make' => 'X', 'model' => 'Y', 'year' => $year, 'fuel' => $fuel, 'plate' => 'P' . random_int(100, 999), ]); } }