resolveUserUnita($user)->values(); $persona = $this->resolvePortalPersona($user, $unitaImmobiliari); $serviceLabels = UnitaRecapitoServizio::serviceLabels(); return view('condomino.anagrafe.show', [ 'persona' => $persona, 'unitaImmobiliari' => $unitaImmobiliari, 'serviceLabels' => $serviceLabels, 'emailRows' => $this->buildEmailRows($persona), 'serviceContactMatrix' => $this->buildServiceContactMatrix($persona, $unitaImmobiliari), ]); } public function update(Request $request): RedirectResponse { /** @var User|null $user */ $user = Auth::user(); $unitaImmobiliari = $this->resolveUserUnita($user)->values(); $persona = $this->resolvePortalPersona($user, $unitaImmobiliari); if (! $persona instanceof Persona) { return back()->withErrors([ 'anagrafe' => 'Non e stato possibile collegare con certezza il tuo profilo a una scheda anagrafica digitale.', ])->withInput(); } $validated = $request->validate([ 'cognome' => 'nullable|string|max:100', 'nome' => 'nullable|string|max:100', 'codice_fiscale' => 'nullable|string|max:16', 'partita_iva' => 'nullable|string|max:16', 'data_nascita' => 'nullable|date', 'residenza_via' => 'nullable|string|max:255', 'telefono_principale' => 'nullable|string|max:50', 'email_principale' => 'nullable|email|max:255', 'email_pec' => 'nullable|email|max:255', 'whatsapp' => 'nullable|string|max:50', 'lingua_preferita' => 'nullable|string|max:10', 'modalita_comunicazione_preferita' => 'nullable|in:email,sms,whatsapp,telefono,posta', 'consenso_privacy' => 'nullable|boolean', 'note' => 'nullable|string|max:4000', 'email_multiple' => 'nullable|array', 'email_multiple.*.id' => 'nullable|integer', 'email_multiple.*.email' => 'nullable|email|max:255', 'email_multiple.*.tipo_email' => 'nullable|string|max:50', 'email_multiple.*.attiva' => 'nullable|boolean', 'service_contacts' => 'nullable|array', ]); DB::transaction(function () use ($validated, $persona, $unitaImmobiliari): void { $this->updatePersona($persona, $validated); $this->syncAdditionalEmails($persona, (array) ($validated['email_multiple'] ?? [])); $this->syncServiceContacts($persona, $unitaImmobiliari, (array) ($validated['service_contacts'] ?? [])); }); return redirect()->route('condomino.anagrafe.show') ->with('success', 'Scheda anagrafica aggiornata. Le modifiche sono tracciate con data, utente e IP.'); } public function downloadModulo(): Response { /** @var User|null $user */ $user = Auth::user(); $unitaImmobiliari = $this->resolveUserUnita($user)->values(); $persona = $this->resolvePortalPersona($user, $unitaImmobiliari); $html = view('condomino.anagrafe.modulo-pdf', [ 'persona' => $persona, 'utente' => $user, 'unitaImmobiliari' => $unitaImmobiliari, 'serviceLabels' => UnitaRecapitoServizio::serviceLabels(), 'emailRows' => $this->buildEmailRows($persona), 'serviceContactMatrix' => $this->buildServiceContactMatrix($persona, $unitaImmobiliari), 'generatedAt' => now(), ])->render(); $options = new Options(); $options->set('isRemoteEnabled', true); $options->set('defaultFont', 'DejaVu Sans'); $dompdf = new Dompdf($options); $dompdf->setPaper('A4', 'portrait'); $dompdf->loadHtml($html, 'UTF-8'); $dompdf->render(); return response($dompdf->output(), 200, [ 'Content-Type' => 'application/pdf', 'Content-Disposition' => 'attachment; filename="modulo-anagrafe-condominiale.pdf"', ]); } private function updatePersona(Persona $persona, array $validated): void { $fields = [ 'cognome', 'nome', 'codice_fiscale', 'partita_iva', 'data_nascita', 'residenza_via', 'telefono_principale', 'email_principale', 'email_pec', 'whatsapp', 'lingua_preferita', 'modalita_comunicazione_preferita', 'note', ]; foreach ($fields as $field) { $newValue = $validated[$field] ?? null; $oldValue = $persona->getAttribute($field); if ((string) $oldValue === (string) $newValue) { continue; } PersonaAudit::registraModifica($persona->id, $field, $oldValue, $newValue); $persona->setAttribute($field, $newValue); } $consensoPrivacy = (bool) ($validated['consenso_privacy'] ?? false); if ((bool) $persona->consenso_privacy !== $consensoPrivacy) { PersonaAudit::registraModifica($persona->id, 'consenso_privacy', $persona->consenso_privacy ? '1' : '0', $consensoPrivacy ? '1' : '0'); $persona->consenso_privacy = $consensoPrivacy; $persona->data_consenso_privacy = $consensoPrivacy ? now() : null; } $persona->save(); } private function syncAdditionalEmails(Persona $persona, array $rows): void { $before = $persona->emailMultiple() ->orderBy('id') ->get(['email', 'tipo_email', 'attiva']) ->map(fn(PersonaEmailMultipla $row): array=> [ 'email' => mb_strtolower(trim((string) $row->email)), 'tipo_email' => (string) $row->tipo_email, 'attiva' => (bool) $row->attiva, ]) ->values() ->all(); $keepIds = []; foreach ($rows as $row) { $email = mb_strtolower(trim((string) ($row['email'] ?? ''))); if ($email === '') { continue; } $attrs = [ 'email' => $email, 'tipo_email' => trim((string) ($row['tipo_email'] ?? 'secondaria')) ?: 'secondaria', 'attiva' => (bool) ($row['attiva'] ?? true), ]; $id = (int) ($row['id'] ?? 0); if ($id > 0) { $existing = $persona->emailMultiple()->whereKey($id)->first(); if ($existing instanceof PersonaEmailMultipla) { $existing->fill($attrs); $existing->save(); $keepIds[] = (int) $existing->id; continue; } } $created = $persona->emailMultiple()->create($attrs); $keepIds[] = (int) $created->id; } $deleteQuery = $persona->emailMultiple(); if ($keepIds !== []) { $deleteQuery->whereNotIn('id', $keepIds); } $deleteQuery->delete(); $after = $persona->emailMultiple() ->orderBy('id') ->get(['email', 'tipo_email', 'attiva']) ->map(fn(PersonaEmailMultipla $row): array=> [ 'email' => mb_strtolower(trim((string) $row->email)), 'tipo_email' => (string) $row->tipo_email, 'attiva' => (bool) $row->attiva, ]) ->values() ->all(); if (json_encode($before) !== json_encode($after)) { PersonaAudit::registraModifica($persona->id, 'email_multiple', json_encode($before), json_encode($after)); } } private function syncServiceContacts(Persona $persona, Collection $unitaImmobiliari, array $matrix): void { $allowedUnitIds = $unitaImmobiliari->pluck('id')->map(fn($id) => (int) $id)->all(); foreach ($allowedUnitIds as $unitaId) { $serviceRows = (array) ($matrix[$unitaId] ?? []); foreach (array_keys(UnitaRecapitoServizio::serviceLabels()) as $serviceType) { $email = mb_strtolower(trim((string) ($serviceRows[$serviceType] ?? ''))); $query = UnitaRecapitoServizio::query() ->where('unita_id', $unitaId) ->where('persona_id', (int) $persona->id) ->where('tipo_servizio', $serviceType); $before = $query->orderBy('id')->pluck('email')->map(fn($value) => mb_strtolower(trim((string) $value)))->values()->all(); if ($email === '') { $query->delete(); $after = []; } else { $record = $query->orderBy('id')->first(); if (! $record instanceof UnitaRecapitoServizio) { $record = new UnitaRecapitoServizio([ 'unita_id' => $unitaId, 'persona_id' => (int) $persona->id, 'tipo_servizio' => $serviceType, 'ordine' => 1, 'is_default' => true, 'attivo' => true, 'sorgente' => 'portale_condomino', ]); } $record->email = $email; $record->etichetta = UnitaRecapitoServizio::serviceLabels()[$serviceType]; $record->last_confirmed_at = now(); $record->save(); UnitaRecapitoServizio::query() ->where('unita_id', $unitaId) ->where('persona_id', (int) $persona->id) ->where('tipo_servizio', $serviceType) ->where('id', '!=', (int) $record->id) ->delete(); $after = [$email]; } if (json_encode($before) !== json_encode($after)) { PersonaAudit::registraModifica( $persona->id, 'unita_recapito:' . $unitaId . ':' . $serviceType, json_encode($before), json_encode($after) ); } } } } /** * @return array> */ private function buildEmailRows(?Persona $persona): array { $rows = []; if ($persona instanceof Persona) { $rows = $persona->emailMultiple() ->orderByDesc('attiva') ->orderBy('tipo_email') ->orderBy('email') ->get() ->map(fn(PersonaEmailMultipla $row): array=> [ 'id' => (int) $row->id, 'email' => (string) $row->email, 'tipo_email' => (string) ($row->tipo_email ?: 'secondaria'), 'attiva' => (bool) $row->attiva, ]) ->values() ->all(); } while (count($rows) < 4) { $rows[] = [ 'id' => null, 'email' => '', 'tipo_email' => 'secondaria', 'attiva' => true, ]; } return $rows; } /** * @return array>> */ private function buildServiceContactMatrix(?Persona $persona, Collection $unitaImmobiliari): array { $matrix = []; $labels = UnitaRecapitoServizio::serviceLabels(); $storedRows = collect(); if ($persona instanceof Persona) { $storedRows = UnitaRecapitoServizio::query() ->where('persona_id', (int) $persona->id) ->whereIn('unita_id', $unitaImmobiliari->pluck('id')->all()) ->get() ->groupBy(['unita_id', 'tipo_servizio']); } foreach ($unitaImmobiliari as $unita) { $unitRows = []; foreach ($labels as $serviceType => $label) { $stored = data_get($storedRows, $unita->id . '.' . $serviceType . '.0.email'); $resolved = $this->resolver->resolveForUnita($unita, $serviceType); $unitRows[$serviceType] = [ 'label' => $label, 'value' => (string) ($stored ?? ''), 'resolved' => $resolved, ]; } $matrix[(int) $unita->id] = $unitRows; } return $matrix; } private function resolvePortalPersona(?User $user, Collection $unitaImmobiliari): ?Persona { if (! $user) { return null; } $email = mb_strtolower(trim((string) $user->email)); if ($email === '') { return null; } $unitIds = $unitaImmobiliari->pluck('id')->map(fn($id) => (int) $id)->all(); $baseQuery = Persona::query()->with(['emailMultiple', 'relazioniUnitaAttive.unitaImmobiliare.stabile']); if ($unitIds !== []) { $baseQuery->whereHas('relazioniUnitaAttive', fn($query) => $query->whereIn('unita_id', $unitIds)); } $primaryMatch = (clone $baseQuery) ->whereRaw('LOWER(email_principale) = ?', [$email]) ->first(); if ($primaryMatch instanceof Persona) { return $primaryMatch; } return (clone $baseQuery) ->whereHas('emailMultiple', fn($query) => $query->whereRaw('LOWER(email) = ?', [$email])->where('attiva', true)) ->first(); } }