resolveRecordKey($record); $this->record = Company::with(['plan', 'subscriptions' => fn ($q) => $q->latest('period_end')->limit(10)]) ->findOrFail($key); } private function resolveRecordKey(mixed $record): int|string { if ($record instanceof Company) { return $record->getKey(); } if (is_string($record) && str_starts_with(ltrim($record), '{')) { $decoded = json_decode($record, true); if (is_array($decoded) && isset($decoded['id'])) { return $decoded['id']; } } return $record; } public function getTitle(): string { return $this->record->display_name ?? $this->record->name; } public function getStats(): array { // Set tenant context to query scoped tables. app(TenantManager::class)->setCurrent($this->record); app(\Spatie\Permission\PermissionRegistrar::class) ->setPermissionsTeamId($this->record->id); $stats = [ 'users' => TenantUser::count(), 'clients' => Client::count(), 'vehicles' => Vehicle::count(), 'work_orders' => WorkOrder::count(), 'work_orders_open' => WorkOrder::whereNotIn('status', ['done', 'cancelled'])->count(), 'parts' => Part::count(), 'parts_low_stock' => Part::where('is_active', true) ->whereColumn('qty', '<=', 'min_qty') ->where('qty', '>', 0) ->count(), 'revenue_this_month' => (float) Payment::whereYear('paid_at', date('Y')) ->whereMonth('paid_at', date('m'))->sum('amount'), 'revenue_last_month' => (float) Payment::whereYear('paid_at', now()->subMonth()->year) ->whereMonth('paid_at', now()->subMonth()->month)->sum('amount'), 'last_login' => TenantUser::whereNotNull('last_login_at')->max('last_login_at'), 'storage_mb' => $this->calculateStorageMb(), ]; app(TenantManager::class)->setCurrent(null); return $stats; } private function calculateStorageMb(): float { try { $bytes = (int) \DB::table('media') ->where('model_type', \App\Models\Central\Company::class) ->where('model_id', $this->record->id) ->sum('size'); return round($bytes / 1024 / 1024, 2); } catch (\Throwable) { return 0; } } public function getDaysUntilExpiry(): ?int { $until = $this->record->active_until ?? $this->record->trial_ends_at; if (! $until) return null; $diff = now()->diffInDays($until, false); return (int) $diff; } protected function getHeaderActions(): array { return [ Actions\Action::make('view_as') ->label('Deschide tenantul') ->icon('heroicon-m-arrow-top-right-on-square') ->color('primary') ->url(fn () => $this->record->url('/app')) ->openUrlInNewTab(), Actions\Action::make('issue_invoice') ->label('Generează factură') ->icon('heroicon-m-document-plus') ->color('success') ->visible(fn () => (bool) $this->record->plan_id) ->schema([ Forms\Components\Select::make('period')->options(\App\Models\Central\Subscription::PERIODS)->default('monthly')->required(), ]) ->action(function (array $data) { $plan = $this->record->plan; if (! $plan) return; $start = today(); $end = $data['period'] === 'yearly' ? $start->copy()->addYear() : $start->copy()->addMonth(); $sub = Subscription::create([ 'company_id' => $this->record->id, 'plan_id' => $plan->id, 'period' => $data['period'], 'amount' => $data['period'] === 'yearly' ? $plan->price_yearly : $plan->price_monthly, 'currency' => $plan->currency, 'status' => 'pending', 'period_start' => $start, 'period_end' => $end, 'due_at' => $start->copy()->addDays(7), 'invoice_number' => Subscription::generateInvoiceNumber(), ]); Notification::make() ->title('Factură generată: ' . $sub->invoice_number) ->body('Suma: ' . number_format($sub->amount, 2) . ' ' . $sub->currency) ->success()->send(); $this->dispatch('$refresh'); }), Actions\Action::make('upload_logo') ->label('Logo') ->icon('heroicon-m-photo') ->color('gray') ->schema([ Forms\Components\FileUpload::make('logo') ->image() ->imageEditor() ->disk('public') ->directory('central-uploads') ->maxSize(2048) ->required(), ]) ->action(function (array $data) { if (empty($data['logo'])) return; $abs = \Illuminate\Support\Facades\Storage::disk('public')->path($data['logo']); if (file_exists($abs)) { $this->record->clearMediaCollection('logo'); $this->record->addMedia($abs)->preservingOriginal()->toMediaCollection('logo'); @unlink($abs); Notification::make()->title('Logo actualizat')->success()->send(); } }), Actions\Action::make('edit') ->label('Editează') ->icon('heroicon-m-pencil') ->url(fn () => CompanyResource::getUrl('edit', ['record' => $this->record])), Actions\Action::make('suspend') ->label('Suspendă')->icon('heroicon-m-no-symbol')->color('danger') ->visible(fn () => in_array($this->record->status, ['active', 'trial'])) ->requiresConfirmation() ->action(function () { app(\App\Services\CompanyProvisioner::class)->suspend($this->record); Notification::make()->title('Tenant suspendat.')->success()->send(); $this->dispatch('$refresh'); }), Actions\Action::make('activate') ->label('Reactivează')->icon('heroicon-m-check-circle')->color('success') ->visible(fn () => in_array($this->record->status, ['suspended', 'expired'])) ->requiresConfirmation() ->action(function () { app(\App\Services\CompanyProvisioner::class)->reactivate($this->record); Notification::make()->title('Tenant activat.')->success()->send(); $this->dispatch('$refresh'); }), ]; } }