'search_clients', 'description' => 'Caută clienți după nume sau telefon (snippet, case-insensitive). Returnează max 10.', 'input_schema' => [ 'type' => 'object', 'properties' => [ 'query' => ['type' => 'string', 'description' => 'Fragment nume sau telefon'], ], 'required' => ['query'], ], ], [ 'name' => 'get_vehicle', 'description' => 'Detalii vehicul după placă (plate) sau VIN, plus ultima fișă de lucru.', 'input_schema' => [ 'type' => 'object', 'properties' => [ 'plate_or_vin' => ['type' => 'string', 'description' => 'Numărul de înmatriculare sau VIN'], ], 'required' => ['plate_or_vin'], ], ], [ 'name' => 'find_parts', 'description' => 'Caută piese în catalog după nume/cod/brand. Returnează max 15 cu stoc și preț.', 'input_schema' => [ 'type' => 'object', 'properties' => [ 'query' => ['type' => 'string'], ], 'required' => ['query'], ], ], [ 'name' => 'recent_workorders', 'description' => 'Ultimele fișe de lucru deschise/recente. Folosește pentru a răspunde la „ce lucrăm acum".', 'input_schema' => [ 'type' => 'object', 'properties' => [ 'limit' => ['type' => 'integer', 'description' => 'Câte (max 20)'], 'only_open' => ['type' => 'boolean', 'description' => 'Doar cele necelinate (default true)'], ], ], ], [ 'name' => 'low_stock_parts', 'description' => 'Listează piesele cu stoc sub minimum (alertă reaprovizionare).', 'input_schema' => [ 'type' => 'object', 'properties' => [ 'limit' => ['type' => 'integer'], ], ], ], ]; /** @return array result payload, JSON-encodable */ public function execute(string $name, array $input): array { return match ($name) { 'search_clients' => $this->searchClients((string) ($input['query'] ?? '')), 'get_vehicle' => $this->getVehicle((string) ($input['plate_or_vin'] ?? '')), 'find_parts' => $this->findParts((string) ($input['query'] ?? '')), 'recent_workorders' => $this->recentWorkOrders( (int) ($input['limit'] ?? 5), (bool) ($input['only_open'] ?? true), ), 'low_stock_parts' => $this->lowStockParts((int) ($input['limit'] ?? 15)), default => ['error' => "unknown tool: {$name}"], }; } private function searchClients(string $q): array { if (trim($q) === '') return ['rows' => []]; $like = '%' . $q . '%'; $rows = Client::where(fn ($w) => $w->where('name', 'like', $like) ->orWhere('phone', 'like', $like) ->orWhere('email', 'like', $like)) ->limit(10) ->get(['id', 'name', 'phone', 'email', 'status']) ->toArray(); return ['count' => count($rows), 'rows' => $rows]; } private function getVehicle(string $key): array { if (trim($key) === '') return ['found' => false]; $v = Vehicle::with('client:id,name,phone') ->where(fn ($w) => $w->where('plate', $key)->orWhere('vin', $key)) ->first(); if (! $v) return ['found' => false]; $lastWo = WorkOrder::where('vehicle_id', $v->id) ->latest('opened_at')->first(['number', 'opened_at', 'status', 'total']); return [ 'found' => true, 'id' => $v->id, 'make' => $v->make, 'model' => $v->model, 'year' => $v->year, 'plate' => $v->plate, 'vin' => $v->vin, 'mileage' => $v->mileage, 'client' => $v->client ? ['name' => $v->client->name, 'phone' => $v->client->phone] : null, 'last_workorder' => $lastWo, ]; } private function findParts(string $q): array { if (trim($q) === '') return ['rows' => []]; $like = '%' . $q . '%'; $rows = Part::where('is_active', true) ->where(fn ($w) => $w->where('name', 'like', $like) ->orWhere('article', 'like', $like) ->orWhere('brand', 'like', $like)) ->limit(15) ->get(['id', 'name', 'article', 'brand', 'category', 'qty', 'unit', 'sell_price']) ->toArray(); return ['count' => count($rows), 'rows' => $rows]; } private function recentWorkOrders(int $limit, bool $onlyOpen): array { $limit = max(1, min(20, $limit)); $q = WorkOrder::with(['client:id,name', 'vehicle:id,plate']) ->orderByDesc('opened_at'); if ($onlyOpen) $q->whereNotIn('status', ['done', 'cancelled']); $rows = $q->limit($limit)->get(['id', 'number', 'client_id', 'vehicle_id', 'status', 'opened_at', 'total']); return ['count' => $rows->count(), 'rows' => $rows->map(fn ($w) => [ 'number' => $w->number, 'client' => $w->client?->name, 'plate' => $w->vehicle?->plate, 'status' => $w->status, 'opened_at' => $w->opened_at?->toDateString(), 'total' => (float) $w->total, ])->all()]; } private function lowStockParts(int $limit): array { $limit = max(1, min(50, $limit)); $rows = Part::where('is_active', true) ->whereColumn('qty', '<=', 'min_qty') ->orderBy('qty') ->limit($limit) ->get(['id', 'name', 'article', 'qty', 'min_qty', 'unit']) ->toArray(); return ['count' => count($rows), 'rows' => $rows]; } }