Files
glastree/app/Http/Controllers/ReportController.php
T
2026-06-09 11:22:56 +02:00

1181 lines
44 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Helpers\ReportColumnRegistry;
use App\Models\Gruppo;
use App\Models\Individuo;
use App\Models\Evento;
use App\Models\Documento;
use App\Models\MailingList;
use App\Models\Contatto;
use App\Models\Tag;
use App\Models\TipologiaDocumento;
use App\Models\ReportCustom;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class ReportController extends Controller
{
public function index()
{
if (!auth()->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);
}
$allTags = Tag::orderBy('name')->get();
return view('report.index', compact(
'stats',
'prebuiltReports',
'customReports',
'tipologieDocumento',
'columnOptions',
'allTags'
));
}
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');
}
if ($request->has('tag_filter')) {
$config['tag_filter'] = $request->input('tag_filter');
}
if ($request->filled('tag_filter_mode')) {
$config['tag_filter_mode'] = $request->input('tag_filter_mode');
}
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),
'tag_statistiche' => $this->reportTagStatistiche(),
'individui_per_tag' => $this->reportIndividuiPerTag(),
'documenti_per_tag' => $this->reportDocumentiPerTag(),
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,
],
[
'id' => 'tag_statistiche',
'nome' => 'Statistiche Tag',
'descrizione' => 'Distribuzione degli elementi per tag (individui, gruppi, eventi, documenti, mailing list)',
'icona' => 'fa-tags',
'colore' => 'indigo',
'has_tipologia_filter' => false,
],
[
'id' => 'individui_per_tag',
'nome' => 'Individui per Tag',
'descrizione' => 'Elenco completo degli individui raggruppati per tag assegnato',
'icona' => 'fa-user-tag',
'colore' => 'info',
'has_tipologia_filter' => false,
],
[
'id' => 'documenti_per_tag',
'nome' => 'Documenti per Tag',
'descrizione' => 'Elenco completo dei documenti raggruppati per tag assegnato',
'icona' => 'fa-tags',
'colore' => 'secondary',
'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 reportTagStatistiche(): array
{
$tags = Tag::orderBy('name')->get();
$rows = $tags->map(function ($tag) {
return [
'name' => $tag->name,
'color' => $tag->color ?? '#6c757d',
'individui' => $tag->individui()->count(),
'gruppi' => $tag->gruppi()->count(),
'eventi' => $tag->eventi()->count(),
'documenti' => $tag->documenti()->count(),
'mailing' => $tag->mailingLists()->count(),
'totale' => $tag->count,
];
})->toArray();
$totalIndividui = array_sum(array_column($rows, 'individui'));
$totalGruppi = array_sum(array_column($rows, 'gruppi'));
$totalEventi = array_sum(array_column($rows, 'eventi'));
$totalDocumenti = array_sum(array_column($rows, 'documenti'));
$totalMailing = array_sum(array_column($rows, 'mailing'));
return [
'title' => 'Statistiche Tag',
'headers' => ['Tag', 'Individui', 'Gruppi', 'Eventi', 'Documenti', 'Mailing List', 'Totale Elementi'],
'rows' => array_map(fn($r) => [$r['name'], $r['individui'], $r['gruppi'], $r['eventi'], $r['documenti'], $r['mailing'], $r['totale']], $rows),
'summary' => 'Tag: ' . count($tags) . ' | Individui: ' . $totalIndividui . ' | Gruppi: ' . $totalGruppi . ' | Eventi: ' . $totalEventi . ' | Documenti: ' . $totalDocumenti . ' | Mailing List: ' . $totalMailing,
'detail_rows' => $rows,
];
}
protected function reportIndividuiPerTag(): array
{
$individui = Individuo::with('tags')
->whereHas('tags')
->orderBy('cognome')
->orderBy('nome')
->get();
$rows = $individui->flatMap(function ($ind) {
if ($ind->tags->isEmpty()) {
return collect();
}
return $ind->tags->map(function ($tag) use ($ind) {
return [
'tag' => $tag->name,
'tag_color' => $tag->color ?? '#6c757d',
'codice' => $ind->codice_id,
'cognome' => $ind->cognome,
'nome' => $ind->nome,
'email' => $ind->email_primaria ?? '-',
];
});
})->toArray();
return [
'title' => 'Individui per Tag',
'headers' => ['Tag', 'Codice', 'Cognome', 'Nome', 'Email'],
'rows' => array_map(fn($r) => [$r['tag'], $r['codice'], $r['cognome'], $r['nome'], $r['email']], $rows),
'summary' => 'Totale righe (individuo × tag): ' . count($rows),
'detail_rows' => $rows,
];
}
protected function reportDocumentiPerTag(): array
{
$documenti = Documento::with('tags')
->whereHas('tags')
->orderBy('nome_file')
->get();
$rows = $documenti->flatMap(function ($doc) {
if ($doc->tags->isEmpty()) {
return collect();
}
return $doc->tags->map(function ($tag) use ($doc) {
return [
'tag' => $tag->name,
'tag_color' => $tag->color ?? '#6c757d',
'nome_file' => $doc->nome_file,
'tipologia' => ucfirst((string) $doc->tipologia),
'data' => $doc->created_at->format('d/m/Y'),
];
});
})->toArray();
return [
'title' => 'Documenti per Tag',
'headers' => ['Tag', 'Nome File', 'Tipologia', 'Data Caricamento'],
'rows' => array_map(fn($r) => [$r['tag'], $r['nome_file'], $r['tipologia'], $r['data']], $rows),
'summary' => 'Totale righe (documento × tag): ' . count($rows),
'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;
// Always load tags if tags column or tag filter is present
$needsTags = in_array('tags.nome', $columns) || !empty($config['tag_filter']);
if ($needsTags) {
$query->with('tags');
}
foreach ($columns as $col) {
if (str_contains($col, '.')) {
$hasRelations = true;
$relation = explode('.', $col)[0];
if ($relation !== 'tags') {
$query->with($relation);
}
}
}
// Apply tag filter
if (!empty($config['tag_filter'])) {
$tagSlugs = Tag::whereIn('id', (array) $config['tag_filter'])->pluck('slug')->toArray();
if (!empty($tagSlugs)) {
$mode = $config['tag_filter_mode'] ?? 'any';
if ($mode === 'all') {
$query->withAllTags($tagSlugs);
} else {
$query->withAnyTags($tagSlugs);
}
}
}
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(),
];
}
}