authorizeRead('individui'); $perPage = in_array((int) $request->perPage, [10, 20, 25, 50, 100]) ? (int) $request->perPage : 20; $query = Individuo::with('contatti', 'tags'); if ($request->filled('tag')) { $tagSlugs = (array) $request->input('tag'); $query->withAnyTags($tagSlugs); } $individui = $query->orderBy('cognome')->orderBy('nome')->paginate($perPage); $allTags = Tag::orderBy('name')->get(); $vista = null; if ($request->has('vista_id')) { $vista = \App\Models\VistaReport::where('id', $request->vista_id) ->where('user_id', auth()->id()) ->first(); } if (!$vista && auth()->check()) { $vista = \App\Models\VistaReport::where('user_id', auth()->id()) ->where('tipo', 'individui') ->where('is_default', true) ->first(); } $allColumns = [ ['key' => 'codice', 'label' => 'Codice'], ['key' => 'cognome', 'label' => 'Cognome'], ['key' => 'nome', 'label' => 'Nome'], ['key' => 'email', 'label' => 'Email'], ['key' => 'telefono', 'label' => 'Telefono'], ['key' => 'tag', 'label' => 'Tag'], ]; $defaultVisible = ['codice', 'cognome', 'nome', 'email', 'telefono', 'tag']; $visibleColumns = $vista && !empty($vista->colonne_visibili) ? $vista->colonne_visibili : $defaultVisible; $gruppi = Gruppo::orderBy('nome')->get(['id', 'nome']); return view('individui.index', compact('individui', 'vista', 'allColumns', 'visibleColumns', 'allTags', 'gruppi')); } public function exportCSV(Request $request) { $this->authorizeRead('individui'); $ids = $request->input('ids', []); $query = Individuo::with('contatti')->orderBy('cognome')->orderBy('nome'); if (is_array($ids) && !empty($ids)) { $query->whereIn('id', array_map('intval', $ids)); } $individui = $query->get(); $response = new StreamedResponse(function () use ($individui) { $handle = fopen('php://output', 'w'); fputcsv($handle, ['Codice', 'Cognome', 'Nome', 'Data Nascita', 'Genere', 'Email', 'Telefono', 'Cellulare', 'Indirizzo', 'CAP', 'Città', 'Provincia', 'Note']); foreach ($individui as $ind) { $email = $ind->contatti->where('tipo', 'email')->where('is_primary', true)->first() ?? $ind->contatti->where('tipo', 'email')->first(); $telefono = $ind->contatti->where('tipo', 'telefono')->where('is_primary', true)->first() ?? $ind->contatti->where('tipo', 'telefono')->first(); $cellulare = $ind->contatti->where('tipo', 'cellulare')->where('is_primary', true)->first() ?? $ind->contatti->where('tipo', 'cellulare')->first(); fputcsv($handle, [ $ind->codice_id, $ind->cognome, $ind->nome, $ind->data_nascita?->format('d/m/Y') ?? '', $ind->genere === 'M' ? 'Maschio' : ($ind->genere === 'F' ? 'Femmina' : ''), $email?->valore ?? '', $telefono?->valore ?? '', $cellulare?->valore ?? '', $ind->indirizzo, $ind->cap, $ind->città, $ind->sigla_provincia, $ind->note, ]); } fclose($handle); }); $response->headers->set('Content-Type', 'text/csv; charset=utf-8'); $response->headers->set('Content-Disposition', 'attachment; filename="individui_' . now()->format('Y-m-d') . '.csv"'); $response->headers->set('Cache-Control', 'no-store'); return $response; } public function create() { $this->authorizeWrite('individui'); $comuni = Comune::orderBy('nome')->get(); $diocesi = Diocesi::orderBy('nome')->get(); $tags = Tag::orderBy('name')->get(); return view('individui.create', compact('comuni', 'diocesi', 'tags')); } public function store(Request $request) { $this->authorizeWrite('individui'); $data = $request->validate([ 'cognome' => 'required|string|max:255', 'nome' => 'required|string|max:255', 'data_nascita' => 'nullable|date', 'indirizzo' => 'nullable|string|max:500', 'cap' => 'nullable|string|max:10', 'città' => 'nullable|string|max:255', 'sigla_provincia' => 'nullable|string|max:2', 'genere' => 'nullable|in:M,F', 'tipo_documento' => 'nullable|in:carta_identita,patente', 'numero_documento' => 'nullable|string|max:50', 'scadenza_documento' => 'nullable|date', 'note' => 'nullable|string', 'tags' => 'nullable|array', 'tags.*' => 'exists:tags,id', ]); $individuo = Individuo::create($data); if ($request->has('tags')) { $individuo->tags()->sync($request->tags); } if ($request->has('contatti')) { foreach ($request->contatti as $contatto) { if (!empty($contatto['tipo']) && !empty($contatto['valore'])) { $individuo->contatti()->create([ 'tipo' => $contatto['tipo'], 'valore' => $contatto['valore'], 'etichetta' => $contatto['etichetta'] ?? null, 'is_primary' => $contatto['is_primary'] ?? false, ]); } } } return redirect()->route('individui.index')->with('success', 'Individuo creato con successo.'); } public function show($individuo) { $this->authorizeRead('individui'); $individuo = Individuo::with(['contatti', 'gruppi' => function ($q) { $q->withPivot('ruolo_ids', 'ruolo_nel_gruppo', 'data_adesione'); }, 'avatar', 'tags'])->findOrFail($individuo); return view('individui.show', compact('individuo')); } public function report($individuo) { $this->authorizeRead('individui'); $individuo = Individuo::with(['contatti', 'gruppi', 'documenti', 'eventiResponsabili', 'avatar'])->findOrFail($individuo); return view('individui.report', compact('individuo')); } public function reportStampa(Request $request) { $this->authorizeRead('individui'); $ids = explode(',', $request->ids ?? ''); $individui = Individuo::with(['contatti', 'gruppi', 'documenti', 'eventiResponsabili', 'avatar']) ->whereIn('id', $ids) ->orderBy('cognome') ->orderBy('nome') ->get(); return view('individui.report-multiplo', compact('individui')); } public function edit($individuo) { $this->authorizeWrite('individui'); $individuo = Individuo::with(['contatti', 'gruppi', 'avatar', 'tags'])->findOrFail($individuo); $tags = Tag::orderBy('name')->get(); $selectedTags = $individuo->tags->pluck('id')->toArray(); return view('individui.edit', compact('individuo', 'tags', 'selectedTags')); } public function update(Request $request, $individuo) { $this->authorizeWrite('individui'); $individuo = Individuo::findOrFail($individuo); \Log::info('=== INDIVIDUO UPDATE START ===', ['id' => $individuo->id]); \Log::info('Request contatti:', ['contatti' => $request->input('contatti'), 'all_keys' => array_keys($request->all())]); $data = $request->validate([ 'cognome' => 'required|string|max:255', 'nome' => 'required|string|max:255', 'data_nascita' => 'nullable|date', 'indirizzo' => 'nullable|string|max:500', 'cap' => 'nullable|string|max:10', 'città' => 'nullable|string|max:255', 'sigla_provincia' => 'nullable|string|max:2', 'genere' => 'nullable|in:M,F', 'tipo_documento' => 'nullable|in:carta_identita,patente', 'numero_documento' => 'nullable|string|max:50', 'scadenza_documento' => 'nullable|date', 'note' => 'nullable|string', 'tags' => 'nullable|array', 'tags.*' => 'exists:tags,id', ]); $individuo->update($data); if ($request->has('tags')) { $individuo->tags()->sync($request->tags); } else { $individuo->tags()->detach(); } \Log::info('Individuo dati salvati', ['id' => $individuo->id]); if ($request->has('contatti')) { $contattiData = $request->input('contatti'); \Log::info('Contatti ricevuti:', ['contatti' => $contattiData, 'tipo' => gettype($contattiData)]); $individuo->contatti()->delete(); \Log::info('Contatti precedenti eliminati, count ora:', ['count' => $individuo->contatti()->count()]); } else { $contattiData = null; } if (!empty($contattiData) && is_array($contattiData)) { $created = 0; foreach ($contattiData as $contatto) { if (!empty($contatto['tipo']) && !empty($contatto['valore'])) { $individuo->contatti()->create([ 'tipo' => $contatto['tipo'], 'valore' => $contatto['valore'], 'etichetta' => $contatto['etichetta'] ?? null, 'is_primary' => !empty($contatto['is_primary']), ]); $created++; } } \Log::info('Contatti creati:', ['count' => $created]); } return redirect()->route('individui.show', $individuo)->with('success', 'Individuo aggiornato con successo!'); } public function destroy($individuo) { $this->authorizeDelete('individui'); $individuo = Individuo::findOrFail($individuo); $individuo->delete(); return redirect()->route('individui.index')->with('success', 'Individuo eliminato.'); } public function elimina(Request $request, $individuo) { $this->authorizeDelete('individui'); $individuo = Individuo::findOrFail($individuo); $eliminaContatti = filter_var($request->input('elimina_contatti', false), FILTER_VALIDATE_BOOLEAN); $eliminaDocumenti = filter_var($request->input('elimina_documenti', false), FILTER_VALIDATE_BOOLEAN); if ($eliminaContatti) { $individuo->contatti()->delete(); } if ($eliminaDocumenti) { foreach ($individuo->documenti as $documento) { if ($documento->file_path && file_exists(storage_path('app/public/' . $documento->file_path))) { unlink(storage_path('app/public/' . $documento->file_path)); } } $individuo->documenti()->delete(); } $individuo->delete(); return redirect()->route('individui.index')->with('success', 'Individuo eliminato con successo.'); } public function massElimina(Request $request) { $this->authorizeDelete('individui'); $ids = $request->input('ids', []); if (!is_array($ids) || empty($ids)) { return redirect()->route('individui.index')->with('error', 'Nessun individuo selezionato.'); } $eliminaContatti = filter_var($request->input('elimina_contatti', false), FILTER_VALIDATE_BOOLEAN); $eliminaDocumenti = filter_var($request->input('elimina_documenti', false), FILTER_VALIDATE_BOOLEAN); $individui = Individuo::whereIn('id', $ids)->get(); foreach ($individui as $individuo) { if ($eliminaContatti) { $individuo->contatti()->delete(); } if ($eliminaDocumenti) { foreach ($individuo->documenti as $documento) { if ($documento->file_path && file_exists(storage_path('app/public/' . $documento->file_path))) { unlink(storage_path('app/public/' . $documento->file_path)); } } $individuo->documenti()->delete(); } $individuo->delete(); } return redirect()->route('individui.index')->with('success', count($individui) . ' individui eliminati con successo.'); } public function massGruppo(Request $request) { $this->authorizeWrite('individui'); $idsInput = $request->input('ids', ''); $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []); if (empty($ids)) { return redirect()->route('individui.index')->with('error', 'Nessun individuo selezionato.'); } $data = $request->validate([ 'gruppi' => 'required|array', 'gruppi.*' => 'exists:gruppi,id', 'mode' => 'required|in:assign,remove', ]); $gruppiIds = $data['gruppi']; $mode = $data['mode']; $count = 0; foreach ($ids as $id) { $individuo = Individuo::find($id); if (!$individuo) continue; if ($mode === 'assign') { $individuo->gruppi()->syncWithoutDetaching($gruppiIds); } else { $individuo->gruppi()->detach($gruppiIds); } $count++; } $actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi'; return redirect()->route('individui.index')->with('success', "Gruppi {$actionLabel} per {$count} individui."); } public function massTag(Request $request) { $this->authorizeWrite('individui'); $idsInput = $request->input('ids', ''); $ids = is_array($idsInput) ? $idsInput : (is_string($idsInput) ? explode(',', $idsInput) : []); if (empty($ids)) { return redirect()->route('individui.index')->with('error', 'Nessun individuo selezionato.'); } $data = $request->validate([ 'tags' => 'required|array', 'tags.*' => 'exists:tags,id', 'mode' => 'required|in:assign,remove', ]); $tagIds = $data['tags']; $mode = $data['mode']; $count = 0; Individuo::whereIn('id', $ids)->chunk(100, function ($individui) use ($tagIds, $mode, &$count) { foreach ($individui as $individuo) { if ($mode === 'assign') { $individuo->tags()->syncWithoutDetaching($tagIds); } else { $individuo->tags()->detach($tagIds); } $count++; } }); $actionLabel = $mode === 'assign' ? 'assegnati' : 'rimossi'; return redirect()->route('individui.index')->with('success', "Tag {$actionLabel} per {$count} individui."); } public function elementiCollegati($individuo) { $individuo = Individuo::findOrFail($individuo); return response()->json([ 'contatti' => $individuo->contatti()->count(), 'gruppi' => $individuo->gruppi()->count(), 'documenti' => $individuo->documenti()->count(), 'eventi' => $individuo->eventiResponsabili()->count(), ]); } public function emailList(Request $request) { $this->authorizeRead('individui'); $ids = $request->input('ids', ''); $idArray = array_filter(array_map('intval', explode(',', $ids))); if (empty($idArray)) { return response()->json([]); } $individui = Individuo::with('contatti')->whereIn('id', $idArray)->get(); $result = []; foreach ($individui as $ind) { $emails = $ind->contatti->where('tipo', 'email')->pluck('valore')->toArray(); $result[] = [ 'id' => $ind->id, 'codice_id' => $ind->codice_id, 'cognome' => $ind->cognome, 'nome' => $ind->nome, 'email' => count($emails) === 1 ? $emails[0] : null, 'emails' => $emails, 'email_count' => count($emails) ]; } return response()->json($result); } public function allIndividui() { $this->authorizeRead('individui'); $individui = Individuo::select('id', 'codice_id', 'cognome', 'nome') ->orderBy('cognome') ->orderBy('nome') ->get(); return response()->json($individui); } public function import() { $this->authorizeWrite('individui'); return view('individui.import'); } public function importStore(Request $request) { $this->authorizeWrite('individui'); $request->validate([ 'file' => 'required|file|mimes:csv,txt', ]); $file = $request->file('file'); $handle = fopen($file->path(), 'r'); $header = fgetcsv($handle, 1000, ','); $imported = 0; $skipped = 0; $errors = []; $rowNumber = 1; while (($row = fgetcsv($handle, 1000, ',')) !== false) { $rowNumber++; if (count($row) < count($header)) { $skipped++; continue; } $data = array_combine($header, $row); if ($data === false) { $skipped++; continue; } $cognome = trim($data['cognome'] ?? ''); $nome = trim($data['nome'] ?? ''); if (empty($cognome) || empty($nome)) { $skipped++; continue; } try { $individuo = Individuo::create([ 'cognome' => $cognome, 'nome' => $nome, 'data_nascita' => !empty(trim($data['data_nascita'] ?? '')) ? trim($data['data_nascita']) : null, 'indirizzo' => !empty(trim($data['indirizzo'] ?? '')) ? trim($data['indirizzo']) : null, 'cap' => !empty(trim($data['cap'] ?? '')) ? trim($data['cap']) : null, 'città' => !empty(trim($data['città'] ?? '')) ? trim($data['città']) : null, 'sigla_provincia' => !empty(trim($data['sigla_provincia'] ?? '')) ? trim($data['sigla_provincia']) : null, 'genere' => !empty(trim($data['genere'] ?? '')) ? strtoupper(trim($data['genere'])) : null, 'tipo_documento' => !empty(trim($data['tipo_documento'] ?? '')) ? trim($data['tipo_documento']) : null, 'numero_documento' => !empty(trim($data['numero_documento'] ?? '')) ? trim($data['numero_documento']) : null, 'scadenza_documento' => !empty(trim($data['scadenza_documento'] ?? '')) ? trim($data['scadenza_documento']) : null, 'note' => !empty(trim($data['note'] ?? '')) ? trim($data['note']) : null, ]); for ($i = 1; $i <= 2; $i++) { $tipoKey = 'contatto_' . $i . '_tipo'; $valoreKey = 'contatto_' . $i . '_valore'; $etichettaKey = 'contatto_' . $i . '_etichetta'; $tipo = trim($data[$tipoKey] ?? ''); $valore = trim($data[$valoreKey] ?? ''); if (!empty($tipo) && !empty($valore)) { $individuo->contatti()->create([ 'tipo' => $tipo, 'valore' => $valore, 'etichetta' => !empty(trim($data[$etichettaKey] ?? '')) ? trim($data[$etichettaKey]) : null, 'is_primary' => $i === 1, ]); } } $imported++; } catch (\Exception $e) { $errors[] = 'Errore riga ' . $rowNumber . ': ' . $e->getMessage(); } } fclose($handle); $message = 'Importati ' . $imported . ' individui.'; if ($skipped > 0) { $message .= ' Saltate ' . $skipped . ' righe (vuote o incomplete).'; } if (count($errors) > 0) { return back()->with('error', $message . ' Errori: ' . implode('; ', $errors)); } return redirect()->route('individui.index')->with('success', $message); } public function downloadTemplate() { $this->authorizeRead('individui'); $headers = ['cognome', 'nome', 'data_nascita', 'indirizzo', 'cap', 'città', 'sigla_provincia', 'genere', 'tipo_documento', 'numero_documento', 'scadenza_documento', 'note', 'contatto_1_tipo', 'contatto_1_valore', 'contatto_1_etichetta', 'contatto_2_tipo', 'contatto_2_valore', 'contatto_2_etichetta']; $example = [ 'Rossi', 'Mario', '1990-05-15', 'Via Roma 10', '00100', 'Roma', 'RM', 'M', 'carta_identita', 'AB123456', '2030-05-15', 'Socio fondatore', 'email', 'mario.rossi@email.com', 'personale', 'cellulare', '3331234567', 'lavoro', ]; $csv = implode(',', $headers) . "\n" . implode(',', $example); return response($csv, 200, [ 'Content-Type' => 'text/csv', 'Content-Disposition' => 'attachment; filename="template_individui.csv"', ]); } public function uploadAvatar(Request $request, $individuo) { $this->authorizeWrite('individui'); $individuo = Individuo::findOrFail($individuo); $request->validate([ 'avatar' => 'required|image|mimes:jpeg,jpg,png,gif|max:2048', ]); $file = $request->file('avatar'); $oldAvatar = $individuo->avatar; if ($oldAvatar) { if (Storage::disk('local')->exists($oldAvatar->file_path)) { Storage::disk('local')->delete($oldAvatar->file_path); } $oldAvatar->delete(); } $path = $file->store('avatars', 'local'); $avatar = Documento::create([ 'tenant_id' => $individuo->tenant_id, 'nome_file' => $file->getClientOriginalName(), 'file_path' => $path, 'tipo' => 'upload', 'tipologia' => 'avatar', 'visibilita' => 'individuo', 'visibilita_target_id' => $individuo->id, 'visibilita_target_type' => Individuo::class, 'mime_type' => $file->getMimeType(), 'dimensione' => $file->getSize(), 'user_id' => auth()->id(), ]); return response()->json([ 'success' => true, 'avatar_url' => url('/individui/' . $individuo->id . '/avatar'), 'avatar_id' => $avatar->id, ]); } public function getAvatar($individuo) { $this->authorizeRead('individui'); $individuo = Individuo::findOrFail($individuo); $avatar = $individuo->avatar; if (!$avatar || !Storage::disk('local')->exists($avatar->file_path)) { abort(404); } return response( Storage::disk('local')->get($avatar->file_path), 200, ['Content-Type' => $avatar->mime_type] ); } public function deleteAvatar($individuo) { $this->authorizeDelete('individui'); $individuo = Individuo::findOrFail($individuo); $avatar = $individuo->avatar; if (!$avatar) { return response()->json(['success' => false, 'message' => 'Nessun avatar da eliminare']); } if (Storage::disk('local')->exists($avatar->file_path)) { Storage::disk('local')->delete($avatar->file_path); } $avatar->delete(); return response()->json(['success' => true]); } }