diff --git a/app/Filament/Pages/Impostazioni/SchedaAmministratore.php b/app/Filament/Pages/Impostazioni/SchedaAmministratore.php index 9934eac..29b9d9c 100755 --- a/app/Filament/Pages/Impostazioni/SchedaAmministratore.php +++ b/app/Filament/Pages/Impostazioni/SchedaAmministratore.php @@ -2094,4 +2094,117 @@ private function safeAmministratoreImpostazioni(Amministratore $amministratore): return is_array($settings) ? $settings : []; } + + public function getStabiliMailboxStatusProperty(): array + { + $user = Auth::user(); + $stabili = $user instanceof User ? StabileContext::accessibleStabili($user) : Stabile::all(); + $res = []; + + foreach ($stabili as $s) { + $cfg = (array) ($s->configurazione_avanzata ?? []); + $posta = (array) ($cfg['posta'] ?? []); + $caselle = (array) ($posta['caselle'] ?? []); + + $emailConfigured = false; + $emailInfo = ''; + $pecConfigured = false; + $pecInfo = ''; + + foreach ($caselle as $c) { + if (! is_array($c) || empty($c['enabled'])) { + continue; + } + $isPec = ($c['tipo'] ?? '') === 'pec'; + $hasConn = ! empty($c['host']) && ! empty($c['username']); + if ($isPec) { + if ($hasConn) { + $pecConfigured = true; + $pecInfo = ($c['email'] ?? $c['username'] ?? '') . ' (' . ($c['host'] ?? '') . ')'; + } + } else { + if ($hasConn) { + $emailConfigured = true; + $emailInfo = ($c['email'] ?? $c['username'] ?? '') . ' (' . ($c['host'] ?? '') . ')'; + } + } + } + + $msgCount = CommunicationMessage::where('stabile_id', $s->id)->count(); + + $res[] = [ + 'id' => (int) $s->id, + 'codice_stabile' => (string) $s->codice_stabile, + 'denominazione' => (string) $s->denominazione, + 'comune' => (string) $s->comune, + 'codice_fiscale' => (string) $s->codice_fiscale, + 'email_configured' => $emailConfigured, + 'email_info' => $emailInfo, + 'pec_configured' => $pecConfigured, + 'pec_info' => $pecInfo, + 'msg_count' => $msgCount, + 'config_url' => url('/admin-filament/condomini/stabile?stabile_id=' . $s->id . '&tab=posta-ufficiale'), + 'webmail_email_url' => url('/admin-filament/comunicazioni/posta-ordinaria?stabile_id=' . $s->id), + 'webmail_pec_url' => url('/admin-filament/comunicazioni/posta-pec?stabile_id=' . $s->id), + ]; + } + + return $res; + } + + public function testStudioMailbox(int $index): void + { + $caselle = (array) data_get($this->data, 'impostazioni.posta.caselle', []); + $box = $caselle[$index] ?? null; + + if (! is_array($box)) { + Notification::make()->title('Configurazione casella non trovata')->danger()->send(); + return; + } + + $service = app(\App\Services\Posta\ImapMailboxService::class); + $res = $service->testMailbox($box); + + if ($res['success']) { + Notification::make() + ->title('Connessione IMAP Studio Riuscita!') + ->body($res['message']) + ->success() + ->send(); + } else { + Notification::make() + ->title('Test Connessione Fallito') + ->body($res['message']) + ->danger() + ->send(); + } + } + + public function syncStudioMailbox(int $index): void + { + $caselle = (array) data_get($this->data, 'impostazioni.posta.caselle', []); + $box = $caselle[$index] ?? null; + + if (! is_array($box)) { + Notification::make()->title('Configurazione casella non trovata')->danger()->send(); + return; + } + + $service = app(\App\Services\Posta\ImapMailboxService::class); + $res = $service->fetchStudioMailbox($this->amministratore, $box, 30); + + if (($res['imported'] ?? 0) > 0) { + Notification::make() + ->title('Sincronizzazione completata') + ->body("Importati {$res['imported']} nuovi messaggi (.EML)") + ->success() + ->send(); + } else { + Notification::make() + ->title('Nessun nuovo messaggio') + ->body($res['message'] ?? 'Nessun messaggio trovato da scaricare.') + ->info() + ->send(); + } + } } diff --git a/app/Filament/Pages/Posta/PostaOrdinariaWebmail.php b/app/Filament/Pages/Posta/PostaOrdinariaWebmail.php index 3631966..dc6602b 100644 --- a/app/Filament/Pages/Posta/PostaOrdinariaWebmail.php +++ b/app/Filament/Pages/Posta/PostaOrdinariaWebmail.php @@ -36,7 +36,7 @@ class PostaOrdinariaWebmail extends Page public string $tipoCasella = 'email'; // 'email' o 'pec' - public ?int $selectedStabileId = null; + public string|int|null $selectedStabileId = null; public string $currentFolder = 'inbox'; // 'inbox', 'starred', 'sent', 'drafts', 'trash', 'attachments' @@ -68,9 +68,11 @@ public function mount(): void { $user = Auth::user(); if ($user instanceof User) { - $reqStabile = request()->integer('stabile_id'); - if ($reqStabile > 0) { - $this->selectedStabileId = $reqStabile; + $reqStabile = request()->query('stabile_id'); + if ($reqStabile === 'studio') { + $this->selectedStabileId = 'studio'; + } elseif (is_numeric($reqStabile) && (int) $reqStabile > 0) { + $this->selectedStabileId = (int) $reqStabile; } else { $this->selectedStabileId = StabileContext::resolveActiveStabileId($user); } @@ -84,9 +86,66 @@ public function getStabiliOptionsProperty(): array return []; } - return StabileContext::accessibleStabili($user) + $options = [ + 'studio' => '🏢 Posta Generale Studio / Amministrazione', + ]; + + $stabili = StabileContext::accessibleStabili($user) ->mapWithKeys(fn(Stabile $s) => [(int) $s->id => trim((string) ($s->codice_stabile . ' - ' . $s->denominazione))]) ->all(); + + return $options + $stabili; + } + + public function getConfigMailboxUrlProperty(): string + { + if ($this->selectedStabileId === 'studio') { + return url('/admin-filament/impostazioni/scheda-amministratore?tab=posta-operativita'); + } + + if (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) { + return url('/admin-filament/condomini/stabile?stabile_id=' . $this->selectedStabileId . '&tab=posta-ufficiale'); + } + + return url('/admin-filament/condomini/stabile?tab=posta-ufficiale'); + } + + public function getIsMailboxConfiguredProperty(): bool + { + $user = Auth::user(); + if (! $user instanceof User) { + return false; + } + + if ($this->selectedStabileId === 'studio') { + $amministratore = $user->amministratore; + if ($amministratore) { + $config = (array) ($amministratore->impostazioni ?? []); + $caselle = (array) ($config['posta']['caselle'] ?? []); + foreach ($caselle as $c) { + if (! empty($c['enabled']) && ! empty($c['host']) && ($this->tipoCasella === 'pec' ? ($c['tipo'] ?? '') === 'pec' : ($c['tipo'] ?? '') !== 'pec')) { + return true; + } + } + } + return false; + } + + if (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) { + $stabile = Stabile::find($this->selectedStabileId); + if ($stabile) { + $config = (array) ($stabile->configurazione_avanzata ?? []); + $caselle = (array) ($config['posta']['caselle'] ?? []); + foreach ($caselle as $c) { + if (! empty($c['enabled']) && ! empty($c['host']) && ($this->tipoCasella === 'pec' ? ($c['tipo'] ?? '') === 'pec' : ($c['tipo'] ?? '') !== 'pec')) { + return true; + } + } + } + return false; + } + + return true; } public function selectFolder(string $folder): void @@ -174,31 +233,56 @@ public function syncNow(): void } $service = app(ImapMailboxService::class); - $stabili = $this->selectedStabileId - ? Stabile::where('id', $this->selectedStabileId)->get() - : StabileContext::accessibleStabili($user); - $totImported = 0; - foreach ($stabili as $stabile) { - $config = (array) ($stabile->configurazione_avanzata ?? []); - $posta = (array) ($config['posta'] ?? []); - $caselle = array_values(array_filter((array) ($posta['caselle'] ?? []), 'is_array')); - foreach ($caselle as $mailbox) { - if (empty($mailbox['enabled'])) { - continue; - } - $mTipo = strtolower((string) ($mailbox['tipo'] ?? 'imap')); - if ($this->tipoCasella === 'pec' && $mTipo !== 'pec') { - continue; - } - if ($this->tipoCasella === 'email' && $mTipo === 'pec') { - continue; + if ($this->selectedStabileId === 'studio') { + $amministratore = $user->amministratore; + if ($amministratore) { + $config = (array) ($amministratore->impostazioni ?? []); + $caselle = array_values(array_filter((array) ($config['posta']['caselle'] ?? []), 'is_array')); + foreach ($caselle as $mailbox) { + if (empty($mailbox['enabled'])) { + continue; + } + $mTipo = strtolower((string) ($mailbox['tipo'] ?? 'imap')); + if ($this->tipoCasella === 'pec' && $mTipo !== 'pec') { + continue; + } + if ($this->tipoCasella === 'email' && $mTipo === 'pec') { + continue; + } + if (! empty($mailbox['host']) && ! empty($mailbox['username'])) { + $res = $service->fetchStudioMailbox($amministratore, $mailbox, 20); + $totImported += (int) ($res['imported'] ?? 0); + } } + } + } else { + $stabili = (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) + ? Stabile::where('id', (int) $this->selectedStabileId)->get() + : StabileContext::accessibleStabili($user); - if (! empty($mailbox['host']) && ! empty($mailbox['username'])) { - $res = $service->fetchMailbox($stabile, $mailbox, 20); - $totImported += (int) ($res['imported'] ?? 0); + foreach ($stabili as $stabile) { + $config = (array) ($stabile->configurazione_avanzata ?? []); + $posta = (array) ($config['posta'] ?? []); + $caselle = array_values(array_filter((array) ($posta['caselle'] ?? []), 'is_array')); + + foreach ($caselle as $mailbox) { + if (empty($mailbox['enabled'])) { + continue; + } + $mTipo = strtolower((string) ($mailbox['tipo'] ?? 'imap')); + if ($this->tipoCasella === 'pec' && $mTipo !== 'pec') { + continue; + } + if ($this->tipoCasella === 'email' && $mTipo === 'pec') { + continue; + } + + if (! empty($mailbox['host']) && ! empty($mailbox['username'])) { + $res = $service->fetchMailbox($stabile, $mailbox, 20); + $totImported += (int) ($res['imported'] ?? 0); + } } } } @@ -283,8 +367,7 @@ public function getMessagesProperty() ? StabileContext::accessibleStabili($user)->pluck('id')->toArray() : []; - $query = CommunicationMessage::with(['stabile', 'ticket']) - ->whereIn('stabile_id', $allowedStabileIds); + $query = CommunicationMessage::with(['stabile', 'ticket']); if ($this->tipoCasella === 'pec') { $query->where('channel', 'pec'); @@ -292,8 +375,15 @@ public function getMessagesProperty() $query->whereIn('channel', ['email', 'gmail']); } - if ($this->selectedStabileId) { - $query->where('stabile_id', $this->selectedStabileId); + if ($this->selectedStabileId === 'studio') { + $query->whereNull('stabile_id'); + } elseif (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) { + $query->where('stabile_id', (int) $this->selectedStabileId); + } else { + $query->where(function ($q) use ($allowedStabileIds) { + $q->whereIn('stabile_id', $allowedStabileIds) + ->orWhereNull('stabile_id'); + }); } if ($this->currentFolder === 'sent') { @@ -335,15 +425,22 @@ public function getFolderCountsProperty(): array ? StabileContext::accessibleStabili($user)->pluck('id')->toArray() : []; - $base = CommunicationMessage::whereIn('stabile_id', $allowedStabileIds); + $base = CommunicationMessage::query(); if ($this->tipoCasella === 'pec') { $base->where('channel', 'pec'); } else { $base->whereIn('channel', ['email', 'gmail']); } - if ($this->selectedStabileId) { - $base->where('stabile_id', $this->selectedStabileId); + if ($this->selectedStabileId === 'studio') { + $base->whereNull('stabile_id'); + } elseif (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) { + $base->where('stabile_id', (int) $this->selectedStabileId); + } else { + $base->where(function ($q) use ($allowedStabileIds) { + $q->whereIn('stabile_id', $allowedStabileIds) + ->orWhereNull('stabile_id'); + }); } return [ diff --git a/app/Filament/Pages/Posta/PostaPecWebmail.php b/app/Filament/Pages/Posta/PostaPecWebmail.php index 3066d6d..4270f90 100644 --- a/app/Filament/Pages/Posta/PostaPecWebmail.php +++ b/app/Filament/Pages/Posta/PostaPecWebmail.php @@ -38,7 +38,7 @@ class PostaPecWebmail extends Page public string $tipoCasella = 'pec'; - public ?int $selectedStabileId = null; + public string|int|null $selectedStabileId = null; public string $currentFolder = 'inbox'; // 'inbox', 'starred', 'sent', 'drafts', 'trash', 'attachments' @@ -70,9 +70,11 @@ public function mount(): void { $user = Auth::user(); if ($user instanceof User) { - $reqStabile = request()->integer('stabile_id'); - if ($reqStabile > 0) { - $this->selectedStabileId = $reqStabile; + $reqStabile = request()->query('stabile_id'); + if ($reqStabile === 'studio') { + $this->selectedStabileId = 'studio'; + } elseif (is_numeric($reqStabile) && (int) $reqStabile > 0) { + $this->selectedStabileId = (int) $reqStabile; } else { $this->selectedStabileId = StabileContext::resolveActiveStabileId($user); } @@ -86,9 +88,66 @@ public function getStabiliOptionsProperty(): array return []; } - return StabileContext::accessibleStabili($user) + $options = [ + 'studio' => '🏢 PEC Generale Studio / Amministrazione', + ]; + + $stabili = StabileContext::accessibleStabili($user) ->mapWithKeys(fn(Stabile $s) => [(int) $s->id => trim((string) ($s->codice_stabile . ' - ' . $s->denominazione))]) ->all(); + + return $options + $stabili; + } + + public function getConfigMailboxUrlProperty(): string + { + if ($this->selectedStabileId === 'studio') { + return url('/admin-filament/impostazioni/scheda-amministratore?tab=posta-operativita'); + } + + if (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) { + return url('/admin-filament/condomini/stabile?stabile_id=' . $this->selectedStabileId . '&tab=posta-ufficiale'); + } + + return url('/admin-filament/condomini/stabile?tab=posta-ufficiale'); + } + + public function getIsMailboxConfiguredProperty(): bool + { + $user = Auth::user(); + if (! $user instanceof User) { + return false; + } + + if ($this->selectedStabileId === 'studio') { + $amministratore = $user->amministratore; + if ($amministratore) { + $config = (array) ($amministratore->impostazioni ?? []); + $caselle = (array) ($config['posta']['caselle'] ?? []); + foreach ($caselle as $c) { + if (! empty($c['enabled']) && ! empty($c['host']) && ($c['tipo'] ?? '') === 'pec') { + return true; + } + } + } + return false; + } + + if (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) { + $stabile = Stabile::find($this->selectedStabileId); + if ($stabile) { + $config = (array) ($stabile->configurazione_avanzata ?? []); + $caselle = (array) ($config['posta']['caselle'] ?? []); + foreach ($caselle as $c) { + if (! empty($c['enabled']) && ! empty($c['host']) && ($c['tipo'] ?? '') === 'pec') { + return true; + } + } + } + return false; + } + + return true; } public function selectFolder(string $folder): void @@ -176,28 +235,50 @@ public function syncNow(): void } $service = app(ImapMailboxService::class); - $stabili = $this->selectedStabileId - ? Stabile::where('id', $this->selectedStabileId)->get() - : StabileContext::accessibleStabili($user); - $totImported = 0; - foreach ($stabili as $stabile) { - $config = (array) ($stabile->configurazione_avanzata ?? []); - $posta = (array) ($config['posta'] ?? []); - $caselle = array_values(array_filter((array) ($posta['caselle'] ?? []), 'is_array')); - foreach ($caselle as $mailbox) { - if (empty($mailbox['enabled'])) { - continue; - } - $mTipo = strtolower((string) ($mailbox['tipo'] ?? 'imap')); - if ($mTipo !== 'pec') { - continue; + if ($this->selectedStabileId === 'studio') { + $amministratore = $user->amministratore; + if ($amministratore) { + $config = (array) ($amministratore->impostazioni ?? []); + $caselle = array_values(array_filter((array) ($config['posta']['caselle'] ?? []), 'is_array')); + foreach ($caselle as $mailbox) { + if (empty($mailbox['enabled'])) { + continue; + } + $mTipo = strtolower((string) ($mailbox['tipo'] ?? 'imap')); + if ($mTipo !== 'pec') { + continue; + } + if (! empty($mailbox['host']) && ! empty($mailbox['username'])) { + $res = $service->fetchStudioMailbox($amministratore, $mailbox, 20); + $totImported += (int) ($res['imported'] ?? 0); + } } + } + } else { + $stabili = (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) + ? Stabile::where('id', (int) $this->selectedStabileId)->get() + : StabileContext::accessibleStabili($user); - if (! empty($mailbox['host']) && ! empty($mailbox['username'])) { - $res = $service->fetchMailbox($stabile, $mailbox, 20); - $totImported += (int) ($res['imported'] ?? 0); + foreach ($stabili as $stabile) { + $config = (array) ($stabile->configurazione_avanzata ?? []); + $posta = (array) ($config['posta'] ?? []); + $caselle = array_values(array_filter((array) ($posta['caselle'] ?? []), 'is_array')); + + foreach ($caselle as $mailbox) { + if (empty($mailbox['enabled'])) { + continue; + } + $mTipo = strtolower((string) ($mailbox['tipo'] ?? 'imap')); + if ($mTipo !== 'pec') { + continue; + } + + if (! empty($mailbox['host']) && ! empty($mailbox['username'])) { + $res = $service->fetchMailbox($stabile, $mailbox, 20); + $totImported += (int) ($res['imported'] ?? 0); + } } } } @@ -283,11 +364,17 @@ public function getMessagesProperty() : []; $query = CommunicationMessage::with(['stabile', 'ticket']) - ->whereIn('stabile_id', $allowedStabileIds) ->where('channel', 'pec'); - if ($this->selectedStabileId) { - $query->where('stabile_id', $this->selectedStabileId); + if ($this->selectedStabileId === 'studio') { + $query->whereNull('stabile_id'); + } elseif (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) { + $query->where('stabile_id', (int) $this->selectedStabileId); + } else { + $query->where(function ($q) use ($allowedStabileIds) { + $q->whereIn('stabile_id', $allowedStabileIds) + ->orWhereNull('stabile_id'); + }); } if ($this->currentFolder === 'sent') { @@ -329,10 +416,17 @@ public function getFolderCountsProperty(): array ? StabileContext::accessibleStabili($user)->pluck('id')->toArray() : []; - $base = CommunicationMessage::whereIn('stabile_id', $allowedStabileIds)->where('channel', 'pec'); + $base = CommunicationMessage::query()->where('channel', 'pec'); - if ($this->selectedStabileId) { - $base->where('stabile_id', $this->selectedStabileId); + if ($this->selectedStabileId === 'studio') { + $base->whereNull('stabile_id'); + } elseif (is_numeric($this->selectedStabileId) && (int) $this->selectedStabileId > 0) { + $base->where('stabile_id', (int) $this->selectedStabileId); + } else { + $base->where(function ($q) use ($allowedStabileIds) { + $q->whereIn('stabile_id', $allowedStabileIds) + ->orWhereNull('stabile_id'); + }); } return [ diff --git a/app/Filament/Pages/UnitaImmobiliarePage.php b/app/Filament/Pages/UnitaImmobiliarePage.php index 6aa3e78..485f132 100755 --- a/app/Filament/Pages/UnitaImmobiliarePage.php +++ b/app/Filament/Pages/UnitaImmobiliarePage.php @@ -1186,6 +1186,7 @@ public function getProtocolloComunicazioniProperty(): array $codStabile = (string) $this->unita->stabile->codice_stabile; $scala = trim((string) $this->unita->scala); $interno = trim((string) $this->unita->interno); + $condIds = $this->resolveLegacyCondIdsForUnita(); $results = []; @@ -1193,16 +1194,19 @@ public function getProtocolloComunicazioniProperty(): array if (DbSchema::connection('gescon_import')->hasTable('protoc_ec')) { $ecQuery = DB::connection('gescon_import')->table('protoc_ec') ->where('cod_stabile', $codStabile) - ->where(function ($q) use ($scala, $interno) { + ->where(function ($q) use ($scala, $interno, $condIds) { $q->where(function ($sub) use ($scala, $interno) { $sub->where('scala', $scala)->where('interno', $interno); }); + if (! empty($condIds)) { + $q->orWhereIn('id_condomino', $condIds); + } }) ->orderByDesc('id') ->get(); foreach ($ecQuery as $r) { - $dtRaw = $r->data_invio ?? null; + $dtRaw = $r->data_invio ?? ($r->data ?? null); $ts = 0; $dtFmt = '—'; if ($dtRaw) { @@ -1215,24 +1219,34 @@ public function getProtocolloComunicazioniProperty(): array } } - $pdfName = trim((string) ($r->nome_pdf ?? '')); - $pdfPath = $pdfName !== '' ? "/mnt/gescon-archives/gescon/{$codStabile}/E_C/{$pdfName}" : null; - $pdfExists = $pdfPath && file_exists($pdfPath); + $candidateNames = array_values(array_unique(array_filter([ + trim((string) ($r->nome_pdf ?? '')), + trim((string) ($r->alleg_4 ?? '')), + trim((string) ($r->lettera_tipo_caricata ?? '')), + ]))); + + $allegati = []; + foreach ($candidateNames as $fn) { + $allegati[] = \App\Services\Documenti\ArchiveFileResolver::resolve($codStabile, $fn, 'E_C'); + } + + $firstAtt = $allegati[0] ?? null; $results[] = [ 'id' => 'ec_' . $r->id, - 'protocollo' => $r->protocollo ?: ('EC #' . $r->id_corrisp), + 'protocollo' => $r->protocollo ?: ('EC #' . ($r->id_corrisp ?? $r->id)), 'tipo' => 'Estratto Conto', 'canale' => $r->tipo_documento ?: 'E-Mail', 'data_invio' => $dtFmt, - 'destinatario' => $r->destinatario ?: $r->nome, - 'ruolo' => $r->c_i === 'I' ? 'Inquilino' : 'Condomino', + 'destinatario' => $r->destinatario ?: ($r->nome ?? '—'), + 'ruolo' => ($r->c_i ?? '') === 'I' ? 'Inquilino' : 'Condomino', 'oggetto' => $r->oggetto ?: 'Invio Estratto Conto', 'importo' => (float) ($r->totale ?? 0), - 'nome_file' => $pdfName, - 'file_path' => $pdfPath, - 'file_exists' => $pdfExists, - 'note' => $r->note, + 'allegati' => $allegati, + 'nome_file' => $firstAtt['filename'] ?? null, + 'file_path' => $firstAtt['path'] ?? null, + 'file_exists' => $firstAtt['exists'] ?? false, + 'note' => $r->note ?? null, 'timestamp_sort' => $ts, ]; } @@ -1240,7 +1254,6 @@ public function getProtocolloComunicazioniProperty(): array // 2. Dati da corrisp_inviata (Lettere, circolari) if (DbSchema::connection('gescon_import')->hasTable('corrisp_inviata')) { - $condIds = $this->resolveLegacyCondIdsForUnita(); $corrQuery = DB::connection('gescon_import')->table('corrisp_inviata') ->where('cod_stabile', $codStabile) ->where(function ($q) use ($condIds) { @@ -1253,7 +1266,7 @@ public function getProtocolloComunicazioniProperty(): array ->get(); foreach ($corrQuery as $r) { - $dtRaw = $r->data_invio ?? null; + $dtRaw = $r->data_invio ?? ($r->data ?? null); $ts = 0; $dtFmt = '—'; if ($dtRaw) { @@ -1266,30 +1279,112 @@ public function getProtocolloComunicazioniProperty(): array } } - $docName = trim((string) ($r->lettera_tipo_caricata ?? '')); - $docPath = $docName !== '' ? "/mnt/gescon-archives/gescon/{$codStabile}/{$docName}" : null; - $docExists = $docPath && file_exists($docPath); + $candidateNames = array_values(array_unique(array_filter([ + trim((string) ($r->lettera_tipo_caricata ?? '')), + trim((string) ($r->nome_alleg_1 ?? '')), + trim((string) ($r->nome_alleg_2 ?? '')), + trim((string) ($r->nome_alleg_3 ?? '')), + trim((string) ($r->nome_alleg_4 ?? '')), + ]))); + + $allegati = []; + foreach ($candidateNames as $fn) { + $allegati[] = \App\Services\Documenti\ArchiveFileResolver::resolve($codStabile, $fn, 'allegati'); + } + + $firstAtt = $allegati[0] ?? null; $results[] = [ 'id' => 'corr_' . $r->id, - 'protocollo' => $r->protocollo ?: ('PROT #' . $r->id_corrisp), + 'protocollo' => $r->protocollo ?: ('PROT #' . ($r->id_corrisp ?? $r->id)), 'tipo' => 'Lettera / Circolare', 'canale' => $r->tipo_documento ?: 'Posta', 'data_invio' => $dtFmt, - 'destinatario' => $r->destinatario ?: 'Condomini', + 'destinatario' => $r->destinatario ?: ($r->nome ?? 'Condomini'), 'ruolo' => 'Condomino', 'oggetto' => $r->oggetto ?: 'Comunicazione Amministrazione', 'importo' => null, - 'nome_file' => $docName, - 'file_path' => $docPath, - 'file_exists' => $docExists, - 'note' => $r->note, + 'allegati' => $allegati, + 'nome_file' => $firstAtt['filename'] ?? null, + 'file_path' => $firstAtt['path'] ?? null, + 'file_exists' => $firstAtt['exists'] ?? false, + 'note' => $r->note ?? null, 'timestamp_sort' => $ts, ]; } } - // 3. Dati da CommunicationMessage (Posta IMAP, PEC, EML) + // 3. Dati da posta_dett (Raccomandate, visure, postalizzazione) + if (DbSchema::connection('gescon_import')->hasTable('posta_dett')) { + $postaQuery = DB::connection('gescon_import')->table('posta_dett') + ->where(function ($q) use ($scala, $interno, $condIds) { + $hasCond = false; + if ($scala !== '' && $interno !== '') { + $q->where(function ($sub) use ($scala, $interno) { + $sub->where('scala', $scala)->where('interno', $interno); + }); + $hasCond = true; + } + if (! empty($condIds)) { + if ($hasCond) { + $q->orWhereIn('id_cond', $condIds); + } else { + $q->whereIn('id_cond', $condIds); + } + } + }) + ->orderByDesc('id') + ->get(); + + foreach ($postaQuery as $r) { + $dtRaw = $r->ora_invio_richiesta ?? ($r->data ?? null); + $ts = 0; + $dtFmt = '—'; + if ($dtRaw) { + try { + $c = Carbon::parse((string) $dtRaw); + $ts = $c->timestamp; + $dtFmt = $c->format('d/m/Y'); + } catch (\Throwable $e) { + $dtFmt = (string) $dtRaw; + } + } + + $candidateNames = array_values(array_unique(array_filter([ + trim((string) ($r->nome_alleg_1 ?? '')), + trim((string) ($r->nome_alleg_4 ?? '')), + trim((string) ($r->documento ?? '')), + trim((string) ($r->risposta ?? '')), + ]))); + + $allegati = []; + foreach ($candidateNames as $fn) { + $allegati[] = \App\Services\Documenti\ArchiveFileResolver::resolve($codStabile, $fn); + } + + $firstAtt = $allegati[0] ?? null; + + $results[] = [ + 'id' => 'posta_dett_' . $r->id, + 'protocollo' => $r->num_raccomandata ? ('RAC #' . $r->num_raccomandata) : ('POSTA #' . ($r->id_richiesta ?? $r->id)), + 'tipo' => $r->tipo_richiesta ?: 'Spedizione Postale / Visura', + 'canale' => 'Posta / Raccomandata', + 'data_invio' => $dtFmt, + 'destinatario' => $r->nome ?: ($r->destinatario ?? '—'), + 'ruolo' => ($r->c_i ?? '') === 'I' ? 'Inquilino' : 'Condomino', + 'oggetto' => $r->stato_richiesta ? ('Stato invio: ' . $r->stato_richiesta) : 'Invio Raccomandata / Documento', + 'importo' => (float) ($r->costo ?? ($r->importo_ec ?? 0)), + 'allegati' => $allegati, + 'nome_file' => $firstAtt['filename'] ?? null, + 'file_path' => $firstAtt['path'] ?? null, + 'file_exists' => $firstAtt['exists'] ?? false, + 'note' => $r->stato_richiesta ?? null, + 'timestamp_sort' => $ts, + ]; + } + } + + // 4. Dati da CommunicationMessage (Posta IMAP, PEC, EML) if (DbSchema::hasTable('communication_messages')) { $unitEmails = []; $recapiti = $this->getRecapitiMulticanaleTableProperty(); @@ -1317,8 +1412,39 @@ public function getProtocolloComunicazioniProperty(): array $dt = $cm->received_at ? Carbon::parse($cm->received_at) : null; $dtFmt = $dt ? $dt->format('d/m/Y H:i') : '—'; $ts = $dt ? $dt->timestamp : 0; - $attachments = is_array($cm->attachments) ? $cm->attachments : []; - $firstAtt = $attachments[0] ?? null; + $rawAttachments = is_array($cm->attachments) ? $cm->attachments : []; + + $allegati = []; + foreach ($rawAttachments as $att) { + $fn = (string) ($att['filename'] ?? 'allegato'); + $fp = (string) ($att['path'] ?? ''); + $exists = $fp !== '' && file_exists($fp); + $allegati[] = [ + 'filename' => $fn, + 'path' => $fp, + 'exists' => $exists, + 'size' => (int) ($att['size'] ?? 0), + 'mime' => (string) ($att['content_type'] ?? 'application/octet-stream'), + 'is_pdf' => str_ends_with(strtolower($fn), '.pdf'), + 'is_image' => (bool) preg_match('/\.(jpg|jpeg|png|webp|gif)$/i', $fn), + ]; + } + + if (empty($allegati) && ! empty($meta['eml_path'])) { + $emlPath = (string) $meta['eml_path']; + $emlName = basename($emlPath); + $allegati[] = [ + 'filename' => $emlName, + 'path' => $emlPath, + 'exists' => file_exists($emlPath), + 'size' => file_exists($emlPath) ? (int) filesize($emlPath) : 0, + 'mime' => 'message/rfc822', + 'is_pdf' => false, + 'is_image' => false, + ]; + } + + $firstAtt = $allegati[0] ?? null; $results[] = [ 'id' => 'comm_' . $cm->id, @@ -1330,9 +1456,10 @@ public function getProtocolloComunicazioniProperty(): array 'ruolo' => $cm->channel === 'pec' ? 'PEC' : 'Email', 'oggetto' => $meta['subject'] ?? \Illuminate\Support\Str::limit($cm->message_text, 60), 'importo' => null, - 'nome_file' => $firstAtt['filename'] ?? (! empty($meta['eml_path']) ? basename($meta['eml_path']) : null), - 'file_path' => $firstAtt['path'] ?? ($meta['eml_path'] ?? null), - 'file_exists' => ($firstAtt['path'] ?? null) ? file_exists($firstAtt['path']) : (! empty($meta['eml_path']) && file_exists($meta['eml_path'])), + 'allegati' => $allegati, + 'nome_file' => $firstAtt['filename'] ?? null, + 'file_path' => $firstAtt['path'] ?? null, + 'file_exists' => $firstAtt['exists'] ?? false, 'note' => $cm->message_text ? \Illuminate\Support\Str::limit($cm->message_text, 120) : null, 'timestamp_sort' => $ts, ]; diff --git a/app/Services/Documenti/ArchiveFileResolver.php b/app/Services/Documenti/ArchiveFileResolver.php new file mode 100644 index 0000000..840742a --- /dev/null +++ b/app/Services/Documenti/ArchiveFileResolver.php @@ -0,0 +1,134 @@ + '', + 'path' => null, + 'exists' => false, + 'size' => 0, + 'mime' => 'application/octet-stream', + 'is_pdf' => false, + 'is_image' => false, + ]; + } + + $cod = str_pad((string) $codStabile, 4, '0', STR_PAD_LEFT); + $candidateSubdirs = [ + $preferredSubdir, + 'E_C', 'e_c', + 'INC_EC', 'inc_ec', + 'allegati', 'Allegati', + 'WA', 'wa', + 'XML', 'xml', + 'posta_ordinaria', 'posta_pec', 'posta', + 'documenti', 'Documenti', + '', + ]; + $candidateSubdirs = array_values(array_unique(array_filter($candidateSubdirs, fn($v) => $v !== null))); + + $baseRoots = [ + "/mnt/gescon-archives/gescon/{$cod}", + "/mnt/gescon-archives/gescon/" . ltrim((string) $codStabile, '0'), + storage_path("app/private/amministratori/ADMX3PD9/stabili/{$cod}"), + storage_path("app/private/amministratori/ADMX3PD9/stabili/" . ltrim((string) $codStabile, '0')), + storage_path("app/public/stabili/{$cod}"), + storage_path("app/public/stabili/" . ltrim((string) $codStabile, '0')), + ]; + + $foundPath = null; + + // 1. Cerca percorsi esatti + foreach ($baseRoots as $root) { + if (! is_dir($root)) { + continue; + } + foreach ($candidateSubdirs as $sub) { + $dir = $sub !== '' ? "{$root}/{$sub}" : $root; + $p = "{$dir}/{$filename}"; + if (file_exists($p)) { + $foundPath = $p; + break 2; + } + } + } + + // 2. Se non trovato e il file ha un'estensione, cerca case-insensitive + if (! $foundPath) { + $lowerName = strtolower($filename); + foreach ($baseRoots as $root) { + if (! is_dir($root)) { + continue; + } + foreach ($candidateSubdirs as $sub) { + $dir = $sub !== '' ? "{$root}/{$sub}" : $root; + if (is_dir($dir)) { + $files = @scandir($dir); + if (is_array($files)) { + foreach ($files as $f) { + if (strtolower($f) === $lowerName) { + $foundPath = "{$dir}/{$f}"; + break 3; + } + } + } + } + } + } + } + + $exists = $foundPath !== null && file_exists($foundPath); + $size = $exists ? (int) @filesize($foundPath) : 0; + $mime = 'application/octet-stream'; + $isPdf = str_ends_with(strtolower($filename), '.pdf'); + $isImg = (bool) preg_match('/\.(jpg|jpeg|png|webp|gif|bmp)$/i', $filename); + + if ($exists) { + $finfo = @finfo_open(FILEINFO_MIME_TYPE); + if ($finfo) { + $detected = @finfo_file($finfo, $foundPath); + if ($detected) { + $mime = $detected; + } + @finfo_close($finfo); + } + } + + if ($isPdf) { + $mime = 'application/pdf'; + } + + return [ + 'filename' => $filename, + 'path' => $foundPath, + 'exists' => $exists, + 'size' => $size, + 'mime' => $mime, + 'is_pdf' => $isPdf, + 'is_image' => $isImg, + ]; + } +} diff --git a/app/Services/Posta/ImapMailboxService.php b/app/Services/Posta/ImapMailboxService.php index eae699a..ddba08a 100644 --- a/app/Services/Posta/ImapMailboxService.php +++ b/app/Services/Posta/ImapMailboxService.php @@ -107,6 +107,175 @@ public function fetchMailbox(Stabile $stabile, array $mailbox, int $maxMessages return $stats; } + /** + * Scarica e memorizza le email/PEC dello studio amministratore in formato .EML + * + * @param \App\Models\Amministratore $amministratore + * @param array $mailbox + * @param int $maxMessages + * @return array{imported: int, skipped: int, errors: int, messages: array} + */ + public function fetchStudioMailbox(\App\Models\Amministratore $amministratore, array $mailbox, int $maxMessages = 50): array + { + $host = trim((string) ($mailbox['host'] ?? '')); + $port = (int) ($mailbox['port'] ?? 993); + $username = trim((string) ($mailbox['username'] ?? ($mailbox['email'] ?? ''))); + $password = (string) ($mailbox['password'] ?? ''); + $encryption = trim((string) ($mailbox['encryption'] ?? 'ssl')) ?: 'ssl'; + $folder = trim((string) ($mailbox['folder'] ?? 'INBOX')) ?: 'INBOX'; + $isPec = strtolower(trim((string) ($mailbox['tipo'] ?? ''))) === 'pec'; + + if ($host === '' || $username === '') { + return [ + 'imported' => 0, + 'skipped' => 0, + 'errors' => 1, + 'message' => 'Parametri IMAP studio incompleti', + 'messages' => [], + ]; + } + + $stats = ['imported' => 0, 'skipped' => 0, 'errors' => 0, 'messages' => []]; + + try { + $this->imapClient->connect($host, $port, $username, $password, $encryption); + $this->imapClient->selectFolder($folder); + $msgIds = $this->imapClient->search('ALL'); + + $recentIds = array_slice(array_reverse($msgIds), 0, $maxMessages); + + foreach ($recentIds as $msgId) { + try { + $rawEml = $this->imapClient->fetchRawEml($msgId); + if (trim($rawEml) === '') { + continue; + } + + $res = $this->ingestStudioEmlString($rawEml, $amministratore, $mailbox, $isPec); + if ($res['status'] === 'imported') { + $stats['imported']++; + $stats['messages'][] = $res; + } elseif ($res['status'] === 'duplicate') { + $stats['skipped']++; + } else { + $stats['errors']++; + } + } catch (\Throwable $e) { + $stats['errors']++; + } + } + + $this->imapClient->disconnect(); + } catch (\Throwable $e) { + $stats['errors']++; + $stats['error_message'] = $e->getMessage(); + } + + return $stats; + } + + /** + * Ingesta un messaggio EML dello studio amministratore + */ + public function ingestStudioEmlString( + string $rawEml, + \App\Models\Amministratore $amministratore, + array $mailboxConfig = [], + bool $forcePec = false + ): array { + $parsed = $this->emlParser->parse($rawEml); + $messageId = $parsed['message_id'] ?: ('studio_' . md5($rawEml)); + + $existing = CommunicationMessage::whereNull('stabile_id') + ->where(function ($q) use ($messageId) { + $q->where('external_message_id', $messageId) + ->orWhere('metadata->message_id', $messageId); + }) + ->first(); + + if ($existing) { + return ['status' => 'duplicate', 'id' => $existing->id]; + } + + $channel = ($forcePec || $parsed['is_pec']) ? 'pec' : 'email'; + $year = $parsed['date'] ? $parsed['date']->format('Y') : date('Y'); + + $postaSubdir = $channel === 'pec' ? 'posta_pec' : 'posta_ordinaria'; + $storageDir = $this->pathService->amministratoreAbsolutePath($amministratore, "studio/{$postaSubdir}/{$year}"); + if (! is_dir($storageDir)) { + @mkdir($storageDir, 0755, true); + } + + $safeMsgId = preg_replace('/[^a-zA-Z0-9_-]/', '_', $messageId); + $emlFilename = "{$safeMsgId}.eml"; + $emlFullPath = "{$storageDir}/{$emlFilename}"; + @file_put_contents($emlFullPath, $rawEml); + + $savedAttachments = []; + if (! empty($parsed['attachments'])) { + $attachmentsDir = "{$storageDir}/allegati/{$safeMsgId}"; + if (! is_dir($attachmentsDir)) { + @mkdir($attachmentsDir, 0755, true); + } + + foreach ($parsed['attachments'] as $idx => $att) { + $safeName = preg_replace('/[^a-zA-Z0-9._-]/', '_', (string) $att['filename']); + if ($safeName === '') { + $safeName = "allegato_{$idx}.dat"; + } + $attPath = "{$attachmentsDir}/{$safeName}"; + @file_put_contents($attPath, $att['content']); + + $savedAttachments[] = [ + 'filename' => $att['filename'], + 'path' => $attPath, + 'content_type' => $att['content_type'], + 'size' => $att['size'], + 'is_inline' => $att['is_inline'], + ]; + } + } + + $senderDisplay = $parsed['from']['name'] ? "{$parsed['from']['name']} <{$parsed['from']['email']}>" : $parsed['from']['email']; + + $comm = CommunicationMessage::create([ + 'channel' => $channel, + 'direction' => 'inbound', + 'external_message_id' => $messageId, + 'stabile_id' => null, + 'sender_name' => $senderDisplay, + 'phone_number' => null, + 'message_text' => $parsed['body_text'] ?: strip_tags((string) $parsed['body_html']), + 'attachments' => $savedAttachments, + 'ticket_id' => null, + 'status' => 'received', + 'received_at' => $parsed['date'] ?: now(), + 'metadata' => [ + 'subject' => $parsed['subject'], + 'sender_email' => $parsed['from']['email'], + 'sender_name' => $parsed['from']['name'], + 'to' => $parsed['to'], + 'cc' => $parsed['cc'], + 'body_html' => $parsed['body_html'], + 'eml_path' => $emlFullPath, + 'is_pec' => $channel === 'pec', + 'is_studio' => true, + 'mailbox_label' => $mailboxConfig['label'] ?? 'Studio', + 'mailbox_email' => $mailboxConfig['email'] ?? '', + ], + ]); + + return [ + 'status' => 'imported', + 'id' => $comm->id, + 'channel' => $channel, + 'subject' => $parsed['subject'], + 'from' => $senderDisplay, + 'date' => $parsed['date']?->format('d/m/Y H:i'), + 'has_attachments' => count($savedAttachments) > 0, + ]; + } + /** * Ingesta un messaggio da stringa EML grezza */ diff --git a/resources/views/filament/pages/impostazioni/partials/scheda-amministratore-posta-operativita.blade.php b/resources/views/filament/pages/impostazioni/partials/scheda-amministratore-posta-operativita.blade.php index 752de3c..d55a65c 100755 --- a/resources/views/filament/pages/impostazioni/partials/scheda-amministratore-posta-operativita.blade.php +++ b/resources/views/filament/pages/impostazioni/partials/scheda-amministratore-posta-operativita.blade.php @@ -1,18 +1,202 @@ -
.EML.
+ {{ $this->opsLastOutput }}
+ {{ $this->opsLastOutput }}
| Stabile | +Codice Fiscale | +Posta Ordinaria (IMAP) | +Posta Certificata (PEC) | +Messaggi | +Azioni Rapide | +
|---|---|---|---|---|---|
|
+
+ {{ $stb['codice_stabile'] }}
+ {{ $stb['denominazione'] }}
+
+ {{ $stb['comune'] ?: '—' }}
+ |
+
+ + {{ $stb['codice_fiscale'] ?: '—' }} + | + +
+ @if($stb['email_configured'])
+
+
+ Configurata
+
+ @if($stb['email_info'])
+
+ {{ $stb['email_info'] }}
+
+ @endif
+ @else
+
+
+ Non configurata
+
+ @endif
+ |
+
+
+ @if($stb['pec_configured'])
+
+
+ Configurata
+
+ @if($stb['pec_info'])
+
+ {{ $stb['pec_info'] }}
+
+ @endif
+ @else
+
+
+ Non configurata
+
+ @endif
+ |
+
+ + @if($stb['msg_count'] > 0) + + {{ $stb['msg_count'] }} + + @else + 0 + @endif + | + ++ + | +
| + Nessuno stabile accessibile censito. + | +|||||
- Usa il pulsante "Sincronizza IMAP (.EML)" in alto per scaricare la posta dalle caselle ufficiali degli stabili. +
+ Se non hai ancora configurato la casella IMAP o PEC, puoi inserire parametri e credenziali per abilitare lo scaricamento e la visualizzazione automatica.
+