composer = app(ServiceComposer::class); $this->company = $this->makeCompany('compose', laborRate: 500); } public function test_add_hourly_labor_uses_rate_times_hours(): void { $labor = Labor::create(['category' => 'Motor', 'name_ro' => 'Schimb ulei', 'hours' => 1.5, 'pricing_mode' => 'hourly', 'is_active' => true]); $wo = $this->makeWorkOrder(); $work = $this->composer->addLabor($wo, $labor); $this->assertEquals(1.5, (float) $work->hours); $this->assertEquals(500.0, (float) $work->price_per_hour); $this->assertEquals(750.0, (float) $work->total); // 1.5 × 500 } public function test_add_fixed_labor_uses_fixed_price(): void { $labor = Labor::create(['category' => 'ITP', 'name_ro' => 'Diagnostic', 'hours' => 1, 'pricing_mode' => 'fixed', 'fixed_price' => 300, 'is_active' => true]); $wo = $this->makeWorkOrder(); $work = $this->composer->addLabor($wo, $labor); $this->assertEquals(300.0, (float) $work->total); } public function test_labor_auto_adds_default_parts(): void { $labor = Labor::create(['category' => 'Motor', 'name_ro' => 'Schimb ulei', 'hours' => 1, 'pricing_mode' => 'hourly', 'is_active' => true]); $oil = Part::create(['name' => 'Ulei 5W30', 'sell_price' => 60, 'buy_price' => 40, 'qty' => 100, 'unit' => 'L', 'is_active' => true]); $filter = Part::create(['name' => 'Filtru ulei', 'sell_price' => 80, 'buy_price' => 50, 'qty' => 20, 'unit' => 'buc', 'is_active' => true]); LaborPart::create(['labor_id' => $labor->id, 'part_id' => $oil->id, 'qty' => 4, 'unit' => 'L']); LaborPart::create(['labor_id' => $labor->id, 'part_id' => $filter->id, 'qty' => 1, 'unit' => 'buc']); $wo = $this->makeWorkOrder(); $this->composer->addLabor($wo, $labor, withParts: true); $this->assertEquals(2, $wo->parts()->count()); $oilLine = $wo->parts()->where('part_id', $oil->id)->first(); $this->assertEquals(4.0, (float) $oilLine->qty); $this->assertEquals(60.0, (float) $oilLine->sell_price); } public function test_add_labor_without_parts_skips_defaults(): void { $labor = Labor::create(['category' => 'Motor', 'name_ro' => 'X', 'hours' => 1, 'pricing_mode' => 'hourly', 'is_active' => true]); $p = Part::create(['name' => 'P', 'sell_price' => 10, 'buy_price' => 5, 'qty' => 5, 'unit' => 'buc', 'is_active' => true]); LaborPart::create(['labor_id' => $labor->id, 'part_id' => $p->id, 'qty' => 1]); $wo = $this->makeWorkOrder(); $this->composer->addLabor($wo, $labor, withParts: false); $this->assertEquals(0, $wo->parts()->count()); } public function test_apply_template_adds_all_lines_and_recalcs(): void { $labor = Labor::create(['category' => 'Motor', 'name_ro' => 'Schimb ulei', 'hours' => 1, 'pricing_mode' => 'hourly', 'is_active' => true]); $oil = Part::create(['name' => 'Ulei', 'sell_price' => 60, 'buy_price' => 40, 'qty' => 100, 'unit' => 'L', 'is_active' => true]); $tpl = ServiceTemplate::create(['name' => 'Revizie', 'is_active' => true]); ServiceTemplateItem::create(['service_template_id' => $tpl->id, 'kind' => 'labor', 'labor_id' => $labor->id, 'name' => 'Schimb ulei', 'hours' => 1]); ServiceTemplateItem::create(['service_template_id' => $tpl->id, 'kind' => 'part', 'part_id' => $oil->id, 'name' => 'Ulei', 'qty' => 4]); $wo = $this->makeWorkOrder(); $r = $this->composer->applyTemplate($wo, $tpl->load('items')); $this->assertEquals(1, $r['labor']); $this->assertEquals(1, $r['parts']); $this->assertEquals(1, $wo->works()->count()); $this->assertEquals(1, $wo->parts()->count()); $wo->refresh(); // labor 1×500 + oil 4×60 = 500 + 240 = 740 $this->assertEquals(740.0, (float) $wo->total); } public function test_templates_isolated_per_tenant(): void { ServiceTemplate::create(['name' => 'A', 'is_active' => true]); $other = $this->makeCompany('other', laborRate: 400); app(TenantManager::class)->setCurrent($other); $this->assertEquals(0, ServiceTemplate::count()); } private function makeCompany(string $slug, float $laborRate): Company { $plan = Plan::firstOrCreate(['slug' => 'test'], ['name' => 'T', 'price' => 0, 'features' => []]); $company = Company::create([ 'plan_id' => $plan->id, 'slug' => $slug, 'name' => ucfirst($slug), 'status' => 'active', 'settings' => ['labor_rate' => $laborRate], ]); app(TenantManager::class)->setCurrent($company); return $company; } private function makeWorkOrder(): WorkOrder { $client = Client::create(['name' => 'C', 'phone' => '+3736' . random_int(1000000, 9999999), 'type' => 'individual', 'status' => 'active']); $vehicle = Vehicle::create(['client_id' => $client->id, 'make' => 'X', 'model' => 'Y', 'plate' => 'P' . random_int(100, 999)]); return WorkOrder::create([ 'number' => WorkOrder::generateNumber($this->company->id), 'client_id' => $client->id, 'vehicle_id' => $vehicle->id, 'opened_at' => now(), 'status' => 'in_work', ]); } }