netgescon-day0/app/Filament/Pages/Condomini/StabilePage.php

1316 lines
51 KiB
PHP

<?php
namespace App\Filament\Pages\Condomini;
use App\Filament\Pages\Impostazioni\GoogleDashboard;
use App\Filament\Pages\Strumenti\Comunicazioni;
use App\Models\CommunicationMessage;
use App\Models\Documento;
use App\Models\DocumentoCollegato;
use App\Models\InsuranceClaim;
use App\Models\InsurancePolicy;
use App\Models\RubricaUniversale;
use App\Models\Stabile as StabileModel;
use App\Models\Ticket;
use App\Models\TicketAttachment;
use App\Models\User;
use App\Services\Support\GmailTicketImportService;
use App\Support\GoogleAccountStore;
use App\Support\Livewire\SupportsRubricaLookup;
use App\Support\StabileContext;
use BackedEnum;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Livewire\WithFileUploads;
use UnitEnum;
class StabilePage extends Page
{
use WithFileUploads;
use SupportsRubricaLookup;
protected static ?string $navigationLabel = 'Stabile';
protected static ?string $title = 'Stabile';
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-building-office-2';
protected static UnitEnum|string|null $navigationGroup = 'Stabile';
protected static ?int $navigationSort = 20;
protected static ?string $slug = 'condomini/stabile';
protected string $view = 'filament.pages.condomini.stabile';
public ?StabileModel $stabile = null;
public string $tab = 'dati-generali';
public bool $mostraRiscaldamento = false;
/** @var array{ordinaria: array<int>, riscaldamento: array<int>, straordinaria: array<int>} */
public array $periodiEmissione = [
'ordinaria' => [],
'riscaldamento' => [],
'straordinaria' => [],
];
public ?int $gestioneOrdinariaId = null;
public ?int $gestioneSelezionataId = null;
public ?string $gestioneSelezionataTipo = null;
public ?string $gestioneDataInizio = null;
public ?string $gestioneDataFine = null;
/** @var array<int> */
public array $gestioneMesi = [];
public ?int $gestioneNumeroRate = null;
public ?string $gestioneCadenza = null;
/** @var array<int, bool> */
public array $checklistFineAnnoState = [];
public ?int $selectedInsurancePolicyId = null;
public string $officialPecCondominio = '';
public string $officialPecStudio = '';
public int $gmailImportMaxMessages = 10;
/** @var array<int,array<string,mixed>> */
public array $officialMailboxes = [];
/** @var array<string,string> */
public array $stableGoogleAccountOptions = [];
public string $insuranceBrokerSearch = '';
public string $insuranceCompanySearch = '';
/** @var array<int,array<string,mixed>> */
public array $insuranceBrokerMatches = [];
/** @var array<int,array<string,mixed>> */
public array $insuranceCompanyMatches = [];
/** @var array<int,array<string,mixed>> */
public array $insurancePaymentRows = [
['label' => '', 'due_date' => null, 'paid_at' => null, 'amount' => null, 'notes' => ''],
['label' => '', 'due_date' => null, 'paid_at' => null, 'amount' => null, 'notes' => ''],
];
/** @var array<string,mixed>|null */
public ?array $insuranceAttachmentPreview = null;
public array $insurancePolicyForm = [
'broker_rubrica_id' => null,
'company_rubrica_id' => null,
'policy_name' => '',
'policy_number' => '',
'policy_reference' => '',
'status' => 'attiva',
'started_at' => null,
'expires_at' => null,
'renewal_at' => null,
'annual_amount' => null,
'payment_split' => '',
'payment_schedule_notes' => '',
'coverage_summary' => '',
'special_conditions' => '',
'claim_form_template' => '',
'notes' => '',
];
public $insurancePolicyDocumentUpload = null;
public $insuranceSignatureUpload = null;
public static function canAccess(): bool
{
$user = Auth::user();
return $user instanceof User
&& $user->hasAnyRole(['super-admin', 'admin', 'amministratore', 'collaboratore']);
}
public function mount(): void
{
$user = Auth::user();
if (! $user instanceof User) {
abort(403);
}
$requestedStabileId = request()->integer('stabile_id');
if ($requestedStabileId > 0) {
StabileContext::setActiveStabileId($user, (int) $requestedStabileId);
}
$activeId = StabileContext::resolveActiveStabileId($user);
if (! $activeId) {
$this->stabile = null;
return;
}
$this->stabile = StabileContext::accessibleStabili($user)->firstWhere('id', $activeId);
if (! $this->stabile) {
$this->stabile = null;
return;
}
$this->stabile->load([
'amministratore',
'datiBancari',
'palazzine',
'documentiCollegati',
]);
$config = (array) ($this->stabile->configurazione_avanzata ?? []);
$this->gestioneOrdinariaId = isset($config['gestione_ordinaria_id']) ? (int) $config['gestione_ordinaria_id'] : null;
$this->checklistFineAnnoState = is_array($config['checklist_fine_anno_state'] ?? null)
? $config['checklist_fine_anno_state']
: [];
$periodi = (array) ($config['periodi_emissione'] ?? []);
$fallbackOrdinaria = $this->normalizeMesi((array) ($this->stabile->rate_ordinarie_mesi ?? []));
if ($fallbackOrdinaria === []) {
$fallbackOrdinaria = $this->extractLegacyRateMonthsFromConfig($config, 'ord');
}
$fallbackRiscaldamento = $this->normalizeMesi((array) ($this->stabile->rate_riscaldamento_mesi ?? []));
if ($fallbackRiscaldamento === []) {
$fallbackRiscaldamento = $this->extractLegacyRateMonthsFromConfig($config, 'ris');
}
$this->periodiEmissione = [
'ordinaria' => $this->normalizeMesi((array) ($periodi['ordinaria'] ?? [])) ?: $fallbackOrdinaria,
'riscaldamento' => $this->normalizeMesi((array) ($periodi['riscaldamento'] ?? [])) ?: $fallbackRiscaldamento,
'straordinaria' => $this->normalizeMesi((array) ($periodi['straordinaria'] ?? [])),
];
$this->mostraRiscaldamento = (bool) ($config['mostra_riscaldamento'] ?? ($this->periodiEmissione['riscaldamento'] !== []));
$gestioneSelezionataId = request()->integer('gestione_id');
if ($gestioneSelezionataId > 0) {
$this->gestioneSelezionataId = $gestioneSelezionataId;
if (! $this->gestioneOrdinariaId) {
$this->gestioneOrdinariaId = $gestioneSelezionataId;
}
}
$this->loadGestioneSelezionata();
$tab = request()->query('tab');
if (is_string($tab) && $tab !== '') {
$this->tab = $tab;
}
if (! array_key_exists($this->tab, $this->tabs())) {
$this->tab = 'dati-generali';
}
$this->initializeOfficialMailSettings();
}
/**
* @return array<string,string>
*/
public function tabs(): array
{
return [
'dati-generali' => 'Dati generali',
'unita-immobiliari' => 'Unità / Locali tecnici',
'palazzine' => 'Palazzine',
'assicurazioni' => 'Assicurazioni',
'tabelle-millesimali' => 'Tabelle millesimali',
'rate-emesse' => 'Rate emesse',
'gestioni' => 'Gestioni',
'documentazione' => 'Documentazione',
'posta-ufficiale' => 'Posta ufficiale',
'protocollo-comunicazioni' => 'Protocollo comunicazioni',
'dati-bancari' => 'Banche e casse',
];
}
public function tabView(): string
{
return match ($this->tab) {
'rate-emesse' => 'admin.stabili.tabs.rate-emesse',
'palazzine' => 'admin.stabili.tabs.palazzine',
'assicurazioni' => 'filament.pages.condomini.tabs.assicurazioni',
'dati-bancari' => 'filament.pages.condomini.tabs.dati-bancari',
'tabelle-millesimali' => 'filament.pages.condomini.tabelle-millesimali-tab',
'gestioni' => 'admin.stabili.tabs.gestioni',
'unita-immobiliari' => 'admin.stabili.tabs.unita-immobiliari',
'documentazione' => 'filament.pages.condomini.tabs.documentazione',
'posta-ufficiale' => 'filament.pages.condomini.tabs.posta-ufficiale',
'protocollo-comunicazioni' => 'filament.pages.condomini.tabs.protocollo-comunicazioni',
default => 'admin.stabili.tabs.dati-generali',
};
}
public function toggleMostraRiscaldamento(): void
{
if (! $this->stabile) {
return;
}
$config = (array) ($this->stabile->configurazione_avanzata ?? []);
$value = ! (bool) ($config['mostra_riscaldamento'] ?? false);
$config['mostra_riscaldamento'] = $value;
$this->stabile->configurazione_avanzata = $config;
$this->stabile->save();
$this->mostraRiscaldamento = $value;
$this->dispatch('refresh-gestioni-stabile');
}
public function savePeriodiEmissione(): void
{
if (! $this->stabile) {
return;
}
$config = (array) ($this->stabile->configurazione_avanzata ?? []);
$config['periodi_emissione'] = [
'ordinaria' => $this->normalizeMesi((array) ($this->periodiEmissione['ordinaria'] ?? [])),
'riscaldamento' => $this->normalizeMesi((array) ($this->periodiEmissione['riscaldamento'] ?? [])),
'straordinaria' => $this->normalizeMesi((array) ($this->periodiEmissione['straordinaria'] ?? [])),
];
$this->stabile->configurazione_avanzata = $config;
$this->stabile->save();
$this->periodiEmissione = $config['periodi_emissione'];
Notification::make()
->title('Periodi emissione rate salvati')
->success()
->send();
}
public function saveGestioneSelezionata(): void
{
if (! $this->stabile || ! $this->gestioneSelezionataId) {
return;
}
$gestione = \App\Models\GestioneContabile::query()->find($this->gestioneSelezionataId);
if (! $gestione) {
return;
}
$gestione->data_inizio = $this->gestioneDataInizio ?: null;
$gestione->data_fine = $this->gestioneDataFine ?: null;
$mesi = $this->normalizeMesi($this->gestioneMesi);
$field = match ($gestione->tipo_gestione) {
'ordinaria' => 'mesi_rate_ordinaria',
'riscaldamento' => 'mesi_rate_riscaldamento',
'straordinaria' => 'mesi_rate_straordinaria',
default => 'mesi_rate_ordinaria',
};
$gestione->{$field} = $mesi;
if ($gestione->tipo_gestione === 'straordinaria') {
$piano = (array) ($gestione->piano_straordinario ?? []);
$piano['numero_rate'] = $this->gestioneNumeroRate;
$piano['cadenza'] = $this->gestioneCadenza;
$gestione->piano_straordinario = $piano;
}
$gestione->save();
Notification::make()
->title('Gestione aggiornata')
->success()
->send();
}
public function saveChecklistFineAnno(): void
{
if (! $this->stabile) {
return;
}
$config = (array) ($this->stabile->configurazione_avanzata ?? []);
$config['gestione_ordinaria_id'] = $this->gestioneOrdinariaId;
$config['checklist_fine_anno_state'] = $this->checklistFineAnnoState;
$this->stabile->configurazione_avanzata = $config;
$this->stabile->save();
Notification::make()
->title('Checklist salvata')
->success()
->send();
}
/**
* Carica la gestione selezionata (o quella ordinaria di riferimento)
* e popola i campi di editing della tab periodi.
*/
private function loadGestioneSelezionata(): void
{
$gestioneId = $this->gestioneSelezionataId ?? $this->gestioneOrdinariaId;
if (! $gestioneId) {
return;
}
$gestione = \App\Models\GestioneContabile::query()->find($gestioneId);
if (! $gestione) {
return;
}
$this->gestioneSelezionataId = $gestione->id;
$this->gestioneSelezionataTipo = $gestione->tipo_gestione;
$this->gestioneDataInizio = $gestione->data_inizio?->format('Y-m-d');
$this->gestioneDataFine = $gestione->data_fine?->format('Y-m-d');
$mesi = match ($gestione->tipo_gestione) {
'ordinaria' => (array) ($gestione->mesi_rate_ordinaria ?? []),
'riscaldamento' => (array) ($gestione->mesi_rate_riscaldamento ?? []),
'straordinaria' => (array) ($gestione->mesi_rate_straordinaria ?? []),
default => [],
};
$this->gestioneMesi = $this->normalizeMesi($mesi);
$piano = (array) ($gestione->piano_straordinario ?? []);
$this->gestioneNumeroRate = isset($piano['numero_rate']) ? (int) $piano['numero_rate'] : null;
$this->gestioneCadenza = $piano['cadenza'] ?? null;
}
/** @param array<int, int|string> $mesi */
private function normalizeMesi(array $mesi): array
{
$mesi = array_values(array_unique(array_map('intval', $mesi)));
sort($mesi);
return $mesi;
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, InsurancePolicy>
*/
public function getInsurancePoliciesProperty()
{
if (! $this->stabile instanceof StabileModel) {
return InsurancePolicy::query()->whereRaw('1 = 0')->get();
}
return InsurancePolicy::query()
->with(['brokerRubrica', 'companyRubrica'])
->where('stabile_id', (int) $this->stabile->id)
->orderByDesc('renewal_at')
->orderByDesc('expires_at')
->orderByDesc('id')
->get();
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int, InsuranceClaim>
*/
public function getInsuranceClaimsRowsProperty()
{
if (! $this->stabile instanceof StabileModel) {
return InsuranceClaim::query()->whereRaw('1 = 0')->get();
}
return InsuranceClaim::query()
->with(['ticket', 'insurancePolicy'])
->where('stabile_id', (int) $this->stabile->id)
->when(
(int) ($this->selectedInsurancePolicyId ?? 0) > 0,
fn($query) => $query->where('insurance_policy_id', (int) $this->selectedInsurancePolicyId)
)
->latest('opened_at')
->latest('id')
->get();
}
/**
* @return array<int, string>
*/
public function getInsuranceRubricaOptionsProperty(): array
{
return RubricaUniversale::query()
->whereNull('deleted_at')
->whereIn('categoria', ['assicurazione', 'fornitore', 'altro'])
->orderBy('ragione_sociale')
->orderBy('cognome')
->orderBy('nome')
->limit(300)
->get(['id', 'ragione_sociale', 'nome', 'cognome'])
->mapWithKeys(function (RubricaUniversale $rubrica): array {
$label = trim((string) ($rubrica->ragione_sociale ?: trim(($rubrica->nome ?? '') . ' ' . ($rubrica->cognome ?? ''))));
return [(int) $rubrica->id => ($label !== '' ? $label : ('Rubrica #' . (int) $rubrica->id))];
})
->all();
}
public function updatedInsuranceBrokerSearch($value = null): void
{
$this->insuranceBrokerSearch = $this->normalizeInsuranceSearchValue($value ?? $this->insuranceBrokerSearch);
$this->searchInsuranceRubrica('broker');
}
public function updatedInsuranceCompanySearch($value = null): void
{
$this->insuranceCompanySearch = $this->normalizeInsuranceSearchValue($value ?? $this->insuranceCompanySearch);
$this->searchInsuranceRubrica('company');
}
public function updatedInsurancePolicyForm($value, string $key): void
{
if (in_array($key, ['broker_rubrica_id', 'company_rubrica_id'], true)) {
$this->insurancePolicyForm[$key] = $this->normalizeInsuranceIdValue($value);
}
}
public function selectInsurancePolicy($policyId): void
{
$policyId = $this->normalizeInsuranceIdValue($policyId);
if ($policyId === null) {
return;
}
$policy = $this->insurancePolicies->firstWhere('id', $policyId);
if (! $policy instanceof InsurancePolicy) {
return;
}
$this->selectedInsurancePolicyId = (int) $policy->id;
$this->insurancePolicyForm = [
'broker_rubrica_id' => $policy->broker_rubrica_id,
'company_rubrica_id' => $policy->company_rubrica_id,
'policy_name' => (string) ($policy->policy_name ?? ''),
'policy_number' => (string) ($policy->policy_number ?? ''),
'policy_reference' => (string) ($policy->policy_reference ?? ''),
'status' => (string) ($policy->status ?? 'attiva'),
'started_at' => optional($policy->started_at)->format('Y-m-d'),
'expires_at' => optional($policy->expires_at)->format('Y-m-d'),
'renewal_at' => optional($policy->renewal_at)->format('Y-m-d'),
'annual_amount' => $policy->annual_amount,
'payment_split' => (string) ($policy->payment_split ?? ''),
'payment_schedule_notes' => (string) ($policy->payment_schedule_notes ?? ''),
'coverage_summary' => (string) ($policy->coverage_summary ?? ''),
'special_conditions' => (string) ($policy->special_conditions ?? ''),
'claim_form_template' => (string) ($policy->claim_form_template ?? ''),
'notes' => (string) ($policy->notes ?? ''),
];
$this->insuranceBrokerSearch = $this->formatRubricaLabel($policy->brokerRubrica);
$this->insuranceCompanySearch = $this->formatRubricaLabel($policy->companyRubrica);
$this->insuranceBrokerMatches = $policy->brokerRubrica ? [$this->mapInsuranceRubricaRow($policy->brokerRubrica)] : [];
$this->insuranceCompanyMatches = $policy->companyRubrica ? [$this->mapInsuranceRubricaRow($policy->companyRubrica)] : [];
$this->insurancePaymentRows = $this->buildInsurancePaymentRowsFromPolicy($policy);
$this->insuranceAttachmentPreview = null;
}
public function resetInsurancePolicyForm(): void
{
$this->selectedInsurancePolicyId = null;
$this->insurancePolicyForm = [
'broker_rubrica_id' => null,
'company_rubrica_id' => null,
'policy_name' => '',
'policy_number' => '',
'policy_reference' => '',
'status' => 'attiva',
'started_at' => null,
'expires_at' => null,
'renewal_at' => null,
'annual_amount' => null,
'payment_split' => '',
'payment_schedule_notes' => '',
'coverage_summary' => '',
'special_conditions' => '',
'claim_form_template' => '',
'notes' => '',
];
$this->insurancePolicyDocumentUpload = null;
$this->insuranceSignatureUpload = null;
$this->insuranceBrokerSearch = '';
$this->insuranceCompanySearch = '';
$this->insuranceBrokerMatches = [];
$this->insuranceCompanyMatches = [];
$this->insurancePaymentRows = $this->defaultInsurancePaymentRows();
$this->insuranceAttachmentPreview = null;
}
public function selectInsuranceBroker($rubricaId): void
{
$rubricaId = $this->normalizeInsuranceIdValue($rubricaId);
if ($rubricaId === null) {
return;
}
$rubrica = RubricaUniversale::query()->find($rubricaId);
if (! $rubrica instanceof RubricaUniversale) {
return;
}
$this->insurancePolicyForm['broker_rubrica_id'] = (int) $rubrica->id;
$this->insuranceBrokerSearch = $this->formatRubricaLabel($rubrica);
$this->insuranceBrokerMatches = [$this->mapInsuranceRubricaRow($rubrica)];
}
public function selectInsuranceCompany($rubricaId): void
{
$rubricaId = $this->normalizeInsuranceIdValue($rubricaId);
if ($rubricaId === null) {
return;
}
$rubrica = RubricaUniversale::query()->find($rubricaId);
if (! $rubrica instanceof RubricaUniversale) {
return;
}
$this->insurancePolicyForm['company_rubrica_id'] = (int) $rubrica->id;
$this->insuranceCompanySearch = $this->formatRubricaLabel($rubrica);
$this->insuranceCompanyMatches = [$this->mapInsuranceRubricaRow($rubrica)];
}
public function resetInsuranceBrokerSelection(): void
{
$this->insurancePolicyForm['broker_rubrica_id'] = null;
$this->insuranceBrokerSearch = '';
$this->insuranceBrokerMatches = [];
}
public function resetInsuranceCompanySelection(): void
{
$this->insurancePolicyForm['company_rubrica_id'] = null;
$this->insuranceCompanySearch = '';
$this->insuranceCompanyMatches = [];
}
public function saveInsurancePolicy(): void
{
if (! $this->stabile instanceof StabileModel) {
return;
}
$this->insurancePolicyForm['broker_rubrica_id'] = $this->normalizeInsuranceIdValue($this->insurancePolicyForm['broker_rubrica_id'] ?? null);
$this->insurancePolicyForm['company_rubrica_id'] = $this->normalizeInsuranceIdValue($this->insurancePolicyForm['company_rubrica_id'] ?? null);
$this->insuranceBrokerSearch = $this->normalizeInsuranceSearchValue($this->insuranceBrokerSearch);
$this->insuranceCompanySearch = $this->normalizeInsuranceSearchValue($this->insuranceCompanySearch);
$data = validator($this->insurancePolicyForm, [
'broker_rubrica_id' => ['nullable', 'integer', 'exists:rubrica_universale,id'],
'company_rubrica_id' => ['nullable', 'integer', 'exists:rubrica_universale,id'],
'policy_name' => ['nullable', 'string', 'max:255'],
'policy_number' => ['nullable', 'string', 'max:255'],
'policy_reference' => ['nullable', 'string', 'max:255'],
'status' => ['required', 'string', 'max:50'],
'started_at' => ['nullable', 'date'],
'expires_at' => ['nullable', 'date'],
'renewal_at' => ['nullable', 'date'],
'annual_amount' => ['nullable', 'numeric'],
'payment_split' => ['nullable', 'string', 'max:255'],
'payment_schedule_notes' => ['nullable', 'string', 'max:5000'],
'coverage_summary' => ['nullable', 'string', 'max:10000'],
'special_conditions' => ['nullable', 'string', 'max:20000'],
'claim_form_template' => ['nullable', 'string', 'max:20000'],
'notes' => ['nullable', 'string', 'max:10000'],
])->validate();
$paymentRows = $this->normalizeInsurancePaymentRows($this->insurancePaymentRows);
$selectedPolicyId = $this->normalizeInsuranceIdValue($this->selectedInsurancePolicyId);
$policy = $selectedPolicyId
? InsurancePolicy::query()->where('stabile_id', (int) $this->stabile->id)->find($selectedPolicyId)
: new InsurancePolicy();
if (! $policy instanceof InsurancePolicy) {
$policy = new InsurancePolicy();
}
$policy->fill([
'stabile_id' => (int) $this->stabile->id,
'broker_rubrica_id' => (int) ($data['broker_rubrica_id'] ?? 0) ?: null,
'company_rubrica_id' => (int) ($data['company_rubrica_id'] ?? 0) ?: null,
'policy_name' => $this->cleanInsuranceNullable($data['policy_name'] ?? null),
'policy_number' => $this->cleanInsuranceNullable($data['policy_number'] ?? null),
'policy_reference' => $this->cleanInsuranceNullable($data['policy_reference'] ?? null),
'status' => $this->cleanInsuranceNullable($data['status'] ?? 'attiva') ?: 'attiva',
'started_at' => $data['started_at'] ?? null,
'expires_at' => $data['expires_at'] ?? null,
'renewal_at' => $data['renewal_at'] ?? null,
'annual_amount' => $data['annual_amount'] ?? null,
'payment_split' => $this->cleanInsuranceNullable($data['payment_split'] ?? null),
'payment_schedule_notes' => $this->cleanInsuranceNullable($this->serializeInsurancePaymentRows($paymentRows)),
'coverage_summary' => $this->cleanInsuranceNullable($data['coverage_summary'] ?? null),
'special_conditions' => $this->cleanInsuranceNullable($data['special_conditions'] ?? null),
'claim_form_template' => $this->cleanInsuranceNullable($data['claim_form_template'] ?? null),
'notes' => $this->cleanInsuranceNullable($data['notes'] ?? null),
'metadata' => array_merge((array) ($policy->metadata ?? []), ['payment_rows' => $paymentRows]),
'created_by' => Auth::id(),
]);
$policy->save();
$this->storeInsuranceUploads($policy);
$this->selectedInsurancePolicyId = (int) $policy->id;
Notification::make()->title('Polizza assicurativa salvata')->success()->send();
}
public function deleteInsurancePolicy($policyId): void
{
if (! $this->stabile instanceof StabileModel) {
return;
}
$policyId = $this->normalizeInsuranceIdValue($policyId);
if ($policyId === null) {
return;
}
$policy = InsurancePolicy::query()->where('stabile_id', (int) $this->stabile->id)->find($policyId);
if (! $policy instanceof InsurancePolicy) {
return;
}
$policy->delete();
if ((int) ($this->selectedInsurancePolicyId ?? 0) === (int) $policyId) {
$this->resetInsurancePolicyForm();
}
Notification::make()->title('Polizza assicurativa eliminata')->success()->send();
}
public function getInsuranceDocumentUrl($policy): ?string
{
if (! $policy instanceof InsurancePolicy) {
return null;
}
$path = $this->normalizeInsuranceSearchValue($policy->policy_document_path ?? '');
$routeName = $this->resolveInsuranceAssetRouteName();
return $path !== ''
&& $routeName !== null
? route($routeName, ['policy' => (int) $policy->id, 'asset' => 'document'])
: null;
}
public function getInsuranceSignatureUrl($policy): ?string
{
if (! $policy instanceof InsurancePolicy) {
return null;
}
$path = $this->normalizeInsuranceSearchValue($policy->signature_image_path ?? '');
$routeName = $this->resolveInsuranceAssetRouteName();
return $path !== ''
&& $routeName !== null
? route($routeName, ['policy' => (int) $policy->id, 'asset' => 'signature'])
: null;
}
public function getInsuranceTicketUrl($ticket): ?string
{
if (! $ticket instanceof Ticket) {
return null;
}
return \App\Filament\Pages\Supporto\TicketGestione::getUrl(['ticket' => (int) $ticket->id, 'tab' => 'scheda'], panel: 'admin-filament');
}
public function openInsuranceAttachmentPreview($policyId, string $asset): void
{
if (! in_array($asset, ['document', 'signature'], true)) {
return;
}
$policyId = $this->normalizeInsuranceIdValue($policyId);
if ($policyId === null) {
return;
}
$policy = InsurancePolicy::query()
->with(['brokerRubrica', 'companyRubrica'])
->where('stabile_id', (int) ($this->stabile?->id ?? 0))
->find($policyId);
if (! $policy instanceof InsurancePolicy) {
return;
}
$path = $asset === 'document'
? trim((string) ($policy->policy_document_path ?? ''))
: trim((string) ($policy->signature_image_path ?? ''));
if ($path === '') {
return;
}
$mime = $asset === 'document'
? (string) ($policy->policy_document_mime ?: Storage::disk('public')->mimeType($path))
: (string) (Storage::disk('public')->mimeType($path) ?: 'image/png');
$resolvedMime = TicketAttachment::normalizeMimeType($mime, basename($path), $path);
$url = $asset === 'document' ? $this->getInsuranceDocumentUrl($policy) : $this->getInsuranceSignatureUrl($policy);
if (! is_string($url) || $url === '') {
return;
}
$this->insuranceAttachmentPreview = [
'name' => $asset === 'document'
? (string) ($policy->policy_document_name ?: ($policy->display_name . '.pdf'))
: ('Firma ' . $policy->display_name),
'description' => $asset === 'document'
? 'Documento polizza archiviato con accesso controllato'
: 'Firma referente polizza',
'url' => $url,
'is_image' => str_starts_with($resolvedMime, 'image/'),
'is_pdf' => $resolvedMime === 'application/pdf',
];
}
public function closeInsuranceAttachmentPreview(): void
{
$this->insuranceAttachmentPreview = null;
}
private function storeInsuranceUploads(InsurancePolicy $policy): void
{
if (is_object($this->insurancePolicyDocumentUpload) && method_exists($this->insurancePolicyDocumentUpload, 'storeAs')) {
$ext = strtolower((string) ($this->insurancePolicyDocumentUpload->getClientOriginalExtension() ?: 'pdf'));
$fileName = 'stabile-' . (int) $policy->stabile_id . '-polizza-' . (int) $policy->id . '-documento.' . $ext;
$path = $this->insurancePolicyDocumentUpload->storeAs('assicurazioni/polizze', $fileName, 'public');
$policy->policy_document_path = $path;
$policy->policy_document_name = (string) $this->insurancePolicyDocumentUpload->getClientOriginalName();
$policy->policy_document_mime = (string) $this->insurancePolicyDocumentUpload->getClientMimeType();
}
if (is_object($this->insuranceSignatureUpload) && method_exists($this->insuranceSignatureUpload, 'storeAs')) {
$ext = strtolower((string) ($this->insuranceSignatureUpload->getClientOriginalExtension() ?: 'png'));
$fileName = 'stabile-' . (int) $policy->stabile_id . '-polizza-' . (int) $policy->id . '-firma.' . $ext;
$policy->signature_image_path = $this->insuranceSignatureUpload->storeAs('assicurazioni/firme', $fileName, 'public');
}
$policy->save();
$this->syncInsuranceDocumentArchive($policy);
$this->insurancePolicyDocumentUpload = null;
$this->insuranceSignatureUpload = null;
}
private function searchInsuranceRubrica(string $target): void
{
$field = $target === 'broker' ? 'insuranceBrokerSearch' : 'insuranceCompanySearch';
$matchField = $target === 'broker' ? 'insuranceBrokerMatches' : 'insuranceCompanyMatches';
$selectedField = $target === 'broker' ? 'broker_rubrica_id' : 'company_rubrica_id';
$raw = $this->normalizeInsuranceSearchValue($this->{$field} ?? '');
$this->{$field} = $raw;
$this->{$matchField} = $this->searchRubricaLookupMatches(
$raw,
$this->normalizeInsuranceIdValue($this->insurancePolicyForm[$selectedField] ?? null),
['assicurazione', 'fornitore', 'altro'],
12
);
}
/**
* @return array<string,mixed>
*/
private function mapInsuranceRubricaRow(RubricaUniversale $rubrica): array
{
return $this->mapRubricaLookupRow($rubrica);
}
private function formatRubricaLabel(?RubricaUniversale $rubrica): string
{
return $this->formatRubricaLookupLabel($rubrica);
}
private function normalizeInsuranceSearchValue($value): string
{
return $this->normalizeRubricaLookupText($value);
}
private function normalizeInsuranceIdValue($value): ?int
{
return $this->normalizeRubricaLookupId($value);
}
/**
* @return array<int,array<string,mixed>>
*/
private function defaultInsurancePaymentRows(): array
{
return [
['label' => '', 'due_date' => null, 'paid_at' => null, 'amount' => null, 'notes' => ''],
['label' => '', 'due_date' => null, 'paid_at' => null, 'amount' => null, 'notes' => ''],
];
}
/**
* @return array<int,array<string,mixed>>
*/
private function buildInsurancePaymentRowsFromPolicy(InsurancePolicy $policy): array
{
$rows = (array) data_get($policy->metadata, 'payment_rows', []);
if ($rows === []) {
$rows = $this->defaultInsurancePaymentRows();
$rows[0]['notes'] = (string) ($policy->payment_schedule_notes ?? '');
}
return $this->normalizeInsurancePaymentRows($rows);
}
/**
* @param array<int,array<string,mixed>> $rows
* @return array<int,array<string,mixed>>
*/
private function normalizeInsurancePaymentRows(array $rows): array
{
$normalized = [];
foreach (range(0, 1) as $index) {
$row = (array) ($rows[$index] ?? []);
$normalized[] = [
'label' => trim((string) ($row['label'] ?? '')),
'due_date' => filled($row['due_date'] ?? null) ? (string) $row['due_date'] : null,
'paid_at' => filled($row['paid_at'] ?? null) ? (string) $row['paid_at'] : null,
'amount' => filled($row['amount'] ?? null) ? (float) $row['amount'] : null,
'notes' => trim((string) ($row['notes'] ?? '')),
];
}
return $normalized;
}
/**
* @param array<int,array<string,mixed>> $rows
*/
private function serializeInsurancePaymentRows(array $rows): string
{
$lines = [];
foreach ($rows as $index => $row) {
if (! filled($row['label'] ?? null) && ! filled($row['amount'] ?? null) && ! filled($row['due_date'] ?? null) && ! filled($row['notes'] ?? null)) {
continue;
}
$parts = ['Riga ' . ($index + 1)];
if (filled($row['label'] ?? null)) {
$parts[] = trim((string) $row['label']);
}
if (filled($row['amount'] ?? null)) {
$parts[] = number_format((float) $row['amount'], 2, ',', '.') . ' EUR';
}
if (filled($row['due_date'] ?? null)) {
$parts[] = 'Scadenza ' . \Carbon\Carbon::parse((string) $row['due_date'])->format('d/m/Y');
}
if (filled($row['paid_at'] ?? null)) {
$parts[] = 'Pagata ' . \Carbon\Carbon::parse((string) $row['paid_at'])->format('d/m/Y');
}
if (filled($row['notes'] ?? null)) {
$parts[] = trim((string) $row['notes']);
}
$lines[] = implode(' · ', $parts);
}
return implode("\n", $lines);
}
private function syncInsuranceDocumentArchive(InsurancePolicy $policy): void
{
$path = trim((string) ($policy->policy_document_path ?? ''));
if ($path === '') {
return;
}
$protocol = 'ASS-POL-' . (int) $policy->stabile_id . '-' . (int) $policy->id;
$record = DocumentoCollegato::withTrashed()->firstOrNew([
'numero_protocollo' => $protocol,
]);
if ($record->trashed()) {
$record->restore();
}
$record->fill([
'stabile_id' => (int) $policy->stabile_id,
'contatto_id' => (int) ($policy->company_rubrica_id ?: $policy->broker_rubrica_id ?: 0) ?: null,
'tipo_documento' => 'assicurazione',
'titolo' => $policy->display_name,
'descrizione' => $this->cleanInsuranceNullable($policy->coverage_summary) ?: 'Documento polizza assicurativa',
'data_sottoscrizione' => $policy->started_at,
'data_scadenza' => $policy->expires_at,
'tipo_contratto' => $this->mapInsuranceContractType($policy->payment_split),
'importo_contratto' => $policy->annual_amount,
'stato_documento' => $policy->status === 'scaduta' ? 'scaduto' : 'attivo',
'path_file_originale' => $path,
'note' => $policy->notes,
'creato_da' => $record->exists ? $record->creato_da : Auth::id(),
'modificato_da' => Auth::id(),
]);
$record->save();
}
private function mapInsuranceContractType(?string $paymentSplit): ?string
{
$value = mb_strtolower(trim((string) $paymentSplit));
return match (true) {
str_contains($value, 'mens') => 'mensile',
str_contains($value, 'bimes') => 'bimestrale',
str_contains($value, 'trimes') => 'trimestrale',
str_contains($value, 'semes') => 'semestrale',
str_contains($value, 'ann') => 'annuale',
$value !== '' => 'altro',
default => null,
};
}
private function resolveInsuranceAssetRouteName(): ?string
{
foreach (['admin.insurance.policies.assets.view', 'superadmin.insurance.policies.assets.view'] as $routeName) {
if (Route::has($routeName)) {
return $routeName;
}
}
return null;
}
private function cleanInsuranceNullable($value): ?string
{
$value = trim((string) $value);
return $value !== '' ? $value : null;
}
private function extractLegacyRateMonthsFromConfig(array $config, string $section): array
{
$flags = (array) data_get($config, 'rata_flags.' . $section, []);
$months = [];
foreach (range(1, 12) as $month) {
$value = $flags[$section . '_rata_' . $month] ?? null;
if ($value === null) {
continue;
}
$normalized = strtolower(trim((string) $value));
if ($normalized !== '' && ! in_array($normalized, ['0', 'false', 'no', 'off', 'n'], true)) {
$months[] = $month;
}
}
return $this->normalizeMesi($months);
}
private function initializeOfficialMailSettings(): void
{
if (! $this->stabile instanceof StabileModel) {
$this->officialMailboxes = [];
return;
}
$config = (array) ($this->stabile->configurazione_avanzata ?? []);
$posta = (array) ($config['posta'] ?? []);
$this->stableGoogleAccountOptions = $this->stabile->amministratore
? app(GoogleAccountStore::class)->options($this->stabile->amministratore)
: [];
$this->officialPecCondominio = trim((string) ($this->stabile->pec_condominio ?? ($posta['pec_condominio'] ?? '')));
$this->officialPecStudio = trim((string) ($posta['pec_studio'] ?? (Schema::hasColumn('stabili', 'pec_amministratore') ? ($this->stabile->pec_amministratore ?? '') : '')));
$this->officialMailboxes = $this->normalizeOfficialMailboxes((array) ($posta['caselle'] ?? []));
if ($this->officialMailboxes === []) {
$this->officialMailboxes = $this->buildDefaultOfficialMailboxes();
}
}
/**
* @param array<int,mixed> $rows
* @return array<int,array<string,mixed>>
*/
private function normalizeOfficialMailboxes(array $rows): array
{
$normalized = [];
foreach ($rows as $row) {
if (! is_array($row)) {
continue;
}
$normalized[] = [
'enabled' => (bool) ($row['enabled'] ?? true),
'tipo' => strtolower(trim((string) ($row['tipo'] ?? 'gmail'))) ?: 'gmail',
'label' => trim((string) ($row['label'] ?? '')),
'google_account_key' => trim((string) ($row['google_account_key'] ?? '')),
'email' => trim((string) ($row['email'] ?? '')),
'host' => trim((string) ($row['host'] ?? '')),
'port' => (int) ($row['port'] ?? 993),
'encryption' => trim((string) ($row['encryption'] ?? 'ssl')) ?: 'ssl',
'username' => trim((string) ($row['username'] ?? '')),
'password' => trim((string) ($row['password'] ?? '')),
'folder' => trim((string) ($row['folder'] ?? 'INBOX')) ?: 'INBOX',
'gmail_query' => trim((string) ($row['gmail_query'] ?? '')),
'crea_ticket_automatico' => (bool) ($row['crea_ticket_automatico'] ?? true),
'mittenti_autorizzati' => trim((string) ($row['mittenti_autorizzati'] ?? '')),
];
}
return $normalized;
}
/**
* @return array<int,array<string,mixed>>
*/
private function buildDefaultOfficialMailboxes(): array
{
if (! $this->stabile instanceof StabileModel) {
return [];
}
if ((string) ($this->stabile->codice_stabile ?? '') === '0023') {
return [[
'enabled' => true,
'tipo' => 'gmail',
'label' => 'Casella ufficiale GERMANICO 79',
'google_account_key' => 'stabile-' . (int) $this->stabile->id . '-pec',
'email' => 'viagermanico79@gmail.com',
'host' => '',
'port' => 993,
'encryption' => 'ssl',
'username' => 'viagermanico79@gmail.com',
'password' => '',
'folder' => 'INBOX',
'gmail_query' => 'in:inbox newer_than:30d',
'crea_ticket_automatico' => true,
'mittenti_autorizzati' => '',
]];
}
return [];
}
public function addOfficialMailbox(): void
{
$this->officialMailboxes[] = [
'enabled' => true,
'tipo' => 'gmail',
'label' => '',
'google_account_key' => '',
'email' => '',
'host' => '',
'port' => 993,
'encryption' => 'ssl',
'username' => '',
'password' => '',
'folder' => 'INBOX',
'gmail_query' => '',
'crea_ticket_automatico' => true,
'mittenti_autorizzati' => '',
];
}
public function removeOfficialMailbox(int $index): void
{
unset($this->officialMailboxes[$index]);
$this->officialMailboxes = array_values($this->officialMailboxes);
}
public function saveOfficialMailSettings(): void
{
if (! $this->stabile instanceof StabileModel) {
return;
}
$mailboxes = array_values(array_filter($this->normalizeOfficialMailboxes($this->officialMailboxes), function (array $row): bool {
return $row['label'] !== '' || $row['email'] !== '' || $row['google_account_key'] !== '';
}));
$config = (array) ($this->stabile->configurazione_avanzata ?? []);
$config['posta'] = array_merge([
'acquisizione' => [
'salva_documenti' => true,
'salva_contabilita' => true,
'mittenti_assicurazione' => '',
'descrizione_default' => 'Documento acquisito da casella stabile',
],
], (array) ($config['posta'] ?? []), [
'pec_condominio' => $this->officialPecCondominio !== '' ? $this->officialPecCondominio : null,
'pec_studio' => $this->officialPecStudio !== '' ? $this->officialPecStudio : null,
'caselle' => $mailboxes,
]);
$this->stabile->pec_condominio = $this->officialPecCondominio !== '' ? $this->officialPecCondominio : null;
if (Schema::hasColumn('stabili', 'pec_amministratore')) {
$this->stabile->pec_amministratore = $this->officialPecStudio !== '' ? $this->officialPecStudio : null;
}
$this->stabile->configurazione_avanzata = $config;
$this->stabile->save();
$this->officialMailboxes = $mailboxes;
Notification::make()
->title('Posta ufficiale dello stabile salvata')
->success()
->send();
}
public function importOfficialGmail(?int $index = null): void
{
if (! $this->stabile instanceof StabileModel) {
return;
}
$mailboxes = $this->normalizeOfficialMailboxes($this->officialMailboxes);
if ($index !== null) {
$mailboxes = isset($mailboxes[$index]) ? [$mailboxes[$index]] : [];
}
$mailboxes = array_values(array_filter($mailboxes, function (array $mailbox): bool {
return (bool) ($mailbox['enabled'] ?? false)
&& strtolower((string) ($mailbox['tipo'] ?? '')) === 'gmail';
}));
if ($mailboxes === []) {
Notification::make()->title('Nessuna casella Gmail attiva da importare')->warning()->send();
return;
}
$results = [];
foreach ($mailboxes as $mailbox) {
$results[] = app(GmailTicketImportService::class)->importMailbox(
$this->stabile,
$mailbox,
max(1, min((int) $this->gmailImportMaxMessages, 50))
);
}
$summary = collect($results)
->map(fn(array $row): string => ($row['mailbox'] ?? 'gmail') . ': ' . (int) ($row['imported'] ?? 0) . ' importate, ' . (int) ($row['skipped'] ?? 0) . ' saltate, ' . (int) ($row['attachments'] ?? 0) . ' allegati')
->implode(' | ');
Notification::make()
->title('Import Gmail completato')
->body($summary !== '' ? $summary : 'Nessun nuovo messaggio importato.')
->success()
->send();
}
public function getGoogleSettingsUrl(): string
{
return GoogleDashboard::getUrl(panel: 'admin-filament');
}
public function getStabilePostaUrl(): string
{
return StabilePosta::getUrl(['stabile_id' => (int) ($this->stabile?->id ?? 0)], panel: 'admin-filament');
}
public function getStableGoogleConnectUrl(): string
{
$label = 'PEC ' . ($this->stabile?->denominazione ?: ('stabile-' . ($this->stabile?->id ?? 0)));
return route('oauth.google.redirect', [
'account_key' => 'stabile-' . ($this->stabile?->id ?? 0) . '-pec',
'label' => $label,
'return_to' => $this->getStabilePostaUrl(),
]);
}
public function getGlobalCommunicationsUrl(): string
{
return Comunicazioni::getUrl(panel: 'admin-filament') . '?stabile_id=' . (int) ($this->stabile?->id ?? 0);
}
/**
* @return array<string,mixed>
*/
public function getDocumentazioneStatsProperty(): array
{
$stabileId = (int) ($this->stabile?->id ?? 0);
if ($stabileId <= 0) {
return ['documenti' => 0, 'documenti_collegati' => 0, 'comunicazioni' => 0, 'caselle' => 0];
}
return [
'documenti' => Documento::query()->where('stabile_id', $stabileId)->count(),
'documenti_collegati' => DocumentoCollegato::query()->where('stabile_id', $stabileId)->count(),
'comunicazioni' => CommunicationMessage::query()->where('stabile_id', $stabileId)->count(),
'caselle' => count(array_filter($this->officialMailboxes, fn(array $row): bool => (bool) ($row['enabled'] ?? false))),
];
}
/**
* @return array<int,string>
*/
public function getDriveTemplateFoldersProperty(): array
{
$folders = config('netgescon.google.drive_template_folders', []);
return is_array($folders)
? array_values(array_filter(array_map('strval', $folders), fn(string $row) : bool => trim($row) !== ''))
: [];
}
/**
* @return array<string,mixed>
*/
public function getCommunicationStatsProperty(): array
{
$stabileId = (int) ($this->stabile?->id ?? 0);
$base = CommunicationMessage::query()->where('stabile_id', $stabileId);
return [
'totale' => (clone $base)->count(),
'email' => (clone $base)->where('channel', 'email')->count(),
'da_lavorare' => (clone $base)->where(function ($query): void {
$query->whereNull('ticket_id')
->orWhereNotIn('status', ['linked_to_ticket', 'processed', 'archived']);
})->count(),
'oggi' => (clone $base)->whereDate('received_at', now()->toDateString())->count(),
];
}
/**
* @return \Illuminate\Database\Eloquent\Collection<int,CommunicationMessage>
*/
public function getCommunicationRowsProperty()
{
$stabileId = (int) ($this->stabile?->id ?? 0);
return CommunicationMessage::query()
->with(['ticket', 'stabile'])
->where('stabile_id', $stabileId)
->orderByDesc('received_at')
->orderByDesc('id')
->limit(30)
->get();
}
/**
* @return array<string,mixed>|null
*/
public function getOfficialGoogleAccountProperty(): ?array
{
if (! $this->stabile?->amministratore) {
return null;
}
return app(GoogleAccountStore::class)->get($this->stabile->amministratore, 'stabile-' . (int) $this->stabile->id . '-pec');
}
}