find($id); if (! $wo) { // Diagnose: does it exist in another tenant? $existsGlobally = WorkOrder::withoutGlobalScopes() ->where('id', $id)->exists(); $tenant = app(\App\Tenancy\TenantManager::class)->current(); $tenantSlug = $tenant?->slug ?? '—'; $msg = $existsGlobally ? sprintf(__('Fișa #%d există, dar nu aparține tenantului „%s".'), $id, $tenantSlug) : sprintf(__('Fișa #%d nu există. Verifică lista de fișe active.'), $id); \Filament\Notifications\Notification::make() ->title(__('Fișă indisponibilă')) ->body($msg) ->danger() ->persistent() ->send(); $this->redirect('/app/work-orders'); return; } $this->record = $wo; } public function setTab(string $tab): void { $this->activeTab = $tab; } public function getTitle(): string|\Illuminate\Contracts\Support\Htmlable { return __('Fișă') . ' ' . ($this->record->number ?? ''); } public function getHeading(): string|\Illuminate\Contracts\Support\Htmlable { return ''; } /** Latest 5 WOs for the vehicle (repair history sidebar). */ public function getRepairHistory(): \Illuminate\Support\Collection { if (! $this->record->vehicle_id) return collect(); return WorkOrder::where('vehicle_id', $this->record->vehicle_id) ->where('id', '!=', $this->record->id) ->orderByDesc('opened_at') ->limit(5) ->get(['id', 'number', 'opened_at', 'total', 'status']); } /** * Timeline events for the right panel. Synthesises events from * WO fields (created, opened, closed, paid, status transitions * captured via activity_log if the trait is added) + notifications sent. */ public function getTimeline(): array { $wo = $this->record; $events = []; if ($wo->created_at) { $events[] = [ 'at' => $wo->created_at, 'kind' => 'created', 'label' => __('Fișă creată'), 'meta' => $wo->number, 'icon' => '📋', ]; } if ($wo->opened_at && $wo->opened_at->ne($wo->created_at)) { $events[] = [ 'at' => $wo->opened_at, 'kind' => 'opened', 'label' => __('Auto primit în service'), 'meta' => null, 'icon' => '🚗', ]; } if ($wo->approved_at) { $events[] = [ 'at' => $wo->approved_at, 'kind' => 'approved', 'label' => __('Aprobat de client'), 'meta' => null, 'icon' => '✓', ]; } if ($wo->closed_at) { $events[] = [ 'at' => $wo->closed_at instanceof \Carbon\Carbon ? $wo->closed_at : \Carbon\Carbon::parse($wo->closed_at), 'kind' => 'closed', 'label' => __('Fișă închisă'), 'meta' => null, 'icon' => '🔒', ]; } // Payments foreach ($wo->payments as $p) { $events[] = [ 'at' => $p->paid_at, 'kind' => 'payment', 'label' => __('Plată primită'), 'meta' => number_format((float) $p->amount, 2, '.', ' ') . ' MDL · ' . __(\App\Models\Tenant\Payment::METHODS[$p->method] ?? $p->method), 'icon' => '💰', ]; } // Activity log (spatie/activitylog) — best-effort read try { $activities = \DB::table('activity_log') ->where('subject_type', WorkOrder::class) ->where('subject_id', $wo->id) ->where('company_id', $wo->company_id) ->orderBy('created_at') ->get(); foreach ($activities as $a) { $events[] = [ 'at' => \Carbon\Carbon::parse($a->created_at), 'kind' => $a->event ?? 'log', 'label' => $a->description ?: ucfirst($a->event ?? 'log'), 'meta' => null, 'icon' => '📝', ]; } } catch (\Throwable) { /* activity_log table may be empty on fresh DB */ } // Sort chronologically desc so newest first usort($events, fn ($a, $b) => $b['at']->timestamp <=> $a['at']->timestamp); return $events; } /** Outbound notifications to the client (SMS/WhatsApp/Telegram/Email). */ public function getNotifications(): \Illuminate\Support\Collection { return \App\Models\Tenant\ClientNotificationLog::where('work_order_id', $this->record->id) ->orderByDesc('sent_at') ->limit(20) ->get(); } public string $newMessage = ''; public function sendMessage(): void { $text = trim($this->newMessage); if ($text === '' || ! $this->record->client) return; $client = $this->record->client; $company = app(\App\Tenancy\TenantManager::class)->current(); if (! $company) return; $dispatcher = app(\App\Services\NotificationDispatcher::class); $sent = false; // Prefer Telegram if the client is linked; fall back to WhatsApp if a phone is set. if (! empty($client->telegram_chat_id)) { try { $sent = app(\App\Services\Notifications\TelegramService::class) ->sendMessage($company, (string) $client->telegram_chat_id, $text); } catch (\Throwable) { /* ignore */ } } // Log the message so it shows up in the chat pane regardless of delivery. \App\Models\Tenant\ClientNotificationLog::create([ 'company_id' => $company->id, 'work_order_id' => $this->record->id, 'client_id' => $client->id, 'channel' => ! empty($client->telegram_chat_id) ? 'telegram' : 'sms', 'template_key' => 'manual', 'message_text' => $text, 'status' => $sent ? 'sent' : 'failed', 'sent_at' => now(), ]); $this->newMessage = ''; \Filament\Notifications\Notification::make() ->title($sent ? __('Mesaj trimis') : __('Trimitere eșuată — verifică integrarea')) ->{$sent ? 'success' : 'warning'}() ->send(); } /** Adjacent WOs for the previous/next buttons in the bottom bar. */ public function getPrevWo(): ?WorkOrder { return WorkOrder::where('id', '<', $this->record->id) ->orderByDesc('id') ->first(['id', 'number']); } public function getNextWo(): ?WorkOrder { return WorkOrder::where('id', '>', $this->record->id) ->orderBy('id') ->first(['id', 'number']); } public function closeWorkOrder(): void { $wo = $this->record; if ($wo->status === 'done') return; $wo->forceFill([ 'status' => 'done', 'closed_at' => now(), ])->save(); $this->record->refresh(); \Filament\Notifications\Notification::make() ->title(__('Fișă închisă')) ->success()->send(); } /** Aggregated finance data — used by right panel in future phases. */ public function getFinance(): array { $r = $this->record; $worksSum = (float) $r->works->sum('total'); $partsSum = (float) $r->parts->sum('total'); $subtotal = $worksSum + $partsSum; $discount = (float) $r->discount; $total = max(0, $subtotal - $discount); $paid = $r->paidAmount(); return [ 'works_sum' => $worksSum, 'parts_sum' => $partsSum, 'subtotal' => $subtotal, 'discount' => $discount, 'total' => $total, 'paid' => $paid, 'balance' => max(0, $total - $paid), ]; } }