netgescon-day0/app/Filament/Pages/Gescon/RubricaUniversaleScheda.php

1262 lines
53 KiB
PHP

<?php
namespace App\Filament\Pages\Gescon;
use App\Models\Fornitore;
use App\Models\FornitoreDipendente;
use App\Models\RubricaContattoCanale;
use App\Models\RubricaRuolo;
use App\Models\RubricaUniversale;
use App\Models\Stabile;
use App\Models\Titolo;
use App\Models\UnitaImmobiliare;
use App\Models\User;
use App\Support\StabileContext;
use Filament\Actions\Action;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Hidden;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Str;
class RubricaUniversaleScheda extends Page
{
protected static ?string $title = 'Scheda anagrafica (Rubrica)';
protected static ?string $slug = 'gescon/anagrafica/rubrica/{record}';
protected static bool $shouldRegisterNavigation = false;
protected string $view = 'filament.pages.gescon.rubrica-universale-scheda';
public RubricaUniversale $rubrica;
/** @var array<int, array<string, mixed>> */
public array $fornitori = [];
/** @var array<int, array<string, mixed>> */
public array $stabili = [];
/** @var array<int, array<string, mixed>> */
public array $contattiPrincipali = [];
/** @var array<int, array<string, mixed>> */
public array $ruoliAttivi = [];
/** @var array<int, array<string, mixed>> */
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<int, int> */
public array $adminStabileIds = [];
public bool $isInlineEditing = false;
public string $sideTab = 'collegamenti';
/** @var array<int,array<string,mixed>> */
public array $fornitoriCollegati = [];
/** @var array<int,array<string,mixed>> */
public array $dipendentiFornitoreRows = [];
/** @var array<int,int|string> */
public array $dipendenteRubricaSelect = [];
/** @var array<int,array<string,string>> */
public array $dipendenteManualDraft = [];
/** @var array<int,string> */
public array $dipendentePbxExtension = [];
/** @var array<string,mixed> */
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<int, array<string, mixed>> $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<int, array<string, mixed>> $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<string, string> */
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<int, string> */
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<int, string> */
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<int, string> */
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();
}
}