> */ public array $fornitori = []; /** @var array> */ public array $stabili = []; /** @var array> */ public array $contattiPrincipali = []; /** @var array> */ public array $ruoliAttivi = []; /** @var array> */ public array $cronistoriaRuoli = []; public int $adminId = 0; public ?int $prevRecordId = null; public ?int $nextRecordId = null; public int $navPerPage = 25; public int $navPage = 1; public int $navPages = 1; /** @var array */ public array $adminStabileIds = []; public bool $isInlineEditing = false; public string $sideTab = 'collegamenti'; /** @var array> */ public array $fornitoriCollegati = []; /** @var array> */ public array $dipendentiFornitoreRows = []; /** @var array */ public array $dipendenteRubricaSelect = []; /** @var array> */ public array $dipendenteManualDraft = []; /** @var array */ public array $dipendentePbxExtension = []; /** @var array */ public array $inlineForm = []; public function mount(int | string $record): void { $user = Auth::user(); if (! $user instanceof User) { abort(403); } if (! $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore'])) { abort(403); } $this->rubrica = RubricaUniversale::query()->findOrFail((int) $record); $activeStabile = StabileContext::getActiveStabile($user); $adminId = (int) ($activeStabile?->amministratore_id ?: 0); $this->adminId = $adminId; $stabiliAdminIds = []; if (! $user->hasRole('super-admin') && $adminId > 0) { $stabiliAdminIds = Stabile::query() ->where('amministratore_id', $adminId) ->pluck('id') ->map(fn($id) => (int) $id) ->all(); } $this->adminStabileIds = $stabiliAdminIds; $this->initArchivioNavigation(); $this->fornitori = Fornitore::query() ->where('amministratore_id', $adminId) ->where('rubrica_id', (int) $this->rubrica->id) ->orderBy('ragione_sociale') ->limit(50) ->get(['id', 'codice_univoco', 'ragione_sociale', 'partita_iva', 'codice_fiscale']) ->map(fn(Fornitore $f) => [ 'id' => (int) $f->id, 'codice' => (string) ($f->codice_univoco ?? ''), 'nome' => (string) ($f->ragione_sociale ?: trim((string) ($f->cognome ?? '') . ' ' . (string) ($f->nome ?? ''))), 'piva' => (string) ($f->partita_iva ?? ''), 'cf' => (string) ($f->codice_fiscale ?? ''), ]) ->all(); $ruoliQuery = RubricaRuolo::query() ->with(['stabile', 'unitaImmobiliare.stabile']) ->where('rubrica_id', (int) $this->rubrica->id); if (! $user->hasRole('super-admin') && ! empty($stabiliAdminIds)) { $ruoliQuery->where(function ($q) use ($stabiliAdminIds) { $q->whereNull('stabile_id') ->orWhereIn('stabile_id', $stabiliAdminIds); }); } $ruoli = $ruoliQuery ->orderByDesc('is_attivo') ->orderByDesc('is_preferito') ->orderByDesc('data_inizio') ->orderByDesc('id') ->limit(200) ->get(); $labels = self::getRuoliLabels(); $this->ruoliAttivi = $ruoli ->filter(fn(RubricaRuolo $r) => (bool) $r->is_attivo) ->map(function (RubricaRuolo $r) use ($labels) { $stabile = $r->stabile ?: $r->unitaImmobiliare?->stabile; $ruoloKey = (string) ($r->ruolo_standard ?? 'altro'); $ruoloLabel = (string) ($labels[$ruoloKey] ?? $ruoloKey); if (! empty($r->ruolo_custom)) { $ruoloLabel .= ' (' . (string) $r->ruolo_custom . ')'; } return [ 'ruolo' => $ruoloLabel, 'stabile_codice' => (string) ($stabile?->codice_operatore ?? $stabile?->codice_stabile ?? ''), 'stabile_nome' => (string) ($stabile?->denominazione ?? ''), 'unita_id' => (int) ($r->unitaImmobiliare?->id ?? 0), 'unita_codice' => (string) ($r->unitaImmobiliare?->codice_unita ?? ''), 'unita_nome' => (string) ($r->unitaImmobiliare?->denominazione ?? ''), 'preferito' => (bool) ($r->is_preferito ?? false), ]; }) ->values() ->all(); $this->cronistoriaRuoli = $ruoli ->map(function (RubricaRuolo $r) use ($labels) { $stabile = $r->stabile ?: $r->unitaImmobiliare?->stabile; $unita = $r->unitaImmobiliare; $ruoloKey = (string) ($r->ruolo_standard ?? 'altro'); $ruoloLabel = (string) ($labels[$ruoloKey] ?? $ruoloKey); if (! empty($r->ruolo_custom)) { $ruoloLabel .= ' (' . (string) $r->ruolo_custom . ')'; } return [ 'ruolo' => $ruoloLabel, 'data_inizio' => optional($r->data_inizio)->format('d/m/Y'), 'data_fine' => $r->data_fine ? optional($r->data_fine)->format('d/m/Y') : null, 'attivo' => (bool) ($r->is_attivo ?? true), 'preferito' => (bool) ($r->is_preferito ?? false), 'stabile_codice' => (string) ($stabile?->codice_operatore ?? $stabile?->codice_stabile ?? ''), 'stabile_nome' => (string) ($stabile?->denominazione ?? ''), 'unita_id' => (int) ($unita?->id ?? 0), 'unita_codice' => (string) ($unita?->codice_unita ?? ''), 'unita_nome' => (string) ($unita?->denominazione ?? ''), ]; }) ->all(); // Collegamenti stabili: includi sia stabili con rubrica_id diretto sia stabili legati tramite ruoli. $stabiliIds = []; foreach ($ruoli as $r) { $sid = (int) ($r->stabile_id ?? 0); if ($sid <= 0) { $sid = (int) ($r->unitaImmobiliare?->stabile_id ?? 0); } if ($sid > 0) { $stabiliIds[] = $sid; } } $stabiliIds = array_values(array_unique($stabiliIds)); $stabiliQuery = Stabile::query()->where('amministratore_id', $adminId); if (! empty($stabiliIds)) { $stabiliQuery->where(function (Builder $q) use ($stabiliIds): void { $q->whereIn('id', $stabiliIds) ->orWhere('rubrica_id', (int) $this->rubrica->id); }); } else { $stabiliQuery->where('rubrica_id', (int) $this->rubrica->id); } $this->stabili = $stabiliQuery ->orderBy('codice_stabile') ->limit(100) ->get(['id', 'codice_stabile', 'cod_stabile', 'denominazione', 'codice_fiscale']) ->map(fn(Stabile $s) => [ 'id' => (int) $s->id, 'codice' => (string) ($s->codice_operatore ?? $s->codice_stabile ?? ''), 'nome' => (string) ($s->denominazione ?? ''), 'cf' => (string) ($s->codice_fiscale ?? ''), ]) ->all(); // Contatti principali (per mostrarli velocemente nella scheda): prende canali principali + fallback ai campi telefono/email. $principali = RubricaContattoCanale::query() ->where('rubrica_id', (int) $this->rubrica->id) ->orderByDesc('is_principale') ->orderByDesc('data_inizio') ->orderByDesc('id') ->limit(20) ->get(); $this->contattiPrincipali = $principali ->map(fn(RubricaContattoCanale $c) => [ 'tipo' => (string) ($c->tipo ?? 'altro'), 'etichetta' => (string) ($c->etichetta ?? ''), 'valore' => (string) ($c->valore ?? ''), 'principale' => (bool) ($c->is_principale ?? false), ]) ->all(); $this->fillInlineForm(); $this->hydrateFornitoriWorkspace(); } public function startInlineEdit(): void { $this->fillInlineForm(); $this->isInlineEditing = true; } public function cancelInlineEdit(): void { $this->fillInlineForm(); $this->isInlineEditing = false; } public function saveInlineEdit(): void { $data = validator($this->inlineForm, [ 'nome' => ['nullable', 'string', 'max:255'], 'cognome' => ['nullable', 'string', 'max:255'], 'ragione_sociale' => ['nullable', 'string', 'max:255'], 'tipo_contatto' => ['nullable', 'string', 'max:120'], 'categoria' => ['nullable', 'string', 'max:120'], 'codice_fiscale' => ['nullable', 'string', 'max:32'], 'partita_iva' => ['nullable', 'string', 'max:32'], 'email' => ['nullable', 'string', 'max:255'], 'pec' => ['nullable', 'string', 'max:255'], 'telefono_ufficio' => ['nullable', 'string', 'max:64'], 'telefono_cellulare' => ['nullable', 'string', 'max:64'], 'telefono_casa' => ['nullable', 'string', 'max:64'], 'indirizzo_completo' => ['nullable', 'string', 'max:255'], 'tipo_utenza_call' => ['nullable', 'string', 'max:120'], 'riferimento_stabile' => ['nullable', 'string', 'max:255'], 'riferimento_unita' => ['nullable', 'string', 'max:255'], 'note' => ['nullable', 'string', 'max:5000'], 'note_segreteria' => ['nullable', 'string', 'max:5000'], ])->validate(); $this->rubrica->fill([ 'nome' => $this->cleanNullable($data['nome'] ?? null), 'cognome' => $this->cleanNullable($data['cognome'] ?? null), 'ragione_sociale' => $this->cleanNullable($data['ragione_sociale'] ?? null), 'tipo_contatto' => $this->cleanNullable($data['tipo_contatto'] ?? null), 'categoria' => $this->cleanNullable($data['categoria'] ?? null), 'codice_fiscale' => $this->cleanNullable($data['codice_fiscale'] ?? null), 'partita_iva' => $this->cleanNullable($data['partita_iva'] ?? null), 'email' => $this->cleanNullable($data['email'] ?? null), 'pec' => $this->cleanNullable($data['pec'] ?? null), 'telefono_ufficio' => $this->cleanNullable($data['telefono_ufficio'] ?? null), 'telefono_cellulare' => $this->cleanNullable($data['telefono_cellulare'] ?? null), 'telefono_casa' => $this->cleanNullable($data['telefono_casa'] ?? null), 'indirizzo_completo' => $this->cleanNullable($data['indirizzo_completo'] ?? null), 'tipo_utenza_call' => $this->cleanNullable($data['tipo_utenza_call'] ?? null), 'riferimento_stabile' => $this->cleanNullable($data['riferimento_stabile'] ?? null), 'riferimento_unita' => $this->cleanNullable($data['riferimento_unita'] ?? null), 'note' => $this->cleanNullable($data['note'] ?? null), 'note_segreteria' => $this->cleanNullable($data['note_segreteria'] ?? null), 'data_ultima_modifica' => now()->toDateString(), 'modificato_da' => Auth::id(), ]); $this->rubrica->save(); $this->mount((int) $this->rubrica->id); $this->isInlineEditing = false; Notification::make() ->title('Contatto aggiornato') ->success() ->send(); } private function initArchivioNavigation(): void { $pp = (int) request()->query('pp', 25); if ($pp <= 0) { $pp = 25; } if (! in_array($pp, [10, 25, 50, 100], true)) { $pp = 25; } $this->navPerPage = $pp; if ($this->adminId <= 0) { $this->prevRecordId = null; $this->nextRecordId = null; $this->navPage = 1; $this->navPages = 1; return; } $base = RubricaUniversale::query()->where('amministratore_id', $this->adminId); $total = (clone $base)->count(); $this->navPages = max(1, (int) ceil($total / $this->navPerPage)); $before = (clone $base) ->where($this->whereBeforeCurrent()) ->count(); $position = $before + 1; $this->navPage = max(1, (int) ceil($position / $this->navPerPage)); $this->prevRecordId = (clone $base) ->where($this->whereBeforeCurrent()) ->tap(fn(Builder $q) => $this->applySort($q, 'desc')) ->value('id'); $this->nextRecordId = (clone $base) ->where($this->whereAfterCurrent()) ->tap(fn(Builder $q) => $this->applySort($q, 'asc')) ->value('id'); } private function applySort(Builder $query, string $direction): void { $dir = strtolower($direction) === 'desc' ? 'DESC' : 'ASC'; $query ->orderByRaw("COALESCE(ragione_sociale, '') {$dir}") ->orderByRaw("COALESCE(cognome, '') {$dir}") ->orderByRaw("COALESCE(nome, '') {$dir}") ->orderBy('id', $dir); } private function whereBeforeCurrent(): \Closure { $ragione = (string) ($this->rubrica->ragione_sociale ?? ''); $cognome = (string) ($this->rubrica->cognome ?? ''); $nome = (string) ($this->rubrica->nome ?? ''); $id = (int) $this->rubrica->id; return function (Builder $q) use ($ragione, $cognome, $nome, $id): void { $q->whereRaw("COALESCE(ragione_sociale, '') < ?", [$ragione]) ->orWhere(function (Builder $q) use ($ragione, $cognome): void { $q->whereRaw("COALESCE(ragione_sociale, '') = ?", [$ragione]) ->whereRaw("COALESCE(cognome, '') < ?", [$cognome]); }) ->orWhere(function (Builder $q) use ($ragione, $cognome, $nome): void { $q->whereRaw("COALESCE(ragione_sociale, '') = ?", [$ragione]) ->whereRaw("COALESCE(cognome, '') = ?", [$cognome]) ->whereRaw("COALESCE(nome, '') < ?", [$nome]); }) ->orWhere(function (Builder $q) use ($ragione, $cognome, $nome, $id): void { $q->whereRaw("COALESCE(ragione_sociale, '') = ?", [$ragione]) ->whereRaw("COALESCE(cognome, '') = ?", [$cognome]) ->whereRaw("COALESCE(nome, '') = ?", [$nome]) ->where('id', '<', $id); }); }; } private function whereAfterCurrent(): \Closure { $ragione = (string) ($this->rubrica->ragione_sociale ?? ''); $cognome = (string) ($this->rubrica->cognome ?? ''); $nome = (string) ($this->rubrica->nome ?? ''); $id = (int) $this->rubrica->id; return function (Builder $q) use ($ragione, $cognome, $nome, $id): void { $q->whereRaw("COALESCE(ragione_sociale, '') > ?", [$ragione]) ->orWhere(function (Builder $q) use ($ragione, $cognome): void { $q->whereRaw("COALESCE(ragione_sociale, '') = ?", [$ragione]) ->whereRaw("COALESCE(cognome, '') > ?", [$cognome]); }) ->orWhere(function (Builder $q) use ($ragione, $cognome, $nome): void { $q->whereRaw("COALESCE(ragione_sociale, '') = ?", [$ragione]) ->whereRaw("COALESCE(cognome, '') = ?", [$cognome]) ->whereRaw("COALESCE(nome, '') > ?", [$nome]); }) ->orWhere(function (Builder $q) use ($ragione, $cognome, $nome, $id): void { $q->whereRaw("COALESCE(ragione_sociale, '') = ?", [$ragione]) ->whereRaw("COALESCE(cognome, '') = ?", [$cognome]) ->whereRaw("COALESCE(nome, '') = ?", [$nome]) ->where('id', '>', $id); }); }; } public function getPrevUrl(): ?string { if (! $this->prevRecordId) { return null; } return self::getUrl(['record' => $this->prevRecordId], panel: 'admin-filament') . '?pp=' . $this->navPerPage; } public function getNextUrl(): ?string { if (! $this->nextRecordId) { return null; } return self::getUrl(['record' => $this->nextRecordId], panel: 'admin-filament') . '?pp=' . $this->navPerPage; } public function getPrevPageUrl(): ?string { $target = $this->navPage - 1; if ($target < 1) { return null; } return $this->getPageUrl($target); } public function getNextPageUrl(): ?string { $target = $this->navPage + 1; if ($target > $this->navPages) { return null; } return $this->getPageUrl($target); } public function getPageUrl(int $page): ?string { if ($this->adminId <= 0) { return null; } $page = max(1, min($page, $this->navPages)); $offset = ($page - 1) * $this->navPerPage; $base = RubricaUniversale::query() ->where('amministratore_id', $this->adminId); $this->applySort($base, 'asc'); $id = (int) ($base->skip($offset)->take(1)->value('id') ?? 0); if ($id <= 0) { return null; } return self::getUrl(['record' => $id], panel: 'admin-filament') . '?pp=' . $this->navPerPage; } public function getPerPageUrl(int $perPage): string { if (! in_array($perPage, [10, 25, 50, 100], true)) { $perPage = 25; } return self::getUrl(['record' => (int) $this->rubrica->id], panel: 'admin-filament') . '?pp=' . $perPage; } protected function getHeaderActions(): array { return [ Action::make('titolo') ->label('Titolo') ->icon('heroicon-o-tag') ->form([ Select::make('titolo_id') ->label('Titolo') ->native(false) ->searchable() ->options(fn() => $this->getTitoliOptions()), ]) ->fillForm(fn(): array=> [ 'titolo_id' => $this->rubrica->titolo_id ? (int) $this->rubrica->titolo_id : null, ]) ->action(function (array $data): void { $titoloId = isset($data['titolo_id']) && is_numeric($data['titolo_id']) ? (int) $data['titolo_id'] : null; $this->rubrica->titolo_id = $titoloId; $this->rubrica->data_ultima_modifica = now()->toDateString(); $this->rubrica->modificato_da = Auth::id(); $this->rubrica->save(); $this->mount((int) $this->rubrica->id); Notification::make() ->title('Titolo aggiornato') ->success() ->send(); }), Action::make('contatti') ->label('Contatti') ->icon('heroicon-o-phone') ->action(function (): void { $this->startInlineEdit(); }), Action::make('contatti_avanzati') ->label('Contatti avanzati') ->icon('heroicon-o-rectangle-stack') ->modalWidth('7xl') ->form([ Repeater::make('canali') ->label('Contatti aggiuntivi') ->defaultItems(0) ->reorderable(true) ->schema([ Hidden::make('id'), Select::make('tipo') ->label('Tipo') ->options([ 'telefono' => 'Telefono', 'cellulare' => 'Cellulare', 'email' => 'Email', 'pec' => 'PEC', 'fax' => 'Fax', 'sito_web' => 'Sito web', 'altro' => 'Altro', ]) ->native(false) ->required() ->columnSpan(2), TextInput::make('etichetta') ->label('Etichetta') ->maxLength(50) ->columnSpan(2), TextInput::make('valore') ->label('Valore') ->required() ->maxLength(255) ->columnSpan(4), Toggle::make('is_principale') ->label('Principale') ->default(false) ->columnSpan(2), Select::make('stabile_id') ->label('Stabile (opzionale)') ->native(false) ->searchable() ->options(fn() => $this->getStabiliOptions()) ->reactive() ->afterStateUpdated(fn($set) => $set('unita_immobiliare_id', null)) ->columnSpan(3), Select::make('unita_immobiliare_id') ->label('Unità (opzionale)') ->native(false) ->searchable() ->options(fn($get) => $this->getUnitaOptions($get('stabile_id'))) ->columnSpan(3), DatePicker::make('data_inizio') ->label('Data inizio') ->columnSpan(2), DatePicker::make('data_fine') ->label('Data fine') ->columnSpan(2), TextInput::make('note') ->label('Note') ->maxLength(255) ->columnSpan(12), ]) ->columns(12), ]) ->fillForm(function (): array { $canali = RubricaContattoCanale::query() ->where('rubrica_id', (int) $this->rubrica->id) ->orderByDesc('is_principale') ->orderByDesc('data_inizio') ->orderByDesc('id') ->limit(200) ->get(); return [ 'canali' => $canali->map(fn(RubricaContattoCanale $c) => [ 'id' => (int) $c->id, 'tipo' => (string) ($c->tipo ?? 'altro'), 'etichetta' => (string) ($c->etichetta ?? ''), 'valore' => (string) ($c->valore ?? ''), 'is_principale' => (bool) ($c->is_principale ?? false), 'stabile_id' => $c->stabile_id ? (int) $c->stabile_id : null, 'unita_immobiliare_id' => $c->unita_immobiliare_id ? (int) $c->unita_immobiliare_id : null, 'data_inizio' => $c->data_inizio?->format('Y-m-d'), 'data_fine' => $c->data_fine?->format('Y-m-d'), 'note' => (string) (($c->meta['note'] ?? null) ?: ''), ])->all(), ]; }) ->action(function (array $data): void { /** @var array> $rows */ $rows = $data['canali'] ?? []; $rows = is_array($rows) ? array_values($rows) : []; DB::transaction(function () use ($rows): void { $keepIds = []; foreach ($rows as $row) { $id = (int) ($row['id'] ?? 0); $tipo = (string) ($row['tipo'] ?? 'altro'); if ($tipo === '') { $tipo = 'altro'; } $attrs = [ 'rubrica_id' => (int) $this->rubrica->id, 'tipo' => $tipo, 'etichetta' => ! empty($row['etichetta']) ? (string) $row['etichetta'] : null, 'valore' => ! empty($row['valore']) ? (string) $row['valore'] : null, 'is_principale' => (bool) ($row['is_principale'] ?? false), 'stabile_id' => ! empty($row['stabile_id']) ? (int) $row['stabile_id'] : null, 'unita_immobiliare_id' => ! empty($row['unita_immobiliare_id']) ? (int) $row['unita_immobiliare_id'] : null, 'data_inizio' => $row['data_inizio'] ?? null, 'data_fine' => $row['data_fine'] ?? null, 'meta' => [ 'note' => ! empty($row['note']) ? (string) $row['note'] : null, ], ]; if ($id > 0) { $existing = RubricaContattoCanale::query() ->where('id', $id) ->where('rubrica_id', (int) $this->rubrica->id) ->first(); if ($existing) { $existing->fill($attrs); $existing->save(); $keepIds[] = (int) $existing->id; continue; } } $created = RubricaContattoCanale::query()->create($attrs); $keepIds[] = (int) $created->id; } $deleteQuery = RubricaContattoCanale::query()->where('rubrica_id', (int) $this->rubrica->id); if (! empty($keepIds)) { $deleteQuery->whereNotIn('id', $keepIds); } $deleteQuery->delete(); }); $this->mount((int) $this->rubrica->id); Notification::make() ->title('Contatti aggiornati') ->success() ->send(); }), Action::make('riferimenti_chiamata') ->label('Riferimenti chiamata') ->icon('heroicon-o-phone-arrow-up-right') ->form([ Select::make('tipo_utenza_call') ->label('Profilo chiamante') ->native(false) ->options([ 'condomino' => 'Condomino', 'inquilino' => 'Inquilino', 'fornitore' => 'Fornitore', 'amministratore' => 'Amministratore', 'altro' => 'Altro', ]), TextInput::make('riferimento_stabile') ->label('Riferimento stabile') ->maxLength(255), TextInput::make('riferimento_unita') ->label('Riferimento unità') ->maxLength(255), TextInput::make('note_segreteria') ->label('Note segreteria') ->maxLength(2000), ]) ->fillForm(fn(): array=> [ 'tipo_utenza_call' => $this->rubrica->tipo_utenza_call, 'riferimento_stabile' => $this->rubrica->riferimento_stabile, 'riferimento_unita' => $this->rubrica->riferimento_unita, 'note_segreteria' => $this->rubrica->note_segreteria, ]) ->action(function (array $data): void { $this->rubrica->tipo_utenza_call = ! empty($data['tipo_utenza_call']) ? (string) $data['tipo_utenza_call'] : null; $this->rubrica->riferimento_stabile = ! empty($data['riferimento_stabile']) ? (string) $data['riferimento_stabile'] : null; $this->rubrica->riferimento_unita = ! empty($data['riferimento_unita']) ? (string) $data['riferimento_unita'] : null; $this->rubrica->note_segreteria = ! empty($data['note_segreteria']) ? (string) $data['note_segreteria'] : null; $this->rubrica->data_ultima_modifica = now()->toDateString(); $this->rubrica->modificato_da = Auth::id(); $this->rubrica->save(); $this->mount((int) $this->rubrica->id); Notification::make() ->title('Riferimenti chiamata aggiornati') ->success() ->send(); }), Action::make('ruoli_legami') ->label('Ruoli / legami') ->icon('heroicon-o-link') ->modalWidth('7xl') ->form([ Repeater::make('ruoli') ->label('Ruoli e legami') ->defaultItems(0) ->reorderable(true) ->schema([ Hidden::make('id'), Select::make('ruolo_standard') ->label('Ruolo') ->options(self::getRuoliLabels()) ->required() ->native(false) ->default('altro') ->columnSpan(2), TextInput::make('ruolo_custom') ->label('Ruolo custom') ->maxLength(255) ->columnSpan(2), Select::make('stabile_id') ->label('Stabile') ->native(false) ->searchable() ->options(fn() => $this->getStabiliOptions()) ->reactive() ->afterStateUpdated(fn($set) => $set('unita_immobiliare_id', null)) ->columnSpan(3), Select::make('unita_immobiliare_id') ->label('Unità') ->native(false) ->searchable() ->options(fn($get) => $this->getUnitaOptions($get('stabile_id'))) ->columnSpan(3), DatePicker::make('data_inizio') ->label('Data inizio') ->columnSpan(2), DatePicker::make('data_fine') ->label('Data fine') ->columnSpan(2), Toggle::make('is_preferito') ->label('Preferito') ->default(false) ->columnSpan(2), Toggle::make('is_attivo') ->label('Attivo') ->default(true) ->columnSpan(2), ]) ->columns(12), ]) ->fillForm(function (): array { $ruoli = RubricaRuolo::query() ->where('rubrica_id', (int) $this->rubrica->id) ->orderByDesc('is_attivo') ->orderByDesc('is_preferito') ->orderByDesc('data_inizio') ->orderByDesc('id') ->limit(200) ->get(); return [ 'ruoli' => $ruoli->map(fn(RubricaRuolo $r) => [ 'id' => (int) $r->id, 'ruolo_standard' => (string) ($r->ruolo_standard ?? 'altro'), 'ruolo_custom' => (string) ($r->ruolo_custom ?? ''), 'stabile_id' => $r->stabile_id ? (int) $r->stabile_id : null, 'unita_immobiliare_id' => $r->unita_immobiliare_id ? (int) $r->unita_immobiliare_id : null, 'data_inizio' => $r->data_inizio?->format('Y-m-d'), 'data_fine' => $r->data_fine?->format('Y-m-d'), 'is_preferito' => (bool) ($r->is_preferito ?? false), 'is_attivo' => (bool) ($r->is_attivo ?? true), ])->all(), ]; }) ->action(function (array $data): void { /** @var array> $rows */ $rows = $data['ruoli'] ?? []; $rows = is_array($rows) ? array_values($rows) : []; DB::transaction(function () use ($rows): void { $keepIds = []; foreach ($rows as $row) { $id = (int) ($row['id'] ?? 0); $ruoloStandard = (string) ($row['ruolo_standard'] ?? 'altro'); $attrs = [ 'rubrica_id' => (int) $this->rubrica->id, 'ruolo_standard' => $ruoloStandard ?: 'altro', 'ruolo_custom' => ! empty($row['ruolo_custom']) ? (string) $row['ruolo_custom'] : null, 'stabile_id' => ! empty($row['stabile_id']) ? (int) $row['stabile_id'] : null, 'unita_immobiliare_id' => ! empty($row['unita_immobiliare_id']) ? (int) $row['unita_immobiliare_id'] : null, 'data_inizio' => $row['data_inizio'] ?? null, 'data_fine' => $row['data_fine'] ?? null, 'is_preferito' => (bool) ($row['is_preferito'] ?? false), 'is_attivo' => (bool) ($row['is_attivo'] ?? true), ]; if ($id > 0) { $existing = RubricaRuolo::query() ->where('id', $id) ->where('rubrica_id', (int) $this->rubrica->id) ->first(); if ($existing) { $existing->fill($attrs); $existing->save(); $keepIds[] = (int) $existing->id; continue; } } $created = RubricaRuolo::query()->create($attrs); $keepIds[] = (int) $created->id; } $deleteQuery = RubricaRuolo::query()->where('rubrica_id', (int) $this->rubrica->id); if (! empty($keepIds)) { $deleteQuery->whereNotIn('id', $keepIds); } $deleteQuery->delete(); }); $this->mount((int) $this->rubrica->id); Notification::make() ->title('Ruoli / legami aggiornati') ->success() ->send(); }), Action::make('torna') ->label('Torna') ->icon('heroicon-o-arrow-left') ->url(fn() => AnagraficaUnica::getUrl(panel: 'admin-filament')), ]; } private function fillInlineForm(): void { $this->inlineForm = [ 'nome' => (string) ($this->rubrica->nome ?? ''), 'cognome' => (string) ($this->rubrica->cognome ?? ''), 'ragione_sociale' => (string) ($this->rubrica->ragione_sociale ?? ''), 'tipo_contatto' => (string) ($this->rubrica->tipo_contatto ?? ''), 'categoria' => (string) ($this->rubrica->categoria ?? ''), 'codice_fiscale' => (string) ($this->rubrica->codice_fiscale ?? ''), 'partita_iva' => (string) ($this->rubrica->partita_iva ?? ''), 'email' => (string) ($this->rubrica->email ?? ''), 'pec' => (string) ($this->rubrica->pec ?? ''), 'telefono_ufficio' => (string) ($this->rubrica->telefono_ufficio ?? ''), 'telefono_cellulare' => (string) ($this->rubrica->telefono_cellulare ?? ''), 'telefono_casa' => (string) ($this->rubrica->telefono_casa ?? ''), 'indirizzo_completo' => (string) ($this->rubrica->indirizzo_completo ?? ''), 'tipo_utenza_call' => (string) ($this->rubrica->tipo_utenza_call ?? ''), 'riferimento_stabile' => (string) ($this->rubrica->riferimento_stabile ?? ''), 'riferimento_unita' => (string) ($this->rubrica->riferimento_unita ?? ''), 'note' => (string) ($this->rubrica->note ?? ''), 'note_segreteria' => (string) ($this->rubrica->note_segreteria ?? ''), ]; } private function cleanNullable(mixed $value): mixed { if (! is_string($value)) { return $value; } $trim = trim($value); return $trim === '' ? null : $trim; } public function getUrlUnita(?int $unitaId): ?string { if (! $unitaId) { return null; } if (! class_exists(\App\Filament\Pages\UnitaImmobiliarePage::class)) { return null; } return \App\Filament\Pages\UnitaImmobiliarePage::getUrl(panel: 'admin-filament') . '?unita_id=' . (int) $unitaId . '&back=' . urlencode((string) request()->getRequestUri()); } /** @return array */ protected static function getRuoliLabels(): array { return [ 'super_admin' => 'Super admin', 'amministratore' => 'Amministratore', 'condomino' => 'Condomino', 'inquilino' => 'Inquilino', 'fornitore' => 'Fornitore', 'manutentore' => 'Manutentore', 'banca' => 'Banca', 'assicurazione' => 'Assicurazione', 'collaboratore' => 'Collaboratore', 'tester' => 'Tester', 'ospite' => 'Ospite', 'altro' => 'Altro', ]; } /** @return array */ protected function getStabiliOptions(): array { $query = Stabile::query(); $user = Auth::user(); $isSuperAdmin = $user instanceof User && method_exists($user, 'hasRole') ? (bool) $user->hasRole('super-admin') : false; if (! $isSuperAdmin && $this->adminId > 0) { $query->where('amministratore_id', $this->adminId); } return $query ->orderBy('codice_stabile') ->limit(200) ->get(['id', 'codice_stabile', 'denominazione']) ->mapWithKeys(fn(Stabile $s) => [ (int) $s->id => trim((string) ($s->codice_operatore ?? $s->codice_stabile ?? '') . ' - ' . (string) ($s->denominazione ?? 'Stabile')), ]) ->all(); } /** @return array */ protected function getTitoliOptions(): array { if (! Schema::hasTable('titoli')) { return []; } return Titolo::query() ->orderBy('ordine') ->orderBy('nome') ->get(['id', 'nome', 'sigla']) ->mapWithKeys(fn(Titolo $t) => [ (int) $t->id => trim((string) ($t->sigla ?: $t->nome)), ]) ->all(); } /** @return array */ protected function getUnitaOptions($stabileId): array { $stabileId = (int) ($stabileId ?? 0); if ($stabileId <= 0) { return []; } $query = UnitaImmobiliare::query()->where('stabile_id', $stabileId); return $query ->orderBy('palazzina_id') ->orderBy('scala') ->orderBy('piano') ->orderBy('interno') ->orderBy('id') ->limit(1000) ->get(['id', 'codice_unita', 'denominazione', 'scala', 'piano', 'interno']) ->mapWithKeys(function (UnitaImmobiliare $u) { $codice = $u->codice_unita ?: ('ID ' . $u->id); $nome = $u->denominazione ?: 'Unità'; $pos = trim('Scala ' . ($u->scala ?? '-') . ' · Piano ' . ($u->piano ?? '-') . ' · Int. ' . ($u->interno ?? '-')); return [(int) $u->id => trim($codice . ' — ' . $nome . ' (' . $pos . ')')]; }) ->all(); } private function hydrateFornitoriWorkspace(): void { $rubricaId = (int) $this->rubrica->id; $cf = trim((string) ($this->rubrica->codice_fiscale ?? '')); $piva = trim((string) ($this->rubrica->partita_iva ?? '')); $email = mb_strtolower(trim((string) ($this->rubrica->email ?? ''))); $query = Fornitore::query(); if ($this->adminId > 0) { $query->where('amministratore_id', $this->adminId); } $query->where(function (Builder $q) use ($rubricaId, $cf, $piva, $email): void { $q->where('rubrica_id', $rubricaId); if ($cf !== '') { $q->orWhere('codice_fiscale', $cf); } if ($piva !== '') { $q->orWhere('partita_iva', $piva); } if ($email !== '') { $q->orWhereRaw('LOWER(email) = ?', [$email]); } }); $fornitori = $query ->orderByRaw("COALESCE(ragione_sociale, '')") ->orderBy('id') ->limit(80) ->get(['id', 'rubrica_id', 'ragione_sociale', 'nome', 'cognome', 'email', 'telefono', 'cellulare', 'codice_fiscale', 'partita_iva']); $this->fornitoriCollegati = $fornitori->map(fn(Fornitore $f) => [ 'id' => (int) $f->id, 'nome' => (string) ($f->ragione_sociale ?: trim((string) ($f->nome ?? '') . ' ' . (string) ($f->cognome ?? '')) ?: ('Fornitore #' . $f->id)), 'rubrica_id' => (int) ($f->rubrica_id ?? 0), 'email' => (string) ($f->email ?? ''), 'telefono' => (string) ($f->telefono ?: $f->cellulare ?: ''), 'cf' => (string) ($f->codice_fiscale ?? ''), 'piva' => (string) ($f->partita_iva ?? ''), ])->all(); $fornitoreIds = $fornitori->pluck('id')->map(fn($v) => (int) $v)->all(); if ($fornitoreIds === []) { $this->dipendentiFornitoreRows = []; return; } $dipendenti = FornitoreDipendente::query() ->with(['user.roles', 'fornitore']) ->whereIn('fornitore_id', $fornitoreIds) ->orderByDesc('attivo') ->orderBy('cognome') ->orderBy('nome') ->limit(300) ->get(); $this->dipendentiFornitoreRows = $dipendenti->map(function (FornitoreDipendente $d): array { $user = $d->user; $this->dipendentePbxExtension[(int) $d->id] = (string) ($user?->pbx_extension ?? ''); return [ 'id' => (int) $d->id, 'fornitore_id' => (int) $d->fornitore_id, 'fornitore_nome' => (string) ($d->fornitore?->ragione_sociale ?: ('Fornitore #' . $d->fornitore_id)), 'nome' => (string) $d->nome_completo, 'email' => (string) ($d->email ?? ''), 'telefono' => (string) ($d->telefono ?? ''), 'attivo' => (bool) $d->attivo, 'user_id' => $user ? (int) $user->id : null, 'ruoli' => $user ? $user->roles->pluck('name')->values()->all() : [], 'pbx_extension' => (string) ($user?->pbx_extension ?? ''), ]; })->all(); } public function collegaRubricaDipendente(int $fornitoreId): void { $rubricaId = (int) ($this->dipendenteRubricaSelect[$fornitoreId] ?? 0); if ($rubricaId <= 0) { Notification::make()->title('Inserisci ID rubrica da collegare')->warning()->send(); return; } $fornitore = Fornitore::query()->find($fornitoreId); if (! $fornitore || ($this->adminId > 0 && (int) ($fornitore->amministratore_id ?? 0) !== $this->adminId)) { Notification::make()->title('Fornitore non valido')->danger()->send(); return; } $rubricaDip = RubricaUniversale::query()->find($rubricaId); if (! $rubricaDip) { Notification::make()->title('Rubrica dipendente non trovata')->danger()->send(); return; } $email = mb_strtolower(trim((string) ($rubricaDip->email ?? ''))); $query = FornitoreDipendente::query()->where('fornitore_id', (int) $fornitore->id); if ($email !== '') { $query->whereRaw('LOWER(email) = ?', [$email]); } else { $query->where('nome', (string) ($rubricaDip->nome ?? '')) ->where('cognome', (string) ($rubricaDip->cognome ?? '')); } $dip = $query->first(); if (! $dip) { $dip = new FornitoreDipendente(); $dip->fornitore_id = (int) $fornitore->id; $dip->created_by_user_id = Auth::id(); } $dip->nome = (string) ($rubricaDip->nome ?: $rubricaDip->ragione_sociale ?: 'Dipendente'); $dip->cognome = (string) ($rubricaDip->cognome ?? ''); $dip->email = $email !== '' ? $email : null; $dip->telefono = (string) ($rubricaDip->telefono_cellulare ?: $rubricaDip->telefono_ufficio ?: ''); $dip->attivo = true; $dip->updated_by_user_id = Auth::id(); $dip->save(); unset($this->dipendenteRubricaSelect[$fornitoreId]); $this->hydrateFornitoriWorkspace(); Notification::make()->title('Dipendente collegato da rubrica')->success()->send(); } public function creaDipendenteManuale(int $fornitoreId): void { $draft = (array) ($this->dipendenteManualDraft[$fornitoreId] ?? []); $nome = trim((string) ($draft['nome'] ?? '')); $cognome = trim((string) ($draft['cognome'] ?? '')); $email = mb_strtolower(trim((string) ($draft['email'] ?? ''))); $telefono = trim((string) ($draft['telefono'] ?? '')); if ($nome === '') { Notification::make()->title('Nome dipendente obbligatorio')->warning()->send(); return; } $fornitore = Fornitore::query()->find($fornitoreId); if (! $fornitore || ($this->adminId > 0 && (int) ($fornitore->amministratore_id ?? 0) !== $this->adminId)) { Notification::make()->title('Fornitore non valido')->danger()->send(); return; } $dip = new FornitoreDipendente(); $dip->fornitore_id = (int) $fornitore->id; $dip->nome = $nome; $dip->cognome = $cognome !== '' ? $cognome : null; $dip->email = $email !== '' ? $email : null; $dip->telefono = $telefono !== '' ? $telefono : null; $dip->attivo = true; $dip->created_by_user_id = Auth::id(); $dip->updated_by_user_id = Auth::id(); $dip->save(); unset($this->dipendenteManualDraft[$fornitoreId]); $this->hydrateFornitoriWorkspace(); Notification::make()->title('Nuovo dipendente creato')->success()->send(); } public function abilitaAccessoDipendente(int $dipendenteId): void { $dip = FornitoreDipendente::query()->with('fornitore')->find($dipendenteId); if (! $dip || ($this->adminId > 0 && (int) ($dip->fornitore?->amministratore_id ?? 0) !== $this->adminId)) { Notification::make()->title('Dipendente non valido')->danger()->send(); return; } $email = mb_strtolower(trim((string) ($dip->email ?? ''))); if ($email === '') { Notification::make()->title('Email dipendente mancante')->warning()->send(); return; } $user = User::query()->whereRaw('LOWER(email) = ?', [$email])->first(); if (! $user) { $tmpPwd = Str::random(12); $user = User::query()->create([ 'name' => trim((string) ($dip->nome_completo ?: 'Utente fornitore')), 'email' => $email, 'password' => Hash::make($tmpPwd), 'email_verified_at' => now(), 'is_active' => true, ]); } $user->assignRole('fornitore'); $dip->user_id = (int) $user->id; $dip->updated_by_user_id = Auth::id(); $dip->save(); $this->hydrateFornitoriWorkspace(); Notification::make()->title('Accesso dipendente abilitato')->success()->send(); } public function salvaInternoDipendente(int $dipendenteId): void { $dip = FornitoreDipendente::query()->with('user', 'fornitore')->find($dipendenteId); if (! $dip || ! $dip->user || ($this->adminId > 0 && (int) ($dip->fornitore?->amministratore_id ?? 0) !== $this->adminId)) { Notification::make()->title('Dipendente/utente non valido')->danger()->send(); return; } if (! Schema::hasColumn('users', 'pbx_extension')) { Notification::make()->title('Campo interno PBX non disponibile')->warning()->send(); return; } $ext = trim((string) ($this->dipendentePbxExtension[$dipendenteId] ?? '')); $dip->user->pbx_extension = $ext !== '' ? $ext : null; $dip->user->save(); $this->hydrateFornitoriWorkspace(); Notification::make()->title('Interno PBX aggiornato')->success()->send(); } }