user()->canAccess('report')) { abort(403); } $stats = [ 'individui' => Individuo::count(), 'gruppi' => Gruppo::count(), 'eventi' => Evento::count(), 'documenti' => Documento::count(), 'mailing_liste' => MailingList::count(), 'contatti' => Contatto::count(), ]; $prebuiltReports = $this->getPrebuiltReports(); $customReports = ReportCustom::where('user_id', auth()->id()) ->orderBy('created_at', 'desc') ->get(); $tipologieDocumento = TipologiaDocumento::attive()->pluck('nome'); $columnOptions = []; foreach (ReportColumnRegistry::allEntityTypes() as $type) { $columnOptions[$type] = ReportColumnRegistry::getColumns($type); } return view('report.index', compact( 'stats', 'prebuiltReports', 'customReports', 'tipologieDocumento', 'columnOptions' )); } public function run(Request $request) { if (!auth()->user()->canAccess('report')) { abort(403); } $reportType = $request->input('report'); $customReportId = $request->input('custom_id'); if ($customReportId) { $report = ReportCustom::findOrFail($customReportId); if ($report->user_id !== auth()->id()) { abort(403); } $data = $this->runCustomReport($report); return view('report.result', compact('data', 'reportType', 'report')); } $data = $this->resolveReport($request); if (!$data) { return redirect()->route('report.index')->with('error', 'Report non trovato.'); } return view('report.result', compact('data', 'reportType')); } public function runCustom($id) { if (!auth()->user()->canAccess('report')) { abort(403); } $report = ReportCustom::findOrFail($id); if ($report->user_id !== auth()->id()) { abort(403); } $data = $this->runCustomReport($report); return view('report.result', ['reportType' => 'custom', 'data' => $data, 'customReport' => $report]); } public function storeCustom(Request $request) { if (!auth()->user()->canAccess('report')) { abort(403); } $data = $request->validate([ 'nome' => 'required|string|max:255', 'descrizione' => 'nullable|string', 'tipo_report' => 'required|string', ]); $config = []; if ($request->has('columns')) { $columns = $request->input('columns'); $config['columns'] = is_array($columns) ? $columns : array_map('trim', explode(',', $columns)); } if ($request->filled('sort_by')) { $config['sort_by'] = $request->input('sort_by'); $config['sort_direction'] = $request->input('sort_direction', 'asc'); } if ($request->filled('limit')) { $config['limit'] = (int) $request->input('limit'); } if ($request->filled('search')) { $config['search'] = $request->input('search'); } if ($request->has('search_columns')) { $config['search_columns'] = $request->input('search_columns'); } ReportCustom::create([ 'user_id' => auth()->id(), 'nome' => $data['nome'], 'descrizione' => $data['descrizione'] ?? null, 'tipo_report' => $data['tipo_report'], 'config' => $config, ]); return redirect()->route('report.index')->with('success', 'Report personalizzato creato.'); } public function destroyCustom($id) { if (!auth()->user()->canAccess('report')) { abort(403); } $report = ReportCustom::where('user_id', auth()->id())->findOrFail($id); $report->delete(); return redirect()->route('report.index')->with('success', 'Report personalizzato eliminato.'); } public function printPreview(Request $request) { if (!auth()->user()->canAccess('report')) { abort(403); } $customReportId = $request->input('custom_id'); if ($customReportId) { $report = ReportCustom::findOrFail($customReportId); if ($report->user_id !== auth()->id()) { abort(403); } $data = $this->runCustomReport($report); return view('report.print-preview', compact('data', 'report')); } $data = $this->resolveReport($request); if (!$data) { return redirect()->route('report.index')->with('error', 'Report non trovato.'); } return view('report.print-preview', compact('data')); } public function exportCSV(Request $request) { if (!auth()->user()->canAccess('report')) { abort(403); } $customReportId = $request->input('custom_id'); if ($customReportId) { $report = ReportCustom::findOrFail($customReportId); if ($report->user_id !== auth()->id()) { abort(403); } $data = $this->runCustomReport($report); $filename = 'report_' . Str::slug($report->nome) . '_' . now()->format('Y-m-d') . '.csv'; } else { $data = $this->resolveReport($request); if (!$data) { return redirect()->route('report.index')->with('error', 'Report non trovato.'); } $filename = 'report_' . $request->input('report') . '_' . now()->format('Y-m-d') . '.csv'; } $response = new \Symfony\Component\HttpFoundation\StreamedResponse(function () use ($data) { $handle = fopen('php://output', 'w'); fprintf($handle, chr(0xEF) . chr(0xBB) . chr(0xBF)); if (!empty($data['headers']) && !empty($data['rows'])) { fputcsv($handle, $data['headers']); foreach ($data['rows'] as $row) { fputcsv($handle, $row); } } fclose($handle); }); $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); $response->headers->set('Content-Disposition', 'attachment; filename="' . $filename . '"'); return $response; } protected function resolveReport(Request $request): ?array { $reportType = $request->input('report'); $tipologiaFilter = $request->input('tipologia_documento'); return match ($reportType) { 'individui_per_genere' => $this->reportIndividuiPerGenere(), 'individui_per_eta' => $this->reportIndividuiPerEta(), 'gruppi_gerarchia' => $this->reportGruppiGerarchia(), 'gruppi_membri' => $this->reportGruppiMembri(), 'eventi_calendario' => $this->reportEventiCalendario(), 'documenti_per_tipo' => $this->reportDocumentiPerTipo(), 'contatti_per_tipo' => $this->reportContattiPerTipo(), 'mailing_list_dettaglio' => $this->reportMailingListDettaglio(), 'individui_senza_contatti' => $this->reportIndividuiSenzaContatti(), 'individui_senza_gruppo' => $this->reportIndividuiSenzaGruppo(), 'eventi_per_gruppo' => $this->reportEventiPerGruppo(), 'documenti_orfani' => $this->reportDocumentiOrfani(), 'individui_completo' => $this->reportIndividuiCompleto(), 'gruppi_completo' => $this->reportGruppiCompleto(), 'eventi_completo' => $this->reportEventiCompleto(), 'documenti_completo' => $this->reportDocumentiCompleto($tipologiaFilter), 'contatti_completo' => $this->reportContattiCompleto(), 'scadenze_documenti' => $this->reportScadenzeDocumenti($tipologiaFilter), default => null, }; } protected function getPrebuiltReports(): array { return [ [ 'id' => 'individui_per_genere', 'nome' => 'Individui per Genere', 'descrizione' => 'Distribuzione degli individui per genere (Maschio/Femmina)', 'icona' => 'fa-venus-mars', 'colore' => 'info', 'has_tipologia_filter' => false, ], [ 'id' => 'individui_per_eta', 'nome' => 'Individui per Fascia d\'Età', 'descrizione' => 'Distribuzione degli individui per fasce d\'età', 'icona' => 'fa-birthday-cake', 'colore' => 'success', 'has_tipologia_filter' => false, ], [ 'id' => 'gruppi_gerarchia', 'nome' => 'Gerarchia Gruppi', 'descrizione' => 'Struttura ad albero completa di tutti i gruppi con livelli', 'icona' => 'fa-sitemap', 'colore' => 'warning', 'has_tipologia_filter' => false, ], [ 'id' => 'gruppi_membri', 'nome' => 'Gruppi e Membri', 'descrizione' => 'Elenco di tutti i gruppi con il conteggio dei membri', 'icona' => 'fa-users', 'colore' => 'primary', 'has_tipologia_filter' => false, ], [ 'id' => 'eventi_calendario', 'nome' => 'Eventi del Mese', 'descrizione' => 'Tutti gli eventi programmati per il mese corrente', 'icona' => 'fa-calendar-alt', 'colore' => 'danger', 'has_tipologia_filter' => false, ], [ 'id' => 'documenti_per_tipo', 'nome' => 'Documenti per Tipologia', 'descrizione' => 'Distribuzione dei documenti per tipologia', 'icona' => 'fa-file-alt', 'colore' => 'secondary', 'has_tipologia_filter' => false, ], [ 'id' => 'contatti_per_tipo', 'nome' => 'Contatti per Tipo', 'descrizione' => 'Distribuzione dei contatti per tipo (telefono, email, ecc.)', 'icona' => 'fa-address-book', 'colore' => 'teal', 'has_tipologia_filter' => false, ], [ 'id' => 'mailing_list_dettaglio', 'nome' => 'Dettaglio Mailing List', 'descrizione' => 'Elenco completo delle mailing list con i contatti associati', 'icona' => 'fa-envelope', 'colore' => 'purple', 'has_tipologia_filter' => false, ], [ 'id' => 'individui_senza_contatti', 'nome' => 'Individui senza Contatti', 'descrizione' => 'Lista degli individui che non hanno nessun contatto registrato', 'icona' => 'fa-user-slash', 'colore' => 'dark', 'has_tipologia_filter' => false, ], [ 'id' => 'individui_senza_gruppo', 'nome' => 'Individui senza Gruppo', 'descrizione' => 'Lista degli individui non associati a nessun gruppo', 'icona' => 'fa-user-friends', 'colore' => 'orange', 'has_tipologia_filter' => false, ], [ 'id' => 'eventi_per_gruppo', 'nome' => 'Eventi per Gruppo', 'descrizione' => 'Distribuzione degli eventi per gruppo di appartenenza', 'icona' => 'fa-calendar-check', 'colore' => 'pink', 'has_tipologia_filter' => false, ], [ 'id' => 'documenti_orfani', 'nome' => 'Documenti Orfani', 'descrizione' => 'Documenti non collegati a nessun individuo, gruppo o evento', 'icona' => 'fa-file-excel', 'colore' => 'gray', 'has_tipologia_filter' => false, ], [ 'id' => 'documenti_completo', 'nome' => 'Documenti (dettaglio completo)', 'descrizione' => 'Elenco completo documenti con uploader, visibilità e filtro per tipologia', 'icona' => 'fa-file-alt', 'colore' => 'indigo', 'has_tipologia_filter' => true, ], [ 'id' => 'scadenze_documenti', 'nome' => 'Scadenze Documenti', 'descrizione' => 'Individui con scadenza documento, filtrabile per tipo documento', 'icona' => 'fa-hourglass-half', 'colore' => 'danger', 'has_tipologia_filter' => true, ], [ 'id' => 'individui_completo', 'nome' => 'Anagrafica Completa', 'descrizione' => 'Elenco completo individui con contatti email e gruppi di appartenenza', 'icona' => 'fa-address-card', 'colore' => 'info', 'has_tipologia_filter' => false, ], [ 'id' => 'gruppi_completo', 'nome' => 'Gruppi (dettaglio completo)', 'descrizione' => 'Gruppi con conteggio membri, diocesi e sottogruppi', 'icona' => 'fa-sitemap', 'colore' => 'warning', 'has_tipologia_filter' => false, ], [ 'id' => 'eventi_completo', 'nome' => 'Eventi (dettaglio completo)', 'descrizione' => 'Tutti gli eventi con gruppi associati, responsabili e luogo', 'icona' => 'fa-calendar-check', 'colore' => 'success', 'has_tipologia_filter' => false, ], [ 'id' => 'contatti_completo', 'nome' => 'Contatti (dettaglio completo)', 'descrizione' => 'Elenco completo contatti con individuo associato', 'icona' => 'fa-address-book', 'colore' => 'teal', 'has_tipologia_filter' => false, ], ]; } protected function reportIndividuiPerGenere(): array { $rows = Individuo::select('genere', DB::raw('count(*) as totale')) ->groupBy('genere') ->get() ->map(function ($item) { $item->genere_label = match ($item->genere) { 'M' => 'Maschio', 'F' => 'Femmina', default => 'Non specificato', }; return $item; }); return [ 'title' => 'Individui per Genere', 'headers' => ['Genere', 'Totale'], 'rows' => $rows->map(fn($r) => [$r->genere_label, $r->totale])->toArray(), 'summary' => 'Totale individui: ' . Individuo::count(), 'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(), ]; } protected function reportIndividuiPerEta(): array { $now = now(); $fasce = [ ['label' => '0-17 (Minorenni)', 'min' => 0, 'max' => 17], ['label' => '18-25 (Giovani)', 'min' => 18, 'max' => 25], ['label' => '26-40 (Adulti)', 'min' => 26, 'max' => 40], ['label' => '41-60 (Adulti)', 'min' => 41, 'max' => 60], ['label' => '61-75 (Senior)', 'min' => 61, 'max' => 75], ['label' => '75+ (Anziani)', 'min' => 76, 'max' => 200], ]; $rows = []; $totalWithDate = 0; foreach ($fasce as $fascia) { $count = Individuo::whereNotNull('data_nascita') ->whereYear('data_nascita', '>=', $now->year - $fascia['max']) ->whereYear('data_nascita', '<=', $now->year - $fascia['min']) ->count(); $rows[] = ['fascia' => $fascia['label'], 'totale' => $count]; $totalWithDate += $count; } $noDate = Individuo::whereNull('data_nascita')->count(); $rows[] = ['fascia' => 'Data non specificata', 'totale' => $noDate]; return [ 'title' => 'Individui per Fascia d\'Età', 'headers' => ['Fascia d\'Età', 'Totale'], 'rows' => array_map(fn($r) => [$r['fascia'], $r['totale']], $rows), 'summary' => 'Totale con data: ' . $totalWithDate . ' | Senza data: ' . $noDate, 'detail_rows' => $rows, ]; } protected function reportGruppiGerarchia(): array { $rootGruppi = Gruppo::whereNull('parent_id')->with(['children', 'diocesi'])->orderBy('nome')->get(); $rows = []; foreach ($rootGruppi as $gruppo) { $rows[] = [ 'livello' => 0, 'nome' => $gruppo->nome, 'diocesi' => $gruppo->diocesi?->nome ?? '-', 'membri' => $gruppo->individui()->count(), 'padre' => '-', ]; $this->addGruppoChildren($gruppo->children, 1, $rows); } return [ 'title' => 'Gerarchia Gruppi', 'headers' => ['Livello', 'Nome Gruppo', 'Diocesi', 'Membri', 'Gruppo Padre'], 'rows' => array_map(fn($r) => [$r['livello'], $r['nome'], $r['diocesi'], $r['membri'], $r['padre']], $rows), 'summary' => 'Totale gruppi: ' . Gruppo::count(), 'detail_rows' => $rows, ]; } protected function addGruppoChildren($children, $level, &$rows): void { foreach ($children as $child) { $parent = $child->parent; $rows[] = [ 'livello' => $level, 'nome' => str_repeat(' ', $level) . $child->nome, 'diocesi' => $child->diocesi?->nome ?? '-', 'membri' => $child->individui()->count(), 'padre' => $parent?->nome ?? '-', ]; $this->addGruppoChildren($child->children, $level + 1, $rows); } } protected function reportGruppiMembri(): array { $gruppi = Gruppo::withCount('individui')->orderBy('nome')->get(); $rows = $gruppi->map(function ($g) { $parent = $g->parent; return [ 'nome' => $g->full_path, 'diocesi' => $g->diocesi?->nome ?? '-', 'membri' => $g->individui_count, 'padre' => $parent?->nome ?? '-', 'responsabili' => $g->getResponsabili()->pluck('cognome')->implode(', ') ?: '-', ]; })->toArray(); return [ 'title' => 'Gruppi e Membri', 'headers' => ['Gruppo', 'Diocesi', 'Membri', 'Gruppo Padre', 'Responsabili'], 'rows' => array_map(fn($r) => [$r['nome'], $r['diocesi'], $r['membri'], $r['padre'], $r['responsabili']], $rows), 'summary' => 'Totale gruppi: ' . $gruppi->count() . ' | Totale membri: ' . $gruppi->sum('individui_count'), 'detail_rows' => $rows, ]; } protected function reportEventiCalendario(): array { $now = now(); $eventi = Evento::whereBetween('data_specifica', [$now->startOfMonth(), $now->endOfMonth()]) ->with(['gruppi', 'responsabili']) ->orderBy('data_specifica') ->get(); $rows = $eventi->map(function ($e) { return [ 'nome' => $e->nome_evento, 'data' => $e->data_specifica?->format('d/m/Y H:i') ?? '-', 'tipo' => ucfirst($e->tipo_recorrenza ?? 'singolo'), 'gruppi' => $e->gruppi->pluck('nome')->implode(', ') ?: '-', 'responsabili' => $e->responsabili->pluck('nome_completo')->implode(', ') ?: '-', 'luogo' => $e->luogo_indirizzo ?? '-', ]; })->toArray(); return [ 'title' => 'Eventi del Mese (' . $now->format('F Y') . ')', 'headers' => ['Evento', 'Data', 'Tipo', 'Gruppi', 'Responsabili', 'Luogo'], 'rows' => array_map(fn($r) => [$r['nome'], $r['data'], $r['tipo'], $r['gruppi'], $r['responsabili'], $r['luogo']], $rows), 'summary' => 'Eventi nel mese: ' . $eventi->count(), 'detail_rows' => $rows, ]; } protected function reportDocumentiPerTipo(): array { $rows = Documento::select('tipologia', DB::raw('count(*) as totale')) ->groupBy('tipologia') ->orderBy('totale', 'desc') ->get(); return [ 'title' => 'Documenti per Tipologia', 'headers' => ['Tipologia', 'Totale'], 'rows' => $rows->map(fn($r) => [ucfirst((string) $r->tipologia), $r->totale])->toArray(), 'summary' => 'Totale documenti: ' . Documento::count(), 'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(), ]; } protected function reportContattiPerTipo(): array { $rows = Contatto::select('tipo', DB::raw('count(*) as totale')) ->groupBy('tipo') ->orderBy('totale', 'desc') ->get(); return [ 'title' => 'Contatti per Tipo', 'headers' => ['Tipo', 'Totale'], 'rows' => $rows->map(fn($r) => [ucfirst((string) $r->tipo), $r->totale])->toArray(), 'summary' => 'Totale contatti: ' . Contatto::count(), 'detail_rows' => $rows->map(fn($r) => (array) $r)->toArray(), ]; } protected function reportMailingListDettaglio(): array { $mailingLists = MailingList::with(['contatti.individuo'])->orderBy('nome')->get(); $rows = []; foreach ($mailingLists as $lista) { foreach ($lista->contatti as $contatto) { $rows[] = [ 'lista' => $lista->nome, 'stato' => $lista->attiva ? 'Attiva' : 'Disattiva', 'individuo' => $contatto->individuo?->nome_completo ?? 'N/D', 'email' => $contatto->valore ?? '-', ]; } } return [ 'title' => 'Dettaglio Mailing List', 'headers' => ['Lista', 'Stato', 'Individuo', 'Email'], 'rows' => array_map(fn($r) => [$r['lista'], $r['stato'], $r['individuo'], $r['email']], $rows), 'summary' => 'Liste: ' . $mailingLists->count() . ' | Contatti totali: ' . count($rows), 'detail_rows' => $rows, ]; } protected function reportIndividuiSenzaContatti(): array { $individui = Individuo::doesntHave('contatti') ->orderBy('cognome') ->orderBy('nome') ->get(); $rows = $individui->map(function ($ind) { return [ 'codice' => $ind->codice_id, 'cognome' => $ind->cognome, 'nome' => $ind->nome, 'email' => $ind->email ?? '-', ]; })->toArray(); return [ 'title' => 'Individui senza Contatti', 'headers' => ['Codice', 'Cognome', 'Nome', 'Email'], 'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['email']], $rows), 'summary' => 'Individui senza contatti: ' . $individui->count(), 'detail_rows' => $rows, ]; } protected function reportIndividuiSenzaGruppo(): array { $individui = Individuo::doesntHave('gruppi') ->orderBy('cognome') ->orderBy('nome') ->get(); $rows = $individui->map(function ($ind) { return [ 'codice' => $ind->codice_id, 'cognome' => $ind->cognome, 'nome' => $ind->nome, 'email' => $ind->email ?? '-', ]; })->toArray(); return [ 'title' => 'Individui senza Gruppo', 'headers' => ['Codice', 'Cognome', 'Nome', 'Email'], 'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['email']], $rows), 'summary' => 'Individui senza gruppo: ' . $individui->count(), 'detail_rows' => $rows, ]; } protected function reportEventiPerGruppo(): array { $gruppi = Gruppo::with(['eventi'])->orderBy('nome')->get(); $rows = []; foreach ($gruppi as $gruppo) { if ($gruppo->eventi->count() > 0) { foreach ($gruppo->eventi as $evento) { $rows[] = [ 'gruppo' => $gruppo->nome, 'evento' => $evento->nome_evento, 'data' => $evento->data_specifica?->format('d/m/Y') ?? '-', 'tipo' => ucfirst($evento->tipo_recorrenza ?? 'singolo'), ]; } } else { $rows[] = [ 'gruppo' => $gruppo->nome, 'evento' => '-', 'data' => '-', 'tipo' => '-', ]; } } return [ 'title' => 'Eventi per Gruppo', 'headers' => ['Gruppo', 'Evento', 'Data', 'Tipo'], 'rows' => array_map(fn($r) => [$r['gruppo'], $r['evento'], $r['data'], $r['tipo']], $rows), 'summary' => 'Gruppi: ' . $gruppi->count(), 'detail_rows' => $rows, ]; } protected function reportDocumentiOrfani(): array { $documenti = Documento::whereNull('visibilita_target_id') ->orWhere('visibilita', 'pubblico') ->orderBy('nome_file') ->get(); $rows = $documenti->map(function ($doc) { return [ 'nome' => $doc->nome_file, 'tipologia' => ucfirst($doc->tipologia ?? ''), 'visibilita' => ucfirst($doc->visibilita ?? ''), 'data' => $doc->created_at->format('d/m/Y'), ]; })->toArray(); return [ 'title' => 'Documenti Orfani', 'headers' => ['Nome', 'Tipologia', 'Visibilità', 'Data'], 'rows' => array_map(fn($r) => [$r['nome'], $r['tipologia'], $r['visibilita'], $r['data']], $rows), 'summary' => 'Documenti orfani: ' . $documenti->count(), 'detail_rows' => $rows, ]; } protected function reportDocumentiCompleto(?string $tipologiaFilter = null): array { $query = Documento::with('user') ->orderBy('created_at', 'desc'); if ($tipologiaFilter) { $query->where('tipologia', $tipologiaFilter); } $documenti = $query->get(); $rows = $documenti->map(function ($doc) { return [ 'nome' => $doc->nome_file, 'tipologia' => ucfirst($doc->tipologia ?? ''), 'visibilita' => ucfirst($doc->visibilita ?? ''), 'dimensione' => $doc->dimensione ? number_format($doc->dimensione / 1024, 1) . ' KB' : '-', 'uploader' => $doc->user?->name ?? '-', 'data' => $doc->created_at->format('d/m/Y'), ]; })->toArray(); $title = $tipologiaFilter ? 'Documenti (tipologia: ' . ucfirst($tipologiaFilter) . ')' : 'Documenti (dettaglio completo)'; return [ 'title' => $title, 'headers' => ['Nome File', 'Tipologia', 'Visibilità', 'Dimensione', 'Caricato Da', 'Data'], 'rows' => array_map(fn($r) => [$r['nome'], $r['tipologia'], $r['visibilita'], $r['dimensione'], $r['uploader'], $r['data']], $rows), 'summary' => 'Totale documenti: ' . $documenti->count(), 'detail_rows' => $rows, ]; } protected function reportScadenzeDocumenti(?string $tipologiaFilter = null): array { $query = Individuo::whereNotNull('scadenza_documento') ->whereNotNull('tipo_documento') ->orderBy('scadenza_documento'); if ($tipologiaFilter) { $query->where('tipo_documento', $tipologiaFilter); } $individui = $query->get(); $rows = $individui->map(function ($ind) { $scadenza = $ind->scadenza_documento; $giorniMancanti = $scadenza ? now()->diffInDays($scadenza, false) : null; return [ 'codice' => $ind->codice_id, 'cognome' => $ind->cognome, 'nome' => $ind->nome, 'tipo_documento' => ucfirst($ind->tipo_documento ?? ''), 'numero' => $ind->numero_documento ?? '-', 'scadenza' => $scadenza?->format('d/m/Y') ?? '-', 'stato' => $giorniMancanti !== null ? ($giorniMancanti < 0 ? ('Scaduto da ' . abs($giorniMancanti) . ' gg') : ($giorniMancanti <= 30 ? 'Scade tra ' . $giorniMancanti . ' gg' : 'In regola (' . $giorniMancanti . ' gg)')) : '-', ]; })->toArray(); $title = $tipologiaFilter ? 'Scadenze Documenti (tipo: ' . ucfirst($tipologiaFilter) . ')' : 'Scadenze Documenti'; return [ 'title' => $title, 'headers' => ['Codice', 'Cognome', 'Nome', 'Tipo Documento', 'Numero', 'Scadenza', 'Stato'], 'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['tipo_documento'], $r['numero'], $r['scadenza'], $r['stato']], $rows), 'summary' => 'Individui con documento: ' . $individui->count(), 'detail_rows' => $rows, ]; } protected function reportIndividuiCompleto(): array { $individui = Individuo::with(['contatti', 'gruppi']) ->orderBy('cognome') ->orderBy('nome') ->get(); $rows = $individui->map(function ($ind) { $email = $ind->contatti->where('tipo', 'email')->first()?->valore ?? $ind->email ?? '-'; $telefono = $ind->contatti->whereIn('tipo', ['telefono', 'cellulare'])->first()?->valore ?? $ind->telefono ?? '-'; $gruppi = $ind->gruppi->pluck('nome')->implode(', ') ?: '-'; return [ 'codice' => $ind->codice_id, 'cognome' => $ind->cognome, 'nome' => $ind->nome, 'data_nascita' => $ind->data_nascita?->format('d/m/Y') ?? '-', 'genere' => match ($ind->genere) { 'M' => 'M', 'F' => 'F', default => '-' }, 'email' => $email, 'telefono' => $telefono, 'città' => $ind->città ?? '-', 'gruppi' => $gruppi, ]; })->toArray(); return [ 'title' => 'Anagrafica Completa Individui', 'headers' => ['Codice', 'Cognome', 'Nome', 'Data Nascita', 'Genere', 'Email', 'Telefono', 'Città', 'Gruppi'], 'rows' => array_map(fn($r) => [$r['codice'], $r['cognome'], $r['nome'], $r['data_nascita'], $r['genere'], $r['email'], $r['telefono'], $r['città'], $r['gruppi']], $rows), 'summary' => 'Totale individui: ' . $individui->count(), 'detail_rows' => $rows, ]; } protected function reportGruppiCompleto(): array { $gruppi = Gruppo::withCount('individui') ->with('diocesi') ->orderBy('nome') ->get(); $rows = $gruppi->map(function ($g) { $parent = $g->parent; return [ 'nome' => $g->nome, 'descrizione' => $g->descrizione ?? '-', 'diocesi' => $g->diocesi?->nome ?? '-', 'livello' => $g->parent_id ? (count($g->getAncestors()) + 1) : 0, 'membri' => $g->individui_count, 'padre' => $parent?->nome ?? '-', 'città' => $g->città_incontro ?? '-', ]; })->toArray(); return [ 'title' => 'Gruppi (dettaglio completo)', 'headers' => ['Nome', 'Descrizione', 'Diocesi', 'Livello', 'Membri', 'Padre', 'Città'], 'rows' => array_map(fn($r) => [$r['nome'], $r['descrizione'], $r['diocesi'], $r['livello'], $r['membri'], $r['padre'], $r['città']], $rows), 'summary' => 'Totale gruppi: ' . $gruppi->count() . ' | Totale membri: ' . $gruppi->sum('individui_count'), 'detail_rows' => $rows, ]; } protected function reportEventiCompleto(): array { $eventi = Evento::with(['gruppi', 'responsabili']) ->orderBy('data_specifica', 'desc') ->get(); $rows = $eventi->map(function ($e) { return [ 'nome' => $e->nome_evento, 'data' => $e->data_specifica?->format('d/m/Y') ?? '-', 'tipo_evento' => $e->tipo_evento ?? '-', 'ricorrenza' => ucfirst($e->tipo_recorrenza ?? 'singolo'), 'gruppi' => $e->gruppi->pluck('nome')->implode(', ') ?: '-', 'responsabili' => $e->responsabili->pluck('nome_completo')->implode(', ') ?: '-', 'luogo' => $e->luogo_indirizzo ?? '-', ]; })->toArray(); return [ 'title' => 'Eventi (dettaglio completo)', 'headers' => ['Evento', 'Data', 'Tipo Evento', 'Ricorrenza', 'Gruppi', 'Responsabili', 'Luogo'], 'rows' => array_map(fn($r) => [$r['nome'], $r['data'], $r['tipo_evento'], $r['ricorrenza'], $r['gruppi'], $r['responsabili'], $r['luogo']], $rows), 'summary' => 'Totale eventi: ' . $eventi->count(), 'detail_rows' => $rows, ]; } protected function reportContattiCompleto(): array { $contatti = Contatto::with('individuo') ->orderBy('tipo') ->orderBy('valore') ->get(); $rows = $contatti->map(function ($c) { return [ 'tipo' => ucfirst($c->tipo), 'valore' => $c->valore, 'etichetta' => $c->etichetta ?? '-', 'primario' => $c->is_primary ? 'Si' : 'No', 'individuo' => $c->individuo?->nome_completo ?? '-', 'codice' => $c->individuo?->codice_id ?? '-', ]; })->toArray(); return [ 'title' => 'Contatti (dettaglio completo)', 'headers' => ['Tipo', 'Valore', 'Etichetta', 'Primario', 'Individuo', 'Codice'], 'rows' => array_map(fn($r) => [$r['tipo'], $r['valore'], $r['etichetta'], $r['primario'], $r['individuo'], $r['codice']], $rows), 'summary' => 'Totale contatti: ' . $contatti->count(), 'detail_rows' => $rows, ]; } protected function runCustomReport(ReportCustom $report): array { $tipo = $report->tipo_report; $config = $report->config ?? []; $query = match ($tipo) { 'individui' => Individuo::query(), 'gruppi' => Gruppo::query(), 'eventi' => Evento::query(), 'documenti' => Documento::query(), 'contatti' => Contatto::query(), default => null, }; if (!$query) { return [ 'title' => $report->nome, 'headers' => ['Errore'], 'rows' => [['Tipo report non valido']], 'summary' => 'Configurazione non valida', 'detail_rows' => [], ]; } $columns = $config['columns'] ?? []; $hasRelations = false; foreach ($columns as $col) { if (str_contains($col, '.')) { $hasRelations = true; $relation = explode('.', $col)[0]; $query->with($relation); } } if (!empty($config['search'])) { $search = $config['search']; $query->where(function ($q) use ($config, $search) { foreach ($config['search_columns'] ?? [] as $col) { if (!str_contains($col, '.')) { $q->orWhere($col, 'like', '%' . $search . '%'); } } }); } if (!empty($config['sort_by'])) { $sortBy = $config['sort_by']; if (!str_contains($sortBy, '.')) { $query->orderBy($sortBy, $config['sort_direction'] ?? 'asc'); } } if (!empty($config['limit'])) { $query->limit((int) $config['limit']); } $results = $query->get(); if (empty($columns)) { $first = $results->first(); $columns = $first ? array_keys($first->toArray()) : []; } $headers = array_map(function ($col) use ($tipo) { return ReportColumnRegistry::getColumnLabel($tipo, $col) ?? $col; }, $columns); $rows = $results->map(function ($item) use ($columns, $tipo) { $row = []; foreach ($columns as $col) { if (str_contains($col, '.')) { $row[] = ReportColumnRegistry::resolveRelationValue($item, $col); } else { $value = $item->{$col} ?? ''; if ($value instanceof \Carbon\Carbon) { $value = $value->format('d/m/Y'); } elseif (is_bool($value)) { $value = $value ? 'Si' : 'No'; } $row[] = $value; } } return $row; })->toArray(); return [ 'title' => $report->nome, 'headers' => $headers, 'rows' => $rows, 'summary' => 'Risultati: ' . count($rows), 'detail_rows' => $results->map(fn($r) => $r->toArray())->toArray(), ]; } }